From 81ed82a302e46d56098fd9ee4a74ca5ed873ee8f Mon Sep 17 00:00:00 2001 From: Adam Gu Date: Wed, 25 Apr 2018 14:43:45 +0800 Subject: [PATCH] Update readme --- README.md | 29 ++-- example/rest_book_client/main.cc | 2 +- example/soap_calc_client/calc_client.cc | 16 +- example/soap_calc_client/calc_client.h | 3 + example/soap_calc_client/main.cc | 7 +- example/soap_calc_server/calc_service.cc | 96 +++++++++-- example/soap_calc_server/calc_service.h | 5 + example/soap_calc_server/main.cc | 3 + src/webcc/common.cc | 7 + src/webcc/common.h | 60 +++---- src/webcc/http_client.cc | 211 +++++++++++++++-------- src/webcc/http_client.h | 41 ++++- src/webcc/http_parser.h | 3 +- src/webcc/http_request_handler.h | 2 +- src/webcc/http_request_parser.h | 2 + src/webcc/http_response.h | 4 +- src/webcc/http_response_parser.h | 2 + src/webcc/http_session.cc | 31 ++-- src/webcc/http_session.h | 13 +- src/webcc/logger.h | 26 +-- src/webcc/rest_server.cc | 39 ++--- src/webcc/rest_server.h | 2 +- src/webcc/soap_client.cc | 7 +- src/webcc/soap_client.h | 9 +- src/webcc/soap_server.cc | 28 ++- src/webcc/soap_server.h | 2 +- src/webcc/soap_service.h | 4 + 27 files changed, 427 insertions(+), 227 deletions(-) diff --git a/README.md b/README.md index 62823cf..fbcfb5c 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,27 @@ # webcc -A lightweight C++ REST and SOAP client and server library based on Boost.Asio. +A lightweight C++ REST and SOAP client and server library based on *Boost.Asio*. -## Tutorials +Please see the `doc` folder for tutorials and `example` folder for examples. -**SOAP:** -- [SOAP Client Tutorial](doc/SoapClientTutorial.md) -- [SOAP Server Tutorial](doc/SoapServerTutorial.md) +## Build Instructions -- [SOAP 客户端教程](doc/SoapClientTutorial_zh-CN.md) +A lot of C++11 features are used, e.g., `std::move`. But C++14 is not required. It means that you can still build `webcc` using VS2013. -## Dependencies +[CMake 3.1.0+](https://cmake.org/) is required as the build system. But if you don't use CMake, you can just copy the `src/webcc` folder to your own project then manage it by yourself. -- C++11 -- Boost 1.66+ +[C++ Boost](https://www.boost.org/) should be 1.66+ because Asio made some broken changes to the API in 1.66. -If enable SOAP support, **pugixml** is needed to parse and compose XML strings. +### Build Options -## Build +The following CMake options determine how you build the projects. They are quite self-explanatory. -The build system is CMake. +```cmake +option(WEBCC_ENABLE_LOG "Enable console logger?" ON) +option(WEBCC_ENABLE_SOAP "Enable SOAP support (need pugixml)?" ON) +option(WEBCC_BUILD_UNITTEST "Build unit test?" ON) +option(WEBCC_BUILD_REST_EXAMPLE "Build REST example?" ON) +option(WEBCC_BUILD_SOAP_EXAMPLE "Build SOAP example?" ON) +``` + +If `WEBCC_ENABLE_SOAP` is `ON`, **pugixml** (already included) is used to parse and compose XML strings. diff --git a/example/rest_book_client/main.cc b/example/rest_book_client/main.cc index 7805852..b086394 100644 --- a/example/rest_book_client/main.cc +++ b/example/rest_book_client/main.cc @@ -31,7 +31,7 @@ public: http_request.Build(); webcc::HttpClient http_client; - webcc::Error error = http_client.SendRequest(http_request, http_response); + webcc::Error error = http_client.MakeRequest(http_request, http_response); return error == webcc::kNoError; } diff --git a/example/soap_calc_client/calc_client.cc b/example/soap_calc_client/calc_client.cc index 886b745..293b0bf 100644 --- a/example/soap_calc_client/calc_client.cc +++ b/example/soap_calc_client/calc_client.cc @@ -2,6 +2,9 @@ #include #include "boost/lexical_cast.hpp" +// Set to 0 to test our own calculator server created with webcc. +#define ACCESS_PARASOFT 0 + CalcClient::CalcClient() { Init(); } @@ -19,13 +22,22 @@ bool CalcClient::Multiply(double x, double y, double* result) { } bool CalcClient::Divide(double x, double y, double* result) { + // ParaSoft's Calculator Service uses different parameter names for Divide. +#if ACCESS_PARASOFT return Calc("divide", "numerator", "denominator", x, y, result); +#else + return Calc("divide", "x", "y", x, y, result); +#endif } -// Set to 0 to test our own calculator server created with webcc. -#define ACCESS_PARASOFT 0 +bool CalcClient::NotExist(double x, double y, double* result) { + return Calc("notexist", "x", "y", x, y, result); +} void CalcClient::Init() { + // Override the default timeout. + timeout_seconds_ = 5; + #if ACCESS_PARASOFT url_ = "/glue/calculator"; host_ = "ws1.parasoft.com"; diff --git a/example/soap_calc_client/calc_client.h b/example/soap_calc_client/calc_client.h index a8cb6ac..9ab8be7 100644 --- a/example/soap_calc_client/calc_client.h +++ b/example/soap_calc_client/calc_client.h @@ -16,6 +16,9 @@ public: bool Divide(double x, double y, double* result); + // For testing purpose. + bool NotExist(double x, double y, double* result); + protected: void Init(); diff --git a/example/soap_calc_client/main.cc b/example/soap_calc_client/main.cc index 74220f0..2903cde 100644 --- a/example/soap_calc_client/main.cc +++ b/example/soap_calc_client/main.cc @@ -12,7 +12,6 @@ int main() { printf("add: %.1f\n", result); } -#if 0 if (calc.Subtract(x, y, &result)) { printf("subtract: %.1f\n", result); } @@ -24,7 +23,11 @@ int main() { if (calc.Divide(x, y, &result)) { printf("divide: %.1f\n", result); } -#endif + + // Try to call a non-existing operation. + if (calc.NotExist(x, y, &result)) { + printf("notexist: %.1f\n", result); + } return 0; } diff --git a/example/soap_calc_server/calc_service.cc b/example/soap_calc_server/calc_service.cc index 5d0930c..4561260 100644 --- a/example/soap_calc_server/calc_service.cc +++ b/example/soap_calc_server/calc_service.cc @@ -1,36 +1,94 @@ #include "calc_service.h" +// Sleep several seconds for the client to test timeout control. +#define SLEEP_FOR_TIMEOUT_TEST 0 + #include "boost/lexical_cast.hpp" +#if SLEEP_FOR_TIMEOUT_TEST +#include "boost/thread/thread.hpp" +#endif + +#include "webcc/logger.h" #include "webcc/soap_request.h" #include "webcc/soap_response.h" +#if SLEEP_FOR_TIMEOUT_TEST +static const int kSleepSeconds = 3; +#endif + bool CalcService::Handle(const webcc::SoapRequest& soap_request, webcc::SoapResponse* soap_response) { - try { - if (soap_request.operation() == "add") { - double x = boost::lexical_cast(soap_request.GetParameter("x")); - double y = boost::lexical_cast(soap_request.GetParameter("y")); + double x = 0.0; + double y = 0.0; + if (!GetParameters(soap_request, &x, &y)) { + return false; + } + + const std::string& op = soap_request.operation(); - double result = x + y; + LOG_INFO("Soap operation '%s': %.2f, %.2f", op.c_str(), x, y); - soap_response->set_soapenv_ns(webcc::kSoapEnvNamespace); - soap_response->set_service_ns({ - "cal", - "http://www.example.com/calculator/" - }); - soap_response->set_operation(soap_request.operation()); - soap_response->set_result_name("Result"); - soap_response->set_result(std::to_string(result)); + std::function calc; - return true; + if (op == "add") { + calc = [](double x, double y) { return x + y; }; - } else { - // NOT_IMPLEMENTED + } else if (op == "subtract") { + calc = [](double x, double y) { return x - y; }; + + } else if (op == "multiply") { + calc = [](double x, double y) { return x * y; }; + + } else if (op == "divide") { + calc = [](double x, double y) { return x / y; }; + + if (y == 0.0) { + LOG_ERRO("Cannot divide by 0."); + return false; } - } catch (boost::bad_lexical_cast&) { - // BAD_REQUEST + } else { + LOG_ERRO("Operation '%s' is not supported.", op.c_str()); + return false; + } + + if (!calc) { + return false; + } + + double result = calc(x, y); + + soap_response->set_soapenv_ns(webcc::kSoapEnvNamespace); + soap_response->set_service_ns({ + "cal", + "http://www.example.com/calculator/" + }); + + soap_response->set_operation(soap_request.operation()); + + soap_response->set_result_name("Result"); + soap_response->set_result(std::to_string(result)); + +#if SLEEP_FOR_TIMEOUT_TEST + LOG_INFO("Sleep %d seconds for the client to test timeout control.", + kSleepSeconds); + boost::this_thread::sleep_for(boost::chrono::seconds(kSleepSeconds)); +#endif + + return true; +} + +bool CalcService::GetParameters(const webcc::SoapRequest& soap_request, + double* x, + double* y) { + try { + *x = boost::lexical_cast(soap_request.GetParameter("x")); + *y = boost::lexical_cast(soap_request.GetParameter("y")); + + } catch (boost::bad_lexical_cast& e) { + LOG_ERRO("Parameter cast error: %s", e.what()); + return false; } - return false; + return true; } diff --git a/example/soap_calc_server/calc_service.h b/example/soap_calc_server/calc_service.h index 4fcf0bf..16c4c62 100644 --- a/example/soap_calc_server/calc_service.h +++ b/example/soap_calc_server/calc_service.h @@ -10,6 +10,11 @@ public: bool Handle(const webcc::SoapRequest& soap_request, webcc::SoapResponse* soap_response) override; + +private: + bool GetParameters(const webcc::SoapRequest& soap_request, + double* x, + double* y); }; #endif // CALC_SERVICE_H_ diff --git a/example/soap_calc_server/main.cc b/example/soap_calc_server/main.cc index e1ac657..be1bd79 100644 --- a/example/soap_calc_server/main.cc +++ b/example/soap_calc_server/main.cc @@ -1,4 +1,5 @@ #include +#include "webcc/logger.h" #include "webcc/soap_server.h" #include "calc_service.h" @@ -14,6 +15,8 @@ int main(int argc, char* argv[]) { return 1; } + LOG_INIT(webcc::VERB, 0); + unsigned short port = std::atoi(argv[1]); std::size_t workers = 2; diff --git a/src/webcc/common.cc b/src/webcc/common.cc index d0014c3..522a088 100644 --- a/src/webcc/common.cc +++ b/src/webcc/common.cc @@ -18,6 +18,13 @@ const std::string kTextXmlUtf8 = "text/xml; charset=utf-8"; const std::string kTextJsonUtf8 = "text/json; charset=utf-8"; +const std::string kHttpHead = "HEAD"; +const std::string kHttpGet = "GET"; +const std::string kHttpPost = "POST"; +const std::string kHttpPatch = "PATCH"; +const std::string kHttpPut = "PUT"; +const std::string kHttpDelete = "DELETE"; + //////////////////////////////////////////////////////////////////////////////// const char* GetErrorMessage(Error error) { diff --git a/src/webcc/common.h b/src/webcc/common.h index 714bb98..c1ee327 100644 --- a/src/webcc/common.h +++ b/src/webcc/common.h @@ -26,6 +26,36 @@ extern const std::string kTextJsonUtf8; //////////////////////////////////////////////////////////////////////////////// +// HTTP methods (verbs) in string ("HEAD", "GET", etc.). +// NOTE: Don't use enum to avoid converting back and forth. +extern const std::string kHttpHead; +extern const std::string kHttpGet; +extern const std::string kHttpPost; +extern const std::string kHttpPatch; +extern const std::string kHttpPut; +extern const std::string kHttpDelete; + +// HTTP response status. +// This is not a full list. +// Full list: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes +// NTOE: Don't use enum class because we want to convert to/from int easily. +struct HttpStatus { + enum Enum { + kOK = 200, + kCreated = 201, + kAccepted = 202, + kNoContent = 204, + kNotModified = 304, + kBadRequest = 400, + kNotFound = 404, + InternalServerError = 500, + kNotImplemented = 501, + kServiceUnavailable = 503, + }; +}; + +//////////////////////////////////////////////////////////////////////////////// + // Error codes. enum Error { kNoError = 0, // OK @@ -55,36 +85,6 @@ const char* GetErrorMessage(Error error); //////////////////////////////////////////////////////////////////////////////// -// HTTP methods (verbs). -// NOTE: Don't use enum to avoid converting back and forth. -const std::string kHttpHead = "HEAD"; -const std::string kHttpGet = "GET"; -const std::string kHttpPost = "POST"; -const std::string kHttpPatch = "PATCH"; -const std::string kHttpPut = "PUT"; -const std::string kHttpDelete = "DELETE"; - -// HTTP response status. -// This is not a full list. -// Full list: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes -// NTOE: Don't use enum class because we want to convert to/from int easily. -struct HttpStatus { - enum Enum { - kOK = 200, - kCreated = 201, - kAccepted = 202, - kNoContent = 204, - kNotModified = 304, - kBadRequest = 400, - kNotFound = 404, - InternalServerError = 500, - kNotImplemented = 501, - kServiceUnavailable = 503, - }; -}; - -//////////////////////////////////////////////////////////////////////////////// - // XML namespace name/url pair. // E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" } // TODO: Rename (add soap prefix) diff --git a/src/webcc/http_client.cc b/src/webcc/http_client.cc index 4e1c4a1..a5155cf 100644 --- a/src/webcc/http_client.cc +++ b/src/webcc/http_client.cc @@ -1,62 +1,73 @@ #include "webcc/http_client.h" +#include "boost/date_time/posix_time/posix_time.hpp" +#include "boost/lambda/bind.hpp" +#include "boost/lambda/lambda.hpp" + #if 0 #include "boost/asio.hpp" #else #include "boost/asio/connect.hpp" -#include "boost/asio/ip/tcp.hpp" #include "boost/asio/read.hpp" #include "boost/asio/write.hpp" #endif #include "webcc/logger.h" -#include "webcc/http_response_parser.h" #include "webcc/http_request.h" #include "webcc/http_response.h" +// NOTE: +// The timeout control is inspired by the following Asio example: +// example\cpp03\timeouts\blocking_tcp_client.cpp + namespace webcc { //////////////////////////////////////////////////////////////////////////////// -// See https://stackoverflow.com/a/9079092 -static void SetTimeout(boost::asio::ip::tcp::socket& socket, - int timeout_seconds) { -#if defined _WINDOWS +static const int kConnectMaxSeconds = 10; +static const int kSendMaxSeconds = 10; +static const int kReceiveMaxSeconds = 30; - int ms = timeout_seconds * 1000; +//////////////////////////////////////////////////////////////////////////////// - const char* optval = reinterpret_cast(&ms); - std::size_t optlen = sizeof(ms); +HttpClient::HttpClient() + : socket_(io_context_) + , timeout_seconds_(kReceiveMaxSeconds) + , deadline_timer_(io_context_) { - setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, optval, optlen); - setsockopt(socket.native_handle(), SOL_SOCKET, SO_SNDTIMEO, optval, optlen); + deadline_timer_.expires_at(boost::posix_time::pos_infin); -#else // POSIX + // Start the persistent actor that checks for deadline expiry. + CheckDeadline(); +} - // TODO: This doesn't work! Consider to control timeout in server side. +Error HttpClient::MakeRequest(const HttpRequest& request, + HttpResponse* response) { + assert(response != NULL); - struct timeval tv; - tv.tv_sec = timeout_seconds; - tv.tv_usec = 0; - setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); - setsockopt(socket.native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + Error error = kNoError; -#endif -} + if ((error = Connect(request)) != kNoError) { + return error; + } + + // Send HTTP request. -//////////////////////////////////////////////////////////////////////////////// + if ((error = SendReqeust(request)) != kNoError) { + return error; + } -HttpClient::HttpClient() - : timeout_seconds_(15) { -} + // Read and parse HTTP response. -Error HttpClient::SendRequest(const HttpRequest& request, - HttpResponse* response) { - assert(response != NULL); + parser_ = std::make_unique(response); - using boost::asio::ip::tcp; + error = ReadResponse(response); - tcp::socket socket(io_context_); + return error; +} + +Error HttpClient::Connect(const HttpRequest& request) { + using boost::asio::ip::tcp; tcp::resolver resolver(io_context_); @@ -66,7 +77,6 @@ Error HttpClient::SendRequest(const HttpRequest& request, } boost::system::error_code ec; - // tcp::resolver::results_type auto endpoints = resolver.resolve(tcp::v4(), request.host(), port, ec); if (ec) { @@ -77,58 +87,121 @@ Error HttpClient::SendRequest(const HttpRequest& request, return kHostResolveError; } - boost::asio::connect(socket, endpoints, ec); - if (ec) { - return kEndpointConnectError; + deadline_timer_.expires_from_now( + boost::posix_time::seconds(kConnectMaxSeconds)); + + ec = boost::asio::error::would_block; + + boost::asio::async_connect(socket_, + endpoints, + boost::lambda::var(ec) = boost::lambda::_1); + + // Block until the asynchronous operation has completed. + do { + io_context_.run_one(); + } while (ec == boost::asio::error::would_block); + + // Determine whether a connection was successfully established. The + // deadline actor may have had a chance to run and close our socket, even + // though the connect operation notionally succeeded. Therefore we must + // check whether the socket is still open before deciding if we succeeded + // or failed. + if (ec || !socket_.is_open()) { + if (ec) { + return kEndpointConnectError; + } else { + return kSocketTimeoutError; + } } - SetTimeout(socket, timeout_seconds_); - - // Send HTTP request. + return kNoError; +} +Error HttpClient::SendReqeust(const HttpRequest& request) { LOG_VERB("http request:\n{\n%s}", request.Dump().c_str()); - try { - boost::asio::write(socket, request.ToBuffers()); - } catch (boost::system::system_error&) { + deadline_timer_.expires_from_now(boost::posix_time::seconds(kSendMaxSeconds)); + + boost::system::error_code ec = boost::asio::error::would_block; + + boost::asio::async_write(socket_, + request.ToBuffers(), + boost::lambda::var(ec) = boost::lambda::_1); + + // Block until the asynchronous operation has completed. + do { + io_context_.run_one(); + } while (ec == boost::asio::error::would_block); + + if (ec) { return kSocketWriteError; } - // Read and parse HTTP response. + return kNoError; +} - HttpResponseParser parser(response); - - // NOTE: - // We must stop trying to read once all content has been received, - // because some servers will block extra call to read_some(). - while (!parser.finished()) { - // read_some() will block until one or more bytes of data has been - // read successfully, or until an error occurs. - std::size_t length = socket.read_some(boost::asio::buffer(buffer_), ec); - - if (length == 0 || ec) { -#if defined _WINDOWS - if (ec.value() == WSAETIMEDOUT) { - return kSocketTimeoutError; - } -#endif - return kSocketReadError; - } +Error HttpClient::ReadResponse(HttpResponse* response) { + deadline_timer_.expires_from_now( + boost::posix_time::seconds(timeout_seconds_)); + + boost::system::error_code ec = boost::asio::error::would_block; + Error error = kNoError; + + socket_.async_read_some( + boost::asio::buffer(buffer_), + [this, &ec, &error, response](boost::system::error_code inner_ec, + std::size_t length) { + ec = inner_ec; + + if (inner_ec || length == 0) { + error = kSocketReadError; + } else { + // Parse the response piece just read. + // If the content has been fully received, next time flag "finished_" + // will be set. + error = parser_->Parse(buffer_.data(), length); + + if (error != kNoError) { + LOG_ERRO("failed to parse http response."); + return; + } + + if (parser_->finished()) { + // Stop trying to read once all content has been received, + // because some servers will block extra call to read_some(). + return; + } + + ReadResponse(response); + } + }); + + // Block until the asynchronous operation has completed. + do { + io_context_.run_one(); + } while (ec == boost::asio::error::would_block); + + if (error == kNoError) { + LOG_VERB("http response:\n{\n%s}", response->Dump().c_str()); + } - // Parse the response piece just read. - // If the content has been fully received, next time flag "finished_" - // will be set. - Error error = parser.Parse(buffer_.data(), length); + return error; +} - if (error != kNoError) { - LOG_ERRO("failed to parse http response."); - return error; - } - } +void HttpClient::CheckDeadline() { + if (deadline_timer_.expires_at() <= + boost::asio::deadline_timer::traits_type::now()) { + // The deadline has passed. + // The socket is closed so that any outstanding asynchronous operations + // are canceled. + boost::system::error_code ignored_ec; + socket_.close(ignored_ec); - LOG_VERB("http response:\n{\n%s}", response->Dump().c_str()); + deadline_timer_.expires_at(boost::posix_time::pos_infin); + } - return kNoError; + // Put the actor back to sleep. + deadline_timer_.async_wait(std::bind(&HttpClient::CheckDeadline, this)); } } // namespace webcc diff --git a/src/webcc/http_client.h b/src/webcc/http_client.h index 4821318..afb2152 100644 --- a/src/webcc/http_client.h +++ b/src/webcc/http_client.h @@ -2,10 +2,14 @@ #define WEBCC_HTTP_CLIENT_H_ #include +#include +#include "boost/asio/deadline_timer.hpp" +#include "boost/asio/ip/tcp.hpp" #include "boost/asio/io_context.hpp" #include "webcc/common.h" +#include "webcc/http_response_parser.h" namespace webcc { @@ -16,19 +20,44 @@ class HttpClient { public: HttpClient(); - // Set socket send & recv timeout. - void set_timeout_seconds(int seconds) { - timeout_seconds_ = seconds; + ~HttpClient() = default; + + HttpClient(const HttpClient&) = delete; + HttpClient& operator=(const HttpClient&) = delete; + + void set_timeout_seconds(int timeout_seconds) { + timeout_seconds_ = timeout_seconds; } - // Send an HTTP request, wait until the response is received. - Error SendRequest(const HttpRequest& request, - HttpResponse* response); + // Make a HTTP request. + // Connect to the server, send the request, wait until the response is + // received. + Error MakeRequest(const HttpRequest& request, HttpResponse* response); + +private: + Error Connect(const HttpRequest& request); + + Error SendReqeust(const HttpRequest& request); + + Error ReadResponse(HttpResponse* response); + + void CheckDeadline(); private: boost::asio::io_context io_context_; + + boost::asio::ip::tcp::socket socket_; + std::array buffer_; + + std::unique_ptr parser_; + + // Maximum seconds to wait before the client cancels the operation. + // Only for receiving response from server. int timeout_seconds_; + + // Timer for the timeout control. + boost::asio::deadline_timer deadline_timer_; }; } // namespace webcc diff --git a/src/webcc/http_parser.h b/src/webcc/http_parser.h index b70bc97..bbab09b 100644 --- a/src/webcc/http_parser.h +++ b/src/webcc/http_parser.h @@ -13,7 +13,8 @@ class HttpParser { public: explicit HttpParser(HttpMessage* message); - ~HttpParser() = default; + virtual ~HttpParser() = default; + HttpParser(const HttpParser&) = delete; HttpParser& operator=(const HttpParser&) = delete; diff --git a/src/webcc/http_request_handler.h b/src/webcc/http_request_handler.h index 720dcae..f85bde6 100644 --- a/src/webcc/http_request_handler.h +++ b/src/webcc/http_request_handler.h @@ -39,7 +39,7 @@ private: void WorkerRoutine(); // Called by the worker routine. - virtual HttpStatus::Enum HandleSession(HttpSessionPtr session) = 0; + virtual void HandleSession(HttpSessionPtr session) = 0; private: Queue queue_; diff --git a/src/webcc/http_request_parser.h b/src/webcc/http_request_parser.h index ce34a88..4feb69b 100644 --- a/src/webcc/http_request_parser.h +++ b/src/webcc/http_request_parser.h @@ -11,6 +11,8 @@ class HttpRequestParser : public HttpParser { public: explicit HttpRequestParser(HttpRequest* request); + ~HttpRequestParser() override = default; + private: Error ParseStartLine(const std::string& line) override; diff --git a/src/webcc/http_response.h b/src/webcc/http_response.h index fa9a7a0..df1d736 100644 --- a/src/webcc/http_response.h +++ b/src/webcc/http_response.h @@ -16,8 +16,8 @@ class HttpResponse : public HttpMessage { const HttpResponse& response); public: - HttpResponse() { - } + HttpResponse() = default; + ~HttpResponse() override = default; int status() const { return status_; diff --git a/src/webcc/http_response_parser.h b/src/webcc/http_response_parser.h index 4824c83..8978874 100644 --- a/src/webcc/http_response_parser.h +++ b/src/webcc/http_response_parser.h @@ -11,6 +11,8 @@ class HttpResponseParser : public HttpParser { public: explicit HttpResponseParser(HttpResponse* response); + ~HttpResponseParser() override = default; + private: // Parse HTTP start line; E.g., "HTTP/1.1 200 OK". Error ParseStartLine(const std::string& line) override; diff --git a/src/webcc/http_session.cc b/src/webcc/http_session.cc index f8a1efd..571fb11 100644 --- a/src/webcc/http_session.cc +++ b/src/webcc/http_session.cc @@ -29,31 +29,32 @@ void HttpSession::Stop() { socket_.close(ec); } -void HttpSession::SetResponseContent(const std::string& content_type, - std::size_t content_length, - std::string&& content) { - response_.SetContentType(content_type); +void HttpSession::SetResponseContent(std::string&& content, + const std::string& content_type) { response_.SetContent(std::move(content)); + response_.SetContentType(content_type); } -void HttpSession::SendResponse() { +void HttpSession::SendResponse(int status) { + response_.set_status(status); DoWrite(); } void HttpSession::DoRead() { - auto handler = std::bind(&HttpSession::HandleRead, - shared_from_this(), - std::placeholders::_1, - std::placeholders::_2); - socket_.async_read_some(boost::asio::buffer(buffer_), handler); + socket_.async_read_some(boost::asio::buffer(buffer_), + std::bind(&HttpSession::HandleRead, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); } void HttpSession::DoWrite() { - auto handler = std::bind(&HttpSession::HandleWrite, - shared_from_this(), - std::placeholders::_1, - std::placeholders::_2); - boost::asio::async_write(socket_, response_.ToBuffers(), handler); + 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, diff --git a/src/webcc/http_session.h b/src/webcc/http_session.h index 296913f..b8fd737 100644 --- a/src/webcc/http_session.h +++ b/src/webcc/http_session.h @@ -33,16 +33,11 @@ public: void Stop(); - void SetResponseStatus(int status) { - response_.set_status(status); - } - - void SetResponseContent(const std::string& content_type, - std::size_t content_length, - std::string&& content); + void SetResponseContent(std::string&& content, + const std::string& content_type); - // Write response back to the client. - void SendResponse(); + // Write response back to the client with the given HTTP status. + void SendResponse(int status); private: void DoRead(); diff --git a/src/webcc/logger.h b/src/webcc/logger.h index d4cc19f..2d0d5f8 100644 --- a/src/webcc/logger.h +++ b/src/webcc/logger.h @@ -23,9 +23,11 @@ void LogInit(int level, int modes); void LogWrite(int level, const char* file, int line, const char* format, ...); +} // namespace webcc + // Initialize the logger with a level. // E.g., LOG_INIT(ERRO, FLUSH) -#define LOG_INIT(level, modes) LogInit(level, modes); +#define LOG_INIT(level, modes) webcc::LogInit(level, modes); #if (defined(WIN32) || defined(_WIN64)) @@ -33,19 +35,19 @@ void LogWrite(int level, const char* file, int line, const char* format, ...); #define __FILENAME__ strrchr("\\" __FILE__, '\\') + 1 #define LOG_VERB(format, ...) \ - LogWrite(VERB, __FILENAME__, __LINE__, format, ##__VA_ARGS__); + webcc::LogWrite(webcc::VERB, __FILENAME__, __LINE__, format, ##__VA_ARGS__); #define LOG_INFO(format, ...) \ - LogWrite(INFO, __FILENAME__, __LINE__, format, ##__VA_ARGS__); + webcc::LogWrite(webcc::INFO, __FILENAME__, __LINE__, format, ##__VA_ARGS__); #define LOG_WARN(format, ...) \ - LogWrite(WARN, __FILENAME__, __LINE__, format, ##__VA_ARGS__); + webcc::LogWrite(webcc::WARN, __FILENAME__, __LINE__, format, ##__VA_ARGS__); #define LOG_ERRO(format, ...) \ - LogWrite(ERRO, __FILENAME__, __LINE__, format, ##__VA_ARGS__); + webcc::LogWrite(webcc::ERRO, __FILENAME__, __LINE__, format, ##__VA_ARGS__); #define LOG_FATA(format, ...) \ - LogWrite(FATA, __FILENAME__, __LINE__, format, ##__VA_ARGS__); + webcc::LogWrite(webcc::FATA, __FILENAME__, __LINE__, format, ##__VA_ARGS__); #else @@ -53,19 +55,19 @@ void LogWrite(int level, const char* file, int line, const char* format, ...); #define __FILENAME__ strrchr("/" __FILE__, '/') + 1 #define LOG_VERB(format, args...) \ - LogWrite(VERB, __FILENAME__, __LINE__, format, ##args); + webcc::LogWrite(webcc::VERB, __FILENAME__, __LINE__, format, ##args); #define LOG_INFO(format, args...) \ - LogWrite(INFO, __FILENAME__, __LINE__, format, ##args); + webcc::LogWrite(webcc::INFO, __FILENAME__, __LINE__, format, ##args); #define LOG_WARN(format, args...) \ - LogWrite(WARN, __FILENAME__, __LINE__, format, ##args); + webcc::LogWrite(webcc::WARN, __FILENAME__, __LINE__, format, ##args); #define LOG_ERRO(format, args...) \ - LogWrite(ERRO, __FILENAME__, __LINE__, format, ##args); + webcc::LogWrite(webcc::ERRO, __FILENAME__, __LINE__, format, ##args); #define LOG_FATA(format, args...) \ - LogWrite(FATA, __FILENAME__, __LINE__, format, ##args); + webcc::LogWrite(webcc::FATA, __FILENAME__, __LINE__, format, ##args); #endif // defined(WIN32) || defined(_WIN64) @@ -89,6 +91,4 @@ void LogWrite(int level, const char* file, int line, const char* format, ...); #endif // WEBCC_ENABLE_LOG -} // namespace webcc - #endif // WEBCC_LOGGER_H_ diff --git a/src/webcc/rest_server.cc b/src/webcc/rest_server.cc index dd5db6f..0b4fc46 100644 --- a/src/webcc/rest_server.cc +++ b/src/webcc/rest_server.cc @@ -59,42 +59,39 @@ bool RestRequestHandler::RegisterService(RestServicePtr service, return service_manager_.AddService(service, url); } -HttpStatus::Enum RestRequestHandler::HandleSession(HttpSessionPtr session) { +void RestRequestHandler::HandleSession(HttpSessionPtr session) { Url url(session->request().url()); if (!url.IsValid()) { - session->SetResponseStatus(HttpStatus::kBadRequest); - session->SendResponse(); - return HttpStatus::kBadRequest; + session->SendResponse(HttpStatus::kBadRequest); + return; } std::vector sub_matches; RestServicePtr service = service_manager_.GetService(url.path(), &sub_matches); if (!service) { LOG_WARN("No service matches the URL: %s", url.path().c_str()); - session->SetResponseStatus(HttpStatus::kBadRequest); - session->SendResponse(); - return HttpStatus::kBadRequest; + session->SendResponse(HttpStatus::kBadRequest); + return; } // TODO: Only for GET? Url::Query query = Url::SplitQuery(url.query()); - // TODO: Error handling. std::string content; - service->Handle(session->request().method(), - sub_matches, - query, - session->request().content(), - &content); - - session->SetResponseStatus(HttpStatus::kOK); - session->SetResponseContent(kTextJsonUtf8, - content.length(), - std::move(content)); - session->SendResponse(); - - return HttpStatus::kOK; + bool ok = service->Handle(session->request().method(), + sub_matches, + query, + session->request().content(), + &content); + if (!ok) { + // TODO: Could be other than kBadRequest. + session->SendResponse(HttpStatus::kBadRequest); + return; + } + + session->SetResponseContent(std::move(content), kTextJsonUtf8); + session->SendResponse(HttpStatus::kOK); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/webcc/rest_server.h b/src/webcc/rest_server.h index ede2324..112ab0c 100644 --- a/src/webcc/rest_server.h +++ b/src/webcc/rest_server.h @@ -75,7 +75,7 @@ public: bool RegisterService(RestServicePtr service, const std::string& url); private: - HttpStatus::Enum HandleSession(HttpSessionPtr session) override; + void HandleSession(HttpSessionPtr session) override; private: RestServiceManager service_manager_; diff --git a/src/webcc/soap_client.cc b/src/webcc/soap_client.cc index 6f45f6a..3bc1055 100644 --- a/src/webcc/soap_client.cc +++ b/src/webcc/soap_client.cc @@ -48,7 +48,12 @@ Error SoapClient::Call(const std::string& operation, HttpResponse http_response; HttpClient http_client; - Error error = http_client.SendRequest(http_request, &http_response); + + if (timeout_seconds_ != -1) { + http_client.set_timeout_seconds(timeout_seconds_); + } + + Error error = http_client.MakeRequest(http_request, &http_response); if (error != kNoError) { return error; diff --git a/src/webcc/soap_client.h b/src/webcc/soap_client.h index 5f4305c..9e842da 100644 --- a/src/webcc/soap_client.h +++ b/src/webcc/soap_client.h @@ -13,12 +13,10 @@ namespace webcc { // class SoapClient { public: - virtual ~SoapClient() { - } + virtual ~SoapClient() = default; protected: - SoapClient() { - } + SoapClient() = default; // A generic wrapper to make a call. // NOTE: The parameters should be movable. @@ -27,6 +25,9 @@ protected: std::string* result); 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. diff --git a/src/webcc/soap_server.cc b/src/webcc/soap_server.cc index d57eabe..7cfd917 100644 --- a/src/webcc/soap_server.cc +++ b/src/webcc/soap_server.cc @@ -16,36 +16,30 @@ bool SoapRequestHandler::RegisterService(SoapServicePtr service, return true; } -HttpStatus::Enum SoapRequestHandler::HandleSession(HttpSessionPtr session) { +void SoapRequestHandler::HandleSession(HttpSessionPtr session) { SoapServicePtr service = GetServiceByUrl(session->request().url()); if (!service) { - session->SetResponseStatus(HttpStatus::kBadRequest); - session->SendResponse(); - return HttpStatus::kBadRequest; + session->SendResponse(HttpStatus::kBadRequest); + return; } // Parse the SOAP request XML. SoapRequest soap_request; if (!soap_request.FromXml(session->request().content())) { - session->SetResponseStatus(HttpStatus::kBadRequest); - session->SendResponse(); - return HttpStatus::kBadRequest; + session->SendResponse(HttpStatus::kBadRequest); + return; } - // TODO: Error handling. SoapResponse soap_response; - service->Handle(soap_request, &soap_response); + if (!service->Handle(soap_request, &soap_response)) { + session->SendResponse(HttpStatus::kBadRequest); + return; + } std::string content; soap_response.ToXml(&content); - - session->SetResponseStatus(HttpStatus::kOK); - session->SetResponseContent(kTextXmlUtf8, - content.length(), - std::move(content)); - session->SendResponse(); - - return HttpStatus::kOK; + session->SetResponseContent(std::move(content), kTextXmlUtf8); + session->SendResponse(HttpStatus::kOK); } SoapServicePtr SoapRequestHandler::GetServiceByUrl(const std::string& url) { diff --git a/src/webcc/soap_server.h b/src/webcc/soap_server.h index 3a32d75..6cfe1a0 100644 --- a/src/webcc/soap_server.h +++ b/src/webcc/soap_server.h @@ -24,7 +24,7 @@ public: bool RegisterService(SoapServicePtr service, const std::string& url); private: - HttpStatus::Enum HandleSession(HttpSessionPtr session) override; + void HandleSession(HttpSessionPtr session) override; SoapServicePtr GetServiceByUrl(const std::string& url); diff --git a/src/webcc/soap_service.h b/src/webcc/soap_service.h index f4e9eda..0cd3d00 100644 --- a/src/webcc/soap_service.h +++ b/src/webcc/soap_service.h @@ -2,6 +2,7 @@ #define WEBCC_SOAP_SERVICE_H_ #include +#include "webcc/common.h" namespace webcc { @@ -17,6 +18,9 @@ public: // Handle SOAP request, output the response. virtual bool Handle(const SoapRequest& soap_request, SoapResponse* soap_response) = 0; + +protected: + HttpStatus::Enum http_status_ = HttpStatus::kOK; }; typedef std::shared_ptr SoapServicePtr;