diff --git a/server/core/admin.cc b/server/core/admin.cc index f92cd5c47..314287e0c 100644 --- a/server/core/admin.cc +++ b/server/core/admin.cc @@ -11,8 +11,7 @@ * Public License. */ -#include "maxscale/admin.hh" -#include "maxscale/hk_heartbeat.h" +#include #include #include @@ -22,15 +21,19 @@ #include #include +#include "maxscale/admin.hh" +#include "maxscale/hk_heartbeat.h" + #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; // TODO: Read values from the configuration -static AdminConfig config = {DEFAULT_ADMIN_HOST, DEFAULT_ADMIN_PORT}; +static AdminConfig config = {DEFAULT_ADMIN_HOST, DEFAULT_ADMIN_PORT, DEFAULT_ADMIN_AUTH}; void admin_main(void* data) { diff --git a/server/core/adminclient.cc b/server/core/adminclient.cc index 418e3de97..ca34a5f19 100644 --- a/server/core/adminclient.cc +++ b/server/core/adminclient.cc @@ -13,6 +13,7 @@ #include "maxscale/adminclient.hh" #include "maxscale/httprequest.hh" +#include "maxscale/httpresponse.hh" #include #include @@ -77,14 +78,9 @@ static bool read_request(int fd, string& output) return true; } -static bool write_response(int fd, enum http_code code, const string& body) +static bool write_response(int fd, const string& body) { - 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; + return write(fd, body.c_str(), body.length()) != -1; } void AdminClient::process() @@ -94,14 +90,24 @@ void AdminClient::process() if (read_request(m_fd, request)) { + string response; SHttpRequest parser(HttpRequest::parse(request)); - - enum http_code status = parser.get() ? HTTP_200_OK : HTTP_400_BAD_REQUEST; - atomic_write_int64(&m_last_activity, hkheartbeat); - /** Echo the request body back */ - write_response(m_fd, HTTP_200_OK, parser->get_json_str()); + if (parser.get()) + { + /** Valid request */ + response = HttpResponse(parser->get_json_str()).get_response(); + } + else + { + request = HttpResponse("", HTTP_400_BAD_REQUEST).get_response(); + } + + if (!write_response(m_fd, response)) + { + MXS_ERROR("Failed to write response to client: %d, %s", errno, mxs_strerror(errno)); + } } else { diff --git a/server/core/httpresponse.cc b/server/core/httpresponse.cc index f78d49d0e..18bf34bc1 100644 --- a/server/core/httpresponse.cc +++ b/server/core/httpresponse.cc @@ -12,8 +12,8 @@ */ #include "maxscale/httpresponse.hh" +#include "maxscale/admin.hh" -#include #include #include @@ -27,25 +27,19 @@ HttpResponse::HttpResponse(string response, enum http_code code): m_body(response), m_code(code) { -} + m_headers["Date"] = get_http_date(); -HttpResponse* HttpResponse::create(json_t* response, enum http_code code) -{ - HttpResponse* rval = NULL; - char* json = json_dumps(response, 0); + // TODO: Add proper modification timestamps + m_headers["Last-Modified"] = m_headers["Date"]; + // TODO: Add proper ETags + m_headers["ETag"] = "bm90LXlldC1pbXBsZW1lbnRlZAo="; - if (json) + enum http_auth auth = mxs_admin_get_config().auth; + + if (auth != HTTP_AUTH_NONE) { - rval = HttpResponse::create(json, code); - MXS_FREE(json); + m_headers["WWW-Authenticate"] = http_auth_to_string(auth); } - - return rval; -} - -HttpResponse* HttpResponse::create(string response, enum http_code code) -{ - return new (std::nothrow) HttpResponse(response, code); } HttpResponse::~HttpResponse() diff --git a/server/core/maxscale/admin.hh b/server/core/maxscale/admin.hh index 03d005a65..dbce8d543 100644 --- a/server/core/maxscale/admin.hh +++ b/server/core/maxscale/admin.hh @@ -19,6 +19,7 @@ #include +#include "http.hh" #include "adminclient.hh" using std::deque; @@ -29,8 +30,9 @@ typedef deque ClientList; /** The admin interface configuration */ struct AdminConfig { - string host; - uint16_t port; + string host; + uint16_t port; + enum http_auth auth; }; class AdminListener @@ -87,4 +89,4 @@ void mxs_admin_shutdown(); * * @return A reference to the administrative interface configuration */ -AdminConfig& mxs_admin_get_config(); \ No newline at end of file +AdminConfig& mxs_admin_get_config(); diff --git a/server/core/maxscale/http.hh b/server/core/maxscale/http.hh index f9a591a14..2f3ff5e14 100644 --- a/server/core/maxscale/http.hh +++ b/server/core/maxscale/http.hh @@ -71,8 +71,40 @@ enum http_code 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 * @@ -226,3 +258,20 @@ static inline const char* http_code_to_string(enum http_code code) return "500 Internal Server Error"; } } + +/** + * @brief Return the current HTTP-date + * + * @return The RFC 1123 compliant date + */ +static inline string get_http_date() +{ + time_t now = time(NULL); + struct tm tm; + char buf[200]; // Enough to store all dates + + gmtime_r(&now, &tm); + strftime(buf, sizeof(buf), "%a, %d %b %y %T GMT", &tm); + + return string(buf); +} diff --git a/server/core/maxscale/httpresponse.hh b/server/core/maxscale/httpresponse.hh index b20466722..3f03f9e40 100644 --- a/server/core/maxscale/httpresponse.hh +++ b/server/core/maxscale/httpresponse.hh @@ -34,12 +34,12 @@ class HttpResponse { public: /** - * @brief Create a new HTTP response - * - * @param request Response body - */ - static HttpResponse* create(string response = "", enum http_code code = HTTP_200_OK); - static HttpResponse* create(json_t* response, enum http_code code = HTTP_200_OK); + * @brief Create new HTTP response + * + * @param response Response body + * @param code HTTP return code + */ + HttpResponse(string response = "", enum http_code code = HTTP_200_OK); ~HttpResponse(); @@ -59,11 +59,10 @@ public: string get_response() const; private: - HttpResponse(string response, enum http_code code); HttpResponse(const HttpResponse&); HttpResponse& operator = (const HttpResponse&); string m_body; /**< Message body */ map m_headers; /**< Message headers */ http_code m_code; /**< The HTTP code for the response */ -}; \ No newline at end of file +}; diff --git a/server/core/test/testhttp.cc b/server/core/test/testhttp.cc index dbcd88dae..fcf9bbdff 100644 --- a/server/core/test/testhttp.cc +++ b/server/core/test/testhttp.cc @@ -12,6 +12,7 @@ */ #include "../maxscale/httprequest.hh" +#include "../maxscale/httpresponse.hh" #include @@ -306,6 +307,23 @@ int test_message_body() 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("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("A Bad gateway", HTTP_502_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; +} + int main(int argc, char** argv) { int rc = 0; @@ -313,6 +331,7 @@ int main(int argc, char** argv) rc += test_basic(); rc += test_headers(); rc += test_message_body(); + rc += test_response(); return rc; }