diff --git a/CMakeLists.txt b/CMakeLists.txt index afde4ac..9763fd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ if(WEBCC_BUILD_UNITTEST) enable_testing() endif() +# Automatically detect _WIN32_WINNT for Asio. # See: https://stackoverflow.com/a/40217291 if(WIN32) macro(get_WIN32_WINNT version) diff --git a/example/soap_calc_client/calc_client.cc b/example/soap_calc_client/calc_client.cc index 293b0bf..6b358fb 100644 --- a/example/soap_calc_client/calc_client.cc +++ b/example/soap_calc_client/calc_client.cc @@ -1,6 +1,7 @@ #include "calc_client.h" #include #include "boost/lexical_cast.hpp" +#include "webcc/logger.h" // Set to 0 to test our own calculator server created with webcc. #define ACCESS_PARASOFT 0 @@ -71,8 +72,9 @@ bool CalcClient::Calc(const std::string& operation, // Error handling if any. if (error != webcc::kNoError) { - std::cerr << "Error: " << error; - std::cerr << ", " << webcc::GetErrorMessage(error) << std::endl; + LOG_ERRO("Operation '%s' failed: %s", + operation.c_str(), + webcc::GetErrorMessage(error)); return false; } diff --git a/example/soap_calc_client/main.cc b/example/soap_calc_client/main.cc index 2903cde..799df26 100644 --- a/example/soap_calc_client/main.cc +++ b/example/soap_calc_client/main.cc @@ -1,7 +1,10 @@ #include +#include "webcc/logger.h" #include "calc_client.h" int main() { + LOG_INIT(webcc::INFO, 0); + CalcClient calc; double x = 1.0; diff --git a/example/soap_calc_server/main.cc b/example/soap_calc_server/main.cc index be1bd79..65f5846 100644 --- a/example/soap_calc_server/main.cc +++ b/example/soap_calc_server/main.cc @@ -15,7 +15,7 @@ int main(int argc, char* argv[]) { return 1; } - LOG_INIT(webcc::VERB, 0); + LOG_INIT(webcc::INFO, 0); unsigned short port = std::atoi(argv[1]); diff --git a/src/webcc/common.cc b/src/webcc/common.cc index 522a088..dfd219c 100644 --- a/src/webcc/common.cc +++ b/src/webcc/common.cc @@ -63,7 +63,7 @@ const char* GetErrorMessage(Error error) { //////////////////////////////////////////////////////////////////////////////// -const Namespace kSoapEnvNamespace{ +const SoapNamespace kSoapEnvNamespace{ "soap", "http://schemas.xmlsoap.org/soap/envelope/" }; diff --git a/src/webcc/common.h b/src/webcc/common.h index c1ee327..c5086b2 100644 --- a/src/webcc/common.h +++ b/src/webcc/common.h @@ -87,8 +87,7 @@ const char* GetErrorMessage(Error error); // XML namespace name/url pair. // E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" } -// TODO: Rename (add soap prefix) -class Namespace { +class SoapNamespace { public: std::string name; std::string url; @@ -99,12 +98,11 @@ public: }; // CSoap's default namespace for SOAP Envelope. -extern const Namespace kSoapEnvNamespace; +extern const SoapNamespace kSoapEnvNamespace; //////////////////////////////////////////////////////////////////////////////// -// Parameter in the SOAP request envelope. -// TODO: Rename (add soap prefix) +// Key-value parameter. class Parameter { public: Parameter() = default; diff --git a/src/webcc/http_client.h b/src/webcc/http_client.h index afb2152..22be370 100644 --- a/src/webcc/http_client.h +++ b/src/webcc/http_client.h @@ -19,7 +19,6 @@ class HttpResponse; class HttpClient { public: HttpClient(); - ~HttpClient() = default; HttpClient(const HttpClient&) = delete; diff --git a/src/webcc/http_request_handler.cc b/src/webcc/http_request_handler.cc index b07c78c..b0fbce5 100644 --- a/src/webcc/http_request_handler.cc +++ b/src/webcc/http_request_handler.cc @@ -22,12 +22,12 @@ void HttpRequestHandler::Start(std::size_t count) { } void HttpRequestHandler::Stop() { - LOG_VERB("Stopping workers..."); + LOG_INFO("Stopping workers..."); // Close pending sessions. for (HttpSessionPtr conn = queue_.Pop(); conn; conn = queue_.Pop()) { - LOG_VERB("Closing pending session..."); - conn->Stop(); + LOG_INFO("Closing pending session..."); + conn->Close(); } // Enqueue a null session to trigger the first worker to stop. @@ -35,17 +35,17 @@ void HttpRequestHandler::Stop() { workers_.join_all(); - LOG_VERB("All workers have been stopped."); + LOG_INFO("All workers have been stopped."); } void HttpRequestHandler::WorkerRoutine() { - LOG_VERB("Worker is running."); + LOG_INFO("Worker is running."); for (;;) { HttpSessionPtr session = queue_.PopOrWait(); if (!session) { - LOG_VERB("Worker is going to stop."); + LOG_INFO("Worker is going to stop."); // For stopping next worker. queue_.Push(HttpSessionPtr()); diff --git a/src/webcc/http_server.cc b/src/webcc/http_server.cc index 7d29324..b71281f 100644 --- a/src/webcc/http_server.cc +++ b/src/webcc/http_server.cc @@ -25,7 +25,7 @@ HttpServer::HttpServer(unsigned short port, std::size_t workers) signals_.add(SIGQUIT); #endif - DoAwaitStop(); + AsyncAwaitStop(); // NOTE: // "reuse_addr=true" means option SO_REUSEADDR will be set. @@ -37,16 +37,13 @@ HttpServer::HttpServer(unsigned short port, std::size_t workers) tcp::endpoint(tcp::v4(), port), true)); // reuse_addr - DoAccept(); -} - -HttpServer::~HttpServer() { + AsyncAccept(); } void HttpServer::Run() { assert(GetRequestHandler() != NULL); - LOG_VERB("Server is going to run..."); + LOG_INFO("Server is going to run..."); // Start worker threads. GetRequestHandler()->Start(workers_); @@ -58,7 +55,7 @@ void HttpServer::Run() { io_context_.run(); } -void HttpServer::DoAccept() { +void HttpServer::AsyncAccept() { acceptor_->async_accept( [this](boost::system::error_code ec, tcp::socket socket) { // Check whether the server was stopped by a signal before this @@ -68,17 +65,19 @@ void HttpServer::DoAccept() { } if (!ec) { + LOG_INFO("Accepted a connection."); + HttpSessionPtr session{ new HttpSession(std::move(socket), GetRequestHandler()) }; session->Start(); } - DoAccept(); + AsyncAccept(); }); } -void HttpServer::DoAwaitStop() { +void HttpServer::AsyncAwaitStop() { signals_.async_wait( [this](boost::system::error_code, int /*signo*/) { // The server is stopped by canceling all outstanding asynchronous diff --git a/src/webcc/http_server.h b/src/webcc/http_server.h index 098897c..fa47ef7 100644 --- a/src/webcc/http_server.h +++ b/src/webcc/http_server.h @@ -26,17 +26,17 @@ public: HttpServer(unsigned short port, std::size_t workers); - virtual ~HttpServer(); + virtual ~HttpServer() = default; // Run the server's io_service loop. void Run(); private: // Initiate an asynchronous accept operation. - void DoAccept(); + void AsyncAccept(); // Wait for a request to stop the server. - void DoAwaitStop(); + void AsyncAwaitStop(); // Get the handler for incoming requests. virtual HttpRequestHandler* GetRequestHandler() = 0; diff --git a/src/webcc/http_session.cc b/src/webcc/http_session.cc index 571fb11..2a5fdb7 100644 --- a/src/webcc/http_session.cc +++ b/src/webcc/http_session.cc @@ -17,14 +17,11 @@ HttpSession::HttpSession(boost::asio::ip::tcp::socket socket, , request_parser_(&request_) { } -HttpSession::~HttpSession() { -} - void HttpSession::Start() { - DoRead(); + AsyncRead(); } -void HttpSession::Stop() { +void HttpSession::Close() { boost::system::error_code ec; socket_.close(ec); } @@ -35,33 +32,24 @@ void HttpSession::SetResponseContent(std::string&& content, response_.SetContentType(content_type); } -void HttpSession::SendResponse(int status) { +void HttpSession::SendResponse(HttpStatus::Enum status) { response_.set_status(status); - DoWrite(); + AsyncWrite(); } -void HttpSession::DoRead() { +void HttpSession::AsyncRead() { socket_.async_read_some(boost::asio::buffer(buffer_), - std::bind(&HttpSession::HandleRead, + std::bind(&HttpSession::ReadHandler, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } -void HttpSession::DoWrite() { - boost::asio::async_write(socket_, - response_.ToBuffers(), - std::bind(&HttpSession::HandleWrite, - shared_from_this(), - std::placeholders::_1, - std::placeholders::_2)); -} - -void HttpSession::HandleRead(boost::system::error_code ec, - std::size_t length) { +void HttpSession::ReadHandler(boost::system::error_code ec, + std::size_t length) { if (ec) { if (ec != boost::asio::error::operation_aborted) { - Stop(); + Close(); } return; } @@ -71,13 +59,13 @@ void HttpSession::HandleRead(boost::system::error_code ec, if (error != kNoError) { // Bad request. response_ = HttpResponse::Fault(HttpStatus::kBadRequest); - DoWrite(); + AsyncWrite(); return; } if (!request_parser_.finished()) { // Continue to read the request. - DoRead(); + AsyncRead(); return; } @@ -87,14 +75,23 @@ void HttpSession::HandleRead(boost::system::error_code ec, request_handler_->Enqueue(shared_from_this()); } +void HttpSession::AsyncWrite() { + boost::asio::async_write(socket_, + response_.ToBuffers(), + std::bind(&HttpSession::WriteHandler, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); +} + // NOTE: // This write handler will be called from main thread (the thread calling // io_context.run), even though DoWrite() is invoked by worker threads. This is // ensured by Asio. -void HttpSession::HandleWrite(boost::system::error_code ec, - std::size_t length) { +void HttpSession::WriteHandler(boost::system::error_code ec, + std::size_t length) { if (!ec) { - LOG_VERB("Response has been sent back."); + LOG_INFO("Response has been sent back."); // Initiate graceful connection closure. boost::system::error_code ec; @@ -104,7 +101,7 @@ void HttpSession::HandleWrite(boost::system::error_code ec, LOG_ERRO("Sending response error: %s", ec.message().c_str()); if (ec != boost::asio::error::operation_aborted) { - Stop(); + Close(); } } } diff --git a/src/webcc/http_session.h b/src/webcc/http_session.h index b8fd737..6136c22 100644 --- a/src/webcc/http_session.h +++ b/src/webcc/http_session.h @@ -23,29 +23,30 @@ public: HttpSession(boost::asio::ip::tcp::socket socket, HttpRequestHandler* handler); - ~HttpSession(); + ~HttpSession() = default; const HttpRequest& request() const { return request_; } + // Start to read and process the client request. void Start(); - void Stop(); + // Close the socket. + void Close(); void SetResponseContent(std::string&& content, const std::string& content_type); - // Write response back to the client with the given HTTP status. - void SendResponse(int status); + // Send response to client with the given status. + void SendResponse(HttpStatus::Enum status); private: - void DoRead(); + void AsyncRead(); + void ReadHandler(boost::system::error_code ec, std::size_t length); - void DoWrite(); - - void HandleRead(boost::system::error_code ec, std::size_t length); - void HandleWrite(boost::system::error_code ec, std::size_t length); + void AsyncWrite(); + void WriteHandler(boost::system::error_code ec, std::size_t length); private: // Socket for the connection. diff --git a/src/webcc/logger.h b/src/webcc/logger.h index 2d0d5f8..92b4fe3 100644 --- a/src/webcc/logger.h +++ b/src/webcc/logger.h @@ -3,10 +3,10 @@ // Simple console logger. -namespace webcc { - #if WEBCC_ENABLE_LOG +namespace webcc { + enum LogLevel { VERB = 0, INFO, diff --git a/src/webcc/rest_server.cc b/src/webcc/rest_server.cc index 0b4fc46..61c558e 100644 --- a/src/webcc/rest_server.cc +++ b/src/webcc/rest_server.cc @@ -76,7 +76,8 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) { } // TODO: Only for GET? - Url::Query query = Url::SplitQuery(url.query()); + UrlQuery query; + Url::SplitQuery(url.query(), &query); std::string content; bool ok = service->Handle(session->request().method(), diff --git a/src/webcc/rest_service.h b/src/webcc/rest_service.h index 49fba3d..9b953ff 100644 --- a/src/webcc/rest_service.h +++ b/src/webcc/rest_service.h @@ -10,6 +10,8 @@ namespace webcc { +class UrlQuery; + // Base class for your REST service. class RestService { public: @@ -20,12 +22,12 @@ public: // \param http_method GET, POST, etc. // \param url_sub_matches The regex sub-matches in the URL, // usually resource ID. - // \param query Query parameters in the URL, key value pairs. + // \param query Query parameters in the URL. // \param request_content Request JSON. // \param response_content Output response JSON. virtual bool Handle(const std::string& http_method, const std::vector& url_sub_matches, - const std::map& query, + const UrlQuery& query, const std::string& request_content, std::string* response_content) = 0; }; diff --git a/src/webcc/soap_client.h b/src/webcc/soap_client.h index 9e842da..1b98bee 100644 --- a/src/webcc/soap_client.h +++ b/src/webcc/soap_client.h @@ -28,8 +28,8 @@ protected: // -1 means default timeout (normally 30s) will be used. int timeout_seconds_ = -1; - Namespace soapenv_ns_; // SOAP envelope namespace. - Namespace service_ns_; // Namespace for your web service. + SoapNamespace soapenv_ns_; // SOAP envelope namespace. + SoapNamespace service_ns_; // Namespace for your web service. // Request URL. std::string url_; diff --git a/src/webcc/soap_message.h b/src/webcc/soap_message.h index ca2be7e..23b25a1 100644 --- a/src/webcc/soap_message.h +++ b/src/webcc/soap_message.h @@ -11,11 +11,11 @@ namespace webcc { class SoapMessage { public: // E.g., set as kSoapEnvNamespace. - void set_soapenv_ns(const Namespace& soapenv_ns) { + void set_soapenv_ns(const SoapNamespace& soapenv_ns) { soapenv_ns_ = soapenv_ns; } - void set_service_ns(const Namespace& service_ns) { + void set_service_ns(const SoapNamespace& service_ns) { service_ns_ = service_ns; } @@ -34,8 +34,7 @@ public: bool FromXml(const std::string& xml_string); protected: - SoapMessage() { - } + SoapMessage() = default; // Convert to SOAP body XML. virtual void ToXmlBody(pugi::xml_node xbody) = 0; @@ -44,8 +43,8 @@ protected: virtual bool FromXmlBody(pugi::xml_node xbody) = 0; protected: - Namespace soapenv_ns_; // SOAP envelope namespace. - Namespace service_ns_; // Namespace for your web service. + SoapNamespace soapenv_ns_; // SOAP envelope namespace. + SoapNamespace service_ns_; // Namespace for your web service. std::string operation_; }; diff --git a/src/webcc/url.cc b/src/webcc/url.cc index 810e1bc..a8ecba5 100644 --- a/src/webcc/url.cc +++ b/src/webcc/url.cc @@ -1,9 +1,43 @@ #include "webcc/url.h" +#include #include namespace webcc { +//////////////////////////////////////////////////////////////////////////////// + +void UrlQuery::Add(std::string&& key, std::string&& value) { + if (!HasKey(key)) { + parameters_.push_back({ std::move(key), std::move(value) }); + } +} + +void UrlQuery::Remove(const std::string& key) { + auto it = Find(key); + if (it != parameters_.end()) { + parameters_.erase(it); + } +} + +const std::string& UrlQuery::GetValue(const std::string& key) const { + static const std::string kEmptyValue; + + auto it = Find(key); + if (it != parameters_.end()) { + return it->value(); + } + return kEmptyValue; +} + +UrlQuery::ConstIterator UrlQuery::Find(const std::string& key) const { + return std::find_if(parameters_.begin(), + parameters_.end(), + [&key](const Parameter& p) { return p.key() == key; }); +} + +//////////////////////////////////////////////////////////////////////////////// + Url::Url(const std::string& str) { std::size_t pos = str.find('?'); if (pos == std::string::npos) { @@ -44,33 +78,29 @@ static bool SplitKeyValue(const std::string& kv, } // static -Url::Query Url::SplitQuery(const std::string& query) { +void Url::SplitQuery(const std::string& str, UrlQuery* query) { const std::size_t NPOS = std::string::npos; - Query result; - // Split into key value pairs separated by '&'. std::size_t i = 0; while (i != NPOS) { - std::size_t j = query.find_first_of('&', i); + std::size_t j = str.find_first_of('&', i); std::string kv; if (j == NPOS) { - kv = query.substr(i); + kv = str.substr(i); i = NPOS; } else { - kv = query.substr(i, j - i); + kv = str.substr(i, j - i); i = j + 1; } std::string key; std::string value; if (SplitKeyValue(kv, &key, &value)) { - result[key] = value; // TODO: Move + query->Add(std::move(key), std::move(value)); } } - - return result; } } // namespace webcc diff --git a/src/webcc/url.h b/src/webcc/url.h index d37bd3b..2c61306 100644 --- a/src/webcc/url.h +++ b/src/webcc/url.h @@ -11,8 +11,37 @@ #include #include +#include "webcc/common.h" + namespace webcc { +//////////////////////////////////////////////////////////////////////////////// + +// URL query parameters. +class UrlQuery { +public: + typedef std::vector Parameters; + + void Add(std::string&& key, std::string&& value); + + void Remove(const std::string& key); + + const std::string& GetValue(const std::string& key) const; + + bool HasKey(const std::string& key) const { + return Find(key) != parameters_.end(); + } + +private: + typedef Parameters::const_iterator ConstIterator; + ConstIterator Find(const std::string& key) const; + +private: + Parameters parameters_; +}; + +//////////////////////////////////////////////////////////////////////////////// + class Url { public: typedef std::map Query; @@ -40,8 +69,8 @@ public: // Split a path into its hierarchical components. static std::vector SplitPath(const std::string& path); - // Split query string into key-value map. - static Query SplitQuery(const std::string& query); + // Split query string into key-value parameters. + static void SplitQuery(const std::string& str, UrlQuery* query); private: std::string path_; diff --git a/src/webcc/utility.cc b/src/webcc/utility.cc index 4a3c2fe..e2e2dd3 100644 --- a/src/webcc/utility.cc +++ b/src/webcc/utility.cc @@ -7,7 +7,6 @@ namespace webcc { // Print the resolved endpoints. // NOTE: Endpoint is one word, don't use "end point". -// TODO void DumpEndpoints(tcp::resolver::results_type& endpoints) { std::cout << "Endpoints: " << endpoints.size() << std::endl;