1396 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1396 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * 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: 2025-01-18
 | |
|  *
 | |
|  * 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 <list>
 | |
| #include <map>
 | |
| #include <sstream>
 | |
| 
 | |
| #include <maxscale/adminusers.h>
 | |
| #include <maxbase/alloc.h>
 | |
| #include <maxscale/housekeeper.h>
 | |
| #include <maxscale/http.hh>
 | |
| #include <maxscale/jansson.hh>
 | |
| #include <maxscale/json_api.hh>
 | |
| #include <maxscale/modulecmd.hh>
 | |
| #include <maxscale/routingworker.hh>
 | |
| 
 | |
| #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<uint32_t>(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<std::string, uint64_t>::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<string, time_t>::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<string, uint64_t>::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<string, time_t>   m_last_modified;
 | |
|     map<string, uint64_t> 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_reload_users(const HttpRequest& request)
 | |
| {
 | |
|     Service* service = service_internal_find(request.uri_part(1).c_str());
 | |
|     mxb_assert(service);
 | |
| 
 | |
|     service->load_users();
 | |
| 
 | |
|     return HttpResponse(MHD_HTTP_NO_CONTENT);
 | |
| }
 | |
| 
 | |
| 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);
 | |
|                 modulecmd_arg_free(args);
 | |
|             }
 | |
| 
 | |
|             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<Resource> SResource;
 | |
|     typedef list<SResource>           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")));
 | |
|         m_post.push_back(SResource(new Resource(cb_reload_users, 3, "services", ":service", "reload")));
 | |
| 
 | |
|         /** 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<string> 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<string>::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;
 | |
|     if (!worker->call([&request, &response, worker]() {
 | |
|                           mxs::WatchdogWorkaround workaround(worker);
 | |
|                           response = handle_request(request);
 | |
|                       },
 | |
|                       mxb::Worker::EXECUTE_AUTO))
 | |
|     {
 | |
|         response = HttpResponse(MHD_HTTP_SERVICE_UNAVAILABLE);
 | |
|     }
 | |
| 
 | |
|     return response;
 | |
| }
 | 
