MXS-1220: Expand HttpResponse class
The class now generates default headers. The ETag and Last-Modified tags do not represent any actual modification time or resource hash. The basic functionality of the HTTP responses is tested by the core test suite. More advanced testing of the whole REST API is still required. Removed the static `create` functions as only the JSON parsing version could generated errors and even then the errors were unlikely. By replacing the static creator function with a normal constructor, the HttpResponse class can now also be created on the stack making its use easier.
This commit is contained in:

committed by
Markus Mäkelä

parent
a73d3e9276
commit
9d0d394361
@ -11,8 +11,7 @@
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
#include "maxscale/admin.hh"
|
||||
#include "maxscale/hk_heartbeat.h"
|
||||
#include <maxscale/cppdefs.hh>
|
||||
|
||||
#include <climits>
|
||||
#include <new>
|
||||
@ -22,15 +21,19 @@
|
||||
#include <maxscale/thread.h>
|
||||
#include <maxscale/utils.h>
|
||||
|
||||
#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)
|
||||
{
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include "maxscale/adminclient.hh"
|
||||
#include "maxscale/httprequest.hh"
|
||||
#include "maxscale/httpresponse.hh"
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
@ -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
|
||||
{
|
||||
|
@ -12,8 +12,8 @@
|
||||
*/
|
||||
|
||||
#include "maxscale/httpresponse.hh"
|
||||
#include "maxscale/admin.hh"
|
||||
|
||||
#include <new>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
@ -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()
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include <maxscale/thread.h>
|
||||
|
||||
#include "http.hh"
|
||||
#include "adminclient.hh"
|
||||
|
||||
using std::deque;
|
||||
@ -29,8 +30,9 @@ typedef deque<SAdminClient> ClientList;
|
||||
/** The admin interface configuration */
|
||||
struct AdminConfig
|
||||
{
|
||||
string host;
|
||||
uint16_t port;
|
||||
string host;
|
||||
uint16_t port;
|
||||
enum http_auth auth;
|
||||
};
|
||||
|
||||
class AdminListener
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,7 +59,6 @@ public:
|
||||
string get_response() const;
|
||||
|
||||
private:
|
||||
HttpResponse(string response, enum http_code code);
|
||||
HttpResponse(const HttpResponse&);
|
||||
HttpResponse& operator = (const HttpResponse&);
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
#include "../maxscale/httprequest.hh"
|
||||
#include "../maxscale/httpresponse.hh"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user