Add response builder to simplify the response build; refine the service interfaces.
This commit is contained in:
@@ -8,21 +8,21 @@
|
|||||||
|
|
||||||
class FileUploadService : public webcc::Service {
|
class FileUploadService : public webcc::Service {
|
||||||
public:
|
public:
|
||||||
void Handle(const webcc::RestRequest& request,
|
webcc::ResponsePtr Handle(webcc::RequestPtr request,
|
||||||
webcc::RestResponse* response) override {
|
const webcc::UrlArgs& args) override {
|
||||||
if (request.http->method() == "POST") {
|
if (request->method() == "POST") {
|
||||||
std::cout << "files: " << request.http->form_parts().size() << std::endl;
|
std::cout << "files: " << request->form_parts().size() << std::endl;
|
||||||
|
|
||||||
for (auto& part : request.http->form_parts()) {
|
for (auto& part : request->form_parts()) {
|
||||||
std::cout << "name: " << part->name() << std::endl;
|
std::cout << "name: " << part->name() << std::endl;
|
||||||
std::cout << "data: " << std::endl << part->data() << std::endl;
|
std::cout << "data: " << std::endl << part->data() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
response->content = "OK";
|
// TODO: media_type: webcc::media_types::kTextPlain; charset = "utf-8";
|
||||||
response->media_type = webcc::media_types::kTextPlain;
|
return webcc::ResponseBuilder{}.Created().Data("OK")();
|
||||||
response->charset = "utf-8";
|
|
||||||
response->status = webcc::Status::kCreated;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return webcc::ResponseBuilder{}.NotImplemented()();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,12 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "boost/core/ignore_unused.hpp"
|
||||||
|
|
||||||
#include "json/json.h"
|
#include "json/json.h"
|
||||||
|
|
||||||
#include "webcc/logger.h"
|
#include "webcc/logger.h"
|
||||||
|
#include "webcc/response_builder.h"
|
||||||
#include "webcc/server.h"
|
#include "webcc/server.h"
|
||||||
|
|
||||||
#include "examples/common/book.h"
|
#include "examples/common/book.h"
|
||||||
@@ -41,11 +44,10 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Get a list of books based on query parameters.
|
// Get a list of books based on query parameters.
|
||||||
void Get(const webcc::UrlQuery& query, webcc::RestResponse* response) override;
|
webcc::ResponsePtr Get(const webcc::UrlQuery& query) override;
|
||||||
|
|
||||||
// Create a new book.
|
// Create a new book.
|
||||||
void Post(const std::string& request_content,
|
webcc::ResponsePtr Post(webcc::RequestPtr request) override;
|
||||||
webcc::RestResponse* response) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Sleep some seconds before send back the response.
|
// Sleep some seconds before send back the response.
|
||||||
@@ -55,7 +57,7 @@ private:
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// The URL is like '/books/{BookID}', and the 'url_matches' parameter
|
// The URL is like '/books/{BookID}', and the 'args' parameter
|
||||||
// contains the matched book ID.
|
// contains the matched book ID.
|
||||||
class BookDetailService : public webcc::DetailService {
|
class BookDetailService : public webcc::DetailService {
|
||||||
public:
|
public:
|
||||||
@@ -65,18 +67,15 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Get the detailed information of a book.
|
// Get the detailed information of a book.
|
||||||
void Get(const webcc::UrlMatches& url_matches,
|
webcc::ResponsePtr Get(const webcc::UrlArgs& args,
|
||||||
const webcc::UrlQuery& query,
|
const webcc::UrlQuery& query) override;
|
||||||
webcc::RestResponse* response) override;
|
|
||||||
|
|
||||||
// Update a book.
|
// Update a book.
|
||||||
void Put(const webcc::UrlMatches& url_matches,
|
webcc::ResponsePtr Put(webcc::RequestPtr request,
|
||||||
const std::string& request_content,
|
const webcc::UrlArgs& args) override;
|
||||||
webcc::RestResponse* response) override;
|
|
||||||
|
|
||||||
// Delete a book.
|
// Delete a book.
|
||||||
void Delete(const webcc::UrlMatches& url_matches,
|
webcc::ResponsePtr Delete(const webcc::UrlArgs& args) override;
|
||||||
webcc::RestResponse* response) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Sleep some seconds before send back the response.
|
// Sleep some seconds before send back the response.
|
||||||
@@ -87,8 +86,9 @@ private:
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Return all books as a JSON array.
|
// Return all books as a JSON array.
|
||||||
void BookListService::Get(const webcc::UrlQuery& /*query*/,
|
webcc::ResponsePtr BookListService::Get(const webcc::UrlQuery& query) {
|
||||||
webcc::RestResponse* response) {
|
boost::ignore_unused(query);
|
||||||
|
|
||||||
Sleep(sleep_seconds_);
|
Sleep(sleep_seconds_);
|
||||||
|
|
||||||
Json::Value json(Json::arrayValue);
|
Json::Value json(Json::arrayValue);
|
||||||
@@ -97,103 +97,86 @@ void BookListService::Get(const webcc::UrlQuery& /*query*/,
|
|||||||
json.append(BookToJson(book));
|
json.append(BookToJson(book));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Simplify
|
// TODO: charset = "utf-8"
|
||||||
response->content = JsonToString(json);
|
return webcc::ResponseBuilder{}.OK().Data(JsonToString(json)).Json()();
|
||||||
response->media_type = webcc::media_types::kApplicationJson;
|
|
||||||
response->charset = "utf-8";
|
|
||||||
response->status = webcc::Status::kOK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BookListService::Post(const std::string& request_content,
|
webcc::ResponsePtr BookListService::Post(webcc::RequestPtr request) {
|
||||||
webcc::RestResponse* response) {
|
|
||||||
Sleep(sleep_seconds_);
|
Sleep(sleep_seconds_);
|
||||||
|
|
||||||
Book book;
|
Book book;
|
||||||
if (JsonStringToBook(request_content, &book)) {
|
if (JsonStringToBook(request->content(), &book)) {
|
||||||
std::string id = g_book_store.AddBook(book);
|
std::string id = g_book_store.AddBook(book);
|
||||||
|
|
||||||
Json::Value json;
|
Json::Value json;
|
||||||
json["id"] = id;
|
json["id"] = id;
|
||||||
|
|
||||||
response->content = JsonToString(json);
|
// TODO: charset = "utf-8"
|
||||||
response->media_type = webcc::media_types::kApplicationJson;
|
return webcc::ResponseBuilder{}.Created().Data(JsonToString(json)).Json()();
|
||||||
response->charset = "utf-8";
|
|
||||||
response->status = webcc::Status::kCreated;
|
|
||||||
} else {
|
} else {
|
||||||
// Invalid JSON
|
// Invalid JSON
|
||||||
response->status = webcc::Status::kBadRequest;
|
return webcc::ResponseBuilder{}.BadRequest()();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
void BookDetailService::Get(const webcc::UrlMatches& url_matches,
|
webcc::ResponsePtr BookDetailService::Get(const webcc::UrlArgs& args,
|
||||||
const webcc::UrlQuery& query,
|
const webcc::UrlQuery& query) {
|
||||||
webcc::RestResponse* response) {
|
|
||||||
Sleep(sleep_seconds_);
|
Sleep(sleep_seconds_);
|
||||||
|
|
||||||
if (url_matches.size() != 1) {
|
if (args.size() != 1) {
|
||||||
// Using kNotFound means the resource specified by the URL cannot be found.
|
// Using kNotFound means the resource specified by the URL cannot be found.
|
||||||
// kBadRequest could be another choice.
|
// kBadRequest could be another choice.
|
||||||
response->status = webcc::Status::kNotFound;
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& book_id = url_matches[0];
|
const std::string& book_id = args[0];
|
||||||
|
|
||||||
const Book& book = g_book_store.GetBook(book_id);
|
const Book& book = g_book_store.GetBook(book_id);
|
||||||
if (book.IsNull()) {
|
if (book.IsNull()) {
|
||||||
response->status = webcc::Status::kNotFound;
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response->content = BookToJsonString(book);
|
// TODO: charset = "utf-8"
|
||||||
response->media_type = webcc::media_types::kApplicationJson;
|
return webcc::ResponseBuilder{}.OK().Data(BookToJsonString(book)).Json()();
|
||||||
response->charset = "utf-8";
|
|
||||||
response->status = webcc::Status::kOK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BookDetailService::Put(const webcc::UrlMatches& url_matches,
|
webcc::ResponsePtr BookDetailService::Put(webcc::RequestPtr request,
|
||||||
const std::string& request_content,
|
const webcc::UrlArgs& args) {
|
||||||
webcc::RestResponse* response) {
|
|
||||||
Sleep(sleep_seconds_);
|
Sleep(sleep_seconds_);
|
||||||
|
|
||||||
if (url_matches.size() != 1) {
|
if (args.size() != 1) {
|
||||||
response->status = webcc::Status::kNotFound;
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& book_id = url_matches[0];
|
const std::string& book_id = args[0];
|
||||||
|
|
||||||
Book book;
|
Book book;
|
||||||
if (!JsonStringToBook(request_content, &book)) {
|
if (!JsonStringToBook(request->content(), &book)) {
|
||||||
response->status = webcc::Status::kBadRequest;
|
return webcc::ResponseBuilder{}.BadRequest()();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
book.id = book_id;
|
book.id = book_id;
|
||||||
g_book_store.UpdateBook(book);
|
g_book_store.UpdateBook(book);
|
||||||
|
|
||||||
response->status = webcc::Status::kOK;
|
return webcc::ResponseBuilder{}.OK()();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BookDetailService::Delete(const webcc::UrlMatches& url_matches,
|
webcc::ResponsePtr BookDetailService::Delete(const webcc::UrlArgs& args) {
|
||||||
webcc::RestResponse* response) {
|
|
||||||
Sleep(sleep_seconds_);
|
Sleep(sleep_seconds_);
|
||||||
|
|
||||||
if (url_matches.size() != 1) {
|
if (args.size() != 1) {
|
||||||
response->status = webcc::Status::kNotFound;
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& book_id = url_matches[0];
|
const std::string& book_id = args[0];
|
||||||
|
|
||||||
if (!g_book_store.DeleteBook(book_id)) {
|
if (!g_book_store.DeleteBook(book_id)) {
|
||||||
response->status = webcc::Status::kNotFound;
|
return webcc::ResponseBuilder{}.NotFound()();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response->status = webcc::Status::kOK;
|
return webcc::ResponseBuilder{}.OK()();
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
class MyService : public webcc::Service {
|
class MyService : public webcc::Service {
|
||||||
public:
|
public:
|
||||||
void Handle(const webcc::RestRequest& request,
|
webcc::ResponsePtr Handle(webcc::RequestPtr request,
|
||||||
webcc::RestResponse* response) override {
|
const webcc::UrlArgs& args) override {
|
||||||
response->status = webcc::Status::kOK;
|
return webcc::ResponseBuilder{}.OK()();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,22 +17,21 @@ public:
|
|||||||
TEST(ServiceManagerTest, URL_RegexBasic) {
|
TEST(ServiceManagerTest, URL_RegexBasic) {
|
||||||
webcc::ServiceManager service_manager;
|
webcc::ServiceManager service_manager;
|
||||||
|
|
||||||
service_manager.AddService(std::make_shared<MyService>(),
|
service_manager.Add(std::make_shared<MyService>(), "/instance/(\\d+)", true);
|
||||||
"/instance/(\\d+)", true);
|
|
||||||
|
|
||||||
std::vector<std::string> matches;
|
|
||||||
|
|
||||||
std::string url = "/instance/12345";
|
std::string url = "/instance/12345";
|
||||||
webcc::ServicePtr service = service_manager.GetService(url, &matches);
|
webcc::UrlArgs args;
|
||||||
|
|
||||||
|
webcc::ServicePtr service = service_manager.Get(url, &args);
|
||||||
|
|
||||||
EXPECT_TRUE(!!service);
|
EXPECT_TRUE(!!service);
|
||||||
|
|
||||||
EXPECT_EQ(1, matches.size());
|
EXPECT_EQ(1, args.size());
|
||||||
EXPECT_EQ("12345", matches[0]);
|
EXPECT_EQ("12345", args[0]);
|
||||||
|
|
||||||
url = "/instance/abcde";
|
url = "/instance/abcde";
|
||||||
matches.clear();
|
args.clear();
|
||||||
service = service_manager.GetService(url, &matches);
|
service = service_manager.Get(url, &args);
|
||||||
|
|
||||||
EXPECT_FALSE(!!service);
|
EXPECT_FALSE(!!service);
|
||||||
}
|
}
|
||||||
@@ -40,25 +39,24 @@ TEST(ServiceManagerTest, URL_RegexBasic) {
|
|||||||
TEST(RestServiceManagerTest, URL_RegexMultiple) {
|
TEST(RestServiceManagerTest, URL_RegexMultiple) {
|
||||||
webcc::ServiceManager service_manager;
|
webcc::ServiceManager service_manager;
|
||||||
|
|
||||||
service_manager.AddService(std::make_shared<MyService>(),
|
service_manager.Add(std::make_shared<MyService>(),
|
||||||
"/study/(\\d+)/series/(\\d+)/instance/(\\d+)",
|
"/study/(\\d+)/series/(\\d+)/instance/(\\d+)", true);
|
||||||
true);
|
|
||||||
|
|
||||||
std::vector<std::string> matches;
|
|
||||||
|
|
||||||
std::string url = "/study/1/series/2/instance/3";
|
std::string url = "/study/1/series/2/instance/3";
|
||||||
webcc::ServicePtr service = service_manager.GetService(url, &matches);
|
webcc::UrlArgs args;
|
||||||
|
|
||||||
|
webcc::ServicePtr service = service_manager.Get(url, &args);
|
||||||
|
|
||||||
EXPECT_TRUE(!!service);
|
EXPECT_TRUE(!!service);
|
||||||
|
|
||||||
EXPECT_EQ(3, matches.size());
|
EXPECT_EQ(3, args.size());
|
||||||
EXPECT_EQ("1", matches[0]);
|
EXPECT_EQ("1", args[0]);
|
||||||
EXPECT_EQ("2", matches[1]);
|
EXPECT_EQ("2", args[1]);
|
||||||
EXPECT_EQ("3", matches[2]);
|
EXPECT_EQ("3", args[2]);
|
||||||
|
|
||||||
url = "/study/a/series/b/instance/c";
|
url = "/study/a/series/b/instance/c";
|
||||||
matches.clear();
|
args.clear();
|
||||||
service = service_manager.GetService(url, &matches);
|
service = service_manager.Get(url, &args);
|
||||||
|
|
||||||
EXPECT_FALSE(!!service);
|
EXPECT_FALSE(!!service);
|
||||||
}
|
}
|
||||||
@@ -66,13 +64,13 @@ TEST(RestServiceManagerTest, URL_RegexMultiple) {
|
|||||||
TEST(RestServiceManagerTest, URL_NonRegex) {
|
TEST(RestServiceManagerTest, URL_NonRegex) {
|
||||||
webcc::ServiceManager service_manager;
|
webcc::ServiceManager service_manager;
|
||||||
|
|
||||||
service_manager.AddService(std::make_shared<MyService>(), "/instances",
|
service_manager.Add(std::make_shared<MyService>(), "/instances", false);
|
||||||
false);
|
|
||||||
|
|
||||||
std::vector<std::string> matches;
|
|
||||||
std::string url = "/instances";
|
std::string url = "/instances";
|
||||||
webcc::ServicePtr service = service_manager.GetService(url, &matches);
|
webcc::UrlArgs args;
|
||||||
|
|
||||||
|
webcc::ServicePtr service = service_manager.Get(url, &args);
|
||||||
|
|
||||||
EXPECT_TRUE(!!service);
|
EXPECT_TRUE(!!service);
|
||||||
EXPECT_EQ(0, matches.size());
|
EXPECT_TRUE(args.empty());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ TEST(UrlTest, Basic) {
|
|||||||
EXPECT_EQ("http", url.scheme());
|
EXPECT_EQ("http", url.scheme());
|
||||||
EXPECT_EQ("example.com", url.host());
|
EXPECT_EQ("example.com", url.host());
|
||||||
EXPECT_EQ("", url.port());
|
EXPECT_EQ("", url.port());
|
||||||
EXPECT_EQ("path", url.path());
|
EXPECT_EQ("/path", url.path());
|
||||||
EXPECT_EQ("", url.query());
|
EXPECT_EQ("", url.query());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ TEST(UrlTest, NoPath2) {
|
|||||||
EXPECT_EQ("http", url.scheme());
|
EXPECT_EQ("http", url.scheme());
|
||||||
EXPECT_EQ("example.com", url.host());
|
EXPECT_EQ("example.com", url.host());
|
||||||
EXPECT_EQ("", url.port());
|
EXPECT_EQ("", url.port());
|
||||||
EXPECT_EQ("", url.path());
|
EXPECT_EQ("/", url.path());
|
||||||
EXPECT_EQ("", url.query());
|
EXPECT_EQ("", url.query());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ TEST(UrlTest, NoPath4) {
|
|||||||
EXPECT_EQ("http", url.scheme());
|
EXPECT_EQ("http", url.scheme());
|
||||||
EXPECT_EQ("example.com", url.host());
|
EXPECT_EQ("example.com", url.host());
|
||||||
EXPECT_EQ("", url.port());
|
EXPECT_EQ("", url.port());
|
||||||
EXPECT_EQ("", url.path());
|
EXPECT_EQ("/", url.path());
|
||||||
EXPECT_EQ("key=value", url.query());
|
EXPECT_EQ("key=value", url.query());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ TEST(UrlTest, NoScheme) {
|
|||||||
EXPECT_EQ("", url.scheme());
|
EXPECT_EQ("", url.scheme());
|
||||||
EXPECT_EQ("", url.host());
|
EXPECT_EQ("", url.host());
|
||||||
EXPECT_EQ("", url.port());
|
EXPECT_EQ("", url.port());
|
||||||
EXPECT_EQ("path/to", url.path());
|
EXPECT_EQ("/path/to", url.path());
|
||||||
EXPECT_EQ("", url.query());
|
EXPECT_EQ("", url.query());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ TEST(UrlTest, NoScheme2) {
|
|||||||
EXPECT_EQ("", url.scheme());
|
EXPECT_EQ("", url.scheme());
|
||||||
EXPECT_EQ("", url.host());
|
EXPECT_EQ("", url.host());
|
||||||
EXPECT_EQ("", url.port());
|
EXPECT_EQ("", url.port());
|
||||||
EXPECT_EQ("path/to", url.path());
|
EXPECT_EQ("/path/to", url.path());
|
||||||
EXPECT_EQ("key=value", url.query());
|
EXPECT_EQ("key=value", url.query());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +78,6 @@ TEST(UrlTest, Full) {
|
|||||||
EXPECT_EQ("https", url.scheme());
|
EXPECT_EQ("https", url.scheme());
|
||||||
EXPECT_EQ("localhost", url.host());
|
EXPECT_EQ("localhost", url.host());
|
||||||
EXPECT_EQ("3000", url.port());
|
EXPECT_EQ("3000", url.port());
|
||||||
EXPECT_EQ("path/to", url.path());
|
EXPECT_EQ("/path/to", url.path());
|
||||||
EXPECT_EQ("key=value", url.query());
|
EXPECT_EQ("key=value", url.query());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public:
|
|||||||
// Send response to client.
|
// Send response to client.
|
||||||
void SendResponse(ResponsePtr response);
|
void SendResponse(ResponsePtr response);
|
||||||
|
|
||||||
|
// TODO: Remove
|
||||||
void SendResponse(Status status);
|
void SendResponse(Status status);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ namespace webcc {
|
|||||||
class RequestBuilder {
|
class RequestBuilder {
|
||||||
public:
|
public:
|
||||||
RequestBuilder() = default;
|
RequestBuilder() = default;
|
||||||
~RequestBuilder() = default;
|
|
||||||
|
|
||||||
RequestBuilder(const RequestBuilder&) = delete;
|
RequestBuilder(const RequestBuilder&) = delete;
|
||||||
RequestBuilder& operator=(const RequestBuilder&) = delete;
|
RequestBuilder& operator=(const RequestBuilder&) = delete;
|
||||||
@@ -119,13 +118,13 @@ private:
|
|||||||
// Files to upload for a POST request.
|
// Files to upload for a POST request.
|
||||||
std::vector<FormPartPtr> form_parts_;
|
std::vector<FormPartPtr> form_parts_;
|
||||||
|
|
||||||
// Compress the request content.
|
// Compress the content.
|
||||||
// NOTE: Most servers don't support compressed requests.
|
// NOTE: Most servers don't support compressed requests.
|
||||||
// Even the requests module from Python doesn't have a built-in support.
|
// Even the requests module from Python doesn't have a built-in support.
|
||||||
// See: https://github.com/kennethreitz/requests/issues/1753
|
// See: https://github.com/kennethreitz/requests/issues/1753
|
||||||
bool gzip_ = false;
|
bool gzip_ = false;
|
||||||
|
|
||||||
// Additional request headers.
|
// Additional headers.
|
||||||
std::vector<std::string> headers_;
|
std::vector<std::string> headers_;
|
||||||
|
|
||||||
// Persistent connection.
|
// Persistent connection.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ RequestHandler::RequestHandler(const std::string& doc_root)
|
|||||||
|
|
||||||
bool RequestHandler::Bind(ServicePtr service, const std::string& url,
|
bool RequestHandler::Bind(ServicePtr service, const std::string& url,
|
||||||
bool is_regex) {
|
bool is_regex) {
|
||||||
return service_manager_.AddService(service, url, is_regex);
|
return service_manager_.Add(service, url, is_regex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RequestHandler::Enqueue(ConnectionPtr connection) {
|
void RequestHandler::Enqueue(ConnectionPtr connection) {
|
||||||
@@ -80,14 +80,12 @@ void RequestHandler::HandleConnection(ConnectionPtr connection) {
|
|||||||
auto request = connection->request();
|
auto request = connection->request();
|
||||||
|
|
||||||
const Url& url = request->url();
|
const Url& url = request->url();
|
||||||
|
UrlArgs args;
|
||||||
RestRequest rest_request{ request };
|
|
||||||
|
|
||||||
LOG_INFO("Request URL path: %s", url.path().c_str());
|
LOG_INFO("Request URL path: %s", url.path().c_str());
|
||||||
|
|
||||||
// Get service by URL path.
|
// Get service by URL path.
|
||||||
auto service = service_manager_.GetService(url.path(),
|
auto service = service_manager_.Get(url.path(), &args);
|
||||||
&rest_request.url_matches);
|
|
||||||
|
|
||||||
if (!service) {
|
if (!service) {
|
||||||
LOG_WARN("No service matches the URL path: %s", url.path().c_str());
|
LOG_WARN("No service matches the URL path: %s", url.path().c_str());
|
||||||
@@ -99,20 +97,14 @@ void RequestHandler::HandleConnection(ConnectionPtr connection) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RestResponse rest_response;
|
ResponsePtr response = service->Handle(request, args);
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send response back to client.
|
// Send response back to client.
|
||||||
|
if (response) {
|
||||||
connection->SendResponse(response);
|
connection->SendResponse(response);
|
||||||
|
} else {
|
||||||
|
connection->SendResponse(Status::kNotImplemented);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RequestHandler::ServeStatic(ConnectionPtr connection) {
|
bool RequestHandler::ServeStatic(ConnectionPtr connection) {
|
||||||
|
|||||||
+1
-2
@@ -13,8 +13,7 @@ using ResponsePtr = std::shared_ptr<Response>;
|
|||||||
|
|
||||||
class Response : public Message {
|
class Response : public Message {
|
||||||
public:
|
public:
|
||||||
explicit Response(Status status = Status::kOK)
|
explicit Response(Status status = Status::kOK) : status_(status) {
|
||||||
: status_(status) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~Response() override = default;
|
~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) {
|
ResponsePtr ListService::Handle(RequestPtr request, const UrlArgs& args) {
|
||||||
const std::string& method = request.http->method();
|
if (request->method() == methods::kGet) {
|
||||||
|
return Get(UrlQuery(request->url().query()));
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
ResponsePtr DetailService::Handle(RequestPtr request, const UrlArgs& args) {
|
||||||
const std::string& method = request.http->method();
|
if (request->method() == methods::kGet) {
|
||||||
|
return Get(args, UrlQuery(request->url().query()));
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
} // namespace webcc
|
||||||
|
|||||||
+14
-43
@@ -15,32 +15,17 @@
|
|||||||
|
|
||||||
#include "webcc/globals.h"
|
#include "webcc/globals.h"
|
||||||
#include "webcc/request.h"
|
#include "webcc/request.h"
|
||||||
|
#include "webcc/response.h"
|
||||||
|
#include "webcc/response_builder.h"
|
||||||
#include "webcc/url.h"
|
#include "webcc/url.h"
|
||||||
|
|
||||||
namespace webcc {
|
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).
|
// Regex sub-matches of the URL (usually resource ID's).
|
||||||
UrlMatches url_matches;
|
// Could also be considered as arguments, so named as UrlArgs.
|
||||||
};
|
using UrlArgs = std::vector<std::string>;
|
||||||
|
|
||||||
// TODO: Add ResponseBuilder instead.
|
|
||||||
struct RestResponse {
|
|
||||||
Status status;
|
|
||||||
|
|
||||||
std::string content;
|
|
||||||
|
|
||||||
std::string media_type;
|
|
||||||
std::string charset;
|
|
||||||
};
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -49,8 +34,8 @@ class Service {
|
|||||||
public:
|
public:
|
||||||
virtual ~Service() = default;
|
virtual ~Service() = default;
|
||||||
|
|
||||||
// Handle request, output response.
|
// Handle request, return response.
|
||||||
virtual void Handle(const RestRequest& request, RestResponse* response) = 0;
|
virtual ResponsePtr Handle(RequestPtr request, const UrlArgs& args) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ServicePtr = std::shared_ptr<Service>;
|
using ServicePtr = std::shared_ptr<Service>;
|
||||||
@@ -59,42 +44,28 @@ using ServicePtr = std::shared_ptr<Service>;
|
|||||||
|
|
||||||
class ListService : public Service {
|
class ListService : public Service {
|
||||||
public:
|
public:
|
||||||
void Handle(const RestRequest& request, RestResponse* response) override;
|
ResponsePtr Handle(RequestPtr request, const UrlArgs& args) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void Get(const UrlQuery& query, RestResponse* response) {
|
virtual ResponsePtr Get(const UrlQuery& query);
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Post(const std::string& request_content,
|
virtual ResponsePtr Post(RequestPtr request);
|
||||||
RestResponse* response) {
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
class DetailService : public Service {
|
class DetailService : public Service {
|
||||||
public:
|
public:
|
||||||
void Handle(const RestRequest& request, RestResponse* response) override;
|
ResponsePtr Handle(RequestPtr request, const UrlArgs& args) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void Get(const UrlMatches& url_matches,
|
virtual ResponsePtr Get(const UrlArgs& args, const UrlQuery& query);
|
||||||
const UrlQuery& query,
|
|
||||||
RestResponse* response) {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Put(const UrlMatches& url_matches,
|
virtual ResponsePtr Put(RequestPtr request, const UrlArgs& args);
|
||||||
const std::string& request_content,
|
|
||||||
RestResponse* response) {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Patch(const UrlMatches& url_matches,
|
virtual ResponsePtr Patch(RequestPtr request, const UrlArgs& args);
|
||||||
const std::string& request_content,
|
|
||||||
RestResponse* response) {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Delete(const UrlMatches& url_matches,
|
virtual ResponsePtr Delete(const UrlArgs& args);
|
||||||
RestResponse* response) {
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace webcc
|
} // namespace webcc
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
bool ServiceManager::AddService(ServicePtr service, const std::string& url,
|
bool ServiceManager::Add(ServicePtr service, const std::string& url,
|
||||||
bool is_regex) {
|
bool is_regex) {
|
||||||
assert(service);
|
assert(service);
|
||||||
|
|
||||||
@@ -30,9 +30,8 @@ bool ServiceManager::AddService(ServicePtr service, const std::string& url,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ServicePtr ServiceManager::GetService(const std::string& url,
|
ServicePtr ServiceManager::Get(const std::string& url, UrlArgs* args) {
|
||||||
UrlMatches* matches) {
|
assert(args != nullptr);
|
||||||
assert(matches != nullptr);
|
|
||||||
|
|
||||||
for (Item& item : items_) {
|
for (Item& item : items_) {
|
||||||
if (item.is_regex) {
|
if (item.is_regex) {
|
||||||
@@ -42,7 +41,7 @@ ServicePtr ServiceManager::GetService(const std::string& url,
|
|||||||
// Any sub-matches?
|
// Any sub-matches?
|
||||||
// NOTE: Start from 1 because match[0] is the whole string itself.
|
// NOTE: Start from 1 because match[0] is the whole string itself.
|
||||||
for (size_t i = 1; i < match.size(); ++i) {
|
for (size_t i = 1; i < match.size(); ++i) {
|
||||||
matches->push_back(match[i].str());
|
args->push_back(match[i].str());
|
||||||
}
|
}
|
||||||
|
|
||||||
return item.service;
|
return item.service;
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ public:
|
|||||||
// The |url| should start with "/" and will be treated as a regular expression
|
// The |url| should start with "/" and will be treated as a regular expression
|
||||||
// if |regex| is true.
|
// if |regex| is true.
|
||||||
// Examples: "/instances", "/instances/(\\d+)".
|
// 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
|
// The |matches| is only available when the |url| bound to the service is a
|
||||||
// regular expression and has sub-expressions.
|
// regular expression and has sub-expressions.
|
||||||
// E.g., the URL bound to the service is "/instances/(\\d+)", now match
|
// E.g., the URL bound to the service is "/instances/(\\d+)", now match
|
||||||
// "/instances/12345" against it, you will get one match of "12345".
|
// "/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:
|
private:
|
||||||
class Item {
|
class Item {
|
||||||
|
|||||||
Reference in New Issue
Block a user