MXS-1220: Rename and reorganize HttpParser

The HttpParser class was renamed to HttpRequest as it parses and processes
only HTTP requests. A second class that creates a HTTP response needs to
be created to handle the response generation.

Moved some of the HTTP constants and helper functions to a separate
http.hh header.
This commit is contained in:
Markus Mäkelä
2017-04-16 19:28:23 +03:00
committed by Markus Mäkelä
parent e34b65658e
commit 4eb121ce35
6 changed files with 282 additions and 82 deletions

View File

@ -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

View File

@ -12,7 +12,7 @@
*/
#include "maxscale/adminclient.hh"
#include "maxscale/httpparser.hh"
#include "maxscale/httprequest.hh"
#include <string>
#include <sstream>
@ -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
{

View File

@ -11,41 +11,34 @@
* Public License.
*/
#include "maxscale/httpparser.hh"
#include "maxscale/httprequest.hh"
#include <ctype.h>
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<string, string> 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()
{
}

View File

@ -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 <maxscale/cppdefs.hh>
#include <string>
#include <maxscale/debug.h>
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";
}
}

View File

@ -18,26 +18,18 @@
#include <map>
#include <tr1/memory>
#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<HttpParser> SHttpParser;
typedef std::shared_ptr<HttpRequest> 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<string, string> m_headers;
string m_body;

View File

@ -11,7 +11,7 @@
* Public License.
*/
#include "../maxscale/httpparser.hh"
#include "../maxscale/httprequest.hh"
#include <sstream>
@ -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),