diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index b718f1dfd..9bf853d82 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -14,7 +14,7 @@ add_library(maxscale-common SHARED hashtable.cc hint.cc housekeeper.cc - httpparser.cc + httprequest.cc listener.cc load_utils.cc log_manager.cc diff --git a/server/core/adminclient.cc b/server/core/adminclient.cc index 87629427a..348659d86 100644 --- a/server/core/adminclient.cc +++ b/server/core/adminclient.cc @@ -12,7 +12,7 @@ */ #include "maxscale/adminclient.hh" -#include "maxscale/httpparser.hh" +#include "maxscale/httprequest.hh" #include #include @@ -77,9 +77,14 @@ static bool read_request(int fd, string& output) return true; } -static bool write_response(int fd, string input) +static bool write_response(int fd, enum http_code code, const string& body) { - return write(fd, input.c_str(), input.length()) != -1; + string payload = "HTTP/1.1 "; + payload += http_code_to_string(code); + payload += "\r\n\r\n"; + payload += body; + + return write(fd, payload.c_str(), payload.length()) != -1; } void AdminClient::process() @@ -89,15 +94,12 @@ void AdminClient::process() if (read_request(m_fd, request)) { - SHttpParser parser(HttpParser::parse(request)); + SHttpRequest parser(HttpRequest::parse(request)); - string status = parser.get() ? "200 OK" : "400 Bad Request"; - - stringstream resp; - resp << "HTTP/1.1 " << status << "\r\n\r\n" << parser->get_body() << "\r\n"; + enum http_code status = parser.get() ? HTTP_200_OK : HTTP_400_BAD_REQUEST; atomic_write_int64(&m_last_activity, hkheartbeat); - write_response(m_fd, resp.str()); + write_response(m_fd, HTTP_200_OK, parser->get_body()); } else { diff --git a/server/core/httpparser.cc b/server/core/httprequest.cc similarity index 52% rename from server/core/httpparser.cc rename to server/core/httprequest.cc index 30de6379e..50ec69e97 100644 --- a/server/core/httpparser.cc +++ b/server/core/httprequest.cc @@ -11,41 +11,34 @@ * Public License. */ -#include "maxscale/httpparser.hh" +#include "maxscale/httprequest.hh" #include -static enum http_verb string_to_http_verb(string& verb) +/** TODO: Move this to a C++ string utility header */ +namespace maxscale { - if (verb == "GET") +static inline string& trim(string& str) +{ + while (isspace(*str.begin())) { - 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; + str.erase(str.begin()); } - return HTTP_UNKNOWN; + while (isspace(*str.rbegin())) + { + str.erase(str.rbegin().base()); + } + + return str; +} } -HttpParser* HttpParser::parse(string request) +HttpRequest* HttpRequest::parse(string data) { - size_t pos = request.find("\r\n"); - string request_line = request.substr(0, pos); - request.erase(0, pos + 2); + size_t pos = data.find("\r\n"); + string request_line = data.substr(0, pos); + data.erase(0, pos + 2); pos = request_line.find(" "); string verb = request_line.substr(0, pos); @@ -61,10 +54,10 @@ HttpParser* HttpParser::parse(string request) map headers; - while ((pos = request.find("\r\n")) != string::npos) + while ((pos = data.find("\r\n")) != string::npos) { - string header_line = request.substr(0, pos); - request.erase(0, pos + 2); + string header_line = data.substr(0, pos); + data.erase(0, pos + 2); if (header_line.length() == 0) { @@ -76,37 +69,34 @@ HttpParser* HttpParser::parse(string request) { string key = header_line.substr(0, pos); header_line.erase(0, pos + 1); - - while (isspace(header_line[0])) - { - header_line.erase(0, 1); - } - - headers[key] = header_line; + headers[key] = mxs::trim(header_line); } } - HttpParser* parser = NULL; + /** The headers are now processed and consumed. The message body is + * the only thing left in the request string. */ + + HttpRequest* request = NULL; enum http_verb verb_value = string_to_http_verb(verb); if (http_version == "HTTP/1.1" && verb_value != HTTP_UNKNOWN) { - parser = new HttpParser(); - parser->m_verb = verb_value; - parser->m_resource = uri; - parser->m_headers = headers; - parser->m_body = request; + request = new HttpRequest(); + request->m_verb = verb_value; + request->m_resource = uri; + request->m_headers = headers; + request->m_body = data; } - return parser; + return request; } -HttpParser::HttpParser() +HttpRequest::HttpRequest() { } -HttpParser::~HttpParser() +HttpRequest::~HttpRequest() { } diff --git a/server/core/maxscale/http.hh b/server/core/maxscale/http.hh new file mode 100644 index 000000000..637525f5b --- /dev/null +++ b/server/core/maxscale/http.hh @@ -0,0 +1,221 @@ +#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 + +#include + +#include + +using std::string; + +/** Supported HTTP verbs */ +enum http_verb +{ + HTTP_UNKNOWN, + HTTP_GET, + HTTP_PUT, + HTTP_POST, + HTTP_OPTIONS, + HTTP_PATCH +}; + +/** 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 +} ; + +/** + * @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; + } + + 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"; + 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"; + } +} diff --git a/server/core/maxscale/httpparser.hh b/server/core/maxscale/httprequest.hh similarity index 84% rename from server/core/maxscale/httpparser.hh rename to server/core/maxscale/httprequest.hh index ed6955ad2..ebcf08f46 100644 --- a/server/core/maxscale/httpparser.hh +++ b/server/core/maxscale/httprequest.hh @@ -18,26 +18,18 @@ #include #include +#include "http.hh" + using std::shared_ptr; using std::string; using std::map; -class HttpParser; +class HttpRequest; /** Typedef for managed pointer */ -typedef std::shared_ptr SHttpParser; +typedef std::shared_ptr SHttpRequest; -enum http_verb -{ - HTTP_UNKNOWN, - HTTP_GET, - HTTP_PUT, - HTTP_POST, - HTTP_OPTIONS, - HTTP_PATCH -}; - -class HttpParser +class HttpRequest { public: /** @@ -47,9 +39,9 @@ public: * * @return Parsed statement or NULL if request is not valid */ - static HttpParser* parse(string request); + static HttpRequest* parse(string request); - ~HttpParser(); + ~HttpRequest(); /** * @brief Return request verb type @@ -113,11 +105,6 @@ public: return m_body; } - void set_body(string body) - { - m_body = body; - } - /** * @brief Get request resource * @@ -129,9 +116,9 @@ public: } private: - HttpParser(); - HttpParser(const HttpParser&); - HttpParser& operator = (const HttpParser&); + HttpRequest(); + HttpRequest(const HttpRequest&); + HttpRequest& operator = (const HttpRequest&); map m_headers; string m_body; diff --git a/server/core/test/testhttp.cc b/server/core/test/testhttp.cc index eb53c0b99..69c9ee469 100644 --- a/server/core/test/testhttp.cc +++ b/server/core/test/testhttp.cc @@ -11,7 +11,7 @@ * Public License. */ -#include "../maxscale/httpparser.hh" +#include "../maxscale/httprequest.hh" #include @@ -88,7 +88,7 @@ int test_basic() { stringstream ss; ss << verbs_pass[i] << " " << paths_pass[j] << " " << proto_pass[k] << "\r\n\r\n"; - SHttpParser parser(HttpParser::parse(ss.str())); + SHttpRequest parser(HttpRequest::parse(ss.str())); TEST(parser.get() != NULL, "Valid HTTP request should be parsed: %s", ss.str().c_str()); TEST(parser->get_resource() == string(paths_pass[j]), "The request path '%s' should be correct: %s", @@ -106,7 +106,7 @@ int test_basic() { stringstream ss; ss << verbs_fail[i] << " " << paths_fail[j] << " " << proto_fail[k] << "\r\n\r\n"; - SHttpParser parser(HttpParser::parse(ss.str())); + SHttpRequest parser(HttpRequest::parse(ss.str())); TEST(parser.get() == NULL, "Invalid HTTP request should not be parsed: %s", ss.str().c_str()); } } @@ -148,8 +148,8 @@ int test_headers() { stringstream ss; ss << "GET / HTTP/1.1\r\n" << headers_pass[i].key << ": " - << headers_pass[i].value <<"\r\n\r\n"; - SHttpParser parser(HttpParser::parse(ss.str())); + << 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),