Add response builder to simplify the response build; refine the service interfaces.
This commit is contained in:
@@ -43,6 +43,7 @@ public:
|
||||
// Send response to client.
|
||||
void SendResponse(ResponsePtr response);
|
||||
|
||||
// TODO: Remove
|
||||
void SendResponse(Status status);
|
||||
|
||||
private:
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace webcc {
|
||||
class RequestBuilder {
|
||||
public:
|
||||
RequestBuilder() = default;
|
||||
~RequestBuilder() = default;
|
||||
|
||||
RequestBuilder(const RequestBuilder&) = delete;
|
||||
RequestBuilder& operator=(const RequestBuilder&) = delete;
|
||||
@@ -119,13 +118,13 @@ private:
|
||||
// Files to upload for a POST request.
|
||||
std::vector<FormPartPtr> form_parts_;
|
||||
|
||||
// Compress the request content.
|
||||
// Compress the content.
|
||||
// NOTE: Most servers don't support compressed requests.
|
||||
// Even the requests module from Python doesn't have a built-in support.
|
||||
// See: https://github.com/kennethreitz/requests/issues/1753
|
||||
bool gzip_ = false;
|
||||
|
||||
// Additional request headers.
|
||||
// Additional headers.
|
||||
std::vector<std::string> headers_;
|
||||
|
||||
// Persistent connection.
|
||||
|
||||
+10
-18
@@ -21,7 +21,7 @@ RequestHandler::RequestHandler(const std::string& doc_root)
|
||||
|
||||
bool RequestHandler::Bind(ServicePtr service, const std::string& url,
|
||||
bool is_regex) {
|
||||
return service_manager_.AddService(service, url, is_regex);
|
||||
return service_manager_.Add(service, url, is_regex);
|
||||
}
|
||||
|
||||
void RequestHandler::Enqueue(ConnectionPtr connection) {
|
||||
@@ -80,14 +80,12 @@ void RequestHandler::HandleConnection(ConnectionPtr connection) {
|
||||
auto request = connection->request();
|
||||
|
||||
const Url& url = request->url();
|
||||
|
||||
RestRequest rest_request{ request };
|
||||
UrlArgs args;
|
||||
|
||||
LOG_INFO("Request URL path: %s", url.path().c_str());
|
||||
|
||||
// Get service by URL path.
|
||||
auto service = service_manager_.GetService(url.path(),
|
||||
&rest_request.url_matches);
|
||||
auto service = service_manager_.Get(url.path(), &args);
|
||||
|
||||
if (!service) {
|
||||
LOG_WARN("No service matches the URL path: %s", url.path().c_str());
|
||||
@@ -99,20 +97,14 @@ void RequestHandler::HandleConnection(ConnectionPtr connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
RestResponse rest_response;
|
||||
service->Handle(rest_request, &rest_response);
|
||||
|
||||
auto response = std::make_shared<Response>(rest_response.status);
|
||||
|
||||
if (!rest_response.content.empty()) {
|
||||
if (!rest_response.media_type.empty()) {
|
||||
response->SetContentType(rest_response.media_type, rest_response.charset);
|
||||
}
|
||||
SetContent(request, response, std::move(rest_response.content));
|
||||
}
|
||||
|
||||
ResponsePtr response = service->Handle(request, args);
|
||||
|
||||
// Send response back to client.
|
||||
connection->SendResponse(response);
|
||||
if (response) {
|
||||
connection->SendResponse(response);
|
||||
} else {
|
||||
connection->SendResponse(Status::kNotImplemented);
|
||||
}
|
||||
}
|
||||
|
||||
bool RequestHandler::ServeStatic(ConnectionPtr connection) {
|
||||
|
||||
+1
-2
@@ -13,8 +13,7 @@ using ResponsePtr = std::shared_ptr<Response>;
|
||||
|
||||
class Response : public Message {
|
||||
public:
|
||||
explicit Response(Status status = Status::kOK)
|
||||
: status_(status) {
|
||||
explicit Response(Status status = Status::kOK) : status_(status) {
|
||||
}
|
||||
|
||||
~Response() override = default;
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "webcc/response_builder.h"
|
||||
|
||||
#include "webcc/base64.h"
|
||||
#include "webcc/logger.h"
|
||||
#include "webcc/utility.h"
|
||||
|
||||
#if WEBCC_ENABLE_GZIP
|
||||
#include "webcc/gzip.h"
|
||||
#endif
|
||||
|
||||
namespace webcc {
|
||||
|
||||
ResponsePtr ResponseBuilder::operator()() {
|
||||
assert(headers_.size() % 2 == 0);
|
||||
|
||||
auto request = std::make_shared<Response>(code_);
|
||||
|
||||
for (std::size_t i = 1; i < headers_.size(); i += 2) {
|
||||
request->SetHeader(std::move(headers_[i - 1]), std::move(headers_[i]));
|
||||
}
|
||||
|
||||
if (!data_.empty()) {
|
||||
SetContent(request, std::move(data_));
|
||||
|
||||
// TODO: charset.
|
||||
if (json_) {
|
||||
request->SetContentType(media_types::kApplicationJson, "");
|
||||
}
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
ResponseBuilder& ResponseBuilder::Date() {
|
||||
headers_.push_back(headers::kDate);
|
||||
headers_.push_back(utility::GetTimestamp());
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ResponseBuilder::SetContent(ResponsePtr response, std::string&& data) {
|
||||
#if WEBCC_ENABLE_GZIP
|
||||
if (gzip_ && data.size() > kGzipThreshold) {
|
||||
std::string compressed;
|
||||
if (gzip::Compress(data, &compressed)) {
|
||||
response->SetContent(std::move(compressed), true);
|
||||
response->SetHeader(headers::kContentEncoding, "gzip");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_WARN("Cannot compress the content data!");
|
||||
}
|
||||
#endif // WEBCC_ENABLE_GZIP
|
||||
|
||||
response->SetContent(std::move(data), true);
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,88 @@
|
||||
#ifndef WEBCC_RESPONSE_BUILDER_H_
|
||||
#define WEBCC_RESPONSE_BUILDER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webcc/response.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class ResponseBuilder {
|
||||
public:
|
||||
ResponseBuilder() = default;
|
||||
|
||||
ResponseBuilder(const ResponseBuilder&) = delete;
|
||||
ResponseBuilder& operator=(const ResponseBuilder&) = delete;
|
||||
|
||||
// Build the response.
|
||||
ResponsePtr operator()();
|
||||
|
||||
// NOTE:
|
||||
// The naming convention doesn't follow Google C++ Style for
|
||||
// consistency and simplicity.
|
||||
|
||||
// Some shortcuts for different status codes:
|
||||
ResponseBuilder& OK() { return Code(Status::kOK); }
|
||||
ResponseBuilder& Created() { return Code(Status::kCreated); }
|
||||
ResponseBuilder& BadRequest() { return Code(Status::kBadRequest); }
|
||||
ResponseBuilder& NotFound() { return Code(Status::kNotFound); }
|
||||
ResponseBuilder& NotImplemented() { return Code(Status::kNotImplemented); }
|
||||
|
||||
ResponseBuilder& Code(Status code) {
|
||||
code_ = code;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ResponseBuilder& Data(const std::string& data) {
|
||||
data_ = data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ResponseBuilder& Data(std::string&& data) {
|
||||
data_ = std::move(data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ResponseBuilder& Json(bool json = true) {
|
||||
json_ = json;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ResponseBuilder& Gzip(bool gzip = true) {
|
||||
gzip_ = gzip;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ResponseBuilder& Header(const std::string& key, const std::string& value) {
|
||||
headers_.push_back(key);
|
||||
headers_.push_back(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Add the Date header to the response.
|
||||
ResponseBuilder& Date();
|
||||
|
||||
private:
|
||||
void SetContent(ResponsePtr response, std::string&& data);
|
||||
|
||||
private:
|
||||
// Status code.
|
||||
Status code_ = Status::kOK;
|
||||
|
||||
// Data to send in the body of the request.
|
||||
std::string data_;
|
||||
|
||||
// Is the data to send a JSON string?
|
||||
bool json_ = false;
|
||||
|
||||
// Compress the response content.
|
||||
bool gzip_ = false;
|
||||
|
||||
// Additional headers.
|
||||
std::vector<std::string> headers_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_RESPONSE_BUILDER_H_
|
||||
+50
-28
@@ -6,40 +6,62 @@ namespace webcc {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
void ListService::Handle(const RestRequest& request, RestResponse* response) {
|
||||
const std::string& method = request.http->method();
|
||||
|
||||
if (method == methods::kGet) {
|
||||
Get(UrlQuery(request.http->url().query()), response);
|
||||
|
||||
} else if (method == methods::kPost) {
|
||||
Post(request.http->content(), response);
|
||||
|
||||
} else {
|
||||
LOG_ERRO("ListService doesn't support '%s' method.", method.c_str());
|
||||
ResponsePtr ListService::Handle(RequestPtr request, const UrlArgs& args) {
|
||||
if (request->method() == methods::kGet) {
|
||||
return Get(UrlQuery(request->url().query()));
|
||||
}
|
||||
|
||||
if (request->method() == methods::kPost) {
|
||||
return Post(request);
|
||||
}
|
||||
|
||||
return ResponsePtr();
|
||||
}
|
||||
|
||||
ResponsePtr ListService::Get(const UrlQuery& query) {
|
||||
return ResponsePtr();
|
||||
}
|
||||
|
||||
ResponsePtr ListService::Post(RequestPtr request) {
|
||||
return ResponsePtr();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
void DetailService::Handle(const RestRequest& request, RestResponse* response) {
|
||||
const std::string& method = request.http->method();
|
||||
|
||||
if (method == methods::kGet) {
|
||||
Get(request.url_matches, UrlQuery(request.http->url().query()), response);
|
||||
|
||||
} else if (method == methods::kPut) {
|
||||
Put(request.url_matches, request.http->content(), response);
|
||||
|
||||
} else if (method == methods::kPatch) {
|
||||
Patch(request.url_matches, request.http->content(), response);
|
||||
|
||||
} else if (method == methods::kDelete) {
|
||||
Delete(request.url_matches, response);
|
||||
|
||||
} else {
|
||||
LOG_ERRO("DetailService doesn't support '%s' method.", method.c_str());
|
||||
ResponsePtr DetailService::Handle(RequestPtr request, const UrlArgs& args) {
|
||||
if (request->method() == methods::kGet) {
|
||||
return Get(args, UrlQuery(request->url().query()));
|
||||
}
|
||||
|
||||
if (request->method() == methods::kPut) {
|
||||
return Put(request, args);
|
||||
}
|
||||
|
||||
if (request->method() == methods::kPatch) {
|
||||
return Patch(request, args);
|
||||
}
|
||||
|
||||
if (request->method() == methods::kDelete) {
|
||||
return Delete(args);
|
||||
}
|
||||
|
||||
return ResponsePtr();
|
||||
}
|
||||
|
||||
ResponsePtr DetailService::Get(const UrlArgs& args, const UrlQuery& query) {
|
||||
return ResponsePtr();
|
||||
}
|
||||
|
||||
ResponsePtr DetailService::Put(RequestPtr request, const UrlArgs& args) {
|
||||
return ResponsePtr();
|
||||
}
|
||||
|
||||
ResponsePtr DetailService::Patch(RequestPtr request, const UrlArgs& args) {
|
||||
return ResponsePtr();
|
||||
}
|
||||
|
||||
ResponsePtr DetailService::Delete(const UrlArgs& args) {
|
||||
return ResponsePtr();
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
+15
-44
@@ -15,32 +15,17 @@
|
||||
|
||||
#include "webcc/globals.h"
|
||||
#include "webcc/request.h"
|
||||
#include "webcc/response.h"
|
||||
#include "webcc/response_builder.h"
|
||||
#include "webcc/url.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Regex sub-matches of the URL.
|
||||
using UrlMatches = std::vector<std::string>;
|
||||
|
||||
struct RestRequest {
|
||||
// Original HTTP request.
|
||||
RequestPtr http;
|
||||
|
||||
// Regex sub-matches of the URL (usually resource ID's).
|
||||
UrlMatches url_matches;
|
||||
};
|
||||
|
||||
// TODO: Add ResponseBuilder instead.
|
||||
struct RestResponse {
|
||||
Status status;
|
||||
|
||||
std::string content;
|
||||
|
||||
std::string media_type;
|
||||
std::string charset;
|
||||
};
|
||||
// Regex sub-matches of the URL (usually resource ID's).
|
||||
// Could also be considered as arguments, so named as UrlArgs.
|
||||
using UrlArgs = std::vector<std::string>;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -49,8 +34,8 @@ class Service {
|
||||
public:
|
||||
virtual ~Service() = default;
|
||||
|
||||
// Handle request, output response.
|
||||
virtual void Handle(const RestRequest& request, RestResponse* response) = 0;
|
||||
// Handle request, return response.
|
||||
virtual ResponsePtr Handle(RequestPtr request, const UrlArgs& args) = 0;
|
||||
};
|
||||
|
||||
using ServicePtr = std::shared_ptr<Service>;
|
||||
@@ -59,42 +44,28 @@ using ServicePtr = std::shared_ptr<Service>;
|
||||
|
||||
class ListService : public Service {
|
||||
public:
|
||||
void Handle(const RestRequest& request, RestResponse* response) override;
|
||||
ResponsePtr Handle(RequestPtr request, const UrlArgs& args) override;
|
||||
|
||||
protected:
|
||||
virtual void Get(const UrlQuery& query, RestResponse* response) {
|
||||
}
|
||||
virtual ResponsePtr Get(const UrlQuery& query);
|
||||
|
||||
virtual void Post(const std::string& request_content,
|
||||
RestResponse* response) {
|
||||
}
|
||||
virtual ResponsePtr Post(RequestPtr request);
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class DetailService : public Service {
|
||||
public:
|
||||
void Handle(const RestRequest& request, RestResponse* response) override;
|
||||
ResponsePtr Handle(RequestPtr request, const UrlArgs& args) override;
|
||||
|
||||
protected:
|
||||
virtual void Get(const UrlMatches& url_matches,
|
||||
const UrlQuery& query,
|
||||
RestResponse* response) {
|
||||
}
|
||||
virtual ResponsePtr Get(const UrlArgs& args, const UrlQuery& query);
|
||||
|
||||
virtual void Put(const UrlMatches& url_matches,
|
||||
const std::string& request_content,
|
||||
RestResponse* response) {
|
||||
}
|
||||
virtual ResponsePtr Put(RequestPtr request, const UrlArgs& args);
|
||||
|
||||
virtual void Patch(const UrlMatches& url_matches,
|
||||
const std::string& request_content,
|
||||
RestResponse* response) {
|
||||
}
|
||||
virtual ResponsePtr Patch(RequestPtr request, const UrlArgs& args);
|
||||
|
||||
virtual void Delete(const UrlMatches& url_matches,
|
||||
RestResponse* response) {
|
||||
}
|
||||
virtual ResponsePtr Delete(const UrlArgs& args);
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
namespace webcc {
|
||||
|
||||
bool ServiceManager::AddService(ServicePtr service, const std::string& url,
|
||||
bool is_regex) {
|
||||
bool ServiceManager::Add(ServicePtr service, const std::string& url,
|
||||
bool is_regex) {
|
||||
assert(service);
|
||||
|
||||
Item item(service, url, is_regex);
|
||||
@@ -30,9 +30,8 @@ bool ServiceManager::AddService(ServicePtr service, const std::string& url,
|
||||
}
|
||||
}
|
||||
|
||||
ServicePtr ServiceManager::GetService(const std::string& url,
|
||||
UrlMatches* matches) {
|
||||
assert(matches != nullptr);
|
||||
ServicePtr ServiceManager::Get(const std::string& url, UrlArgs* args) {
|
||||
assert(args != nullptr);
|
||||
|
||||
for (Item& item : items_) {
|
||||
if (item.is_regex) {
|
||||
@@ -42,7 +41,7 @@ ServicePtr ServiceManager::GetService(const std::string& url,
|
||||
// Any sub-matches?
|
||||
// NOTE: Start from 1 because match[0] is the whole string itself.
|
||||
for (size_t i = 1; i < match.size(); ++i) {
|
||||
matches->push_back(match[i].str());
|
||||
args->push_back(match[i].str());
|
||||
}
|
||||
|
||||
return item.service;
|
||||
|
||||
@@ -21,13 +21,13 @@ public:
|
||||
// The |url| should start with "/" and will be treated as a regular expression
|
||||
// if |regex| is true.
|
||||
// Examples: "/instances", "/instances/(\\d+)".
|
||||
bool AddService(ServicePtr service, const std::string& url, bool is_regex);
|
||||
bool Add(ServicePtr service, const std::string& url, bool is_regex);
|
||||
|
||||
// The |matches| is only available when the |url| bound to the service is a
|
||||
// regular expression and has sub-expressions.
|
||||
// E.g., the URL bound to the service is "/instances/(\\d+)", now match
|
||||
// "/instances/12345" against it, you will get one match of "12345".
|
||||
ServicePtr GetService(const std::string& url, UrlMatches* matches);
|
||||
ServicePtr Get(const std::string& url, UrlArgs* args);
|
||||
|
||||
private:
|
||||
class Item {
|
||||
|
||||
Reference in New Issue
Block a user