Add response builder to simplify the response build; refine the service interfaces.

This commit is contained in:
Chunting Gu
2019-06-21 17:41:02 +08:00
parent 4140b4f94c
commit 8a7f53313b
14 changed files with 316 additions and 207 deletions
+1
View File
@@ -43,6 +43,7 @@ public:
// Send response to client.
void SendResponse(ResponsePtr response);
// TODO: Remove
void SendResponse(Status status);
private:
+2 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+57
View File
@@ -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
+88
View File
@@ -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
View File
@@ -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
View File
@@ -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
+5 -6
View File
@@ -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;
+2 -2
View File
@@ -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 {