From 8a7f53313b65da111539036c29e08320d8900ba9 Mon Sep 17 00:00:00 2001 From: Chunting Gu Date: Fri, 21 Jun 2019 17:41:02 +0800 Subject: [PATCH] Add response builder to simplify the response build; refine the service interfaces. --- examples/file_upload_server.cc | 18 ++--- examples/rest_book_server.cc | 103 +++++++++++---------------- unittest/service_manager_unittest.cc | 56 +++++++-------- unittest/url_unittest.cc | 12 ++-- webcc/connection.h | 1 + webcc/request_builder.h | 5 +- webcc/request_handler.cc | 28 +++----- webcc/response.h | 3 +- webcc/response_builder.cc | 57 +++++++++++++++ webcc/response_builder.h | 88 +++++++++++++++++++++++ webcc/service.cc | 68 ++++++++++++------ webcc/service.h | 65 +++++------------ webcc/service_manager.cc | 11 ++- webcc/service_manager.h | 4 +- 14 files changed, 314 insertions(+), 205 deletions(-) create mode 100644 webcc/response_builder.cc create mode 100644 webcc/response_builder.h diff --git a/examples/file_upload_server.cc b/examples/file_upload_server.cc index dfb1da8..6cc83c1 100644 --- a/examples/file_upload_server.cc +++ b/examples/file_upload_server.cc @@ -8,21 +8,21 @@ class FileUploadService : public webcc::Service { public: - void Handle(const webcc::RestRequest& request, - webcc::RestResponse* response) override { - if (request.http->method() == "POST") { - std::cout << "files: " << request.http->form_parts().size() << std::endl; + webcc::ResponsePtr Handle(webcc::RequestPtr request, + const webcc::UrlArgs& args) override { + if (request->method() == "POST") { + 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 << "data: " << std::endl << part->data() << std::endl; } - response->content = "OK"; - response->media_type = webcc::media_types::kTextPlain; - response->charset = "utf-8"; - response->status = webcc::Status::kCreated; + // TODO: media_type: webcc::media_types::kTextPlain; charset = "utf-8"; + return webcc::ResponseBuilder{}.Created().Data("OK")(); } + + return webcc::ResponseBuilder{}.NotImplemented()(); } }; diff --git a/examples/rest_book_server.cc b/examples/rest_book_server.cc index fa1fbbb..e926b61 100644 --- a/examples/rest_book_server.cc +++ b/examples/rest_book_server.cc @@ -4,9 +4,12 @@ #include #include +#include "boost/core/ignore_unused.hpp" + #include "json/json.h" #include "webcc/logger.h" +#include "webcc/response_builder.h" #include "webcc/server.h" #include "examples/common/book.h" @@ -41,11 +44,10 @@ public: protected: // 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. - void Post(const std::string& request_content, - webcc::RestResponse* response) override; + webcc::ResponsePtr Post(webcc::RequestPtr request) override; private: // 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. class BookDetailService : public webcc::DetailService { public: @@ -65,18 +67,15 @@ public: protected: // Get the detailed information of a book. - void Get(const webcc::UrlMatches& url_matches, - const webcc::UrlQuery& query, - webcc::RestResponse* response) override; + webcc::ResponsePtr Get(const webcc::UrlArgs& args, + const webcc::UrlQuery& query) override; // Update a book. - void Put(const webcc::UrlMatches& url_matches, - const std::string& request_content, - webcc::RestResponse* response) override; + webcc::ResponsePtr Put(webcc::RequestPtr request, + const webcc::UrlArgs& args) override; // Delete a book. - void Delete(const webcc::UrlMatches& url_matches, - webcc::RestResponse* response) override; + webcc::ResponsePtr Delete(const webcc::UrlArgs& args) override; private: // Sleep some seconds before send back the response. @@ -87,8 +86,9 @@ private: // ----------------------------------------------------------------------------- // Return all books as a JSON array. -void BookListService::Get(const webcc::UrlQuery& /*query*/, - webcc::RestResponse* response) { +webcc::ResponsePtr BookListService::Get(const webcc::UrlQuery& query) { + boost::ignore_unused(query); + Sleep(sleep_seconds_); Json::Value json(Json::arrayValue); @@ -97,103 +97,86 @@ void BookListService::Get(const webcc::UrlQuery& /*query*/, json.append(BookToJson(book)); } - // TODO: Simplify - response->content = JsonToString(json); - response->media_type = webcc::media_types::kApplicationJson; - response->charset = "utf-8"; - response->status = webcc::Status::kOK; + // TODO: charset = "utf-8" + return webcc::ResponseBuilder{}.OK().Data(JsonToString(json)).Json()(); } -void BookListService::Post(const std::string& request_content, - webcc::RestResponse* response) { +webcc::ResponsePtr BookListService::Post(webcc::RequestPtr request) { Sleep(sleep_seconds_); Book book; - if (JsonStringToBook(request_content, &book)) { + if (JsonStringToBook(request->content(), &book)) { std::string id = g_book_store.AddBook(book); Json::Value json; json["id"] = id; - response->content = JsonToString(json); - response->media_type = webcc::media_types::kApplicationJson; - response->charset = "utf-8"; - response->status = webcc::Status::kCreated; + // TODO: charset = "utf-8" + return webcc::ResponseBuilder{}.Created().Data(JsonToString(json)).Json()(); } else { // Invalid JSON - response->status = webcc::Status::kBadRequest; + return webcc::ResponseBuilder{}.BadRequest()(); } } // ----------------------------------------------------------------------------- -void BookDetailService::Get(const webcc::UrlMatches& url_matches, - const webcc::UrlQuery& query, - webcc::RestResponse* response) { +webcc::ResponsePtr BookDetailService::Get(const webcc::UrlArgs& args, + const webcc::UrlQuery& query) { Sleep(sleep_seconds_); - if (url_matches.size() != 1) { + if (args.size() != 1) { // Using kNotFound means the resource specified by the URL cannot be found. // kBadRequest could be another choice. - response->status = webcc::Status::kNotFound; - return; + return webcc::ResponseBuilder{}.NotFound()(); } - const std::string& book_id = url_matches[0]; + const std::string& book_id = args[0]; const Book& book = g_book_store.GetBook(book_id); if (book.IsNull()) { - response->status = webcc::Status::kNotFound; - return; + return webcc::ResponseBuilder{}.NotFound()(); } - response->content = BookToJsonString(book); - response->media_type = webcc::media_types::kApplicationJson; - response->charset = "utf-8"; - response->status = webcc::Status::kOK; + // TODO: charset = "utf-8" + return webcc::ResponseBuilder{}.OK().Data(BookToJsonString(book)).Json()(); } -void BookDetailService::Put(const webcc::UrlMatches& url_matches, - const std::string& request_content, - webcc::RestResponse* response) { +webcc::ResponsePtr BookDetailService::Put(webcc::RequestPtr request, + const webcc::UrlArgs& args) { Sleep(sleep_seconds_); - if (url_matches.size() != 1) { - response->status = webcc::Status::kNotFound; - return; + if (args.size() != 1) { + return webcc::ResponseBuilder{}.NotFound()(); } - const std::string& book_id = url_matches[0]; + const std::string& book_id = args[0]; Book book; - if (!JsonStringToBook(request_content, &book)) { - response->status = webcc::Status::kBadRequest; - return; + if (!JsonStringToBook(request->content(), &book)) { + return webcc::ResponseBuilder{}.BadRequest()(); } book.id = book_id; g_book_store.UpdateBook(book); - response->status = webcc::Status::kOK; + return webcc::ResponseBuilder{}.OK()(); } -void BookDetailService::Delete(const webcc::UrlMatches& url_matches, - webcc::RestResponse* response) { +webcc::ResponsePtr BookDetailService::Delete(const webcc::UrlArgs& args) { Sleep(sleep_seconds_); - if (url_matches.size() != 1) { - response->status = webcc::Status::kNotFound; - return; + if (args.size() != 1) { + return webcc::ResponseBuilder{}.NotFound()(); } - const std::string& book_id = url_matches[0]; + const std::string& book_id = args[0]; if (!g_book_store.DeleteBook(book_id)) { - response->status = webcc::Status::kNotFound; - return; + return webcc::ResponseBuilder{}.NotFound()(); } - response->status = webcc::Status::kOK; + return webcc::ResponseBuilder{}.OK()(); } // ----------------------------------------------------------------------------- diff --git a/unittest/service_manager_unittest.cc b/unittest/service_manager_unittest.cc index 1655a9a..51a956a 100644 --- a/unittest/service_manager_unittest.cc +++ b/unittest/service_manager_unittest.cc @@ -6,9 +6,9 @@ class MyService : public webcc::Service { public: - void Handle(const webcc::RestRequest& request, - webcc::RestResponse* response) override { - response->status = webcc::Status::kOK; + webcc::ResponsePtr Handle(webcc::RequestPtr request, + const webcc::UrlArgs& args) override { + return webcc::ResponseBuilder{}.OK()(); } }; @@ -17,22 +17,21 @@ public: TEST(ServiceManagerTest, URL_RegexBasic) { webcc::ServiceManager service_manager; - service_manager.AddService(std::make_shared(), - "/instance/(\\d+)", true); - - std::vector matches; + service_manager.Add(std::make_shared(), "/instance/(\\d+)", true); 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_EQ(1, matches.size()); - EXPECT_EQ("12345", matches[0]); + EXPECT_EQ(1, args.size()); + EXPECT_EQ("12345", args[0]); url = "/instance/abcde"; - matches.clear(); - service = service_manager.GetService(url, &matches); + args.clear(); + service = service_manager.Get(url, &args); EXPECT_FALSE(!!service); } @@ -40,25 +39,24 @@ TEST(ServiceManagerTest, URL_RegexBasic) { TEST(RestServiceManagerTest, URL_RegexMultiple) { webcc::ServiceManager service_manager; - service_manager.AddService(std::make_shared(), - "/study/(\\d+)/series/(\\d+)/instance/(\\d+)", - true); - - std::vector matches; + service_manager.Add(std::make_shared(), + "/study/(\\d+)/series/(\\d+)/instance/(\\d+)", true); 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_EQ(3, matches.size()); - EXPECT_EQ("1", matches[0]); - EXPECT_EQ("2", matches[1]); - EXPECT_EQ("3", matches[2]); + EXPECT_EQ(3, args.size()); + EXPECT_EQ("1", args[0]); + EXPECT_EQ("2", args[1]); + EXPECT_EQ("3", args[2]); url = "/study/a/series/b/instance/c"; - matches.clear(); - service = service_manager.GetService(url, &matches); + args.clear(); + service = service_manager.Get(url, &args); EXPECT_FALSE(!!service); } @@ -66,13 +64,13 @@ TEST(RestServiceManagerTest, URL_RegexMultiple) { TEST(RestServiceManagerTest, URL_NonRegex) { webcc::ServiceManager service_manager; - service_manager.AddService(std::make_shared(), "/instances", - false); + service_manager.Add(std::make_shared(), "/instances", false); - std::vector matches; 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_EQ(0, matches.size()); + EXPECT_TRUE(args.empty()); } diff --git a/unittest/url_unittest.cc b/unittest/url_unittest.cc index 3215073..6708f94 100644 --- a/unittest/url_unittest.cc +++ b/unittest/url_unittest.cc @@ -8,7 +8,7 @@ TEST(UrlTest, Basic) { EXPECT_EQ("http", url.scheme()); EXPECT_EQ("example.com", url.host()); EXPECT_EQ("", url.port()); - EXPECT_EQ("path", url.path()); + EXPECT_EQ("/path", url.path()); EXPECT_EQ("", url.query()); } @@ -28,7 +28,7 @@ TEST(UrlTest, NoPath2) { EXPECT_EQ("http", url.scheme()); EXPECT_EQ("example.com", url.host()); EXPECT_EQ("", url.port()); - EXPECT_EQ("", url.path()); + EXPECT_EQ("/", url.path()); EXPECT_EQ("", url.query()); } @@ -48,7 +48,7 @@ TEST(UrlTest, NoPath4) { EXPECT_EQ("http", url.scheme()); EXPECT_EQ("example.com", url.host()); EXPECT_EQ("", url.port()); - EXPECT_EQ("", url.path()); + EXPECT_EQ("/", url.path()); EXPECT_EQ("key=value", url.query()); } @@ -58,7 +58,7 @@ TEST(UrlTest, NoScheme) { EXPECT_EQ("", url.scheme()); EXPECT_EQ("", url.host()); EXPECT_EQ("", url.port()); - EXPECT_EQ("path/to", url.path()); + EXPECT_EQ("/path/to", url.path()); EXPECT_EQ("", url.query()); } @@ -68,7 +68,7 @@ TEST(UrlTest, NoScheme2) { EXPECT_EQ("", url.scheme()); EXPECT_EQ("", url.host()); EXPECT_EQ("", url.port()); - EXPECT_EQ("path/to", url.path()); + EXPECT_EQ("/path/to", url.path()); EXPECT_EQ("key=value", url.query()); } @@ -78,6 +78,6 @@ TEST(UrlTest, Full) { EXPECT_EQ("https", url.scheme()); EXPECT_EQ("localhost", url.host()); EXPECT_EQ("3000", url.port()); - EXPECT_EQ("path/to", url.path()); + EXPECT_EQ("/path/to", url.path()); EXPECT_EQ("key=value", url.query()); } diff --git a/webcc/connection.h b/webcc/connection.h index 480bb1d..7e30f99 100644 --- a/webcc/connection.h +++ b/webcc/connection.h @@ -43,6 +43,7 @@ public: // Send response to client. void SendResponse(ResponsePtr response); + // TODO: Remove void SendResponse(Status status); private: diff --git a/webcc/request_builder.h b/webcc/request_builder.h index 0d75d51..95a1f65 100644 --- a/webcc/request_builder.h +++ b/webcc/request_builder.h @@ -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 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 headers_; // Persistent connection. diff --git a/webcc/request_handler.cc b/webcc/request_handler.cc index 5bbdbb8..8dd4859 100644 --- a/webcc/request_handler.cc +++ b/webcc/request_handler.cc @@ -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(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) { diff --git a/webcc/response.h b/webcc/response.h index 159ce77..e79e825 100644 --- a/webcc/response.h +++ b/webcc/response.h @@ -13,8 +13,7 @@ using ResponsePtr = std::shared_ptr; class Response : public Message { public: - explicit Response(Status status = Status::kOK) - : status_(status) { + explicit Response(Status status = Status::kOK) : status_(status) { } ~Response() override = default; diff --git a/webcc/response_builder.cc b/webcc/response_builder.cc new file mode 100644 index 0000000..05dc635 --- /dev/null +++ b/webcc/response_builder.cc @@ -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(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 diff --git a/webcc/response_builder.h b/webcc/response_builder.h new file mode 100644 index 0000000..0934365 --- /dev/null +++ b/webcc/response_builder.h @@ -0,0 +1,88 @@ +#ifndef WEBCC_RESPONSE_BUILDER_H_ +#define WEBCC_RESPONSE_BUILDER_H_ + +#include +#include + +#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 headers_; +}; + +} // namespace webcc + +#endif // WEBCC_RESPONSE_BUILDER_H_ diff --git a/webcc/service.cc b/webcc/service.cc index b64645a..e997780 100644 --- a/webcc/service.cc +++ b/webcc/service.cc @@ -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); +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(); +} - } else if (method == methods::kPost) { - Post(request.http->content(), response); +ResponsePtr ListService::Get(const UrlQuery& query) { + return ResponsePtr(); +} - } else { - LOG_ERRO("ListService doesn't support '%s' method.", method.c_str()); - } +ResponsePtr ListService::Post(RequestPtr request) { + return ResponsePtr(); } // ----------------------------------------------------------------------------- -void DetailService::Handle(const RestRequest& request, RestResponse* response) { - const std::string& method = request.http->method(); +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); + } - if (method == methods::kGet) { - Get(request.url_matches, UrlQuery(request.http->url().query()), response); + return ResponsePtr(); +} - } else if (method == methods::kPut) { - Put(request.url_matches, request.http->content(), response); +ResponsePtr DetailService::Get(const UrlArgs& args, const UrlQuery& query) { + return ResponsePtr(); +} - } else if (method == methods::kPatch) { - Patch(request.url_matches, request.http->content(), response); +ResponsePtr DetailService::Put(RequestPtr request, const UrlArgs& args) { + return ResponsePtr(); +} - } else if (method == methods::kDelete) { - Delete(request.url_matches, response); +ResponsePtr DetailService::Patch(RequestPtr request, const UrlArgs& args) { + return ResponsePtr(); +} - } else { - LOG_ERRO("DetailService doesn't support '%s' method.", method.c_str()); - } +ResponsePtr DetailService::Delete(const UrlArgs& args) { + return ResponsePtr(); } } // namespace webcc diff --git a/webcc/service.h b/webcc/service.h index 0e08454..58166a9 100644 --- a/webcc/service.h +++ b/webcc/service.h @@ -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; - -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; // ----------------------------------------------------------------------------- @@ -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; @@ -59,42 +44,28 @@ using ServicePtr = std::shared_ptr; 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 void Put(const UrlMatches& url_matches, - const std::string& request_content, - RestResponse* response) { - } - - virtual void Patch(const UrlMatches& url_matches, - const std::string& request_content, - RestResponse* response) { - } - - virtual void Delete(const UrlMatches& url_matches, - RestResponse* response) { - } + virtual ResponsePtr Get(const UrlArgs& args, const UrlQuery& query); + + virtual ResponsePtr Put(RequestPtr request, const UrlArgs& args); + + virtual ResponsePtr Patch(RequestPtr request, const UrlArgs& args); + + virtual ResponsePtr Delete(const UrlArgs& args); }; } // namespace webcc diff --git a/webcc/service_manager.cc b/webcc/service_manager.cc index 8231fcd..4cd2082 100644 --- a/webcc/service_manager.cc +++ b/webcc/service_manager.cc @@ -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; diff --git a/webcc/service_manager.h b/webcc/service_manager.h index 8e5315f..bf4e913 100644 --- a/webcc/service_manager.h +++ b/webcc/service_manager.h @@ -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 {