/* * Copyright (c) 2016 MariaDB Corporation Ab * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file and at www.mariadb.com/bsl11. * * Change Date: 2023-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2 or later of the General * Public License. */ #include "internal/resource.hh" #include #include #include #include #include #include #include #include #include #include #include #include "internal/config_runtime.hh" #include "internal/filter.hh" #include "internal/httprequest.hh" #include "internal/httpresponse.hh" #include "internal/modules.hh" #include "internal/monitor.hh" #include "internal/monitormanager.hh" #include "internal/query_classifier.hh" #include "internal/server.hh" #include "internal/service.hh" #include "internal/session.hh" using std::list; using std::map; using std::string; using std::stringstream; using maxscale::Monitor; Resource::Resource(ResourceCallback cb, int components, ...) : m_cb(cb) , m_is_glob(false) , m_constraints(NONE) { va_list args; va_start(args, components); for (int i = 0; i < components; i++) { string part = va_arg(args, const char*); m_path.push_back(part); if (part == "?") { m_is_glob = true; } } va_end(args); } Resource::~Resource() { } bool Resource::match(const HttpRequest& request) const { bool rval = false; if (request.uri_part_count() == m_path.size() || m_is_glob) { rval = true; size_t parts = MXS_MIN(request.uri_part_count(), m_path.size()); for (size_t i = 0; i < parts; i++) { if (m_path[i] != request.uri_part(i) && !matching_variable_path(m_path[i], request.uri_part(i))) { rval = false; break; } } } return rval; } static void remove_null_parameters(json_t* json) { if (json_t* parameters = mxs_json_pointer(json, MXS_JSON_PTR_PARAMETERS)) { const char* key; json_t* value; void* tmp; json_object_foreach_safe(parameters, tmp, key, value) { if (json_is_null(value)) { json_object_del(parameters, key); } } } } HttpResponse Resource::call(const HttpRequest& request) const { if (json_t* json = request.get_json()) { remove_null_parameters(json); } return m_cb(request); } bool Resource::matching_variable_path(const string& path, const string& target) const { bool rval = false; if (path[0] == ':') { if ((path == ":service" && service_find(target.c_str())) || (path == ":server" && Server::find_by_unique_name(target)) || (path == ":filter" && filter_find(target.c_str())) || (path == ":monitor" && MonitorManager::find_monitor(target.c_str())) || (path == ":module" && get_module(target.c_str(), NULL)) || (path == ":inetuser" && admin_inet_user_exists(target.c_str())) || (path == ":unixuser" && admin_linux_account_enabled(target.c_str()))) { rval = true; } else if (path == ":session") { size_t id = atoi(target.c_str()); MXS_SESSION* ses = session_get_by_id(id); if (ses) { session_put_ref(ses); rval = true; } } else if (path == ":thread") { char* end; int id = strtol(target.c_str(), &end, 10); if (*end == '\0' && mxs_rworker_get(id)) { rval = true; } } } else if (path == "?") { /** Wildcard match */ rval = true; } return rval; } void Resource::add_constraint(resource_constraint type) { m_constraints |= static_cast(type); } bool Resource::requires_body() const { return m_constraints & REQUIRE_BODY; } namespace { bool option_rdns_is_on(const HttpRequest& request) { return request.get_option("rdns") == "true"; } static bool drop_path_part(std::string& path) { size_t pos = path.find_last_of('/'); bool rval = false; if (pos != std::string::npos) { path.erase(pos); rval = true; } return rval && path.length(); } /** * Class that keeps track of resource modification times */ class ResourceWatcher { public: ResourceWatcher() : m_init(time(NULL)) { } void modify(const std::string& orig_path) { std::string path = orig_path; do { map::iterator it = m_etag.find(path); if (it != m_etag.end()) { it->second++; } else { // First modification m_etag[path] = 1; } m_last_modified[path] = time(NULL); } while (drop_path_part(path)); } time_t last_modified(const string& path) const { map::const_iterator it = m_last_modified.find(path); if (it != m_last_modified.end()) { return it->second; } // Resource has not yet been updated return m_init; } uint64_t etag(const string& path) const { map::const_iterator it = m_etag.find(path); if (it != m_etag.end()) { return it->second; } // Resource has not yet been updated return 0; } private: time_t m_init; map m_last_modified; map m_etag; }; HttpResponse cb_stop_monitor(const HttpRequest& request) { Monitor* monitor = MonitorManager::find_monitor(request.uri_part(1).c_str()); if (monitor) { MonitorManager::stop_monitor(monitor); } return HttpResponse(MHD_HTTP_NO_CONTENT); } HttpResponse cb_start_monitor(const HttpRequest& request) { Monitor* monitor = MonitorManager::find_monitor(request.uri_part(1).c_str()); if (monitor) { MonitorManager::start_monitor(monitor); } return HttpResponse(MHD_HTTP_NO_CONTENT); } HttpResponse cb_stop_service(const HttpRequest& request) { Service* service = service_internal_find(request.uri_part(1).c_str()); serviceStop(service); return HttpResponse(MHD_HTTP_NO_CONTENT); } HttpResponse cb_start_service(const HttpRequest& request) { Service* service = service_internal_find(request.uri_part(1).c_str()); serviceStart(service); return HttpResponse(MHD_HTTP_NO_CONTENT); } HttpResponse cb_create_server(const HttpRequest& request) { mxb_assert(request.get_json()); if (runtime_create_server_from_json(request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_alter_server(const HttpRequest& request) { auto server = Server::find_by_unique_name(request.uri_part(1)); mxb_assert(server && request.get_json()); if (runtime_alter_server_from_json(server, request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse do_alter_server_relationship(const HttpRequest& request, const char* type) { auto server = Server::find_by_unique_name(request.uri_part(1)); mxb_assert(server && request.get_json()); if (runtime_alter_server_relationships_from_json(server, type, request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_alter_server_service_relationship(const HttpRequest& request) { return do_alter_server_relationship(request, "services"); } HttpResponse cb_alter_server_monitor_relationship(const HttpRequest& request) { return do_alter_server_relationship(request, "monitors"); } HttpResponse cb_create_monitor(const HttpRequest& request) { mxb_assert(request.get_json()); if (runtime_create_monitor_from_json(request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_create_filter(const HttpRequest& request) { mxb_assert(request.get_json()); if (runtime_create_filter_from_json(request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_create_service(const HttpRequest& request) { mxb_assert(request.get_json()); if (runtime_create_service_from_json(request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_create_service_listener(const HttpRequest& request) { Service* service = service_internal_find(request.uri_part(1).c_str()); mxb_assert(service && request.get_json()); if (runtime_create_listener_from_json(service, request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_alter_monitor(const HttpRequest& request) { Monitor* monitor = MonitorManager::find_monitor(request.uri_part(1).c_str()); mxb_assert(monitor && request.get_json()); if (runtime_alter_monitor_from_json(monitor, request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_alter_monitor_server_relationship(const HttpRequest& request) { Monitor* monitor = MonitorManager::find_monitor(request.uri_part(1).c_str()); mxb_assert(monitor && request.get_json()); if (runtime_alter_monitor_relationships_from_json(monitor, request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_alter_service(const HttpRequest& request) { Service* service = service_internal_find(request.uri_part(1).c_str()); mxb_assert(service && request.get_json()); if (runtime_alter_service_from_json(service, request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_alter_service_server_relationship(const HttpRequest& request) { Service* service = service_internal_find(request.uri_part(1).c_str()); mxb_assert(service && request.get_json()); if (runtime_alter_service_relationships_from_json(service, CN_SERVERS, request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_alter_service_filter_relationship(const HttpRequest& request) { Service* service = service_internal_find(request.uri_part(1).c_str()); mxb_assert(service && request.get_json()); if (runtime_alter_service_relationships_from_json(service, CN_FILTERS, request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_alter_logs(const HttpRequest& request) { mxb_assert(request.get_json()); if (runtime_alter_logs_from_json(request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_alter_qc(const HttpRequest& request) { mxb_assert(request.get_json()); if (runtime_alter_qc_from_json(request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_delete_server(const HttpRequest& request) { auto server = Server::find_by_unique_name(request.uri_part(1).c_str()); mxb_assert(server); if (runtime_destroy_server(server)) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_delete_monitor(const HttpRequest& request) { Monitor* monitor = MonitorManager::find_monitor(request.uri_part(1).c_str()); mxb_assert(monitor); if (runtime_destroy_monitor(monitor)) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_delete_listener(const HttpRequest& request) { Service* service = service_internal_find(request.uri_part(1).c_str()); mxb_assert(service); std::string listener = request.uri_part(3); if (!service_has_named_listener(service, listener.c_str())) { return HttpResponse(MHD_HTTP_NOT_FOUND); } else if (!runtime_destroy_listener(service, listener.c_str())) { return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } return HttpResponse(MHD_HTTP_NO_CONTENT); } HttpResponse cb_delete_service(const HttpRequest& request) { Service* service = service_internal_find(request.uri_part(1).c_str()); mxb_assert(service); if (runtime_destroy_service(service)) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_delete_filter(const HttpRequest& request) { auto filter = filter_find(request.uri_part(1).c_str()); mxb_assert(filter); if (runtime_destroy_filter(filter)) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_all_servers(const HttpRequest& request) { return HttpResponse(MHD_HTTP_OK, Server::server_list_to_json(request.host())); } HttpResponse cb_get_server(const HttpRequest& request) { auto server = Server::find_by_unique_name(request.uri_part(1)); mxb_assert(server); return HttpResponse(MHD_HTTP_OK, server->to_json(request.host())); } HttpResponse cb_all_services(const HttpRequest& request) { return HttpResponse(MHD_HTTP_OK, service_list_to_json(request.host())); } HttpResponse cb_get_service(const HttpRequest& request) { Service* service = service_internal_find(request.uri_part(1).c_str()); mxb_assert(service); return HttpResponse(MHD_HTTP_OK, service_to_json(service, request.host())); } HttpResponse cb_get_all_service_listeners(const HttpRequest& request) { Service* service = service_internal_find(request.uri_part(1).c_str()); return HttpResponse(MHD_HTTP_OK, service_listener_list_to_json(service, request.host())); } HttpResponse cb_get_service_listener(const HttpRequest& request) { Service* service = service_internal_find(request.uri_part(1).c_str()); std::string listener = request.uri_part(3); mxb_assert(service); if (!service_has_named_listener(service, listener.c_str())) { return HttpResponse(MHD_HTTP_NOT_FOUND); } return HttpResponse(MHD_HTTP_OK, service_listener_to_json(service, listener.c_str(), request.host())); } HttpResponse cb_all_filters(const HttpRequest& request) { return HttpResponse(MHD_HTTP_OK, filter_list_to_json(request.host())); } HttpResponse cb_get_filter(const HttpRequest& request) { auto filter = filter_find(request.uri_part(1).c_str()); mxb_assert(filter); return HttpResponse(MHD_HTTP_OK, filter_to_json(filter, request.host())); } HttpResponse cb_all_monitors(const HttpRequest& request) { return HttpResponse(MHD_HTTP_OK, MonitorManager::monitor_list_to_json(request.host())); } HttpResponse cb_get_monitor(const HttpRequest& request) { Monitor* monitor = MonitorManager::find_monitor(request.uri_part(1).c_str()); mxb_assert(monitor); return HttpResponse(MHD_HTTP_OK, MonitorManager::monitor_to_json(monitor, request.host())); } HttpResponse cb_all_sessions(const HttpRequest& request) { bool rdns = option_rdns_is_on(request); return HttpResponse(MHD_HTTP_OK, session_list_to_json(request.host(), rdns)); } HttpResponse cb_get_session(const HttpRequest& request) { int id = atoi(request.uri_part(1).c_str()); MXS_SESSION* session = session_get_by_id(id); if (session) { bool rdns = option_rdns_is_on(request); json_t* json = session_to_json(session, request.host(), rdns); session_put_ref(session); return HttpResponse(MHD_HTTP_OK, json); } return HttpResponse(MHD_HTTP_NOT_FOUND); } HttpResponse cb_maxscale(const HttpRequest& request) { return HttpResponse(MHD_HTTP_OK, config_maxscale_to_json(request.host())); } HttpResponse cb_alter_maxscale(const HttpRequest& request) { mxb_assert(request.get_json()); if (runtime_alter_maxscale_from_json(request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_logs(const HttpRequest& request) { return HttpResponse(MHD_HTTP_OK, mxs_logs_to_json(request.host())); } HttpResponse cb_flush(const HttpRequest& request) { int code = MHD_HTTP_INTERNAL_SERVER_ERROR; // Flush logs if (mxs_log_rotate()) { code = MHD_HTTP_NO_CONTENT; } return HttpResponse(code); } HttpResponse cb_all_threads(const HttpRequest& request) { return HttpResponse(MHD_HTTP_OK, mxs_rworker_list_to_json(request.host())); } HttpResponse cb_qc(const HttpRequest& request) { return HttpResponse(MHD_HTTP_OK, qc_as_json(request.host()).release()); } HttpResponse cb_qc_classify(const HttpRequest& request) { string sql = request.get_option("sql"); return HttpResponse(MHD_HTTP_OK, qc_classify_as_json(request.host(), sql).release()); } HttpResponse cb_qc_cache(const HttpRequest& request) { return HttpResponse(MHD_HTTP_OK, qc_cache_as_json(request.host()).release()); } HttpResponse cb_thread(const HttpRequest& request) { int id = atoi(request.last_uri_part().c_str()); return HttpResponse(MHD_HTTP_OK, mxs_rworker_to_json(request.host(), id)); } HttpResponse cb_tasks(const HttpRequest& request) { auto host = request.host(); return HttpResponse(MHD_HTTP_OK, mxs_json_resource(host, MXS_JSON_API_TASKS, hk_tasks_json(host))); } HttpResponse cb_all_modules(const HttpRequest& request) { return HttpResponse(MHD_HTTP_OK, module_list_to_json(request.host())); } HttpResponse cb_module(const HttpRequest& request) { const MXS_MODULE* module = get_module(request.last_uri_part().c_str(), NULL); return HttpResponse(MHD_HTTP_OK, module_to_json(module, request.host())); } HttpResponse cb_all_users(const HttpRequest& request) { return HttpResponse(MHD_HTTP_OK, admin_all_users_to_json(request.host(), USER_TYPE_ALL)); } HttpResponse cb_all_inet_users(const HttpRequest& request) { return HttpResponse(MHD_HTTP_OK, admin_all_users_to_json(request.host(), USER_TYPE_INET)); } HttpResponse cb_all_unix_users(const HttpRequest& request) { return HttpResponse(MHD_HTTP_OK, admin_all_users_to_json(request.host(), USER_TYPE_UNIX)); } HttpResponse cb_inet_user(const HttpRequest& request) { string user = request.uri_part(2); return HttpResponse(MHD_HTTP_OK, admin_user_to_json(request.host(), user.c_str(), USER_TYPE_INET)); } HttpResponse cb_unix_user(const HttpRequest& request) { string user = request.uri_part(2); return HttpResponse(MHD_HTTP_OK, admin_user_to_json(request.host(), user.c_str(), USER_TYPE_UNIX)); } HttpResponse cb_monitor_wait(const HttpRequest& request) { MonitorManager::debug_wait_one_tick(); return HttpResponse(MHD_HTTP_OK); } HttpResponse cb_create_user(const HttpRequest& request) { mxb_assert(request.get_json()); if (runtime_create_user_from_json(request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_alter_user(const HttpRequest& request) { auto user = request.last_uri_part(); auto type = request.uri_part(1); if (runtime_alter_user(user, type, request.get_json())) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_delete_user(const HttpRequest& request) { string user = request.last_uri_part(); string type = request.uri_part(1); if ((type == CN_INET && runtime_remove_user(user.c_str(), USER_TYPE_INET)) || (type == CN_UNIX && runtime_remove_user(user.c_str(), USER_TYPE_UNIX))) { return HttpResponse(MHD_HTTP_NO_CONTENT); } return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } HttpResponse cb_set_server(const HttpRequest& request) { SERVER* server = Server::find_by_unique_name(request.uri_part(1)); int opt = SERVER::status_from_string(request.get_option(CN_STATE).c_str()); if (opt) { string errmsg; if (MonitorManager::set_server_status(server, opt, &errmsg)) { if (status_is_in_maint(opt) && request.get_option(CN_FORCE) == CN_YES) { dcb_hangup_foreach(server); } return HttpResponse(MHD_HTTP_NO_CONTENT); } else { return HttpResponse(MHD_HTTP_FORBIDDEN, mxs_json_error("%s", errmsg.c_str())); } } return HttpResponse(MHD_HTTP_FORBIDDEN, mxs_json_error("Invalid or missing value for the `%s` parameter", CN_STATE)); } HttpResponse cb_clear_server(const HttpRequest& request) { SERVER* server = Server::find_by_unique_name(request.uri_part(1)); int opt = SERVER::status_from_string(request.get_option(CN_STATE).c_str()); if (opt) { string errmsg; if (MonitorManager::clear_server_status(server, opt, &errmsg)) { return HttpResponse(MHD_HTTP_NO_CONTENT); } else { return HttpResponse(MHD_HTTP_FORBIDDEN, mxs_json_error("%s", errmsg.c_str())); } } return HttpResponse(MHD_HTTP_FORBIDDEN, mxs_json_error("Invalid or missing value for the `%s` parameter", CN_STATE)); } HttpResponse cb_modulecmd(const HttpRequest& request) { std::string module = request.uri_part(2); std::string identifier = request.uri_segment(3, request.uri_part_count()); std::string verb = request.get_verb(); const MODULECMD* cmd = modulecmd_find_command(module.c_str(), identifier.c_str()); if (cmd) { if ((!MODULECMD_MODIFIES_DATA(cmd) && verb == MHD_HTTP_METHOD_GET) || (MODULECMD_MODIFIES_DATA(cmd) && verb == MHD_HTTP_METHOD_POST)) { int n_opts = (int)request.get_option_count(); char* opts[n_opts]; request.copy_options(opts); MODULECMD_ARG* args = modulecmd_arg_parse(cmd, n_opts, (const void**)opts); bool rval = false; json_t* output = NULL; if (args) { rval = modulecmd_call_command(cmd, args, &output); } for (int i = 0; i < n_opts; i++) { MXS_FREE(opts[i]); } int rc; if (output && json_object_get(output, "errors") == NULL) { /** * Store the command output in the meta field. This allows * all the commands to conform to the JSON API even though * the content of the field can vary from command to command. * * If the output is an JSON API error, we don't do anything to it */ std::string self = "/"; // The uri_segment doesn't have the leading slash self += request.uri_segment(0, request.uri_part_count()); output = mxs_json_metadata(request.host(), self.c_str(), output); } if (rval) { rc = output ? MHD_HTTP_OK : MHD_HTTP_NO_CONTENT; } else { rc = MHD_HTTP_FORBIDDEN; json_t* err = modulecmd_get_json_error(); if (err) { if (!output) { // No output, only errors output = err; } else { // Both output and errors json_t* output_err = json_object_get(output, "errors"); if (output_err) { // The output already contains an error array and append to it json_array_append(output_err, json_object_get(err, "errors")); } else { // No error, we can just assign the error array json_object_set(output, "errors", json_object_get(err, "errors")); } json_decref(err); } } } return HttpResponse(rc, output); } } return HttpResponse(MHD_HTTP_NOT_FOUND); } HttpResponse cb_send_ok(const HttpRequest& request) { mxs_rworker_watchdog(); return HttpResponse(MHD_HTTP_OK); } class RootResource { RootResource(const RootResource&); RootResource& operator=(const RootResource&); public: typedef std::shared_ptr SResource; typedef list ResourceList; /** * Create REST API resources * * Each resource represents either a collection of resources, an individual * resource, a sub-resource of a resource or an "action" endpoint which * executes an action. * * The resources are defined by the Resource class. Each resource maps to a * HTTP method and one or more paths. The path components can contain either * an explicit string, a colon-prefixed object type or a question mark for * a path component that matches everything. */ RootResource() { // Special resources required by OPTION etc. m_get.push_back(SResource(new Resource(cb_send_ok, 0))); m_get.push_back(SResource(new Resource(cb_send_ok, 1, "*"))); m_get.push_back(SResource(new Resource(cb_all_servers, 1, "servers"))); m_get.push_back(SResource(new Resource(cb_get_server, 2, "servers", ":server"))); m_get.push_back(SResource(new Resource(cb_all_services, 1, "services"))); m_get.push_back(SResource(new Resource(cb_get_service, 2, "services", ":service"))); m_get.push_back(SResource(new Resource(cb_get_all_service_listeners, 3, "services", ":service", "listeners"))); m_get.push_back(SResource(new Resource(cb_get_service_listener, 4, "services", ":service", "listeners", "?"))); m_get.push_back(SResource(new Resource(cb_all_filters, 1, "filters"))); m_get.push_back(SResource(new Resource(cb_get_filter, 2, "filters", ":filter"))); m_get.push_back(SResource(new Resource(cb_all_monitors, 1, "monitors"))); m_get.push_back(SResource(new Resource(cb_get_monitor, 2, "monitors", ":monitor"))); m_get.push_back(SResource(new Resource(cb_all_sessions, 1, "sessions"))); m_get.push_back(SResource(new Resource(cb_get_session, 2, "sessions", ":session"))); m_get.push_back(SResource(new Resource(cb_maxscale, 1, "maxscale"))); m_get.push_back(SResource(new Resource(cb_qc, 2, "maxscale", "query_classifier"))); m_get.push_back(SResource(new Resource(cb_qc_classify, 3, "maxscale", "query_classifier", "classify"))); m_get.push_back(SResource(new Resource(cb_qc_cache, 3, "maxscale", "query_classifier", "cache"))); m_get.push_back(SResource(new Resource(cb_all_threads, 2, "maxscale", "threads"))); m_get.push_back(SResource(new Resource(cb_thread, 3, "maxscale", "threads", ":thread"))); m_get.push_back(SResource(new Resource(cb_logs, 2, "maxscale", "logs"))); m_get.push_back(SResource(new Resource(cb_tasks, 2, "maxscale", "tasks"))); m_get.push_back(SResource(new Resource(cb_all_modules, 2, "maxscale", "modules"))); m_get.push_back(SResource(new Resource(cb_module, 3, "maxscale", "modules", ":module"))); /** For all read-only module commands */ m_get.push_back(SResource(new Resource(cb_modulecmd, 4, "maxscale", "modules", ":module", "?"))); m_get.push_back(SResource(new Resource(cb_all_users, 1, "users"))); m_get.push_back(SResource(new Resource(cb_all_inet_users, 2, "users", "inet"))); m_get.push_back(SResource(new Resource(cb_all_unix_users, 2, "users", "unix"))); m_get.push_back(SResource(new Resource(cb_inet_user, 3, "users", "inet", ":inetuser"))); m_get.push_back(SResource(new Resource(cb_unix_user, 3, "users", "unix", ":unixuser"))); /** Debug utility endpoints */ m_get.push_back(SResource(new Resource(cb_monitor_wait, 3, "maxscale", "debug", "monitor_wait"))); /** Create new resources */ m_post.push_back(SResource(new Resource(cb_create_server, 1, "servers"))); m_post.push_back(SResource(new Resource(cb_create_monitor, 1, "monitors"))); m_post.push_back(SResource(new Resource(cb_create_filter, 1, "filters"))); m_post.push_back(SResource(new Resource(cb_create_service, 1, "services"))); m_post.push_back(SResource(new Resource(cb_create_service_listener, 3, "services", ":service", "listeners"))); m_post.push_back(SResource(new Resource(cb_create_user, 2, "users", "inet"))); m_post.push_back(SResource(new Resource(cb_create_user, 2, "users", "unix"))); /** All of the above require a request body */ for (ResourceList::iterator it = m_post.begin(); it != m_post.end(); it++) { SResource& r = *it; r->add_constraint(Resource::REQUIRE_BODY); } /** * NOTE: all POST resources added after this DO NOT require a request body. */ /** For all module commands that modify state/data */ m_post.push_back(SResource(new Resource(cb_modulecmd, 4, "maxscale", "modules", ":module", "?"))); m_post.push_back(SResource(new Resource(cb_flush, 3, "maxscale", "logs", "flush"))); /** Update resources */ m_patch.push_back(SResource(new Resource(cb_alter_server, 2, "servers", ":server"))); m_patch.push_back(SResource(new Resource(cb_alter_monitor, 2, "monitors", ":monitor"))); m_patch.push_back(SResource(new Resource(cb_alter_service, 2, "services", ":service"))); m_patch.push_back(SResource(new Resource(cb_alter_logs, 2, "maxscale", "logs"))); m_patch.push_back(SResource(new Resource(cb_alter_maxscale, 1, "maxscale"))); m_patch.push_back(SResource(new Resource(cb_alter_qc, 2, "maxscale", "query_classifier"))); m_patch.push_back(SResource(new Resource(cb_alter_user, 3, "users", "inet", ":inetuser"))); /** Update resource relationships directly */ m_patch.push_back(SResource(new Resource(cb_alter_server_service_relationship, 4, "servers", ":server", "relationships", "services"))); m_patch.push_back(SResource(new Resource(cb_alter_server_monitor_relationship, 4, "servers", ":server", "relationships", "monitors"))); m_patch.push_back(SResource(new Resource(cb_alter_monitor_server_relationship, 4, "monitors", ":monitor", "relationships", "servers"))); m_patch.push_back(SResource(new Resource(cb_alter_service_server_relationship, 4, "services", ":service", "relationships", "servers"))); m_patch.push_back(SResource(new Resource(cb_alter_service_filter_relationship, 4, "services", ":service", "relationships", "filters"))); /** All patch resources require a request body */ for (ResourceList::iterator it = m_patch.begin(); it != m_patch.end(); it++) { SResource& r = *it; r->add_constraint(Resource::REQUIRE_BODY); } /** * NOTE: all PATCH resources added after this DO NOT require a request body. */ /** Change resource states */ m_put.push_back(SResource(new Resource(cb_stop_monitor, 3, "monitors", ":monitor", "stop"))); m_put.push_back(SResource(new Resource(cb_start_monitor, 3, "monitors", ":monitor", "start"))); m_put.push_back(SResource(new Resource(cb_stop_service, 3, "services", ":service", "stop"))); m_put.push_back(SResource(new Resource(cb_start_service, 3, "services", ":service", "start"))); m_put.push_back(SResource(new Resource(cb_set_server, 3, "servers", ":server", "set"))); m_put.push_back(SResource(new Resource(cb_clear_server, 3, "servers", ":server", "clear"))); m_delete.push_back(SResource(new Resource(cb_delete_server, 2, "servers", ":server"))); m_delete.push_back(SResource(new Resource(cb_delete_monitor, 2, "monitors", ":monitor"))); m_delete.push_back(SResource(new Resource(cb_delete_service, 2, "services", ":service"))); m_delete.push_back(SResource(new Resource(cb_delete_filter, 2, "filters", ":filter"))); m_delete.push_back(SResource(new Resource(cb_delete_user, 3, "users", "inet", ":inetuser"))); m_delete.push_back(SResource(new Resource(cb_delete_user, 3, "users", "unix", ":unixuser"))); /** The wildcard for listener name isn't a good solution as it adds * a burden to the callback and requires it to do the checking but it'll * do for the time being */ m_delete.push_back(SResource(new Resource(cb_delete_listener, 4, "services", ":service", "listeners", "?"))); } ~RootResource() { } ResourceList::const_iterator find_resource(const ResourceList& list, const HttpRequest& request) const { for (ResourceList::const_iterator it = list.begin(); it != list.end(); it++) { Resource& r = *(*it); if (r.match(request)) { return it; } } return list.end(); } HttpResponse process_request_type(const ResourceList& list, const HttpRequest& request) { ResourceList::const_iterator it = find_resource(list, request); if (it != list.end()) { Resource& r = *(*it); if (r.requires_body() && request.get_json() == NULL) { return HttpResponse(MHD_HTTP_FORBIDDEN, mxs_json_error("Missing request body")); } return r.call(request); } return HttpResponse(MHD_HTTP_NOT_FOUND); } string get_supported_methods(const HttpRequest& request) { list l; if (find_resource(m_get, request) != m_get.end()) { l.push_back(MHD_HTTP_METHOD_GET); } if (find_resource(m_put, request) != m_put.end()) { l.push_back(MHD_HTTP_METHOD_PUT); } if (find_resource(m_post, request) != m_post.end()) { l.push_back(MHD_HTTP_METHOD_POST); } if (find_resource(m_delete, request) != m_delete.end()) { l.push_back(MHD_HTTP_METHOD_DELETE); } stringstream rval; if (l.size() > 0) { rval << l.front(); l.pop_front(); } for (list::iterator it = l.begin(); it != l.end(); it++) { rval << ", " << *it; } return rval.str(); } HttpResponse process_request(const HttpRequest& request) { if (request.get_verb() == MHD_HTTP_METHOD_GET) { return process_request_type(m_get, request); } else if (request.get_verb() == MHD_HTTP_METHOD_PUT) { return process_request_type(m_put, request); } else if (request.get_verb() == MHD_HTTP_METHOD_PATCH) { return process_request_type(m_patch, request); } else if (request.get_verb() == MHD_HTTP_METHOD_POST) { return process_request_type(m_post, request); } else if (request.get_verb() == MHD_HTTP_METHOD_DELETE) { return process_request_type(m_delete, request); } else if (request.get_verb() == MHD_HTTP_METHOD_OPTIONS) { string methods = get_supported_methods(request); if (methods.size() > 0) { HttpResponse response(MHD_HTTP_OK); response.add_header(HTTP_RESPONSE_HEADER_ACCEPT, methods); return response; } } else if (request.get_verb() == MHD_HTTP_METHOD_HEAD) { /** Do a GET and just drop the body of the response */ HttpResponse response = process_request_type(m_get, request); response.drop_response(); return response; } return HttpResponse(MHD_HTTP_METHOD_NOT_ALLOWED); } private: ResourceList m_get; /**< GET request handlers */ ResourceList m_put; /**< PUT request handlers */ ResourceList m_post; /**< POST request handlers */ ResourceList m_delete; /**< DELETE request handlers */ ResourceList m_patch; /**< PATCH request handlers */ }; static RootResource resources; /**< Core resource set */ static ResourceWatcher watcher; /**< Modification watcher */ static bool request_modifies_data(const string& verb) { return verb == MHD_HTTP_METHOD_POST || verb == MHD_HTTP_METHOD_PUT || verb == MHD_HTTP_METHOD_DELETE || verb == MHD_HTTP_METHOD_PATCH; } static bool request_reads_data(const string& verb) { return verb == MHD_HTTP_METHOD_GET || verb == MHD_HTTP_METHOD_HEAD; } static bool request_precondition_met(const HttpRequest& request, HttpResponse& response) { bool rval = true; string str; const string& uri = request.get_uri(); if ((str = request.get_header(MHD_HTTP_HEADER_IF_MODIFIED_SINCE)).length()) { if (watcher.last_modified(uri) <= http_from_date(str)) { rval = false; response = HttpResponse(MHD_HTTP_NOT_MODIFIED); } } else if ((str = request.get_header(MHD_HTTP_HEADER_IF_UNMODIFIED_SINCE)).length()) { if (watcher.last_modified(uri) > http_from_date(str)) { rval = false; response = HttpResponse(MHD_HTTP_PRECONDITION_FAILED); } } else if ((str = request.get_header(MHD_HTTP_HEADER_IF_MATCH)).length()) { str = str.substr(1, str.length() - 2); if (watcher.etag(uri) != strtoul(str.c_str(), NULL, 10)) { rval = false; response = HttpResponse(MHD_HTTP_PRECONDITION_FAILED); } } else if ((str = request.get_header(MHD_HTTP_HEADER_IF_NONE_MATCH)).length()) { str = str.substr(1, str.length() - 2); if (watcher.etag(uri) == strtoul(str.c_str(), NULL, 10)) { rval = false; response = HttpResponse(MHD_HTTP_NOT_MODIFIED); } } return rval; } static HttpResponse handle_request(const HttpRequest& request) { MXS_DEBUG("%s %s %s", request.get_verb().c_str(), request.get_uri().c_str(), request.get_json_str().c_str()); HttpResponse rval; if (request_precondition_met(request, rval)) { rval = resources.process_request(request); if (request_modifies_data(request.get_verb())) { switch (rval.get_code()) { case MHD_HTTP_OK: case MHD_HTTP_NO_CONTENT: case MHD_HTTP_CREATED: watcher.modify(request.get_uri()); break; default: break; } } else if (request_reads_data(request.get_verb())) { const string& uri = request.get_uri(); rval.add_header(HTTP_RESPONSE_HEADER_LAST_MODIFIED, http_to_date(watcher.last_modified(uri))); stringstream ss; ss << "\"" << watcher.etag(uri) << "\""; rval.add_header(HTTP_RESPONSE_HEADER_ETAG, ss.str()); } } return rval; } } HttpResponse resource_handle_request(const HttpRequest& request) { mxs::RoutingWorker* worker = mxs::RoutingWorker::get(mxs::RoutingWorker::MAIN); HttpResponse response; worker->call([&request, &response, worker]() { mxs::WatchdogWorkaround workaround(worker); response = handle_request(request); }, mxb::Worker::EXECUTE_AUTO); return response; }