MXS-1220: Use libmicrohttpd for the HTTP handling
The HTTP side of the REST API is better handled with an actual library. The libmicrohttpd library provides a convenient way of handling the HTTP traffic between the clients and MaxScale.
This commit is contained in:
committed by
Markus Mäkelä
parent
eb3ff1cc7b
commit
d242203279
@ -1,6 +1,5 @@
|
||||
add_library(maxscale-common SHARED
|
||||
admin.cc
|
||||
adminclient.cc
|
||||
adminusers.cc
|
||||
alloc.cc
|
||||
atomic.cc
|
||||
@ -74,6 +73,7 @@ target_link_libraries(maxscale-common
|
||||
rt
|
||||
m
|
||||
stdc++
|
||||
microhttpd
|
||||
)
|
||||
|
||||
add_dependencies(maxscale-common pcre2 connector-c)
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
|
||||
#include <climits>
|
||||
#include <new>
|
||||
#include <microhttpd.h>
|
||||
|
||||
#include <maxscale/atomic.h>
|
||||
#include <maxscale/debug.h>
|
||||
@ -23,171 +24,66 @@
|
||||
|
||||
#include "maxscale/admin.hh"
|
||||
#include "maxscale/hk_heartbeat.h"
|
||||
#include "maxscale/resource.hh"
|
||||
|
||||
#define DEFAULT_ADMIN_HOST "127.0.0.1"
|
||||
#define DEFAULT_ADMIN_PORT 8080
|
||||
#define DEFAULT_ADMIN_AUTH HTTP_AUTH_NONE
|
||||
|
||||
static AdminListener* admin = NULL;
|
||||
static THREAD admin_thread;
|
||||
static THREAD timeout_thread;
|
||||
static struct MHD_Daemon* http_daemon = NULL;
|
||||
|
||||
// TODO: Read values from the configuration
|
||||
static AdminConfig config = {DEFAULT_ADMIN_HOST, DEFAULT_ADMIN_PORT, DEFAULT_ADMIN_AUTH};
|
||||
int handle_client(void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
const char *url,
|
||||
const char *method,
|
||||
const char *version,
|
||||
const char *upload_data,
|
||||
size_t *upload_data_size,
|
||||
void **con_cls)
|
||||
|
||||
void admin_main(void* data)
|
||||
{
|
||||
AdminListener* admin = reinterpret_cast<AdminListener*>(data);
|
||||
admin->start();
|
||||
}
|
||||
string verb(method);
|
||||
json_t* json = NULL;
|
||||
|
||||
void timeout_main(void *data)
|
||||
{
|
||||
AdminListener* admin = reinterpret_cast<AdminListener*>(data);
|
||||
admin->check_timeouts();
|
||||
}
|
||||
if (verb == "POST" || verb == "PUT" || verb == "PATCH")
|
||||
{
|
||||
json_error_t err = {};
|
||||
if ((json = json_loadb(upload_data, *upload_data_size, 0, &err)) == NULL)
|
||||
{
|
||||
return MHD_NO;
|
||||
}
|
||||
}
|
||||
|
||||
AdminConfig& mxs_admin_get_config()
|
||||
{
|
||||
return config;
|
||||
HttpRequest request(connection, url, method, json);
|
||||
HttpResponse reply = resource_handle_request(request);
|
||||
|
||||
string data = reply.get_response();
|
||||
|
||||
struct MHD_Response *response = MHD_create_response_from_buffer(data.size(),
|
||||
(void*)data.c_str(),
|
||||
MHD_RESPMEM_MUST_COPY);
|
||||
|
||||
for (map<string, string>::const_iterator it = reply.get_headers().begin();
|
||||
it != reply.get_headers().end(); it++)
|
||||
{
|
||||
MHD_add_response_header(response, it->first.c_str(), it->second.c_str());
|
||||
}
|
||||
|
||||
MHD_queue_response(connection, reply.get_code(), response);
|
||||
MHD_destroy_response(response);
|
||||
return MHD_YES;
|
||||
}
|
||||
|
||||
bool mxs_admin_init()
|
||||
{
|
||||
ss_dassert(admin == NULL);
|
||||
bool rval = false;
|
||||
struct sockaddr_storage addr = {};
|
||||
int sock = open_network_socket(MXS_SOCKET_LISTENER, &addr, config.host.c_str(), config.port);
|
||||
http_daemon = MHD_start_daemon(MHD_USE_EPOLL_INTERNALLY | MHD_USE_DUAL_STACK,
|
||||
DEFAULT_ADMIN_PORT, NULL, NULL,
|
||||
handle_client, NULL, MHD_OPTION_END);
|
||||
return http_daemon != NULL;
|
||||
|
||||
if (sock > -1)
|
||||
{
|
||||
setblocking(sock);
|
||||
|
||||
if (listen(sock, INT_MAX) == 0)
|
||||
{
|
||||
admin = new (std::nothrow) AdminListener(sock);
|
||||
|
||||
if (admin)
|
||||
{
|
||||
if (thread_start(&admin_thread, admin_main, admin) &&
|
||||
thread_start(&timeout_thread, timeout_main, admin))
|
||||
{
|
||||
rval = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
admin->stop();
|
||||
delete admin;
|
||||
admin = NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_OOM();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Failed to start listening on '[%s]:%u': %d, %s",
|
||||
config.host.c_str(), config.port, errno, mxs_strerror(errno));
|
||||
close(sock);
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
void mxs_admin_shutdown()
|
||||
{
|
||||
if (admin)
|
||||
{
|
||||
admin->stop();
|
||||
thread_wait(timeout_thread);
|
||||
thread_wait(admin_thread);
|
||||
delete admin;
|
||||
admin = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
AdminListener::AdminListener(int sock):
|
||||
m_socket(sock),
|
||||
m_active(1),
|
||||
m_timeout(10)
|
||||
{
|
||||
}
|
||||
|
||||
AdminListener::~AdminListener()
|
||||
{
|
||||
close(m_socket);
|
||||
}
|
||||
|
||||
void AdminListener::handle_clients()
|
||||
{
|
||||
AdminClient* client;
|
||||
|
||||
while ((client = accept_client()))
|
||||
{
|
||||
SAdminClient sclient(client);
|
||||
ClientList::iterator it = m_clients.insert(m_clients.begin(), sclient);
|
||||
sclient->process();
|
||||
m_clients.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void AdminListener::start()
|
||||
{
|
||||
while (atomic_read(&m_active))
|
||||
{
|
||||
MXS_EXCEPTION_GUARD(handle_clients());
|
||||
}
|
||||
}
|
||||
|
||||
void AdminListener::stop()
|
||||
{
|
||||
atomic_write(&m_active, 0);
|
||||
}
|
||||
|
||||
AdminClient* AdminListener::accept_client()
|
||||
{
|
||||
AdminClient* rval = NULL;
|
||||
struct sockaddr_storage addr = {};
|
||||
socklen_t len = sizeof (addr);
|
||||
int fd = accept(m_socket, (struct sockaddr*) &addr, &len);
|
||||
|
||||
if (fd > -1)
|
||||
{
|
||||
rval = new AdminClient(fd, addr, m_timeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Failed to accept client: %d, %s\n", errno, mxs_strerror(errno));
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
void AdminListener::handle_timeouts()
|
||||
{
|
||||
int64_t now = hkheartbeat;
|
||||
|
||||
for (ClientList::iterator it = m_clients.begin(); it != m_clients.end(); it++)
|
||||
{
|
||||
SAdminClient& client = *it;
|
||||
|
||||
if (now - client->last_activity() > m_timeout * 10)
|
||||
{
|
||||
client->close_connection();
|
||||
}
|
||||
}
|
||||
|
||||
/** Sleep for roughly one housekeeper heartbeat */
|
||||
thread_millisleep(100);
|
||||
}
|
||||
|
||||
void AdminListener::check_timeouts()
|
||||
{
|
||||
while (atomic_read(&m_active))
|
||||
{
|
||||
MXS_EXCEPTION_GUARD(handle_timeouts());
|
||||
}
|
||||
MHD_stop_daemon(http_daemon);
|
||||
}
|
||||
|
||||
@ -1,113 +0,0 @@
|
||||
/*
|
||||
* 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: 2019-07-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 "maxscale/adminclient.hh"
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#include <maxscale/atomic.h>
|
||||
#include <maxscale/hk_heartbeat.h>
|
||||
#include <maxscale/log_manager.h>
|
||||
#include <maxscale/thread.h>
|
||||
#include <maxscale/spinlock.hh>
|
||||
|
||||
#include "maxscale/httprequest.hh"
|
||||
#include "maxscale/httpresponse.hh"
|
||||
#include "maxscale/resource.hh"
|
||||
|
||||
using std::string;
|
||||
using std::stringstream;
|
||||
using mxs::SpinLockGuard;
|
||||
|
||||
AdminClient::AdminClient(int fd, const struct sockaddr_storage& addr, int timeout):
|
||||
m_fd(fd),
|
||||
m_last_activity(atomic_read_int64(&hkheartbeat)),
|
||||
m_addr(addr)
|
||||
{
|
||||
}
|
||||
|
||||
void AdminClient::close_connection()
|
||||
{
|
||||
SpinLockGuard guard(m_lock);
|
||||
|
||||
if (m_fd != -1)
|
||||
{
|
||||
close(m_fd);
|
||||
m_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
AdminClient::~AdminClient()
|
||||
{
|
||||
close_connection();
|
||||
}
|
||||
|
||||
static bool read_request(int fd, string& output)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
char buf[1024];
|
||||
int bufsize = sizeof(buf) - 1;
|
||||
|
||||
int rc = read(fd, buf, bufsize);
|
||||
|
||||
if (rc == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
buf[rc] = '\0';
|
||||
output += buf;
|
||||
|
||||
if (rc < bufsize)
|
||||
{
|
||||
/** Complete request read */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool write_response(int fd, const string& body)
|
||||
{
|
||||
return write(fd, body.c_str(), body.length()) != -1;
|
||||
}
|
||||
|
||||
void AdminClient::process()
|
||||
{
|
||||
string data;
|
||||
atomic_write_int64(&m_last_activity, hkheartbeat);
|
||||
|
||||
if (read_request(m_fd, data))
|
||||
{
|
||||
HttpResponse response(HTTP_400_BAD_REQUEST);
|
||||
SHttpRequest request(HttpRequest::parse(data));
|
||||
atomic_write_int64(&m_last_activity, hkheartbeat);
|
||||
|
||||
if (request.get())
|
||||
{
|
||||
response = resource_handle_request(*request);
|
||||
}
|
||||
|
||||
if (!write_response(m_fd, response.get_response()))
|
||||
{
|
||||
MXS_ERROR("Failed to write response to client: %d, %s", errno, mxs_strerror(errno));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Failed to read client request: %d, %s", errno, mxs_strerror(errno));
|
||||
}
|
||||
}
|
||||
@ -82,186 +82,13 @@ static void process_uri(string& uri, deque<string>& uri_parts)
|
||||
}
|
||||
}
|
||||
|
||||
static bool process_options(string& uri, map<string, string>& options)
|
||||
HttpRequest::HttpRequest(struct MHD_Connection *connection, string url, string method, json_t* data):
|
||||
m_json(data),
|
||||
m_resource(url),
|
||||
m_verb(method),
|
||||
m_connection(connection)
|
||||
{
|
||||
size_t pos = uri.find("?");
|
||||
|
||||
if (pos != string::npos)
|
||||
{
|
||||
string optionstr = uri.substr(pos + 1);
|
||||
uri.erase(pos);
|
||||
|
||||
char buf[optionstr.size() + 1];
|
||||
strcpy(buf, optionstr.c_str());
|
||||
char* saved;
|
||||
char* tok = strtok_r(buf, ",", &saved);
|
||||
|
||||
while (tok && *tok)
|
||||
{
|
||||
string opt(tok);
|
||||
pos = opt.find("=");
|
||||
|
||||
if (pos != string::npos)
|
||||
{
|
||||
string key = opt.substr(0, pos);
|
||||
string value = opt.substr(pos + 1);
|
||||
|
||||
if (key.length() && value.length())
|
||||
{
|
||||
options[key] = value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/** Invalid option */
|
||||
return false;
|
||||
}
|
||||
|
||||
tok = strtok_r(NULL, ",", &saved);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool process_headers(string& data, map<string, string>& headers)
|
||||
{
|
||||
size_t pos;
|
||||
|
||||
while ((pos = data.find("\r\n")) != string::npos)
|
||||
{
|
||||
string header_line = data.substr(0, pos);
|
||||
data.erase(0, pos + 2);
|
||||
|
||||
if (header_line.length() == 0)
|
||||
{
|
||||
/** End of headers */
|
||||
break;
|
||||
}
|
||||
|
||||
if ((pos = header_line.find(":")) != string::npos)
|
||||
{
|
||||
string key = header_line.substr(0, pos);
|
||||
header_line.erase(0, pos + 1);
|
||||
headers[key] = mxs::trim(header_line);
|
||||
}
|
||||
else
|
||||
{
|
||||
/** Invalid header */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HttpRequest* HttpRequest::parse(string data)
|
||||
{
|
||||
size_t pos = data.find("\r\n");
|
||||
|
||||
if (pos == string::npos)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
string request_line = data.substr(0, pos);
|
||||
data.erase(0, pos + 2);
|
||||
|
||||
/** Request method */
|
||||
if ((pos = request_line.find(" ")) == string::npos)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
string verb = request_line.substr(0, pos);
|
||||
request_line.erase(0, pos + 1);
|
||||
|
||||
/** Get the combined URL/option string */
|
||||
if ((pos = request_line.find(" ")) == string::npos)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
string uri = request_line.substr(0, pos);
|
||||
request_line.erase(0, pos + 1);
|
||||
|
||||
map<string, string> options;
|
||||
|
||||
/** Process request options */
|
||||
if (!process_options(uri, options))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Split the URI into separate parts */
|
||||
deque<string> uri_parts;
|
||||
process_uri(uri, uri_parts);
|
||||
|
||||
pos = request_line.find("\r\n");
|
||||
string http_version = request_line.substr(0, pos);
|
||||
request_line.erase(0, pos + 2);
|
||||
|
||||
|
||||
map<string, string> headers;
|
||||
|
||||
/** Process request headers */
|
||||
if (!process_headers(data, headers))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* The headers are now processed and consumed. The message body is
|
||||
* the only thing left in the request string and it should be a JSON object.
|
||||
* Attempt to parse it and return an error if it fails.
|
||||
*/
|
||||
|
||||
bool ok = false;
|
||||
HttpRequest* request = NULL;
|
||||
enum http_verb verb_value = string_to_http_verb(verb);
|
||||
json_error_t json_error = {};
|
||||
json_t* body = NULL;
|
||||
|
||||
/** Remove leading and trailing whitespace */
|
||||
if (data.length())
|
||||
{
|
||||
mxs::trim(data);
|
||||
}
|
||||
|
||||
if (http_version == "HTTP/1.1" && verb_value != HTTP_UNKNOWN)
|
||||
{
|
||||
if (data.length() && (body = json_loads(data.c_str(), 0, &json_error)) == NULL)
|
||||
{
|
||||
MXS_DEBUG("JSON error in input on line %d column %d: %s (%s)",
|
||||
json_error.line, json_error.column, json_error.text,
|
||||
data.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ok)
|
||||
{
|
||||
request = new HttpRequest();
|
||||
request->m_headers = headers;
|
||||
request->m_options = options;
|
||||
request->m_json.reset(body);
|
||||
request->m_json_string = data;
|
||||
request->m_resource = uri;
|
||||
request->m_resource_parts = uri_parts;
|
||||
request->m_verb = verb_value;
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
HttpRequest::HttpRequest():
|
||||
m_json(NULL),
|
||||
m_verb(HTTP_UNKNOWN)
|
||||
{
|
||||
|
||||
process_uri(url, m_resource_parts);
|
||||
}
|
||||
|
||||
HttpRequest::~HttpRequest()
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
using std::string;
|
||||
using std::stringstream;
|
||||
|
||||
HttpResponse::HttpResponse(enum http_code code, string response):
|
||||
HttpResponse::HttpResponse(int code, string response):
|
||||
m_body(response),
|
||||
m_code(code)
|
||||
{
|
||||
@ -33,13 +33,6 @@ HttpResponse::HttpResponse(enum http_code code, string response):
|
||||
m_headers["Last-Modified"] = m_headers["Date"];
|
||||
// TODO: Add proper ETags
|
||||
m_headers["ETag"] = "bm90LXlldC1pbXBsZW1lbnRlZAo=";
|
||||
|
||||
enum http_auth auth = mxs_admin_get_config().auth;
|
||||
|
||||
if (auth != HTTP_AUTH_NONE)
|
||||
{
|
||||
m_headers["WWW-Authenticate"] = http_auth_to_string(auth);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::~HttpResponse()
|
||||
@ -50,20 +43,17 @@ void HttpResponse::add_header(string name, string value)
|
||||
{
|
||||
m_headers[name] = value;
|
||||
}
|
||||
const map<string, string>& HttpResponse::get_headers() const
|
||||
{
|
||||
return m_headers;
|
||||
}
|
||||
|
||||
string HttpResponse::get_response() const
|
||||
{
|
||||
stringstream response;
|
||||
response << "HTTP/1.1 " << http_code_to_string(m_code) << "\r\n";
|
||||
|
||||
for (map<string, string>::const_iterator it = m_headers.begin();
|
||||
it != m_headers.end(); it++)
|
||||
{
|
||||
response << it->first << ": " << it->second << "\r\n";
|
||||
}
|
||||
|
||||
/** End of headers, add the body */
|
||||
response << "\r\n" << m_body;
|
||||
|
||||
return response.str();
|
||||
return m_body;
|
||||
}
|
||||
|
||||
int HttpResponse::get_code() const
|
||||
{
|
||||
return m_code;
|
||||
}
|
||||
|
||||
@ -20,57 +20,6 @@
|
||||
#include <maxscale/thread.h>
|
||||
|
||||
#include "http.hh"
|
||||
#include "adminclient.hh"
|
||||
|
||||
using std::deque;
|
||||
using std::string;
|
||||
|
||||
typedef deque<SAdminClient> ClientList;
|
||||
|
||||
/** The admin interface configuration */
|
||||
struct AdminConfig
|
||||
{
|
||||
string host;
|
||||
uint16_t port;
|
||||
enum http_auth auth;
|
||||
};
|
||||
|
||||
class AdminListener
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Create a new admin interface instance
|
||||
*
|
||||
* @param sock Listener socket for the interface
|
||||
*/
|
||||
AdminListener(int sock);
|
||||
~AdminListener();
|
||||
|
||||
/**
|
||||
* Start the admin interface
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Stop the admin listener
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Close timed out connections
|
||||
*/
|
||||
void check_timeouts();
|
||||
|
||||
private:
|
||||
void handle_clients();
|
||||
void handle_timeouts();
|
||||
AdminClient* accept_client();
|
||||
|
||||
int m_socket; /**< The network socket we listen on */
|
||||
int m_active; /**< Positive value if the admin is active */
|
||||
int m_timeout; /**< Network timeout in seconds */
|
||||
ClientList m_clients;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Start the administrative interface
|
||||
@ -83,10 +32,3 @@ bool mxs_admin_init();
|
||||
* @brief Shutdown the administrative interface
|
||||
*/
|
||||
void mxs_admin_shutdown();
|
||||
|
||||
/**
|
||||
* @brief Get the administative interface configuration
|
||||
*
|
||||
* @return A reference to the administrative interface configuration
|
||||
*/
|
||||
AdminConfig& mxs_admin_get_config();
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* 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: 2019-07-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 <maxscale/cppdefs.hh>
|
||||
|
||||
#include <map>
|
||||
#include <sys/socket.h>
|
||||
#include <tr1/memory>
|
||||
|
||||
#include <maxscale/atomic.h>
|
||||
#include <maxscale/spinlock.hh>
|
||||
|
||||
using mxs::SpinLock;
|
||||
|
||||
class AdminClient
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Create a new client connection
|
||||
*
|
||||
* @param fd Client socket
|
||||
* @param addr Network address where @c fd is connected to
|
||||
* @param timeout Network timeout for reads and writes
|
||||
*/
|
||||
AdminClient(int fd, const struct sockaddr_storage& addr, int timeout);
|
||||
|
||||
~AdminClient();
|
||||
|
||||
/**
|
||||
* @brief Process one request
|
||||
*/
|
||||
void process();
|
||||
|
||||
/**
|
||||
* @brief Close the connection
|
||||
*/
|
||||
void close_connection();
|
||||
|
||||
/**
|
||||
* @brief Get last activity timestamp
|
||||
*
|
||||
* @return The hkheartbeat of the last activity
|
||||
*/
|
||||
int64_t last_activity()
|
||||
{
|
||||
return atomic_read_int64(&m_last_activity);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_fd; /**< The client socket */
|
||||
int64_t m_last_activity;
|
||||
struct sockaddr_storage m_addr; /**< Network info for the client */
|
||||
SpinLock m_lock;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<AdminClient> SAdminClient;
|
||||
@ -20,245 +20,6 @@
|
||||
|
||||
using std::string;
|
||||
|
||||
/** Supported HTTP verbs */
|
||||
enum http_verb
|
||||
{
|
||||
HTTP_UNKNOWN,
|
||||
HTTP_GET,
|
||||
HTTP_PUT,
|
||||
HTTP_POST,
|
||||
HTTP_OPTIONS,
|
||||
HTTP_PATCH,
|
||||
HTTP_HEAD
|
||||
};
|
||||
|
||||
/** Possible HTTP return codes */
|
||||
enum http_code
|
||||
{
|
||||
HTTP_200_OK,
|
||||
HTTP_201_CREATED,
|
||||
HTTP_202_ACCEPTED,
|
||||
HTTP_204_NO_CONTENT,
|
||||
HTTP_301_MOVED_PERMANENTLY,
|
||||
HTTP_302_FOUND,
|
||||
HTTP_303_SEE_OTHER,
|
||||
HTTP_304_NOT_MODIFIED,
|
||||
HTTP_307_TEMPORARY_REDIRECT,
|
||||
HTTP_308_PERMANENT_REDIRECT,
|
||||
HTTP_400_BAD_REQUEST,
|
||||
HTTP_401_UNAUTHORIZED,
|
||||
HTTP_403_FORBIDDEN,
|
||||
HTTP_404_NOT_FOUND,
|
||||
HTTP_405_METHOD_NOT_ALLOWED,
|
||||
HTTP_406_NOT_ACCEPTABLE,
|
||||
HTTP_409_CONFLICT,
|
||||
HTTP_411_LENGTH_REQUIRED,
|
||||
HTTP_412_PRECONDITION_FAILED,
|
||||
HTTP_413_PAYLOAD_TOO_LARGE,
|
||||
HTTP_414_URI_TOO_LONG,
|
||||
HTTP_415_UNSUPPORTED_MEDIA_TYPE,
|
||||
HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
HTTP_423_LOCKED,
|
||||
HTTP_428_PRECONDITION_REQUIRED,
|
||||
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE,
|
||||
HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
HTTP_501_NOT_IMPLEMENTED,
|
||||
HTTP_502_BAD_GATEWAY,
|
||||
HTTP_503_SERVICE_UNAVAILABLE,
|
||||
HTTP_504_GATEWAY_TIMEOUT,
|
||||
HTTP_505_HTTP_VERSION_NOT_SUPPORTED,
|
||||
HTTP_506_VARIANT_ALSO_NEGOTIATES,
|
||||
HTTP_507_INSUFFICIENT_STORAGE,
|
||||
HTTP_508_LOOP_DETECTED,
|
||||
HTTP_510_NOT_EXTENDED
|
||||
};
|
||||
|
||||
/** Supported authentication types */
|
||||
enum http_auth
|
||||
{
|
||||
HTTP_AUTH_NONE,
|
||||
HTTP_AUTH_BASIC,
|
||||
HTTP_AUTH_DIGEST
|
||||
} ;
|
||||
|
||||
/**
|
||||
* @brief Convert auth type to string
|
||||
*
|
||||
* @param auth Authentication value to convert
|
||||
*
|
||||
* @return Converted value in string format
|
||||
*/
|
||||
static inline const char* http_auth_to_string(enum http_auth auth)
|
||||
{
|
||||
switch (auth)
|
||||
{
|
||||
case HTTP_AUTH_NONE:
|
||||
return "None";
|
||||
case HTTP_AUTH_BASIC:
|
||||
return "Basic";
|
||||
case HTTP_AUTH_DIGEST:
|
||||
return "Digest";
|
||||
|
||||
default:
|
||||
ss_dassert(false);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert string to HTTP verb
|
||||
*
|
||||
* @param verb String containing HTTP verb
|
||||
*
|
||||
* @return Enum value of the verb
|
||||
*/
|
||||
static inline enum http_verb string_to_http_verb(string& verb)
|
||||
{
|
||||
if (verb == "GET")
|
||||
{
|
||||
return HTTP_GET;
|
||||
}
|
||||
else if (verb == "POST")
|
||||
{
|
||||
return HTTP_POST;
|
||||
}
|
||||
else if (verb == "PUT")
|
||||
{
|
||||
return HTTP_PUT;
|
||||
}
|
||||
else if (verb == "PATCH")
|
||||
{
|
||||
return HTTP_PATCH;
|
||||
}
|
||||
else if (verb == "OPTIONS")
|
||||
{
|
||||
return HTTP_OPTIONS;
|
||||
}
|
||||
else if (verb == "HEAD")
|
||||
{
|
||||
return HTTP_HEAD;
|
||||
}
|
||||
|
||||
return HTTP_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert HTTP verb enum to string
|
||||
*
|
||||
* @param verb Enum to convert
|
||||
*
|
||||
* @return String representation of the enum
|
||||
*/
|
||||
static inline const char* http_verb_to_string(enum http_verb verb)
|
||||
{
|
||||
switch (verb)
|
||||
{
|
||||
case HTTP_GET:
|
||||
return "GET";
|
||||
case HTTP_POST:
|
||||
return "POST";
|
||||
case HTTP_PUT:
|
||||
return "PUT";
|
||||
case HTTP_PATCH:
|
||||
return "PATCH";
|
||||
case HTTP_OPTIONS:
|
||||
return "OPTIONS";
|
||||
case HTTP_HEAD:
|
||||
return "HEAD";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert HTTP code to string
|
||||
*
|
||||
* @param code The code to convert
|
||||
*
|
||||
* @return The HTTP/1.1 string version of the code
|
||||
*/
|
||||
static inline const char* http_code_to_string(enum http_code code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case HTTP_200_OK:
|
||||
return "200 OK";
|
||||
case HTTP_201_CREATED:
|
||||
return "201 Created";
|
||||
case HTTP_202_ACCEPTED:
|
||||
return "202 Accepted";
|
||||
case HTTP_204_NO_CONTENT:
|
||||
return "204 No Content";
|
||||
case HTTP_301_MOVED_PERMANENTLY:
|
||||
return "301 Moved Permanently";
|
||||
case HTTP_302_FOUND:
|
||||
return "302 Found";
|
||||
case HTTP_303_SEE_OTHER:
|
||||
return "303 See Other";
|
||||
case HTTP_304_NOT_MODIFIED:
|
||||
return "304 Not Modified";
|
||||
case HTTP_307_TEMPORARY_REDIRECT:
|
||||
return "307 Temporary Redirect";
|
||||
case HTTP_308_PERMANENT_REDIRECT:
|
||||
return "308 Permanent Redirect";
|
||||
case HTTP_400_BAD_REQUEST:
|
||||
return "400 Bad Request";
|
||||
case HTTP_401_UNAUTHORIZED:
|
||||
return "401 Unauthorized";
|
||||
case HTTP_403_FORBIDDEN:
|
||||
return "403 Forbidden";
|
||||
case HTTP_404_NOT_FOUND:
|
||||
return "404 Not Found";
|
||||
case HTTP_405_METHOD_NOT_ALLOWED:
|
||||
return "405 Method Not Allowed";
|
||||
case HTTP_406_NOT_ACCEPTABLE:
|
||||
return "406 Not Acceptable";
|
||||
case HTTP_409_CONFLICT:
|
||||
return "409 Conflict";
|
||||
case HTTP_411_LENGTH_REQUIRED:
|
||||
return "411 Length Required";
|
||||
case HTTP_412_PRECONDITION_FAILED:
|
||||
return "412 Precondition Failed";
|
||||
case HTTP_413_PAYLOAD_TOO_LARGE:
|
||||
return "413 Payload Too Large";
|
||||
case HTTP_414_URI_TOO_LONG:
|
||||
return "414 URI Too Long";
|
||||
case HTTP_415_UNSUPPORTED_MEDIA_TYPE:
|
||||
return "415 Unsupported Media Type";
|
||||
case HTTP_422_UNPROCESSABLE_ENTITY:
|
||||
return "422 Unprocessable Entity";
|
||||
case HTTP_423_LOCKED:
|
||||
return "423 Locked";
|
||||
case HTTP_428_PRECONDITION_REQUIRED:
|
||||
return "428 Precondition Required";
|
||||
case HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE:
|
||||
return "431 Request Header Fields Too Large";
|
||||
case HTTP_500_INTERNAL_SERVER_ERROR:
|
||||
return "500 Internal Server Error";
|
||||
case HTTP_501_NOT_IMPLEMENTED:
|
||||
return "501 Not Implemented";
|
||||
case HTTP_502_BAD_GATEWAY:
|
||||
return "502 Bad Gateway";
|
||||
case HTTP_503_SERVICE_UNAVAILABLE:
|
||||
return "503 Service Unavailable";
|
||||
case HTTP_504_GATEWAY_TIMEOUT:
|
||||
return "504 Gateway Timeout";
|
||||
case HTTP_505_HTTP_VERSION_NOT_SUPPORTED:
|
||||
return "505 HTTP Version Not Supported";
|
||||
case HTTP_506_VARIANT_ALSO_NEGOTIATES:
|
||||
return "506 Variant Also Negotiates";
|
||||
case HTTP_507_INSUFFICIENT_STORAGE:
|
||||
return "507 Insufficient Storage";
|
||||
case HTTP_508_LOOP_DETECTED:
|
||||
return "508 Loop Detected";
|
||||
case HTTP_510_NOT_EXTENDED:
|
||||
return "510 Not Extended";
|
||||
default:
|
||||
ss_dassert(false);
|
||||
return "500 Internal Server Error";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the current HTTP-date
|
||||
*
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
#include <string>
|
||||
#include <tr1/memory>
|
||||
#include <cstdint>
|
||||
#include <microhttpd.h>
|
||||
|
||||
#include <maxscale/jansson.hh>
|
||||
#include <maxscale/utils.hh>
|
||||
@ -36,6 +37,22 @@ class HttpRequest;
|
||||
/** Typedef for managed pointer */
|
||||
typedef std::shared_ptr<HttpRequest> SHttpRequest;
|
||||
|
||||
static int value_iterator(void *cls,
|
||||
enum MHD_ValueKind kind,
|
||||
const char *key,
|
||||
const char *value)
|
||||
{
|
||||
std::pair<string, string>* cmp = (std::pair<string, string>*)cls;
|
||||
|
||||
if (cmp->first == key)
|
||||
{
|
||||
cmp->second = value;
|
||||
return MHD_NO;
|
||||
}
|
||||
|
||||
return MHD_YES;
|
||||
}
|
||||
|
||||
class HttpRequest
|
||||
{
|
||||
public:
|
||||
@ -46,7 +63,7 @@ public:
|
||||
*
|
||||
* @return Parsed statement or NULL if request is not valid
|
||||
*/
|
||||
static HttpRequest* parse(string request);
|
||||
HttpRequest(struct MHD_Connection *connection, string url, string method, json_t* data);
|
||||
|
||||
~HttpRequest();
|
||||
|
||||
@ -55,23 +72,11 @@ public:
|
||||
*
|
||||
* @return One of the HTTP verb values
|
||||
*/
|
||||
enum http_verb get_verb() const
|
||||
const string& get_verb() const
|
||||
{
|
||||
return m_verb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if a request contains the specified header
|
||||
*
|
||||
* @param header Header to check
|
||||
*
|
||||
* @return True if header is in the request
|
||||
*/
|
||||
bool have_header(const string& header) const
|
||||
{
|
||||
return m_headers.find(header) != m_headers.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get header value
|
||||
*
|
||||
@ -81,15 +86,13 @@ public:
|
||||
*/
|
||||
string get_header(const string header) const
|
||||
{
|
||||
string rval;
|
||||
map<string, string>::const_iterator it = m_headers.find(header);
|
||||
std::pair<string, string> p;
|
||||
p.first = header;
|
||||
|
||||
if (it != m_headers.end())
|
||||
{
|
||||
rval = it->second;
|
||||
}
|
||||
MHD_get_connection_values(m_connection, MHD_HEADER_KIND,
|
||||
value_iterator, &p);
|
||||
|
||||
return rval;
|
||||
return p.second;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,15 +104,13 @@ public:
|
||||
*/
|
||||
string get_option(const string option) const
|
||||
{
|
||||
string rval;
|
||||
map<string, string>::const_iterator it = m_options.find(option);
|
||||
std::pair<string, string> p;
|
||||
p.first = option;
|
||||
|
||||
if (it != m_options.end())
|
||||
{
|
||||
rval = it->second;
|
||||
}
|
||||
MHD_get_connection_values(m_connection, MHD_GET_ARGUMENT_KIND,
|
||||
value_iterator, &p);
|
||||
|
||||
return rval;
|
||||
return p.second;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,15 +166,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
HttpRequest();
|
||||
HttpRequest(const HttpRequest&);
|
||||
HttpRequest& operator = (const HttpRequest&);
|
||||
|
||||
map<string, string> m_headers; /**< Request headers */
|
||||
map<string, string> m_options; /**< Request options */
|
||||
Closer<json_t*> m_json; /**< Request body */
|
||||
string m_json_string; /**< String version of @c m_json */
|
||||
string m_resource; /**< Requested resource */
|
||||
deque<string> m_resource_parts; /**< @c m_resource split into parts */
|
||||
enum http_verb m_verb; /**< Request method */
|
||||
string m_verb; /**< Request method */
|
||||
struct MHD_Connection* m_connection;
|
||||
};
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <tr1/memory>
|
||||
#include <microhttpd.h>
|
||||
|
||||
#include <maxscale/jansson.hh>
|
||||
|
||||
@ -39,7 +40,7 @@ public:
|
||||
* @param response Response body
|
||||
* @param code HTTP return code
|
||||
*/
|
||||
HttpResponse(enum http_code code = HTTP_200_OK, string response = "");
|
||||
HttpResponse(int code = MHD_HTTP_OK, string response = "");
|
||||
|
||||
~HttpResponse();
|
||||
|
||||
@ -51,6 +52,13 @@ public:
|
||||
*/
|
||||
void add_header(string name, string value);
|
||||
|
||||
/**
|
||||
* @brief Get headers for this response
|
||||
*
|
||||
* @return Map of headers and values
|
||||
*/
|
||||
const map<string, string>& get_headers() const;
|
||||
|
||||
/**
|
||||
* @brief Get the response in string format
|
||||
*
|
||||
@ -58,8 +66,15 @@ public:
|
||||
*/
|
||||
string get_response() const;
|
||||
|
||||
/**
|
||||
* @brief Get the HTTP response code
|
||||
*
|
||||
* @return The HTTP response code
|
||||
*/
|
||||
int get_code() const;
|
||||
|
||||
private:
|
||||
string m_body; /**< Message body */
|
||||
map<string, string> m_headers; /**< Message headers */
|
||||
enum http_code m_code; /**< The HTTP code for the response */
|
||||
int m_code; /**< The HTTP code for the response */
|
||||
};
|
||||
|
||||
@ -60,7 +60,7 @@ protected:
|
||||
virtual HttpResponse handle(HttpRequest& request)
|
||||
{
|
||||
ss_dassert(false);
|
||||
return HttpResponse(HTTP_500_INTERNAL_SERVER_ERROR);
|
||||
return HttpResponse(MHD_HTTP_INTERNAL_SERVER_ERROR);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -50,7 +50,7 @@ protected:
|
||||
{
|
||||
// TODO: Generate this via the inter-thread messaging system
|
||||
Closer<json_t*> servers(server_list_to_json());
|
||||
return HttpResponse(HTTP_200_OK, mxs::json_dump(servers, flags));
|
||||
return HttpResponse(MHD_HTTP_OK, mxs::json_dump(servers, flags));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -61,11 +61,11 @@ protected:
|
||||
// TODO: Generate this via the inter-thread messaging system
|
||||
Closer<json_t*> server_js(server_to_json(server));
|
||||
// Show one server
|
||||
return HttpResponse(HTTP_200_OK, mxs::json_dump(server_js, flags));
|
||||
return HttpResponse(MHD_HTTP_OK, mxs::json_dump(server_js, flags));
|
||||
}
|
||||
else
|
||||
{
|
||||
return HttpResponse(HTTP_404_NOT_FOUND);
|
||||
return HttpResponse(MHD_HTTP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,10 +80,10 @@ protected:
|
||||
|
||||
if (request.uri_part_count() == 1)
|
||||
{
|
||||
// TODO: Generate this via the inter-thread messaging system
|
||||
// TODO: Generate this via the inter-thread messaging system
|
||||
Closer<json_t*> all_services(service_list_to_json());
|
||||
// Show all services
|
||||
return HttpResponse(HTTP_200_OK, mxs::json_dump(all_services, flags));
|
||||
return HttpResponse(MHD_HTTP_OK, mxs::json_dump(all_services, flags));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -93,11 +93,11 @@ protected:
|
||||
{
|
||||
Closer<json_t*> service_js(service_to_json(service));
|
||||
// Show one service
|
||||
return HttpResponse(HTTP_200_OK, mxs::json_dump(service_js, flags));
|
||||
return HttpResponse(MHD_HTTP_OK, mxs::json_dump(service_js, flags));
|
||||
}
|
||||
else
|
||||
{
|
||||
return HttpResponse(HTTP_404_NOT_FOUND);
|
||||
return HttpResponse(MHD_HTTP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -114,7 +114,7 @@ protected:
|
||||
{
|
||||
Closer<json_t*> filters(filter_list_to_json());
|
||||
// Show all filters
|
||||
return HttpResponse(HTTP_200_OK, mxs::json_dump(filters, flags));
|
||||
return HttpResponse(MHD_HTTP_OK, mxs::json_dump(filters, flags));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -124,11 +124,11 @@ protected:
|
||||
{
|
||||
Closer<json_t*> filter_js(filter_to_json(filter));
|
||||
// Show one filter
|
||||
return HttpResponse(HTTP_200_OK, mxs::json_dump(filter_js, flags));
|
||||
return HttpResponse(MHD_HTTP_OK, mxs::json_dump(filter_js, flags));
|
||||
}
|
||||
else
|
||||
{
|
||||
return HttpResponse(HTTP_404_NOT_FOUND);
|
||||
return HttpResponse(MHD_HTTP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -145,7 +145,7 @@ protected:
|
||||
{
|
||||
Closer<json_t*> monitors(monitor_list_to_json());
|
||||
// Show all monitors
|
||||
return HttpResponse(HTTP_200_OK, mxs::json_dump(monitors, flags));
|
||||
return HttpResponse(MHD_HTTP_OK, mxs::json_dump(monitors, flags));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -155,11 +155,11 @@ protected:
|
||||
{
|
||||
Closer<json_t*> monitor_js(monitor_to_json(monitor));
|
||||
// Show one monitor
|
||||
return HttpResponse(HTTP_200_OK, mxs::json_dump(monitor_js, flags));
|
||||
return HttpResponse(MHD_HTTP_OK, mxs::json_dump(monitor_js, flags));
|
||||
}
|
||||
else
|
||||
{
|
||||
return HttpResponse(HTTP_404_NOT_FOUND);
|
||||
return HttpResponse(MHD_HTTP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -173,7 +173,7 @@ protected:
|
||||
if (request.uri_part_count() == 1)
|
||||
{
|
||||
// Show all sessions
|
||||
return HttpResponse(HTTP_200_OK);
|
||||
return HttpResponse(MHD_HTTP_OK);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -187,11 +187,11 @@ protected:
|
||||
Closer<json_t*> ses_json(session_to_json(session));
|
||||
session_put_ref(session);
|
||||
// Show session statistics
|
||||
return HttpResponse(HTTP_200_OK, mxs::json_dump(ses_json, flags));
|
||||
return HttpResponse(MHD_HTTP_OK, mxs::json_dump(ses_json, flags));
|
||||
}
|
||||
else
|
||||
{
|
||||
return HttpResponse(HTTP_404_NOT_FOUND);
|
||||
return HttpResponse(MHD_HTTP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -202,7 +202,7 @@ class UsersResource: public Resource
|
||||
protected:
|
||||
HttpResponse handle(HttpRequest& request)
|
||||
{
|
||||
return HttpResponse(HTTP_200_OK);
|
||||
return HttpResponse(MHD_HTTP_OK);
|
||||
}
|
||||
};
|
||||
|
||||
@ -216,17 +216,17 @@ protected:
|
||||
// Flush logs
|
||||
if (mxs_log_rotate() == 0)
|
||||
{
|
||||
return HttpResponse(HTTP_200_OK);
|
||||
return HttpResponse(MHD_HTTP_OK);
|
||||
}
|
||||
else
|
||||
{
|
||||
return HttpResponse(HTTP_500_INTERNAL_SERVER_ERROR);
|
||||
return HttpResponse(MHD_HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show log status
|
||||
return HttpResponse(HTTP_200_OK);
|
||||
return HttpResponse(MHD_HTTP_OK);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -237,7 +237,7 @@ protected:
|
||||
HttpResponse handle(HttpRequest& request)
|
||||
{
|
||||
// Show thread status
|
||||
return HttpResponse(HTTP_200_OK);
|
||||
return HttpResponse(MHD_HTTP_OK);
|
||||
}
|
||||
};
|
||||
|
||||
@ -247,7 +247,7 @@ protected:
|
||||
HttpResponse handle(HttpRequest& request)
|
||||
{
|
||||
// Show housekeeper tasks
|
||||
return HttpResponse(HTTP_200_OK);
|
||||
return HttpResponse(MHD_HTTP_OK);
|
||||
}
|
||||
};
|
||||
|
||||
@ -257,7 +257,7 @@ protected:
|
||||
HttpResponse handle(HttpRequest& request)
|
||||
{
|
||||
// Show modules
|
||||
return HttpResponse(HTTP_200_OK);
|
||||
return HttpResponse(MHD_HTTP_OK);
|
||||
}
|
||||
};
|
||||
|
||||
@ -275,7 +275,7 @@ public:
|
||||
protected:
|
||||
HttpResponse handle(HttpRequest& request)
|
||||
{
|
||||
return HttpResponse(HTTP_200_OK);
|
||||
return HttpResponse(MHD_HTTP_OK);
|
||||
}
|
||||
};
|
||||
|
||||
@ -296,7 +296,7 @@ public:
|
||||
protected:
|
||||
HttpResponse handle(HttpRequest& request)
|
||||
{
|
||||
return HttpResponse(HTTP_200_OK);
|
||||
return HttpResponse(MHD_HTTP_OK);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -23,7 +23,6 @@ add_executable(testmaxscalepcre2 testmaxscalepcre2.cc)
|
||||
add_executable(testmodulecmd testmodulecmd.cc)
|
||||
add_executable(testconfig testconfig.cc)
|
||||
add_executable(trxboundaryparser_profile trxboundaryparser_profile.cc)
|
||||
add_executable(testhttp testhttp.cc)
|
||||
target_link_libraries(test_atomic maxscale-common)
|
||||
target_link_libraries(test_adminusers maxscale-common)
|
||||
target_link_libraries(test_buffer maxscale-common)
|
||||
@ -49,7 +48,6 @@ target_link_libraries(testmaxscalepcre2 maxscale-common)
|
||||
target_link_libraries(testmodulecmd maxscale-common)
|
||||
target_link_libraries(testconfig maxscale-common)
|
||||
target_link_libraries(trxboundaryparser_profile maxscale-common)
|
||||
target_link_libraries(testhttp maxscale-common)
|
||||
add_test(TestAtomic test_atomic)
|
||||
add_test(TestAdminUsers test_adminusers)
|
||||
add_test(TestBuffer test_buffer)
|
||||
@ -81,7 +79,6 @@ add_test(TestTrxCompare_Select test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../..
|
||||
add_test(TestTrxCompare_Set test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/set.test)
|
||||
add_test(TestTrxCompare_Update test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/update.test)
|
||||
add_test(TestTrxCompare_MaxScale test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/maxscale.test)
|
||||
add_test(TestHttp testhttp)
|
||||
|
||||
# This test requires external dependencies and thus cannot be run
|
||||
# as a part of the core test set
|
||||
|
||||
@ -1,466 +0,0 @@
|
||||
/*
|
||||
* 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: 2019-07-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 "../maxscale/httprequest.hh"
|
||||
#include "../maxscale/httpresponse.hh"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
using std::stringstream;
|
||||
using std::string;
|
||||
|
||||
#define TEST(a, b, ...) do{if (!(a)){printf(b "\n", ##__VA_ARGS__);return 1;}}while(false)
|
||||
|
||||
const char* verbs_pass[] =
|
||||
{
|
||||
"GET",
|
||||
"PUT",
|
||||
"POST",
|
||||
"OPTIONS",
|
||||
"PATCH",
|
||||
"HEAD",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char* verbs_fail[] =
|
||||
{
|
||||
"LOAD",
|
||||
"STORE",
|
||||
"PUBLISH",
|
||||
"Something that's not a verb",
|
||||
"⬠",
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct
|
||||
{
|
||||
const char* input;
|
||||
const char* output;
|
||||
} paths_pass[] =
|
||||
{
|
||||
{ "/", "/" },
|
||||
{ "*", "*" },
|
||||
{ "/test/", "/test" },
|
||||
{ "/test", "/test" },
|
||||
{ "/servers/list", "/servers/list" },
|
||||
{ "/servers/list/", "/servers/list" },
|
||||
{ "/?test=true", "/" },
|
||||
{ "/test/?test=y", "/test" },
|
||||
{ "/?", "/" },
|
||||
{}
|
||||
};
|
||||
|
||||
const char* paths_fail[] =
|
||||
{
|
||||
"-strikethrough-",
|
||||
"_underline_",
|
||||
"*bold*",
|
||||
"?",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char* proto_pass[] =
|
||||
{
|
||||
"HTTP/1.1",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char* proto_fail[] =
|
||||
{
|
||||
"HTTP/2.0",
|
||||
"SMTP/0.0",
|
||||
"CDC/1.0",
|
||||
NULL
|
||||
};
|
||||
|
||||
int test_basic()
|
||||
{
|
||||
/** Test parts that should pass */
|
||||
for (int i = 0; verbs_pass[i]; i++)
|
||||
{
|
||||
for (int j = 0; paths_pass[j].input; j++)
|
||||
{
|
||||
for (int k = 0; proto_pass[k]; k++)
|
||||
{
|
||||
stringstream ss;
|
||||
ss << verbs_pass[i] << " " << paths_pass[j].input << " " << proto_pass[k] << "\r\n\r\n";
|
||||
SHttpRequest parser(HttpRequest::parse(ss.str()));
|
||||
TEST(parser.get() != NULL, "Valid HTTP request should be parsed: %s", ss.str().c_str());
|
||||
TEST(parser->get_uri() == string(paths_pass[j].output),
|
||||
"The request path '%s' should be correct: %s",
|
||||
paths_pass[j].output, parser->get_uri().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Test parts that should fail */
|
||||
for (int i = 0; verbs_fail[i]; i++)
|
||||
{
|
||||
for (int j = 0; paths_fail[j]; j++)
|
||||
{
|
||||
for (int k = 0; proto_fail[k]; k++)
|
||||
{
|
||||
stringstream ss;
|
||||
ss << verbs_fail[i] << " " << paths_fail[j] << " " << proto_fail[k] << "\r\n\r\n";
|
||||
SHttpRequest parser(HttpRequest::parse(ss.str()));
|
||||
TEST(parser.get() == NULL, "Invalid HTTP request should not be parsed: %s", ss.str().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct
|
||||
{
|
||||
const char* key;
|
||||
const char* value;
|
||||
} headers_pass[] =
|
||||
{
|
||||
{"Accept", "*/*"},
|
||||
{"User-Agent", "curl/7.51.0"},
|
||||
{"Authorization", "bWF4dXNlcjptYXhwd2QK"},
|
||||
{"Content-Type", "application/json"},
|
||||
{"Date", "1.1.2017 10:10:10"},
|
||||
{"Host", "127.0.0.1:8080"},
|
||||
{"If-Match", "bWF4dXNlcjptYXhwd2QK"},
|
||||
{"If-Modified-Since", "Mon, 18 Nov 2013 08:14:29 -0600"},
|
||||
{"If-None-Match", "bWF4dXNlcjptYXhwd2QK"},
|
||||
{"If-Unmodified-Since", "Mon, 18 Nov 2013 08:14:29 -0600"},
|
||||
{"X-HTTP-Method-Override", "PATCH"},
|
||||
{"Allow", "GET, PATCH, PUT"},
|
||||
{"Accept-Patch", "application/json-patch"},
|
||||
{"Date", "Mon, 18 Nov 2013 08:14:29 -0600"},
|
||||
{"ETag", "bWF4dXNlcjptYXhwd2QK"},
|
||||
{"Last-Modified", "Mon, 18 Nov 2013 08:14:29 -0600"},
|
||||
{"Location", "/servers/server1"},
|
||||
{"WWW-Authenticate", "Basic"},
|
||||
{0}
|
||||
};
|
||||
|
||||
int test_headers()
|
||||
{
|
||||
for (int i = 0; headers_pass[i].key; i++)
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "GET / HTTP/1.1\r\n" << headers_pass[i].key << ": "
|
||||
<< headers_pass[i].value << "\r\n\r\n";
|
||||
SHttpRequest parser(HttpRequest::parse(ss.str()));
|
||||
TEST(parser.get() != NULL, "Valid HTTP request should be parsed: %s", ss.str().c_str());
|
||||
TEST(parser->get_header(headers_pass[i].key).length() > 0, "Header should be found");
|
||||
TEST(parser->get_header(headers_pass[i].key) == string(headers_pass[i].value),
|
||||
"Header value should be correct: %s", parser->get_header(headers_pass[i].key).c_str());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
The following JSON tests are imported from the Jansson test suite
|
||||
*/
|
||||
|
||||
const char* body_pass[] =
|
||||
{
|
||||
"{\"i\": [1]}",
|
||||
"{\"i\": [1.8011670033376514e-308]}",
|
||||
"{\"i\": [123.456e78]}",
|
||||
"{\"i\": [-1]}",
|
||||
"{\"i\": [-123]}",
|
||||
"{\"i\": [\"\u0821 three-byte UTF-8\"]}",
|
||||
"{\"i\": [123]}",
|
||||
"{\"i\": [1E+2]}",
|
||||
"{\"i\": [123e45]}",
|
||||
"{\"i\": [false]}",
|
||||
"{\"i\": [\"\u002c one-byte UTF-8\"]}",
|
||||
"{\"i\": {\"a\":[]}}",
|
||||
"{\"i\": [\"abcdefghijklmnopqrstuvwxyz1234567890 \"]}",
|
||||
"{\"i\": [-0]}",
|
||||
"{\"i\": [\"\"]}",
|
||||
"{\"i\": [1,2,3,4]}",
|
||||
"{\"i\": [\"a\", \"b\", \"c\"]}",
|
||||
"{\"foo\": \"bar\", \"core\": \"dump\"}",
|
||||
"{\"i\": [true, false, true, true, null, false]}",
|
||||
"{\"b\": [\"a\"]}",
|
||||
"{\"i\": [true]}",
|
||||
"{\"i\": {}}",
|
||||
"{\"i\": [{}]}",
|
||||
"{\"i\": [0]}",
|
||||
"{\"i\": [123.456789]}",
|
||||
"{\"i\": [1e+2]}",
|
||||
"{\"i\": [\"\u0123 two-byte UTF-8\"]}",
|
||||
"{\"i\": [123e-10000000]}",
|
||||
"{\"i\": [null]}",
|
||||
"{\"i\": [\"€þıœəßð some utf-8 ĸʒ×ŋµåäö𝄞\"]}",
|
||||
"{\"i\": [1e-2]}",
|
||||
"{\"i\": [1E22]}",
|
||||
"{\"i\": [1E-2]}",
|
||||
"{\"i\": []}",
|
||||
|
||||
/** Additional tests */
|
||||
"{\"this is\": \"a JSON value\"}",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char* body_fail[] =
|
||||
{
|
||||
"{{}",
|
||||
"{[-123foo]}",
|
||||
"{[1,}",
|
||||
"{[troo}",
|
||||
"{{\"a\"}",
|
||||
"{[-123123123123123123123123123123]}",
|
||||
"{{[}",
|
||||
"{[1.]}",
|
||||
"{[1ea]}",
|
||||
"{['}",
|
||||
"{[-012]}",
|
||||
"{[012]}",
|
||||
"{{\"a}",
|
||||
"{[{}",
|
||||
"{[123123123123123123123123123123]}",
|
||||
"{[1,2,3]}",
|
||||
"{foo}",
|
||||
"{[\"\a <-- invalid escape\"]}",
|
||||
"{[{}}",
|
||||
"{[\" <-- tab character\"]}",
|
||||
"{[\"a\"}",
|
||||
"{{'a'}",
|
||||
"{[,}",
|
||||
"{{\"a\":}",
|
||||
"{{\"a\":\"a}",
|
||||
"{[-123123e100000]}",
|
||||
"{[\"null escape \u0000 not allowed\"]}",
|
||||
"{[1,}",
|
||||
"{2,}",
|
||||
"{3,}",
|
||||
"{4,}",
|
||||
"{5,}",
|
||||
"{]}",
|
||||
"{null}",
|
||||
"{[-123.123foo]}",
|
||||
"{[}",
|
||||
"{aå}",
|
||||
"{{\"foo\u0000bar\": 42}{\"a\":\"a\" 123}}",
|
||||
"{[\"a}",
|
||||
"{[123123e100000]}",
|
||||
"{[1e]}",
|
||||
"{[1,]}",
|
||||
"{{,}",
|
||||
"{[-foo]}",
|
||||
"{å}",
|
||||
"{{\"}",
|
||||
"{[\"null byte not allowed\"]}",
|
||||
"{[}",
|
||||
"{[1,2,3]foo}",
|
||||
|
||||
/** Additional tests */
|
||||
"Hello World!",
|
||||
"<p>I am a paragraph</p>",
|
||||
"",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char* body_verbs_pass[] =
|
||||
{
|
||||
"PUT",
|
||||
"POST",
|
||||
"PATCH",
|
||||
NULL
|
||||
};
|
||||
|
||||
int test_message_body()
|
||||
{
|
||||
for (int i = 0; body_pass[i]; i++)
|
||||
{
|
||||
for (int j = 0; body_verbs_pass[j]; j++)
|
||||
{
|
||||
/** Only PUT/POST/PATCH methods should have request bodies */
|
||||
stringstream ss;
|
||||
ss << body_verbs_pass[j] << " / HTTP/1.1\r\n\r\n" << body_pass[i];
|
||||
SHttpRequest parser(HttpRequest::parse(ss.str()));
|
||||
TEST(parser.get() != NULL, "Valid request body should be parsed: %s",
|
||||
ss.str().c_str());
|
||||
TEST(parser->get_json(), "Body should be found");
|
||||
TEST(parser->get_json_str() == body_pass[i], "Body value should be correct: %s",
|
||||
parser->get_json_str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; body_pass[i]; i++)
|
||||
{
|
||||
for (int j = 0; verbs_pass[j]; j++)
|
||||
{
|
||||
stringstream ss;
|
||||
ss << verbs_pass[j] << " / HTTP/1.1\r\n\r\n" << body_fail[i];
|
||||
SHttpRequest parser(HttpRequest::parse(ss.str()));
|
||||
TEST(parser.get() == NULL, "Invalid request body should not be parsed: %s",
|
||||
ss.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct
|
||||
{
|
||||
const char* input;
|
||||
const char* key;
|
||||
const char* value;
|
||||
} options_pass[] =
|
||||
{
|
||||
{ "/", "", "" },
|
||||
{ "*", "", "" },
|
||||
{ "/?a=b", "a", "b" },
|
||||
{ "/?a=b,c=d", "a", "b" },
|
||||
{ "/?a=b,c=d", "c", "d" },
|
||||
{ "/test?q=w", "q", "w" },
|
||||
{ "/servers/list?all=false", "all", "false" },
|
||||
{ "/servers/list/?pretty=true", "pretty", "true"},
|
||||
{ "/?test=true", "test", "true" },
|
||||
{ "/test/?test=y", "test", "y" },
|
||||
{ "/?", "", "" },
|
||||
{}
|
||||
};
|
||||
|
||||
const char* options_fail[] =
|
||||
{
|
||||
"/?,",
|
||||
"/??",
|
||||
"/test?/",
|
||||
"/test/?a,b",
|
||||
"/test?a,",
|
||||
NULL
|
||||
};
|
||||
|
||||
int test_options()
|
||||
{
|
||||
for (int i = 0; options_pass[i].input; i++)
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "GET " << options_pass[i].input << " HTTP/1.1\r\n\r\n";
|
||||
SHttpRequest parser(HttpRequest::parse(ss.str()));
|
||||
|
||||
TEST(parser.get() != NULL, "Valid option should be parsed: %s", ss.str().c_str());
|
||||
TEST(parser->get_option(options_pass[i].key) == options_pass[i].value,
|
||||
"The option value for '%s' should be '%s': %s",
|
||||
options_pass[i].key, options_pass[i].value,
|
||||
parser->get_option(options_pass[i].key).c_str());
|
||||
}
|
||||
|
||||
for (int i = 0; options_fail[i]; i++)
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "GET " << options_fail[i] << " HTTP/1.1\r\n\r\n";
|
||||
SHttpRequest parser(HttpRequest::parse(ss.str()));
|
||||
TEST(parser.get() == NULL, "Invalid option should not be parsed: %s", ss.str().c_str());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_response()
|
||||
{
|
||||
TEST(HttpResponse().get_response().find("200 OK") != string::npos,
|
||||
"Default constructor should return a 200 OK with no body");
|
||||
TEST(HttpResponse(HTTP_200_OK, "Test").get_response().find("\r\n\r\nTest") != string::npos,
|
||||
"Custom payload should be found in the response");
|
||||
TEST(HttpResponse(HTTP_204_NO_CONTENT).get_response().find("204 No Content") != string::npos,
|
||||
"Using custom header should generate correct response");
|
||||
|
||||
HttpResponse response(HTTP_502_BAD_GATEWAY, "A Bad gateway");
|
||||
TEST(response.get_response().find("\r\n\r\nA Bad gateway") != string::npos &&
|
||||
response.get_response().find("502 Bad Gateway") != string::npos,
|
||||
"Both custom response body and return code should be found");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct
|
||||
{
|
||||
const char* input;
|
||||
const char* value;
|
||||
int offset;
|
||||
size_t size;
|
||||
} resource_parts[] =
|
||||
{
|
||||
{"/", "", 0, 1},
|
||||
{"/?a=b", "", 0, 1},
|
||||
|
||||
{"/servers/", "servers", 0, 1},
|
||||
{"/servers", "servers", 0, 1},
|
||||
{"servers", "servers", 0, 1},
|
||||
|
||||
{"/servers/my-server", "servers", 0, 2},
|
||||
{"/servers/my-server/", "servers", 0, 2},
|
||||
{"servers/my-server", "servers", 0, 2},
|
||||
{"/servers/my-server", "my-server", 1, 2},
|
||||
{"/servers/my-server/", "my-server", 1, 2},
|
||||
{"servers/my-server", "my-server", 1, 2},
|
||||
|
||||
{"/servers/my-server/user", "servers", 0, 3},
|
||||
{"/servers/my-server/user", "servers", 0, 3},
|
||||
{"servers/my-server/user", "servers", 0, 3},
|
||||
{"/servers/my-server/user", "my-server", 1, 3},
|
||||
{"/servers/my-server/user/", "my-server", 1, 3},
|
||||
{"servers/my-server/user", "my-server", 1, 3},
|
||||
{"/servers/my-server/user", "user", 2, 3},
|
||||
{"/servers/my-server/user/", "user", 2, 3},
|
||||
{"servers/my-server/user", "user", 2, 3},
|
||||
|
||||
{"/servers?a=b", "servers", 0, 1},
|
||||
{"/servers/?a=b", "servers", 0, 1},
|
||||
{"servers/?a=b", "servers", 0, 1},
|
||||
{"/servers/my-server?a=b", "my-server", 1, 2},
|
||||
{"/servers/my-server/?a=b", "my-server", 1, 2},
|
||||
{"servers/my-server/?a=b", "my-server", 1, 2},
|
||||
{"/servers/my-server/user?a=b", "user", 2, 3},
|
||||
{"/servers/my-server/user/?a=b", "user", 2, 3},
|
||||
{"servers/my-server/user?a=b", "user", 2, 3},
|
||||
|
||||
{}
|
||||
};
|
||||
|
||||
int test_resource_parts()
|
||||
{
|
||||
for (int i = 0; resource_parts[i].input; i++)
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "GET " << resource_parts[i].input << " HTTP/1.1\r\n\r\n";
|
||||
SHttpRequest request(HttpRequest::parse(ss.str()));
|
||||
|
||||
TEST(request.get(), "Request should be OK: %s", ss.str().c_str());
|
||||
|
||||
TEST(request->uri_part_count() == resource_parts[i].size,
|
||||
"Request should have %lu parts: %lu", resource_parts[i].size,
|
||||
request->uri_part_count());
|
||||
|
||||
string value = request->uri_part(resource_parts[i].offset);
|
||||
TEST(value == resource_parts[i].value,
|
||||
"Request part at %d should be '%s': %s", resource_parts[i].offset,
|
||||
resource_parts[i].value, value.c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
rc += test_basic();
|
||||
rc += test_headers();
|
||||
rc += test_message_body();
|
||||
rc += test_response();
|
||||
rc += test_resource_parts();
|
||||
|
||||
return rc;
|
||||
}
|
||||
Reference in New Issue
Block a user