From a3cab444dcbe997c8ebf3952d074da34dad585b9 Mon Sep 17 00:00:00 2001 From: Chunting Gu Date: Wed, 26 Jun 2019 17:34:39 +0800 Subject: [PATCH] Server API rework. --- examples/CMakeLists.txt | 3 + examples/file_upload_server.cc | 9 +-- examples/rest_book_server.cc | 77 +++++++++++------- examples/static_server.cc | 39 +++++++++ unittest/CMakeLists.txt | 1 - unittest/service_manager_unittest.cc | 3 +- webcc/common.cc | 3 +- webcc/common.h | 3 + webcc/globals.h | 9 +++ webcc/request.h | 33 ++++---- webcc/request_handler.cc | 116 +++++++++++++++++++-------- webcc/request_handler.h | 50 ++++++++++-- webcc/server.cc | 7 +- webcc/server.h | 44 +++++----- webcc/service.cc | 67 ---------------- webcc/service.h | 73 ----------------- webcc/service_manager.cc | 59 -------------- webcc/service_manager.h | 66 --------------- webcc/view.cc | 43 ++++++++++ webcc/view.h | 22 +++++ 20 files changed, 342 insertions(+), 385 deletions(-) create mode 100644 examples/static_server.cc delete mode 100644 webcc/service.cc delete mode 100644 webcc/service.h delete mode 100644 webcc/service_manager.cc delete mode 100644 webcc/service_manager.h create mode 100644 webcc/view.cc create mode 100644 webcc/view.h diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 29b5625..b482753 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -50,3 +50,6 @@ target_link_libraries(file_upload_client ${EXAMPLE_LIBS}) add_executable(file_upload_server file_upload_server.cc) target_link_libraries(file_upload_server ${EXAMPLE_LIBS}) + +add_executable(static_server static_server.cc) +target_link_libraries(static_server ${EXAMPLE_LIBS}) diff --git a/examples/file_upload_server.cc b/examples/file_upload_server.cc index 6cc83c1..80cca1a 100644 --- a/examples/file_upload_server.cc +++ b/examples/file_upload_server.cc @@ -2,14 +2,14 @@ #include #include "webcc/logger.h" +#include "webcc/response_builder.h" #include "webcc/server.h" // ----------------------------------------------------------------------------- -class FileUploadService : public webcc::Service { +class FileUploadView : public webcc::View { public: - webcc::ResponsePtr Handle(webcc::RequestPtr request, - const webcc::UrlArgs& args) override { + webcc::ResponsePtr Handle(webcc::RequestPtr request) override { if (request->method() == "POST") { std::cout << "files: " << request->form_parts().size() << std::endl; @@ -47,10 +47,9 @@ int main(int argc, char* argv[]) { std::size_t workers = 2; try { - // TODO: doc root webcc::Server server(port, workers); - server.Bind(std::make_shared(), "/upload", false); + server.Route("/upload", std::make_shared(), { "POST" }); server.Run(); diff --git a/examples/rest_book_server.cc b/examples/rest_book_server.cc index e926b61..d4b7395 100644 --- a/examples/rest_book_server.cc +++ b/examples/rest_book_server.cc @@ -4,8 +4,6 @@ #include #include -#include "boost/core/ignore_unused.hpp" - #include "json/json.h" #include "webcc/logger.h" @@ -36,18 +34,29 @@ static void Sleep(int seconds) { // ----------------------------------------------------------------------------- -class BookListService : public webcc::ListService { +class BookListView : public webcc::View { public: - explicit BookListService(int sleep_seconds) - : sleep_seconds_(sleep_seconds) { + explicit BookListView(int sleep_seconds) : sleep_seconds_(sleep_seconds) { + } + + webcc::ResponsePtr Handle(webcc::RequestPtr request) override { + if (request->method() == "GET") { + return Get(request->query()); + } + + if (request->method() == "POST") { + return Post(request); + } + + return{}; } protected: // Get a list of books based on query parameters. - webcc::ResponsePtr Get(const webcc::UrlQuery& query) override; + webcc::ResponsePtr Get(const webcc::UrlQuery& query); // Create a new book. - webcc::ResponsePtr Post(webcc::RequestPtr request) override; + webcc::ResponsePtr Post(webcc::RequestPtr request); private: // Sleep some seconds before send back the response. @@ -59,23 +68,38 @@ private: // The URL is like '/books/{BookID}', and the 'args' parameter // contains the matched book ID. -class BookDetailService : public webcc::DetailService { +class BookDetailView : public webcc::View { public: - explicit BookDetailService(int sleep_seconds) - : sleep_seconds_(sleep_seconds) { + explicit BookDetailView(int sleep_seconds) : sleep_seconds_(sleep_seconds) { + } + + webcc::ResponsePtr Handle(webcc::RequestPtr request) override { + if (request->method() == "GET") { + return Get(request->args(), request->query()); + } + + if (request->method() == "PUT") { + return Put(request, request->args()); + } + + if (request->method() == "DELETE") { + return Delete(request->args()); + } + + return {}; } protected: // Get the detailed information of a book. webcc::ResponsePtr Get(const webcc::UrlArgs& args, - const webcc::UrlQuery& query) override; + const webcc::UrlQuery& query); // Update a book. webcc::ResponsePtr Put(webcc::RequestPtr request, - const webcc::UrlArgs& args) override; + const webcc::UrlArgs& args); // Delete a book. - webcc::ResponsePtr Delete(const webcc::UrlArgs& args) override; + webcc::ResponsePtr Delete(const webcc::UrlArgs& args); private: // Sleep some seconds before send back the response. @@ -86,9 +110,7 @@ private: // ----------------------------------------------------------------------------- // Return all books as a JSON array. -webcc::ResponsePtr BookListService::Get(const webcc::UrlQuery& query) { - boost::ignore_unused(query); - +webcc::ResponsePtr BookListView::Get(const webcc::UrlQuery& /*query*/) { Sleep(sleep_seconds_); Json::Value json(Json::arrayValue); @@ -101,7 +123,7 @@ webcc::ResponsePtr BookListService::Get(const webcc::UrlQuery& query) { return webcc::ResponseBuilder{}.OK().Data(JsonToString(json)).Json()(); } -webcc::ResponsePtr BookListService::Post(webcc::RequestPtr request) { +webcc::ResponsePtr BookListView::Post(webcc::RequestPtr request) { Sleep(sleep_seconds_); Book book; @@ -121,8 +143,8 @@ webcc::ResponsePtr BookListService::Post(webcc::RequestPtr request) { // ----------------------------------------------------------------------------- -webcc::ResponsePtr BookDetailService::Get(const webcc::UrlArgs& args, - const webcc::UrlQuery& query) { +webcc::ResponsePtr BookDetailView::Get(const webcc::UrlArgs& args, + const webcc::UrlQuery& query) { Sleep(sleep_seconds_); if (args.size() != 1) { @@ -142,8 +164,8 @@ webcc::ResponsePtr BookDetailService::Get(const webcc::UrlArgs& args, return webcc::ResponseBuilder{}.OK().Data(BookToJsonString(book)).Json()(); } -webcc::ResponsePtr BookDetailService::Put(webcc::RequestPtr request, - const webcc::UrlArgs& args) { +webcc::ResponsePtr BookDetailView::Put(webcc::RequestPtr request, + const webcc::UrlArgs& args) { Sleep(sleep_seconds_); if (args.size() != 1) { @@ -163,7 +185,7 @@ webcc::ResponsePtr BookDetailService::Put(webcc::RequestPtr request, return webcc::ResponseBuilder{}.OK()(); } -webcc::ResponsePtr BookDetailService::Delete(const webcc::UrlArgs& args) { +webcc::ResponsePtr BookDetailView::Delete(const webcc::UrlArgs& args) { Sleep(sleep_seconds_); if (args.size() != 1) { @@ -209,14 +231,15 @@ int main(int argc, char* argv[]) { std::size_t workers = 2; try { - // TODO: doc root webcc::Server server(port, workers); - server.Bind(std::make_shared(sleep_seconds), - "/books", false); + server.Route("/books", + std::make_shared(sleep_seconds), + { "GET", "POST" }); - server.Bind(std::make_shared(sleep_seconds), - "/books/(\\d+)", true); + server.Route(webcc::R("/books/(\\d+)"), + std::make_shared(sleep_seconds), + { "GET", "PUT", "DELETE" }); server.Run(); diff --git a/examples/static_server.cc b/examples/static_server.cc new file mode 100644 index 0000000..adaf537 --- /dev/null +++ b/examples/static_server.cc @@ -0,0 +1,39 @@ +// A general HTTP server serving static files. + +#include +#include + +#include "webcc/logger.h" +#include "webcc/server.h" + +// ----------------------------------------------------------------------------- + +void Help(const char* argv0) { + std::cout << "Usage: " << argv0 << " " << std::endl; + std::cout << " E.g.," << std::endl; + std::cout << " " << argv0 << " 8080 D:/www" << std::endl; +} + +int main(int argc, char* argv[]) { + if (argc < 3) { + Help(argv[0]); + return 1; + } + + WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); + + std::uint16_t port = static_cast(std::atoi(argv[1])); + std::string doc_root = argv[2]; + + try { + webcc::Server server(port, 1, doc_root); + + server.Run(); + + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return 1; + } + + return 0; +} diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index ce9a551..1143b85 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -3,7 +3,6 @@ set(UT_SRCS base64_unittest.cc request_parser_unittest.cc - service_manager_unittest.cc url_unittest.cc utility_unittest.cc ) diff --git a/unittest/service_manager_unittest.cc b/unittest/service_manager_unittest.cc index 51a956a..e519aa3 100644 --- a/unittest/service_manager_unittest.cc +++ b/unittest/service_manager_unittest.cc @@ -13,7 +13,7 @@ public: }; // ----------------------------------------------------------------------------- - +/* TEST(ServiceManagerTest, URL_RegexBasic) { webcc::ServiceManager service_manager; @@ -74,3 +74,4 @@ TEST(RestServiceManagerTest, URL_NonRegex) { EXPECT_TRUE(!!service); EXPECT_TRUE(args.empty()); } +*/ \ No newline at end of file diff --git a/webcc/common.cc b/webcc/common.cc index d0d1161..b6b7fa1 100644 --- a/webcc/common.cc +++ b/webcc/common.cc @@ -25,8 +25,7 @@ const char CRLF[] = { '\r', '\n' }; // ----------------------------------------------------------------------------- -// Read entire file into string. -static bool ReadFile(const Path& path, std::string* output) { +bool ReadFile(const Path& path, std::string* output) { // Flag "ate": seek to the end of stream immediately after open. bfs::ifstream stream{ path, std::ios::binary | std::ios::ate }; if (stream.fail()) { diff --git a/webcc/common.h b/webcc/common.h index 04979f1..7204a9f 100644 --- a/webcc/common.h +++ b/webcc/common.h @@ -19,6 +19,9 @@ using Path = boost::filesystem::path; using Payload = std::vector; +// Read entire file into string. +bool ReadFile(const Path& path, std::string* output); + // ----------------------------------------------------------------------------- using Header = std::pair; diff --git a/webcc/globals.h b/webcc/globals.h index 35786d5..9718e33 100644 --- a/webcc/globals.h +++ b/webcc/globals.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "webcc/config.h" @@ -43,6 +44,14 @@ namespace webcc { // ----------------------------------------------------------------------------- +using Strings = std::vector; + +// Regex sub-matches of the URL (usually resource ID's). +// Could also be considered as arguments, so named as UrlArgs. +using UrlArgs = std::vector; + +// ----------------------------------------------------------------------------- + const char* const kCRLF = "\r\n"; const std::size_t kInvalidLength = std::string::npos; diff --git a/webcc/request.h b/webcc/request.h index 5c0a3c7..727e9ad 100644 --- a/webcc/request.h +++ b/webcc/request.h @@ -23,33 +23,24 @@ public: ~Request() override = default; - const std::string& method() const { - return method_; - } + const std::string& method() const { return method_; } + void set_method(const std::string& method) { method_ = method; } - void set_method(const std::string& method) { - method_ = method; - } + const Url& url() const { return url_; } + void set_url(const std::string& url) { url_.Init(url); } - const Url& url() const { - return url_; - } + const std::string& host() const { return url_.host(); } + const std::string& port() const { return url_.port(); } - void set_url(const std::string& url) { - url_.Init(url); - } + UrlQuery query() const { return UrlQuery(url_.query()); } + // TODO: Remove void AddQuery(const std::string& key, const std::string& value) { url_.AddQuery(key, value); } - const std::string& host() const { - return url_.host(); - } - - const std::string& port() const { - return url_.port(); - } + const UrlArgs& args() const { return args_; } + void set_args(const UrlArgs& args) { args_ = args; } std::string port(const std::string& default_port) const { return port().empty() ? default_port : port(); @@ -81,6 +72,10 @@ private: Url url_; + // The URL regex matched arguments (usually resource ID's). + // Used by server only. + UrlArgs args_; + std::vector form_parts_; std::string boundary_; diff --git a/webcc/request_handler.cc b/webcc/request_handler.cc index 8dd4859..aa10d18 100644 --- a/webcc/request_handler.cc +++ b/webcc/request_handler.cc @@ -1,8 +1,11 @@ #include "webcc/request_handler.h" +#include #include #include // for move() -#include + +#include "boost/algorithm/string.hpp" +#include "boost/filesystem/fstream.hpp" #include "webcc/logger.h" #include "webcc/request.h" @@ -13,15 +16,41 @@ #include "webcc/gzip.h" #endif +namespace bfs = boost::filesystem; + namespace webcc { -RequestHandler::RequestHandler(const std::string& doc_root) +RequestHandler::RequestHandler(const Path& doc_root) : doc_root_(doc_root) { } -bool RequestHandler::Bind(ServicePtr service, const std::string& url, - bool is_regex) { - return service_manager_.Add(service, url, is_regex); +bool RequestHandler::Route(const std::string& url, ViewPtr view, + const Strings& methods) { + assert(view); + + // TODO: More error check + + routes_.push_back({ url, {}, view, methods }); + + return true; +} + +bool RequestHandler::Route(const RegexUrl& regex_url, ViewPtr view, + const Strings& methods) { + assert(view); + + // TODO: More error check + + try { + + routes_.push_back({ "", regex_url(), view, methods }); + + } catch (const std::regex_error& e) { + LOG_ERRO("Not a valid regular expression: %s", e.what()); + return false; + } + + return true; } void RequestHandler::Enqueue(ConnectionPtr connection) { @@ -72,11 +101,11 @@ void RequestHandler::WorkerRoutine() { break; } - HandleConnection(connection); + Handle(connection); } } -void RequestHandler::HandleConnection(ConnectionPtr connection) { +void RequestHandler::Handle(ConnectionPtr connection) { auto request = connection->request(); const Url& url = request->url(); @@ -84,11 +113,11 @@ void RequestHandler::HandleConnection(ConnectionPtr connection) { LOG_INFO("Request URL path: %s", url.path().c_str()); - // Get service by URL path. - auto service = service_manager_.Get(url.path(), &args); + // Find view + auto view = FindView(request->method(), url.path(), &args); - if (!service) { - LOG_WARN("No service matches the URL path: %s", url.path().c_str()); + if (!view) { + LOG_WARN("No view matches the URL path: %s", url.path().c_str()); if (!ServeStatic(connection)) { connection->SendResponse(Status::kNotFound); @@ -97,9 +126,13 @@ void RequestHandler::HandleConnection(ConnectionPtr connection) { return; } - ResponsePtr response = service->Handle(request, args); + // Save the (regex matched) URL args to request object. + request->set_args(args); + + // Ask the matched view to process the request. + ResponsePtr response = view->Handle(request); - // Send response back to client. + // Send the response back. if (response) { connection->SendResponse(response); } else { @@ -107,6 +140,38 @@ void RequestHandler::HandleConnection(ConnectionPtr connection) { } } +ViewPtr RequestHandler::FindView(const std::string& method, + const std::string& url, UrlArgs* args) { + assert(args != nullptr); + + for (auto& route : routes_) { + if (std::find(route.methods.begin(), route.methods.end(), method) == + route.methods.end()) { + continue; + } + + if (route.url.empty()) { + std::smatch match; + + if (std::regex_match(url, match, route.url_regex)) { + // Any sub-matches? + // Start from 1 because match[0] is the whole string itself. + for (size_t i = 1; i < match.size(); ++i) { + args->push_back(match[i].str()); + } + + return route.view; + } + } else { + if (boost::iequals(route.url, url)) { + return route.view; + } + } + } + + return ViewPtr(); +} + bool RequestHandler::ServeStatic(ConnectionPtr connection) { auto request = connection->request(); std::string path = request->url().path(); @@ -116,33 +181,18 @@ bool RequestHandler::ServeStatic(ConnectionPtr connection) { path += "index.html"; } - // Determine the file extension. - std::string extension; - std::size_t last_slash_pos = path.find_last_of("/"); - std::size_t last_dot_pos = path.find_last_of("."); - if (last_dot_pos != std::string::npos && last_dot_pos > last_slash_pos) { - extension = path.substr(last_dot_pos + 1); - } + Path p = doc_root_ / path; - // Open the file to send back. - std::string full_path = doc_root_ + path; - std::ifstream ifs(full_path.c_str(), std::ios::in | std::ios::binary); - if (!ifs) { - // The file doesn't exist. + std::string content; + if (!ReadFile(p, &content)) { connection->SendResponse(Status::kNotFound); return false; } - - // Fill out the content to be sent to the client. - std::string content; - char buf[512]; - while (ifs.read(buf, sizeof(buf)).gcount() > 0) { - content.append(buf, ifs.gcount()); - } - + auto response = std::make_shared(Status::kOK); if (!content.empty()) { + std::string extension = p.extension().string(); response->SetContentType(media_types::FromExtension(extension), ""); response->SetContent(std::move(content), true); } diff --git a/webcc/request_handler.h b/webcc/request_handler.h index aa63de0..de9dd42 100644 --- a/webcc/request_handler.h +++ b/webcc/request_handler.h @@ -2,26 +2,50 @@ #define WEBCC_REQUEST_HANDLER_H_ #include +#include #include #include #include "webcc/connection.h" #include "webcc/queue.h" -#include "webcc/service_manager.h" +#include "webcc/view.h" namespace webcc { -// The common handler for all incoming requests. +// ----------------------------------------------------------------------------- + +// Wrapper for regular expression URL. +class RegexUrl { +public: + explicit RegexUrl(const std::string& url) : url_(url) { + } + + std::regex operator()() const { + std::regex::flag_type flags = std::regex::ECMAScript | std::regex::icase; + + return std::regex(url_, flags); + } + +private: + std::string url_; +}; + +using R = RegexUrl; // A shortcut + +// ----------------------------------------------------------------------------- + class RequestHandler { public: - explicit RequestHandler(const std::string& doc_root); + explicit RequestHandler(const Path& doc_root); virtual ~RequestHandler() = default; RequestHandler(const RequestHandler&) = delete; RequestHandler& operator=(const RequestHandler&) = delete; - bool Bind(ServicePtr service, const std::string& url, bool is_regex); + bool Route(const std::string& url, ViewPtr view, const Strings& methods); + + bool Route(const RegexUrl& regex_url, ViewPtr view, const Strings& methods); // Put the connection into the queue. void Enqueue(ConnectionPtr connection); @@ -40,7 +64,11 @@ private: // then send the response back to the client. // The connection will keep alive if it's a persistent connection. When next // request comes, this connection will be put back to the queue again. - virtual void HandleConnection(ConnectionPtr connection); + virtual void Handle(ConnectionPtr connection); + + // Find the view by HTTP method and URL. + ViewPtr FindView(const std::string& method, const std::string& url, + UrlArgs* args); // TODO bool ServeStatic(ConnectionPtr connection); @@ -48,15 +76,23 @@ private: void SetContent(RequestPtr request, ResponsePtr response, std::string&& content); +private: + struct RouteInfo { + std::string url; + std::regex url_regex; + ViewPtr view; + Strings methods; + }; + private: // The directory containing the files to be served. - std::string doc_root_; + Path doc_root_; Queue queue_; std::vector workers_; - ServiceManager service_manager_; + std::vector routes_; }; } // namespace webcc diff --git a/webcc/server.cc b/webcc/server.cc index 063e86b..8d6eef2 100644 --- a/webcc/server.cc +++ b/webcc/server.cc @@ -11,8 +11,7 @@ using tcp = boost::asio::ip::tcp; namespace webcc { -Server::Server(std::uint16_t port, std::size_t workers, - const std::string& doc_root) +Server::Server(std::uint16_t port, std::size_t workers, const Path& doc_root) : acceptor_(io_context_), signals_(io_context_), workers_(workers), request_handler_(doc_root) { RegisterSignals(); @@ -52,10 +51,6 @@ Server::Server(std::uint16_t port, std::size_t workers, } } -bool Server::Bind(ServicePtr service, const std::string& url, bool is_regex) { - return request_handler_.Bind(service, url, is_regex); -} - void Server::Run() { if (!acceptor_.is_open()) { LOG_ERRO("Server is NOT going to run."); diff --git a/webcc/server.h b/webcc/server.h index cc7444d..cd09c9d 100644 --- a/webcc/server.h +++ b/webcc/server.h @@ -1,6 +1,7 @@ #ifndef WEBCC_SERVER_H_ #define WEBCC_SERVER_H_ +#include #include #include "boost/asio/io_context.hpp" @@ -10,33 +11,41 @@ #include "webcc/connection.h" #include "webcc/connection_pool.h" #include "webcc/request_handler.h" -#include "webcc/service.h" +#include "webcc/view.h" namespace webcc { -// HTTP server accepts TCP connections from TCP clients. -// NOTE: Only support IPv4. class Server { public: - Server(std::uint16_t port, std::size_t workers, - const std::string& doc_root = ""); + Server(std::uint16_t port, std::size_t workers, const Path& doc_root = {}); virtual ~Server() = default; Server(const Server&) = delete; Server& operator=(const Server&) = delete; - // Bind a service to the given URL path. - // The URL should start with "/" and it will be treated as a regular - // expression if |is_regex| is true. - // Examples: - // - "/instances" - // - "/instances/(\\d+)" - // Binding to the same URL multiple times is allowed, but only the last one - // takes effect. - bool Bind(ServicePtr service, const std::string& url, bool is_regex); - - // Run the server's io_service loop. + // Route a URL to a view. + // The URL should start with "/". E.g., "/instances". + bool Route(const std::string& url, ViewPtr view, const Strings& methods) { + return request_handler_.Route(url, view, methods); + } + + bool Route(const std::string& url, ViewPtr view) { + return Route(url, view, { methods::kGet }); + } + + // Route a regular expression URL to a view. + // The URL should start with "/" and be a regular expression. + // E.g., "/instances/(\\d+)". + bool Route(const RegexUrl& regex_url, ViewPtr view, const Strings& methods) { + return request_handler_.Route(regex_url, view, methods); + } + + bool Route(const RegexUrl& regex_url, ViewPtr view) { + return request_handler_.Route(regex_url, view, { methods::kGet }); + } + + // Run the loop. void Run(); private: @@ -49,9 +58,6 @@ private: // Wait for a request to stop the server. void DoAwaitStop(); - // Get the handler for incoming requests. - //virtual RequestHandler* GetRequestHandler(); - // The io_context used to perform asynchronous operations. boost::asio::io_context io_context_; diff --git a/webcc/service.cc b/webcc/service.cc deleted file mode 100644 index e997780..0000000 --- a/webcc/service.cc +++ /dev/null @@ -1,67 +0,0 @@ -#include "webcc/service.h" - -#include "webcc/logger.h" - -namespace webcc { - -// ----------------------------------------------------------------------------- - -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(); -} - -// ----------------------------------------------------------------------------- - -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 diff --git a/webcc/service.h b/webcc/service.h deleted file mode 100644 index 58166a9..0000000 --- a/webcc/service.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef WEBCC_SERVICE_H_ -#define WEBCC_SERVICE_H_ - -// NOTE: -// The design of RestListService and RestDetailService is very similar to -// XxxListView and XxxDetailView in Python Django Rest Framework. -// Deriving from them instead of RestService can simplify your own REST services -// a lot. But if you find the filtered parameters cannot meet your needs, feel -// free to derive from RestService directly. - -#include -#include -#include -#include - -#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 (usually resource ID's). -// Could also be considered as arguments, so named as UrlArgs. -using UrlArgs = std::vector; - -// ----------------------------------------------------------------------------- - -// Base class for your service. -class Service { -public: - virtual ~Service() = default; - - // Handle request, return response. - virtual ResponsePtr Handle(RequestPtr request, const UrlArgs& args) = 0; -}; - -using ServicePtr = std::shared_ptr; - -// ----------------------------------------------------------------------------- - -class ListService : public Service { -public: - ResponsePtr Handle(RequestPtr request, const UrlArgs& args) override; - -protected: - virtual ResponsePtr Get(const UrlQuery& query); - - virtual ResponsePtr Post(RequestPtr request); -}; - -// ----------------------------------------------------------------------------- - -class DetailService : public Service { -public: - ResponsePtr Handle(RequestPtr request, const UrlArgs& args) override; - -protected: - 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 - -#endif // WEBCC_SERVICE_H_ diff --git a/webcc/service_manager.cc b/webcc/service_manager.cc deleted file mode 100644 index 4cd2082..0000000 --- a/webcc/service_manager.cc +++ /dev/null @@ -1,59 +0,0 @@ -#include "webcc/service_manager.h" - -#include - -#include "webcc/logger.h" - -namespace webcc { - -bool ServiceManager::Add(ServicePtr service, const std::string& url, - bool is_regex) { - assert(service); - - Item item(service, url, is_regex); - - if (!is_regex) { - items_.push_back(std::move(item)); - return true; - } - - std::regex::flag_type flags = std::regex::ECMAScript | std::regex::icase; - - try { - // Compile the regex. - item.url_regex.assign(url, flags); - items_.push_back(std::move(item)); - return true; - } catch (const std::regex_error& e) { - LOG_ERRO("URL is not a valid regular expression: %s", e.what()); - return false; - } -} - -ServicePtr ServiceManager::Get(const std::string& url, UrlArgs* args) { - assert(args != nullptr); - - for (Item& item : items_) { - if (item.is_regex) { - std::smatch match; - - if (std::regex_match(url, match, item.url_regex)) { - // Any sub-matches? - // NOTE: Start from 1 because match[0] is the whole string itself. - for (size_t i = 1; i < match.size(); ++i) { - args->push_back(match[i].str()); - } - - return item.service; - } - } else { - if (item.url == url) { - return item.service; - } - } - } - - return ServicePtr(); -} - -} // namespace webcc diff --git a/webcc/service_manager.h b/webcc/service_manager.h deleted file mode 100644 index bf4e913..0000000 --- a/webcc/service_manager.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef WEBCC_SERVICE_MANAGER_H_ -#define WEBCC_SERVICE_MANAGER_H_ - -#include // NOLINT -#include -#include // for move() -#include - -#include "webcc/service.h" - -namespace webcc { - -class ServiceManager { -public: - ServiceManager() = default; - - ServiceManager(const ServiceManager&) = delete; - ServiceManager& operator=(const ServiceManager&) = delete; - - // Add a service and bind it with the given URL. - // The |url| should start with "/" and will be treated as a regular expression - // if |regex| is true. - // Examples: "/instances", "/instances/(\\d+)". - 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 Get(const std::string& url, UrlArgs* args); - -private: - class Item { - public: - Item(ServicePtr _service, const std::string& _url, bool _is_regex) - : service(_service), url(_url), is_regex(_is_regex) { - } - - Item(const Item&) = default; - Item& operator=(const Item&) = default; - - Item(Item&& rhs) - : service(rhs.service), - url(std::move(rhs.url)), - is_regex(rhs.is_regex), - url_regex(std::move(rhs.url_regex)) { - } - - ServicePtr service; - - // URL string, e.g., "/instances/(\\d+)". - std::string url; - - // If the URL is a regular expression or not. - bool is_regex; - - // Compiled regex for URL string. - std::regex url_regex; - }; - - std::vector items_; -}; - -} // namespace webcc - -#endif // WEBCC_SERVICE_MANAGER_H_ diff --git a/webcc/view.cc b/webcc/view.cc new file mode 100644 index 0000000..c2a740b --- /dev/null +++ b/webcc/view.cc @@ -0,0 +1,43 @@ +#include "webcc/view.h" + +namespace webcc { + +// ----------------------------------------------------------------------------- +// +//ResponsePtr DetailView::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 DetailView::Get(const UrlArgs& args, const UrlQuery& query) { +// return ResponsePtr(); +//} +// +//ResponsePtr DetailView::Put(RequestPtr request, const UrlArgs& args) { +// return ResponsePtr(); +//} +// +//ResponsePtr DetailView::Patch(RequestPtr request, const UrlArgs& args) { +// return ResponsePtr(); +//} +// +//ResponsePtr DetailView::Delete(const UrlArgs& args) { +// return ResponsePtr(); +//} + +} // namespace webcc diff --git a/webcc/view.h b/webcc/view.h new file mode 100644 index 0000000..9bb44af --- /dev/null +++ b/webcc/view.h @@ -0,0 +1,22 @@ +#ifndef WEBCC_VIEW_H_ +#define WEBCC_VIEW_H_ + +#include + +#include "webcc/request.h" +#include "webcc/response.h" + +namespace webcc { + +class View { +public: + virtual ~View() = default; + + virtual ResponsePtr Handle(RequestPtr request) = 0; +}; + +using ViewPtr = std::shared_ptr; + +} // namespace webcc + +#endif // WEBCC_VIEW_H_