From 9bf45e6ecb5fed3ecf527fecda20a7c57807e5e1 Mon Sep 17 00:00:00 2001 From: Adam Gu Date: Mon, 4 Jun 2018 12:03:22 +0800 Subject: [PATCH] Add async-client support; refine http message dump format. --- CMakeLists.txt | 28 ++-- example/http/async_client/CMakeLists.txt | 4 + example/http/async_client/main.cc | 49 ++++++ example/http/client/CMakeLists.txt | 4 + example/http/client/main.cc | 38 +++++ example/rest/book_async_client/CMakeLists.txt | 4 + example/rest/book_async_client/main.cc | 137 +++++++++++++++ .../book_client}/CMakeLists.txt | 0 .../book_client}/main.cc | 66 ++++---- .../book_server}/CMakeLists.txt | 0 .../book_server}/book_services.cc | 28 ++++ .../book_server}/book_services.h | 26 ++- example/rest/book_server/main.cc | 52 ++++++ example/rest_book_server/main.cc | 41 ----- .../calc_client}/CMakeLists.txt | 4 - .../calc_client}/calc_client.cc | 2 +- .../calc_client}/calc_client.h | 0 .../calc_client}/calculator.wsdl | 0 .../calc_client}/main.cc | 0 .../calc_server}/CMakeLists.txt | 4 - .../calc_server}/calc_service.cc | 0 .../calc_server}/calc_service.h | 0 .../calc_server}/main.cc | 2 + src/webcc/CMakeLists.txt | 4 + src/webcc/globals.cc | 2 +- src/webcc/globals.h | 40 ++--- src/webcc/http_async_client.cc | 158 +++++++++--------- src/webcc/http_async_client.h | 72 +++++--- src/webcc/http_client.cc | 94 +++++------ src/webcc/http_client.h | 26 +-- src/webcc/http_message.cc | 45 +++++ src/webcc/http_message.h | 10 ++ src/webcc/http_parser.cc | 3 +- src/webcc/http_parser.h | 4 +- src/webcc/http_request.cc | 26 --- src/webcc/http_request.h | 12 +- src/webcc/http_request_handler.h | 4 +- src/webcc/http_response.cc | 25 --- src/webcc/http_response.h | 13 +- src/webcc/http_server.h | 4 +- src/webcc/http_session.h | 5 +- src/webcc/rest_async_client.cc | 27 +++ src/webcc/rest_async_client.h | 60 +++++++ src/webcc/rest_client.cc | 15 +- src/webcc/rest_client.h | 49 +++--- src/webcc/rest_request_handler.h | 1 + src/webcc/rest_service_manager.h | 4 +- src/webcc/soap_client.cc | 8 +- 48 files changed, 798 insertions(+), 402 deletions(-) create mode 100644 example/http/async_client/CMakeLists.txt create mode 100644 example/http/async_client/main.cc create mode 100644 example/http/client/CMakeLists.txt create mode 100644 example/http/client/main.cc create mode 100644 example/rest/book_async_client/CMakeLists.txt create mode 100644 example/rest/book_async_client/main.cc rename example/{rest_book_client => rest/book_client}/CMakeLists.txt (100%) rename example/{rest_book_client => rest/book_client}/main.cc (59%) rename example/{rest_book_server => rest/book_server}/CMakeLists.txt (100%) rename example/{rest_book_server => rest/book_server}/book_services.cc (84%) rename example/{rest_book_server => rest/book_server}/book_services.h (65%) create mode 100644 example/rest/book_server/main.cc delete mode 100644 example/rest_book_server/main.cc rename example/{soap_calc_client => soap/calc_client}/CMakeLists.txt (69%) rename example/{soap_calc_client => soap/calc_client}/calc_client.cc (98%) rename example/{soap_calc_client => soap/calc_client}/calc_client.h (100%) rename example/{soap_calc_client => soap/calc_client}/calculator.wsdl (100%) rename example/{soap_calc_client => soap/calc_client}/main.cc (100%) rename example/{soap_calc_server => soap/calc_server}/CMakeLists.txt (68%) rename example/{soap_calc_server => soap/calc_server}/calc_service.cc (100%) rename example/{soap_calc_server => soap/calc_server}/calc_service.h (100%) rename example/{soap_calc_server => soap/calc_server}/main.cc (99%) create mode 100644 src/webcc/rest_async_client.cc create mode 100644 src/webcc/rest_async_client.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9763fd8..82d64b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,7 @@ project(webcc) 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) +option(WEBCC_BUILD_EXAMPLE "Build examples?" ON) if(WEBCC_ENABLE_LOG) add_definitions(-DWEBCC_ENABLE_LOG) @@ -45,7 +44,12 @@ if(WIN32) message(STATUS "_WIN32_WINNT=${ver}") # Asio needs this! add_definitions(-D_WIN32_WINNT=${ver}) -endif(WIN32) +endif() + +if(WIN32) + # Disable warning on boost string algorithms. + add_definitions(-D_SCL_SECURE_NO_WARNINGS) +endif() # Group sources by dir. # Usage: source_group_by_dir(SRCS) @@ -104,18 +108,22 @@ endif() add_subdirectory(src/webcc) -if(WEBCC_BUILD_REST_EXAMPLE) +if(WEBCC_BUILD_EXAMPLE) + add_subdirectory(${PROJECT_SOURCE_DIR}/example/http/client) + add_subdirectory(${PROJECT_SOURCE_DIR}/example/http/async_client) + # REST example needs jsoncpp to parse and create JSON. add_subdirectory(${PROJECT_SOURCE_DIR}/third_party/jsoncpp) include_directories(${PROJECT_SOURCE_DIR}/third_party/jsoncpp) - add_subdirectory(${PROJECT_SOURCE_DIR}/example/rest_book_server) - add_subdirectory(${PROJECT_SOURCE_DIR}/example/rest_book_client) -endif() + add_subdirectory(${PROJECT_SOURCE_DIR}/example/rest/book_server) + add_subdirectory(${PROJECT_SOURCE_DIR}/example/rest/book_client) + add_subdirectory(${PROJECT_SOURCE_DIR}/example/rest/book_async_client) -if(WEBCC_BUILD_SOAP_EXAMPLE) - add_subdirectory(${PROJECT_SOURCE_DIR}/example/soap_calc_server) - add_subdirectory(${PROJECT_SOURCE_DIR}/example/soap_calc_client) + if(WEBCC_ENABLE_SOAP) + add_subdirectory(${PROJECT_SOURCE_DIR}/example/soap/calc_server) + add_subdirectory(${PROJECT_SOURCE_DIR}/example/soap/calc_client) + endif() endif() if(WEBCC_BUILD_UNITTEST) diff --git a/example/http/async_client/CMakeLists.txt b/example/http/async_client/CMakeLists.txt new file mode 100644 index 0000000..819bd02 --- /dev/null +++ b/example/http/async_client/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(http_async_client main.cc) + +target_link_libraries(http_async_client webcc ${Boost_LIBRARIES}) +target_link_libraries(http_async_client "${CMAKE_THREAD_LIBS_INIT}") diff --git a/example/http/async_client/main.cc b/example/http/async_client/main.cc new file mode 100644 index 0000000..60b8840 --- /dev/null +++ b/example/http/async_client/main.cc @@ -0,0 +1,49 @@ +#include + +#include "boost/asio/io_context.hpp" + +#include "webcc/logger.h" +#include "webcc/http_async_client.h" + +// In order to test this client, create a file index.html whose content is +// simply "Hello, World!", then start a HTTP server with Python 3: +// $ python -m http.server +// The default port number should be 8000. + +void Test(boost::asio::io_context& ioc) { + std::shared_ptr request(new webcc::HttpRequest()); + + request->set_method(webcc::kHttpGet); + request->set_url("/index.html"); + request->SetHost("localhost", "8000"); + + request->Build(); + + webcc::HttpAsyncClientPtr client(new webcc::HttpAsyncClient(ioc)); + + // Response handler. + auto handler = [](std::shared_ptr response, + webcc::Error error) { + if (error == webcc::kNoError) { + std::cout << response->content() << std::endl; + } else { + std::cout << webcc::DescribeError(error) << std::endl; + } + }; + + client->Request(request, handler); +} + +int main() { + LOG_INIT(webcc::ERRO, 0); + + boost::asio::io_context ioc; + + Test(ioc); + Test(ioc); + Test(ioc); + + ioc.run(); + + return 0; +} diff --git a/example/http/client/CMakeLists.txt b/example/http/client/CMakeLists.txt new file mode 100644 index 0000000..723ef64 --- /dev/null +++ b/example/http/client/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(http_client main.cc) + +target_link_libraries(http_client webcc ${Boost_LIBRARIES}) +target_link_libraries(http_client "${CMAKE_THREAD_LIBS_INIT}") diff --git a/example/http/client/main.cc b/example/http/client/main.cc new file mode 100644 index 0000000..d84f664 --- /dev/null +++ b/example/http/client/main.cc @@ -0,0 +1,38 @@ +#include + +#include "webcc/logger.h" +#include "webcc/http_client.h" + +// In order to test this client, create a file index.html whose content is +// simply "Hello, World!", then start a HTTP server with Python 3: +// $ python -m http.server +// The default port number should be 8000. + +void Test() { + webcc::HttpRequest request; + + request.set_method(webcc::kHttpGet); + request.set_url("/index.html"); + request.SetHost("localhost", "8000"); + + request.Build(); + + webcc::HttpResponse response; + + webcc::HttpClient client; + if (!client.Request(request)) { + return; + } + + std::cout << response.content() << std::endl; +} + +int main() { + LOG_INIT(webcc::ERRO, 0); + + Test(); + Test(); + Test(); + + return 0; +} diff --git a/example/rest/book_async_client/CMakeLists.txt b/example/rest/book_async_client/CMakeLists.txt new file mode 100644 index 0000000..e21f082 --- /dev/null +++ b/example/rest/book_async_client/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(rest_book_async_client main.cc) + +target_link_libraries(rest_book_async_client webcc jsoncpp ${Boost_LIBRARIES}) +target_link_libraries(rest_book_async_client "${CMAKE_THREAD_LIBS_INIT}") diff --git a/example/rest/book_async_client/main.cc b/example/rest/book_async_client/main.cc new file mode 100644 index 0000000..fb805d0 --- /dev/null +++ b/example/rest/book_async_client/main.cc @@ -0,0 +1,137 @@ +#include + +#include "json/json.h" + +#include "webcc/logger.h" +#include "webcc/rest_async_client.h" + +// ----------------------------------------------------------------------------- + +// Write a JSON object to string. +std::string JsonToString(const Json::Value& json) { + Json::StreamWriterBuilder builder; + return Json::writeString(builder, json); +} + +// ----------------------------------------------------------------------------- + +class BookListClient { + public: + BookListClient(boost::asio::io_context& io_context, + const std::string& host, const std::string& port) + : client_(io_context, host, port) { + } + + void ListBooks(webcc::HttpResponseHandler handler) { + std::cout << "ListBooks" << std::endl; + + client_.Get("/books", handler); + } + + void CreateBook(const std::string& id, + const std::string& title, + double price, + webcc::HttpResponseHandler handler) { + std::cout << "CreateBook: " << id << " " << title << " " << price + << std::endl; + + Json::Value json(Json::objectValue); + json["id"] = id; + json["title"] = title; + json["price"] = price; + + client_.Post("/books", JsonToString(json), handler); + } + + private: + webcc::RestAsyncClient client_; +}; + +// ----------------------------------------------------------------------------- + +class BookDetailClient { +public: + BookDetailClient(boost::asio::io_context& io_context, + const std::string& host, const std::string& port) + : rest_client_(io_context, host, port) { + } + + void GetBook(const std::string& id, webcc::HttpResponseHandler handler) { + std::cout << "GetBook: " << id << std::endl; + + rest_client_.Get("/book/" + id, handler); + } + + void UpdateBook(const std::string& id, + const std::string& title, + double price, + webcc::HttpResponseHandler handler) { + std::cout << "UpdateBook: " << id << " " << title << " " << price + << std::endl; + + // NOTE: ID is already in the URL. + Json::Value json(Json::objectValue); + json["title"] = title; + json["price"] = price; + + rest_client_.Put("/book/" + id, JsonToString(json), handler); + } + + void DeleteBook(const std::string& id, webcc::HttpResponseHandler handler) { + std::cout << "DeleteBook: " << id << std::endl; + + rest_client_.Delete("/book/" + id, handler); + } + +private: + webcc::RestAsyncClient rest_client_; +}; + +// ----------------------------------------------------------------------------- + +void Help(const char* argv0) { + std::cout << "Usage: " << argv0 << " " << std::endl; + std::cout << " E.g.," << std::endl; + std::cout << " " << argv0 << " localhost 8080" << std::endl; +} + +int main(int argc, char* argv[]) { + if (argc != 3) { + Help(argv[0]); + return 1; + } + + LOG_INIT(webcc::ERRO, 0); + + std::string host = argv[1]; + std::string port = argv[2]; + + boost::asio::io_context io_context; + + BookListClient list_client(io_context, host, port); + BookDetailClient detail_client(io_context, host, port); + + // Response handler. + auto handler = [](std::shared_ptr response, + webcc::Error error) { + if (error == webcc::kNoError) { + std::cout << response->content() << std::endl; + } else { + std::cout << webcc::DescribeError(error) << std::endl; + } + }; + + list_client.ListBooks(handler); + list_client.CreateBook("1", "1984", 12.3, handler); + + detail_client.GetBook("1", handler); + detail_client.UpdateBook("1", "1Q84", 32.1, handler); + detail_client.GetBook("1", handler); + detail_client.DeleteBook("1", handler); + + list_client.ListBooks(handler); + + io_context.run(); + + return 0; +} diff --git a/example/rest_book_client/CMakeLists.txt b/example/rest/book_client/CMakeLists.txt similarity index 100% rename from example/rest_book_client/CMakeLists.txt rename to example/rest/book_client/CMakeLists.txt diff --git a/example/rest_book_client/main.cc b/example/rest/book_client/main.cc similarity index 59% rename from example/rest_book_client/main.cc rename to example/rest/book_client/main.cc index 0849646..28321ac 100644 --- a/example/rest_book_client/main.cc +++ b/example/rest/book_client/main.cc @@ -1,12 +1,8 @@ #include -#include "boost/algorithm/string.hpp" #include "json/json.h" #include "webcc/logger.h" -#include "webcc/http_client.h" -#include "webcc/http_request.h" -#include "webcc/http_response.h" #include "webcc/rest_client.h" // ----------------------------------------------------------------------------- @@ -21,26 +17,28 @@ std::string JsonToString(const Json::Value& json) { class BookListClient { public: - BookListClient(const std::string& host, const std::string& port) - : rest_client_(host, port) { + BookListClient(const std::string& host, const std::string& port, + int timeout_seconds) + : client_(host, port) { + client_.set_timeout_seconds(timeout_seconds); } bool ListBooks() { std::cout << "ListBooks" << std::endl; - webcc::HttpResponse http_response; - if (!rest_client_.Get("/books", &http_response)) { + if (!client_.Get("/books")) { + std::cout << webcc::DescribeError(client_.error()) << std::endl; return false; } - std::cout << http_response.content() << std::endl; + std::cout << client_.response_content() << std::endl; return true; } bool CreateBook(const std::string& id, const std::string& title, double price) { - std::cout << "CreateBook: " << id << " " << title << " " << price + std::cout << "CreateBook: " << id << ", " << title << ", " << price << std::endl; Json::Value json(Json::objectValue); @@ -48,44 +46,46 @@ public: json["title"] = title; json["price"] = price; - webcc::HttpResponse http_response; - if (!rest_client_.Post("/books", JsonToString(json), &http_response)) { + if (!client_.Post("/books", JsonToString(json))) { + std::cout << webcc::DescribeError(client_.error()) << std::endl; return false; } - std::cout << http_response.status() << std::endl; + std::cout << client_.response_status() << std::endl; return true; } private: - webcc::RestClient rest_client_; + webcc::RestClient client_; }; // ----------------------------------------------------------------------------- class BookDetailClient { public: - BookDetailClient(const std::string& host, const std::string& port) + BookDetailClient(const std::string& host, const std::string& port, + int timeout_seconds) : rest_client_(host, port) { + rest_client_.set_timeout_seconds(timeout_seconds); } bool GetBook(const std::string& id) { std::cout << "GetBook: " << id << std::endl; - webcc::HttpResponse http_response; - if (!rest_client_.Get("/book/" + id, &http_response)) { + if (!rest_client_.Get("/book/" + id)) { + std::cout << webcc::DescribeError(rest_client_.error()) << std::endl; return false; } - std::cout << http_response.content() << std::endl; + std::cout << rest_client_.response_content() << std::endl; return true; } bool UpdateBook(const std::string& id, const std::string& title, double price) { - std::cout << "UpdateBook: " << id << " " << title << " " << price + std::cout << "UpdateBook: " << id << ", " << title << ", " << price << std::endl; // NOTE: ID is already in the URL. @@ -93,24 +93,24 @@ public: json["title"] = title; json["price"] = price; - webcc::HttpResponse http_response; - if (!rest_client_.Put("/book/" + id, JsonToString(json), &http_response)) { + if (!rest_client_.Put("/book/" + id, JsonToString(json))) { + std::cout << webcc::DescribeError(rest_client_.error()) << std::endl; return false; } - std::cout << http_response.status() << std::endl; + std::cout << rest_client_.response_status() << std::endl; return true; } bool DeleteBook(const std::string& id) { std::cout << "DeleteBook: " << id << std::endl; - webcc::HttpResponse http_response; - if (!rest_client_.Delete("/book/" + id, &http_response)) { + if (!rest_client_.Delete("/book/" + id)) { + std::cout << webcc::DescribeError(rest_client_.error()) << std::endl; return false; } - std::cout << http_response.status() << std::endl; + std::cout << rest_client_.response_status() << std::endl; return true; } @@ -121,24 +121,30 @@ private: // ----------------------------------------------------------------------------- void Help(const char* argv0) { - std::cout << "Usage: " << argv0 << " " << std::endl; + std::cout << "Usage: " << argv0 << " [timeout]" << std::endl; std::cout << " E.g.," << std::endl; std::cout << " " << argv0 << " localhost 8080" << std::endl; + std::cout << " " << argv0 << " localhost 8080 2" << std::endl; } int main(int argc, char* argv[]) { - if (argc != 3) { + if (argc < 3) { Help(argv[0]); return 1; } - LOG_INIT(webcc::ERRO, 0); + LOG_INIT(webcc::VERB, 0); std::string host = argv[1]; std::string port = argv[2]; - BookListClient list_client(host, port); - BookDetailClient detail_client(host, port); + int timeout_seconds = -1; + if (argc > 3) { + timeout_seconds = std::atoi(argv[3]); + } + + BookListClient list_client(host, port, timeout_seconds); + BookDetailClient detail_client(host, port, timeout_seconds); list_client.ListBooks(); list_client.CreateBook("1", "1984", 12.3); diff --git a/example/rest_book_server/CMakeLists.txt b/example/rest/book_server/CMakeLists.txt similarity index 100% rename from example/rest_book_server/CMakeLists.txt rename to example/rest/book_server/CMakeLists.txt diff --git a/example/rest_book_server/book_services.cc b/example/rest/book_server/book_services.cc similarity index 84% rename from example/rest_book_server/book_services.cc rename to example/rest/book_server/book_services.cc index 7cc15a5..a3c4f69 100644 --- a/example/rest_book_server/book_services.cc +++ b/example/rest/book_server/book_services.cc @@ -4,7 +4,9 @@ #include #include "boost/lexical_cast.hpp" +#include "boost/thread/thread.hpp" #include "json/json.h" +#include "webcc/logger.h" // ----------------------------------------------------------------------------- @@ -121,6 +123,11 @@ static bool BookFromJson(const std::string& json, Book* book) { // TODO: Support query parameters. bool BookListService::Get(const webcc::UrlQuery& /* query */, std::string* response_content) { + if (sleep_seconds_ > 0) { + LOG_INFO("Sleep %d seconds...", sleep_seconds_); + boost::this_thread::sleep_for(boost::chrono::seconds(sleep_seconds_)); + } + Json::Value root(Json::arrayValue); for (const Book& book : g_book_store.books()) { root.append(book.ToJson()); @@ -136,10 +143,16 @@ bool BookListService::Get(const webcc::UrlQuery& /* query */, // No response content. bool BookListService::Post(const std::string& request_content, std::string* /* response_content */) { + if (sleep_seconds_ > 0) { + LOG_INFO("Sleep %d seconds...", sleep_seconds_); + boost::this_thread::sleep_for(boost::chrono::seconds(sleep_seconds_)); + } + Book book; if (BookFromJson(request_content, &book)) { return g_book_store.AddBook(book); } + return false; } @@ -148,6 +161,11 @@ bool BookListService::Post(const std::string& request_content, bool BookDetailService::Get(const std::vector& url_sub_matches, const webcc::UrlQuery& query, std::string* response_content) { + if (sleep_seconds_ > 0) { + LOG_INFO("Sleep %d seconds...", sleep_seconds_); + boost::this_thread::sleep_for(boost::chrono::seconds(sleep_seconds_)); + } + if (url_sub_matches.size() != 1) { return false; } @@ -168,6 +186,11 @@ bool BookDetailService::Get(const std::vector& url_sub_matches, bool BookDetailService::Put(const std::vector& url_sub_matches, const std::string& request_content, std::string* response_content) { + if (sleep_seconds_ > 0) { + LOG_INFO("Sleep %d seconds...", sleep_seconds_); + boost::this_thread::sleep_for(boost::chrono::seconds(sleep_seconds_)); + } + if (url_sub_matches.size() != 1) { return false; } @@ -185,6 +208,11 @@ bool BookDetailService::Put(const std::vector& url_sub_matches, bool BookDetailService::Delete( const std::vector& url_sub_matches) { + if (sleep_seconds_ > 0) { + LOG_INFO("Sleep %d seconds...", sleep_seconds_); + boost::this_thread::sleep_for(boost::chrono::seconds(sleep_seconds_)); + } + if (url_sub_matches.size() != 1) { return false; } diff --git a/example/rest_book_server/book_services.h b/example/rest/book_server/book_services.h similarity index 65% rename from example/rest_book_server/book_services.h rename to example/rest/book_server/book_services.h index 4bdd773..61be687 100644 --- a/example/rest_book_server/book_services.h +++ b/example/rest/book_server/book_services.h @@ -12,17 +12,25 @@ // - /books?name={BookName} // The query parameters could be regular expressions. class BookListService : public webcc::RestListService { + public: + BookListService(int sleep_seconds) : sleep_seconds_(sleep_seconds) { + } + protected: // Return a list of books based on query parameters. // URL examples: // - /books // - /books?name={BookName} bool Get(const webcc::UrlQuery& query, - std::string* response_content) final; + std::string* response_content) override; // Create a new book. bool Post(const std::string& request_content, - std::string* response_content) final; + std::string* response_content) override; + + private: + // Sleep for the client to test timeout control. + int sleep_seconds_ = 0; }; // ----------------------------------------------------------------------------- @@ -30,16 +38,24 @@ class BookListService : public webcc::RestListService { // The URL is like '/books/{BookID}', and the 'url_sub_matches' parameter // contains the matched book ID. class BookDetailService : public webcc::RestDetailService { + public: + BookDetailService(int sleep_seconds) : sleep_seconds_(sleep_seconds) { + } + protected: bool Get(const std::vector& url_sub_matches, const webcc::UrlQuery& query, - std::string* response_content) final; + std::string* response_content) override; bool Put(const std::vector& url_sub_matches, const std::string& request_content, - std::string* response_content) final; + std::string* response_content) override; + + bool Delete(const std::vector& url_sub_matches) override; - bool Delete(const std::vector& url_sub_matches) final; + private: + // Sleep for the client to test timeout control. + int sleep_seconds_ = 0; }; #endif // BOOK_SERVICE_H_ diff --git a/example/rest/book_server/main.cc b/example/rest/book_server/main.cc new file mode 100644 index 0000000..b6fbb2d --- /dev/null +++ b/example/rest/book_server/main.cc @@ -0,0 +1,52 @@ +#include + +#include "webcc/logger.h" +#include "webcc/rest_server.h" + +#include "book_services.h" + +void Help(const char* argv0) { + std::cout << "Usage: " << argv0 << " [seconds]" << std::endl; + std::cout << "If |seconds| is provided, the server will sleep these seconds " + "before sending back each response." + << std::endl; + std::cout << " E.g.," << std::endl; + std::cout << " " << argv0 << " 8080" << std::endl; + std::cout << " " << argv0 << " 8080 3" << std::endl; +} + +int main(int argc, char* argv[]) { + if (argc < 2) { + Help(argv[0]); + return 1; + } + + LOG_INIT(webcc::VERB, 0); + + unsigned short port = std::atoi(argv[1]); + + int sleep_seconds = 0; + if (argc >= 3) { + sleep_seconds = std::atoi(argv[2]); + } + + std::size_t workers = 2; + + try { + webcc::RestServer server(port, workers); + + server.Bind(std::make_shared(sleep_seconds), + "/books", false); + + server.Bind(std::make_shared(sleep_seconds), + "/book/(\\d+)", true); + + server.Run(); + + } catch (std::exception& e) { + std::cerr << "Exception: " << e.what() << std::endl; + return 1; + } + + return 0; +} diff --git a/example/rest_book_server/main.cc b/example/rest_book_server/main.cc deleted file mode 100644 index 94b2ee9..0000000 --- a/example/rest_book_server/main.cc +++ /dev/null @@ -1,41 +0,0 @@ -#include - -#include "webcc/logger.h" -#include "webcc/rest_server.h" - -#include "book_services.h" - -static void Help(const char* argv0) { - std::cout << "Usage: " << argv0 << " " << std::endl; - std::cout << " E.g.," << std::endl; - std::cout << " " << argv0 << " 8080" << std::endl; -} - -int main(int argc, char* argv[]) { - if (argc != 2) { - Help(argv[0]); - return 1; - } - - LOG_INIT(webcc::VERB, 0); - - unsigned short port = std::atoi(argv[1]); - - std::size_t workers = 2; - - try { - webcc::RestServer server(port, workers); - - server.Bind(std::make_shared(), "/books", false); - - server.Bind(std::make_shared(), "/book/(\\d+)", true); - - server.Run(); - - } catch (std::exception& e) { - std::cerr << "Exception: " << e.what() << std::endl; - return 1; - } - - return 0; -} diff --git a/example/soap_calc_client/CMakeLists.txt b/example/soap/calc_client/CMakeLists.txt similarity index 69% rename from example/soap_calc_client/CMakeLists.txt rename to example/soap/calc_client/CMakeLists.txt index 43cf6db..60745be 100644 --- a/example/soap_calc_client/CMakeLists.txt +++ b/example/soap/calc_client/CMakeLists.txt @@ -1,7 +1,3 @@ -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - file(GLOB SRCS *.cc *.h) add_executable(soap_calc_client ${SRCS}) diff --git a/example/soap_calc_client/calc_client.cc b/example/soap/calc_client/calc_client.cc similarity index 98% rename from example/soap_calc_client/calc_client.cc rename to example/soap/calc_client/calc_client.cc index 6b358fb..dfcaef4 100644 --- a/example/soap_calc_client/calc_client.cc +++ b/example/soap/calc_client/calc_client.cc @@ -74,7 +74,7 @@ bool CalcClient::Calc(const std::string& operation, if (error != webcc::kNoError) { LOG_ERRO("Operation '%s' failed: %s", operation.c_str(), - webcc::GetErrorMessage(error)); + webcc::DescribeError(error)); return false; } diff --git a/example/soap_calc_client/calc_client.h b/example/soap/calc_client/calc_client.h similarity index 100% rename from example/soap_calc_client/calc_client.h rename to example/soap/calc_client/calc_client.h diff --git a/example/soap_calc_client/calculator.wsdl b/example/soap/calc_client/calculator.wsdl similarity index 100% rename from example/soap_calc_client/calculator.wsdl rename to example/soap/calc_client/calculator.wsdl diff --git a/example/soap_calc_client/main.cc b/example/soap/calc_client/main.cc similarity index 100% rename from example/soap_calc_client/main.cc rename to example/soap/calc_client/main.cc diff --git a/example/soap_calc_server/CMakeLists.txt b/example/soap/calc_server/CMakeLists.txt similarity index 68% rename from example/soap_calc_server/CMakeLists.txt rename to example/soap/calc_server/CMakeLists.txt index 1e2f762..616776b 100644 --- a/example/soap_calc_server/CMakeLists.txt +++ b/example/soap/calc_server/CMakeLists.txt @@ -1,7 +1,3 @@ -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - file(GLOB SRCS *.cc *.h) add_executable(soap_calc_server ${SRCS}) diff --git a/example/soap_calc_server/calc_service.cc b/example/soap/calc_server/calc_service.cc similarity index 100% rename from example/soap_calc_server/calc_service.cc rename to example/soap/calc_server/calc_service.cc diff --git a/example/soap_calc_server/calc_service.h b/example/soap/calc_server/calc_service.h similarity index 100% rename from example/soap_calc_server/calc_service.h rename to example/soap/calc_server/calc_service.h diff --git a/example/soap_calc_server/main.cc b/example/soap/calc_server/main.cc similarity index 99% rename from example/soap_calc_server/main.cc rename to example/soap/calc_server/main.cc index c09989c..4cfeff1 100644 --- a/example/soap_calc_server/main.cc +++ b/example/soap/calc_server/main.cc @@ -1,6 +1,8 @@ #include + #include "webcc/logger.h" #include "webcc/soap_server.h" + #include "calc_service.h" static void Help(const char* argv0) { diff --git a/src/webcc/CMakeLists.txt b/src/webcc/CMakeLists.txt index 71a1847..22db3de 100644 --- a/src/webcc/CMakeLists.txt +++ b/src/webcc/CMakeLists.txt @@ -4,6 +4,8 @@ add_definitions(-DBOOST_ASIO_NO_DEPRECATED) set(SRCS globals.cc globals.h + http_async_client.cc + http_async_client.h http_client.cc http_client.h http_message.cc @@ -27,6 +29,8 @@ set(SRCS logger.cc logger.h queue.h + rest_async_client.cc + rest_async_client.h rest_client.cc rest_client.h rest_request_handler.cc diff --git a/src/webcc/globals.cc b/src/webcc/globals.cc index 2c64408..23d347a 100644 --- a/src/webcc/globals.cc +++ b/src/webcc/globals.cc @@ -31,7 +31,7 @@ const std::string kHttpDelete = "DELETE"; // ----------------------------------------------------------------------------- -const char* GetErrorMessage(Error error) { +const char* DescribeError(Error error) { switch (error) { case kHostResolveError: return "Cannot resolve the host."; diff --git a/src/webcc/globals.h b/src/webcc/globals.h index 7f6f60a..c8e41ce 100644 --- a/src/webcc/globals.h +++ b/src/webcc/globals.h @@ -4,38 +4,15 @@ #include #include -namespace webcc { - // ----------------------------------------------------------------------------- // Macros -// Explicitly declare the assignment operator as deleted. -#define DISALLOW_ASSIGN(TypeName) TypeName& operator=(const TypeName&) = delete; - // Explicitly declare the copy constructor and assignment operator as deleted. -#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&) = delete; \ - DISALLOW_ASSIGN(TypeName) - -// Explicitly declare all implicit constructors as deleted, namely the -// default constructor, copy constructor and operator= functions. -// This is especially useful for classes containing only static methods. -#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ - TypeName() = delete; \ - DISALLOW_COPY_AND_ASSIGN(TypeName) - -// Disallow copying a type, but provide default construction, move construction -// and move assignment. Especially useful for move-only structs. -#define MOVE_ONLY_WITH_DEFAULT_CONSTRUCTORS(TypeName) \ - TypeName() = default; \ - MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(TypeName) - -// Disallow copying a type, and only provide move construction and move -// assignment. Especially useful for move-only structs. -#define MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(TypeName) \ - TypeName(TypeName&&) = default; \ - TypeName& operator=(TypeName&&) = default; \ - DISALLOW_COPY_AND_ASSIGN(TypeName) +#define DELETE_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName& operator=(const TypeName&) = delete; + +namespace webcc { // ----------------------------------------------------------------------------- // Constants @@ -46,6 +23,11 @@ const std::size_t kBufferSize = 1024; const std::size_t kInvalidLength = static_cast(-1); +// Timeout seconds. +const int kMaxConnectSeconds = 10; +const int kMaxSendSeconds = 10; +const int kMaxReceiveSeconds = 30; + extern const std::string kContentType; extern const std::string kContentLength; extern const std::string kSoapAction; @@ -107,7 +89,7 @@ enum Error { }; // Return a descriptive message for the given error code. -const char* GetErrorMessage(Error error); +const char* DescribeError(Error error); // ----------------------------------------------------------------------------- diff --git a/src/webcc/http_async_client.cc b/src/webcc/http_async_client.cc index 705273c..44443ad 100644 --- a/src/webcc/http_async_client.cc +++ b/src/webcc/http_async_client.cc @@ -1,41 +1,42 @@ #include "webcc/http_async_client.h" -#if 0 -#include "boost/asio.hpp" -#else #include "boost/asio/connect.hpp" #include "boost/asio/read.hpp" #include "boost/asio/write.hpp" -#endif -#include "webcc/http_response_parser.h" -#include "webcc/http_request.h" -#include "webcc/http_response.h" - -using boost::asio::ip::tcp; +#include "webcc/logger.h" namespace webcc { HttpAsyncClient::HttpAsyncClient(boost::asio::io_context& io_context) - : socket_(io_context) { - + : socket_(io_context), + timeout_seconds_(kMaxReceiveSeconds), + deadline_timer_(io_context) { resolver_.reset(new tcp::resolver(io_context)); - response_.reset(new HttpResponse()); - parser_.reset(new HttpResponseParser(response_.get())); + response_parser_.reset(new HttpResponseParser(response_.get())); + + deadline_timer_.expires_at(boost::posix_time::pos_infin); + + // Start the persistent actor that checks for deadline expiry. + CheckDeadline(); } -Error HttpAsyncClient::SendRequest(std::shared_ptr request, - HttpResponseHandler response_handler) { +Error HttpAsyncClient::Request(std::shared_ptr request, + HttpResponseHandler response_handler) { + assert(request); + assert(response_handler); + request_ = request; + response_handler_ = response_handler; std::string port = request->port(); if (port.empty()) { port = "80"; } - auto handler = std::bind(&HttpAsyncClient::HandleResolve, - this, + auto handler = std::bind(&HttpAsyncClient::ResolveHandler, + shared_from_this(), std::placeholders::_1, std::placeholders::_2); @@ -44,110 +45,113 @@ Error HttpAsyncClient::SendRequest(std::shared_ptr request, return kNoError; } -void HttpAsyncClient::HandleResolve(boost::system::error_code ec, - tcp::resolver::results_type results) { +void HttpAsyncClient::ResolveHandler(boost::system::error_code ec, + tcp::resolver::results_type results) { if (ec) { - std::cerr << "Resolve: " << ec.message() << std::endl; - // return kHostResolveError; + LOG_ERRO("Can't resolve host (%s): %s, %s", ec.message().c_str(), + request_->host().c_str(), request_->port().c_str()); + response_handler_(response_, kHostResolveError); } else { endpoints_ = results; - DoConnect(endpoints_.begin()); + AsyncConnect(endpoints_.begin()); } } -void HttpAsyncClient::DoConnect(tcp::resolver::results_type::iterator endpoint_it) { +void HttpAsyncClient::AsyncConnect(tcp::resolver::results_type::iterator endpoint_it) { if (endpoint_it != endpoints_.end()) { + deadline_timer_.expires_from_now( + boost::posix_time::seconds(kMaxConnectSeconds)); + socket_.async_connect(endpoint_it->endpoint(), - std::bind(&HttpAsyncClient::HandleConnect, - this, - std::placeholders::_1, - endpoint_it)); + std::bind(&HttpAsyncClient::ConnectHandler, + shared_from_this(), + std::placeholders::_1, + endpoint_it)); } } -void HttpAsyncClient::HandleConnect(boost::system::error_code ec, - tcp::resolver::results_type::iterator endpoint_it) { +void HttpAsyncClient::ConnectHandler( + boost::system::error_code ec, + tcp::resolver::results_type::iterator endpoint_it) { if (ec) { - // Will be here if the end point is v6. - std::cout << "Connect error: " << ec.message() << std::endl; - // return kEndpointConnectError; - + // Will be here if the endpoint is IPv6. + response_handler_(response_, kEndpointConnectError); socket_.close(); - // Try the next available endpoint. - DoConnect(++endpoint_it); + AsyncConnect(++endpoint_it); } else { - DoWrite(); + AsyncWrite(); } } -// Send HTTP request. -void HttpAsyncClient::DoWrite() { +void HttpAsyncClient::AsyncWrite() { + deadline_timer_.expires_from_now(boost::posix_time::seconds(kMaxSendSeconds)); + boost::asio::async_write(socket_, request_->ToBuffers(), - std::bind(&HttpAsyncClient::HandleWrite, - this, - std::placeholders::_1)); + std::bind(&HttpAsyncClient::WriteHandler, + shared_from_this(), + std::placeholders::_1)); } -void HttpAsyncClient::HandleWrite(boost::system::error_code ec) { +void HttpAsyncClient::WriteHandler(boost::system::error_code ec) { if (ec) { - //return kSocketWriteError; - return; + response_handler_(response_, kSocketWriteError); + } else { + AsyncRead(); } - - DoRead(); } -void HttpAsyncClient::DoRead() { +void HttpAsyncClient::AsyncRead() { + deadline_timer_.expires_from_now( + boost::posix_time::seconds(timeout_seconds_)); + socket_.async_read_some(boost::asio::buffer(buffer_), - std::bind(&HttpAsyncClient::HandleRead, - this, - std::placeholders::_1, - std::placeholders::_2)); + std::bind(&HttpAsyncClient::ReadHandler, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); } -void HttpAsyncClient::HandleRead(boost::system::error_code ec, - std::size_t length) { +void HttpAsyncClient::ReadHandler(boost::system::error_code ec, + std::size_t length) { if (ec || length == 0) { - //return kSocketReadError; + response_handler_(response_, kSocketReadError); return; } // 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); + // If the content has been fully received, |finished()| will be true. + Error error = response_parser_->Parse(buffer_.data(), length); if (error != kNoError) { - //return error; + response_handler_(response_, error); + return; } - if (parser_->finished()) { + if (response_parser_->finished()) { + response_handler_(response_, error); return; } - // Read and parse HTTP 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()) { - // size_t length = socket_.read_some(boost::asio::buffer(buffer_), ec); + AsyncRead(); +} - //if (length == 0 || ec) { - // return kSocketReadError; - //} +void HttpAsyncClient::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); - // 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); + deadline_timer_.expires_at(boost::posix_time::pos_infin); + } - //if (error != kNoError) { - // return error; - //} - //} + // Put the actor back to sleep. + deadline_timer_.async_wait(std::bind(&HttpAsyncClient::CheckDeadline, + shared_from_this())); } } // namespace webcc diff --git a/src/webcc/http_async_client.h b/src/webcc/http_async_client.h index 00004bf..6825b65 100644 --- a/src/webcc/http_async_client.h +++ b/src/webcc/http_async_client.h @@ -2,61 +2,79 @@ #define WEBCC_HTTP_ASYNC_CLIENT_H_ #include +#include +#include -#include "boost/smart_ptr/scoped_ptr.hpp" - +#include "boost/asio/deadline_timer.hpp" #include "boost/asio/io_context.hpp" #include "boost/asio/ip/tcp.hpp" -#include "webcc/common.h" +#include "webcc/globals.h" +#include "webcc/http_request.h" +#include "webcc/http_response.h" #include "webcc/http_response_parser.h" namespace webcc { -class HttpRequest; -class HttpResponse; +typedef std::function HttpResponseHandler; + +class HttpAsyncClient : public std::enable_shared_from_this { + public: + explicit HttpAsyncClient(boost::asio::io_context& io_context); -typedef void(*HttpResponseHandler)(std::shared_ptr); + DELETE_COPY_AND_ASSIGN(HttpAsyncClient); -class HttpAsyncClient { -public: - HttpAsyncClient(boost::asio::io_context& io_context); + void set_timeout_seconds(int timeout_seconds) { + timeout_seconds_ = timeout_seconds; + } - Error SendRequest(std::shared_ptr request, - HttpResponseHandler response_handler); + // Asynchronously connect to the server, send the request, read the response, + // and call the |response_handler| when all these finish. + Error Request(HttpRequestPtr request, HttpResponseHandler response_handler); -private: - void HandleResolve(boost::system::error_code ec, - boost::asio::ip::tcp::resolver::results_type results); + private: + using tcp = boost::asio::ip::tcp; - void DoConnect(boost::asio::ip::tcp::resolver::results_type::iterator endpoint_it); + void ResolveHandler(boost::system::error_code ec, + tcp::resolver::results_type results); - void HandleConnect(boost::system::error_code ec, - boost::asio::ip::tcp::resolver::results_type::iterator endpoint_it); + void AsyncConnect(tcp::resolver::results_type::iterator endpoint_it); - void DoWrite(); + void ConnectHandler(boost::system::error_code ec, + tcp::resolver::results_type::iterator endpoint_it); - void HandleWrite(boost::system::error_code ec); + void AsyncWrite(); + void WriteHandler(boost::system::error_code ec); - void DoRead(); + void AsyncRead(); + void ReadHandler(boost::system::error_code ec, std::size_t length); - void HandleRead(boost::system::error_code ec, std::size_t length); + void CheckDeadline(); -private: - boost::asio::ip::tcp::socket socket_; + tcp::socket socket_; std::shared_ptr request_; - std::unique_ptr resolver_; - boost::asio::ip::tcp::resolver::results_type endpoints_; + std::unique_ptr resolver_; + tcp::resolver::results_type endpoints_; std::array buffer_; - std::unique_ptr parser_; + std::unique_ptr response_parser_; + + HttpResponsePtr response_; + HttpResponseHandler response_handler_; - std::shared_ptr response_; + // 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_; }; +typedef std::shared_ptr HttpAsyncClientPtr; + } // namespace webcc #endif // WEBCC_HTTP_ASYNC_CLIENT_H_ diff --git a/src/webcc/http_client.cc b/src/webcc/http_client.cc index c7b4a55..304916a 100644 --- a/src/webcc/http_client.cc +++ b/src/webcc/http_client.cc @@ -19,43 +19,33 @@ namespace webcc { -static const int kConnectMaxSeconds = 10; -static const int kSendMaxSeconds = 10; -static const int kReceiveMaxSeconds = 30; - HttpClient::HttpClient() : socket_(io_context_), - timeout_seconds_(kReceiveMaxSeconds), + timeout_seconds_(kMaxReceiveSeconds), deadline_timer_(io_context_) { deadline_timer_.expires_at(boost::posix_time::pos_infin); + response_.reset(new HttpResponse()); + response_parser_.reset(new HttpResponseParser(response_.get())); + // Start the persistent actor that checks for deadline expiry. CheckDeadline(); } -Error HttpClient::Request(const HttpRequest& request, HttpResponse* response) { - assert(response != nullptr); - - Error error = kNoError; - - if ((error = Connect(request)) != kNoError) { - return error; +bool HttpClient::Request(const HttpRequest& request) { + if ((error_ = Connect(request)) != kNoError) { + return false; } - // Send HTTP request. - - if ((error = SendReqeust(request)) != kNoError) { - return error; + if ((error_ = SendReqeust(request)) != kNoError) { + return false; } - // Read and parse HTTP response. - - // NOTE: Don't use make_unique because it's since C++14. - parser_.reset(new HttpResponseParser(response)); - - error = ReadResponse(response); + if ((error_ = ReadResponse()) != kNoError) { + return false; + } - return error; + return true; } Error HttpClient::Connect(const HttpRequest& request) { @@ -72,15 +62,13 @@ Error HttpClient::Connect(const HttpRequest& request) { auto endpoints = resolver.resolve(tcp::v4(), request.host(), port, ec); if (ec) { - LOG_ERRO("cannot resolve host: %s, %s", - request.host().c_str(), - port.c_str()); - + LOG_ERRO("Can't resolve host (%s): %s, %s", ec.message().c_str(), + request.host().c_str(), port.c_str()); return kHostResolveError; } deadline_timer_.expires_from_now( - boost::posix_time::seconds(kConnectMaxSeconds)); + boost::posix_time::seconds(kMaxConnectSeconds)); ec = boost::asio::error::would_block; @@ -110,9 +98,9 @@ Error HttpClient::Connect(const HttpRequest& request) { } Error HttpClient::SendReqeust(const HttpRequest& request) { - LOG_VERB("http request:\n{\n%s}", request.Dump().c_str()); + LOG_VERB("HTTP request:\n%s", request.Dump(4, "> ").c_str()); - deadline_timer_.expires_from_now(boost::posix_time::seconds(kSendMaxSeconds)); + deadline_timer_.expires_from_now(boost::posix_time::seconds(kMaxSendSeconds)); boost::system::error_code ec = boost::asio::error::would_block; @@ -132,7 +120,7 @@ Error HttpClient::SendReqeust(const HttpRequest& request) { return kNoError; } -Error HttpClient::ReadResponse(HttpResponse* response) { +Error HttpClient::ReadResponse() { deadline_timer_.expires_from_now( boost::posix_time::seconds(timeout_seconds_)); @@ -141,31 +129,33 @@ Error HttpClient::ReadResponse(HttpResponse* response) { socket_.async_read_some( boost::asio::buffer(buffer_), - [this, &ec, &error, response](boost::system::error_code inner_ec, - std::size_t length) { + [this, &ec, &error](boost::system::error_code inner_ec, + std::size_t length) { ec = inner_ec; if (inner_ec || length == 0) { + LOG_ERRO("Socket read error."); 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); + return; + } + + // Parse the response piece just read. + // If the content has been fully received, next time flag "finished_" + // will be set. + error = response_parser_->Parse(buffer_.data(), length); + + if (error != kNoError) { + LOG_ERRO("Failed to parse HTTP response."); + return; + } + + if (response_parser_->finished()) { + // Stop trying to read once all content has been received, + // because some servers will block extra call to read_some(). + return; } + + ReadResponse(); }); // Block until the asynchronous operation has completed. @@ -174,7 +164,7 @@ Error HttpClient::ReadResponse(HttpResponse* response) { } while (ec == boost::asio::error::would_block); if (error == kNoError) { - LOG_VERB("http response:\n{\n%s}", response->Dump().c_str()); + LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str()); } return error; diff --git a/src/webcc/http_client.h b/src/webcc/http_client.h index d35fb8f..470e6d7 100644 --- a/src/webcc/http_client.h +++ b/src/webcc/http_client.h @@ -9,32 +9,37 @@ #include "boost/asio/ip/tcp.hpp" #include "webcc/globals.h" +#include "webcc/http_request.h" +#include "webcc/http_response.h" #include "webcc/http_response_parser.h" namespace webcc { -class HttpRequest; -class HttpResponse; - class HttpClient { public: HttpClient(); + ~HttpClient() = default; + DELETE_COPY_AND_ASSIGN(HttpClient); + void set_timeout_seconds(int timeout_seconds) { timeout_seconds_ = timeout_seconds; } - // Connect to the server, send the request, wait until the response is - // received. - Error Request(const HttpRequest& request, HttpResponse* response); + HttpResponsePtr response() const { return response_; } + + Error error() const { return error_; } + + // Connect to server, send request, wait until response is received. + bool Request(const HttpRequest& request); private: Error Connect(const HttpRequest& request); Error SendReqeust(const HttpRequest& request); - Error ReadResponse(HttpResponse* response); + Error ReadResponse(); void CheckDeadline(); @@ -44,7 +49,10 @@ class HttpClient { std::array buffer_; - std::unique_ptr parser_; + HttpResponsePtr response_; + std::unique_ptr response_parser_; + + Error error_ = kNoError; // Maximum seconds to wait before the client cancels the operation. // Only for receiving response from server. @@ -52,8 +60,6 @@ class HttpClient { // Timer for the timeout control. boost::asio::deadline_timer deadline_timer_; - - DISALLOW_COPY_AND_ASSIGN(HttpClient); }; } // namespace webcc diff --git a/src/webcc/http_message.cc b/src/webcc/http_message.cc index 97240cb..59c39d3 100644 --- a/src/webcc/http_message.cc +++ b/src/webcc/http_message.cc @@ -1,5 +1,9 @@ #include "webcc/http_message.h" +#include + +#include "boost/algorithm/string.hpp" + namespace webcc { void HttpMessage::SetHeader(const std::string& name, const std::string& value) { @@ -13,4 +17,45 @@ void HttpMessage::SetHeader(const std::string& name, const std::string& value) { headers_.push_back({ name, value }); } +void HttpMessage::Dump(std::ostream& os, std::size_t indent, + const std::string& prefix) const { + std::string indent_str; + if (indent > 0) { + indent_str.append(indent, ' '); + } + indent_str.append(prefix); + + os << indent_str << start_line_; + + for (const HttpHeader& h : headers_) { + os << indent_str << h.name << ": " << h.value << std::endl; + } + + os << std::endl; + + if (!content_.empty()) { + if (indent == 0) { + os << content_ << std::endl; + } else { + std::vector splitted; + boost::split(splitted, content_, boost::is_any_of("\r\n")); + for (const std::string& line : splitted) { + os << indent_str << line << std::endl; + } + } + } +} + +std::string HttpMessage::Dump(std::size_t indent, + const std::string& prefix) const { + std::stringstream ss; + Dump(ss, indent, prefix); + return ss.str(); +} + +std::ostream& operator<<(std::ostream& os, const HttpMessage& message) { + message.Dump(os); + return os; +} + } // namespace webcc diff --git a/src/webcc/http_message.h b/src/webcc/http_message.h index b4fa265..21a3778 100644 --- a/src/webcc/http_message.h +++ b/src/webcc/http_message.h @@ -53,6 +53,14 @@ class HttpMessage { SetContentLength(content_.size()); } + // Dump to output stream. + void Dump(std::ostream& os, std::size_t indent = 0, + const std::string& prefix = "") const; + + // Dump to string, only used by logger. + std::string Dump(std::size_t indent = 0, + const std::string& prefix = "") const; + protected: void SetContentLength(std::size_t content_length) { content_length_ = content_length; @@ -69,6 +77,8 @@ class HttpMessage { std::string content_; }; +std::ostream& operator<<(std::ostream& os, const HttpMessage& message); + } // namespace webcc #endif // WEBCC_HTTP_MESSAGE_H_ diff --git a/src/webcc/http_parser.cc b/src/webcc/http_parser.cc index 680102d..2a50281 100644 --- a/src/webcc/http_parser.cc +++ b/src/webcc/http_parser.cc @@ -117,7 +117,8 @@ void HttpParser::ParseContentLength(const std::string& line) { } void HttpParser::Finish() { - message_->SetContent(content_); + // Move temp content to message. + message_->SetContent(std::move(content_)); finished_ = true; } diff --git a/src/webcc/http_parser.h b/src/webcc/http_parser.h index 8a0550e..7df45d2 100644 --- a/src/webcc/http_parser.h +++ b/src/webcc/http_parser.h @@ -16,6 +16,8 @@ class HttpParser { virtual ~HttpParser() = default; + DELETE_COPY_AND_ASSIGN(HttpParser); + bool finished() const { return finished_; } @@ -50,8 +52,6 @@ class HttpParser { bool content_length_parsed_; bool header_parsed_; bool finished_; - - DISALLOW_COPY_AND_ASSIGN(HttpParser); }; } // namespace webcc diff --git a/src/webcc/http_request.cc b/src/webcc/http_request.cc index f621c61..a2e0e32 100644 --- a/src/webcc/http_request.cc +++ b/src/webcc/http_request.cc @@ -1,27 +1,7 @@ #include "webcc/http_request.h" -#include - -#include "boost/algorithm/string.hpp" - namespace webcc { -std::ostream& operator<<(std::ostream& os, const HttpRequest& request) { - os << request.start_line(); - - for (const HttpHeader& h : request.headers_) { - os << h.name << ": " << h.value << std::endl; - } - - os << std::endl; - - if (!request.content().empty()) { - os << request.content() << std::endl; - } - - return os; -} - void HttpRequest::SetHost(const std::string& host, const std::string& port) { host_ = host; port_ = port; @@ -73,10 +53,4 @@ std::vector HttpRequest::ToBuffers() const { return buffers; } -std::string HttpRequest::Dump() const { - std::stringstream ss; - ss << *this; - return ss.str(); -} - } // namespace webcc diff --git a/src/webcc/http_request.h b/src/webcc/http_request.h index eac4eaa..f2bd237 100644 --- a/src/webcc/http_request.h +++ b/src/webcc/http_request.h @@ -1,6 +1,7 @@ #ifndef WEBCC_HTTP_REQUEST_H_ #define WEBCC_HTTP_REQUEST_H_ +#include #include #include @@ -10,10 +11,6 @@ namespace webcc { -class HttpRequest; - -std::ostream& operator<<(std::ostream& os, const HttpRequest& request); - class HttpRequest : public HttpMessage { public: HttpRequest() = default; @@ -60,12 +57,7 @@ class HttpRequest : public HttpMessage { // and not be changed until the write operation has completed. std::vector ToBuffers() const; - // Dump as string, only used by logger. - std::string Dump() const; - private: - friend std::ostream& operator<<(std::ostream& os, const HttpRequest& request); - // HTTP method. std::string method_; @@ -78,6 +70,8 @@ class HttpRequest : public HttpMessage { std::string port_; }; +typedef std::shared_ptr HttpRequestPtr; + } // namespace webcc #endif // WEBCC_HTTP_REQUEST_H_ diff --git a/src/webcc/http_request_handler.h b/src/webcc/http_request_handler.h index 71e983d..162b2ed 100644 --- a/src/webcc/http_request_handler.h +++ b/src/webcc/http_request_handler.h @@ -21,6 +21,8 @@ class HttpRequestHandler { HttpRequestHandler() = default; virtual ~HttpRequestHandler() = default; + DELETE_COPY_AND_ASSIGN(HttpRequestHandler); + // Put the session into the queue. void Enqueue(HttpSessionPtr session); @@ -38,8 +40,6 @@ class HttpRequestHandler { Queue queue_; boost::thread_group workers_; - - DISALLOW_COPY_AND_ASSIGN(HttpRequestHandler); }; } // namespace webcc diff --git a/src/webcc/http_response.cc b/src/webcc/http_response.cc index 2858c17..4914803 100644 --- a/src/webcc/http_response.cc +++ b/src/webcc/http_response.cc @@ -1,26 +1,7 @@ #include "webcc/http_response.h" -#include -#include - namespace webcc { -std::ostream& operator<<(std::ostream& os, const HttpResponse& response) { - os << response.start_line(); - - for (const HttpHeader& h : response.headers_) { - os << h.name << ": " << h.value << std::endl; - } - - os << std::endl; - - if (!response.content().empty()) { - os << response.content() << std::endl; - } - - return os; -} - namespace status_strings { const std::string OK = "HTTP/1.1 200 OK\r\n"; @@ -115,10 +96,4 @@ HttpResponse HttpResponse::Fault(HttpStatus::Enum status) { return response; } -std::string HttpResponse::Dump() const { - std::stringstream ss; - ss << *this; - return ss.str(); -} - } // namespace webcc diff --git a/src/webcc/http_response.h b/src/webcc/http_response.h index adf4f79..d6a66b5 100644 --- a/src/webcc/http_response.h +++ b/src/webcc/http_response.h @@ -1,6 +1,7 @@ #ifndef WEBCC_HTTP_RESPONSE_H_ #define WEBCC_HTTP_RESPONSE_H_ +#include #include #include @@ -10,10 +11,6 @@ namespace webcc { -class HttpResponse; - -std::ostream& operator<<(std::ostream& os, const HttpResponse& response); - class HttpResponse : public HttpMessage { public: HttpResponse() = default; @@ -32,19 +29,15 @@ class HttpResponse : public HttpMessage { // and not be changed until the write operation has completed. std::vector ToBuffers() const; - // Dump as string, only used by logger. - std::string Dump() const; - // Get a fault response when HTTP status is not OK. static HttpResponse Fault(HttpStatus::Enum status); private: - friend std::ostream& operator<<(std::ostream& os, - const HttpResponse& response); - int status_ = HttpStatus::kOK; }; +typedef std::shared_ptr HttpResponsePtr; + } // namespace webcc #endif // WEBCC_HTTP_RESPONSE_H_ diff --git a/src/webcc/http_server.h b/src/webcc/http_server.h index eec7bdf..5deca99 100644 --- a/src/webcc/http_server.h +++ b/src/webcc/http_server.h @@ -25,6 +25,8 @@ class HttpServer { virtual ~HttpServer() = default; + DELETE_COPY_AND_ASSIGN(HttpServer); + // Run the server's io_service loop. void Run(); @@ -49,8 +51,6 @@ class HttpServer { // Acceptor used to listen for incoming connections. boost::scoped_ptr acceptor_; - - DISALLOW_COPY_AND_ASSIGN(HttpServer); }; } // namespace webcc diff --git a/src/webcc/http_session.h b/src/webcc/http_session.h index efe12d7..34424b5 100644 --- a/src/webcc/http_session.h +++ b/src/webcc/http_session.h @@ -18,14 +18,13 @@ class HttpRequestHandler; class HttpSession : public std::enable_shared_from_this { public: - HttpSession(const HttpSession&) = delete; - HttpSession& operator=(const HttpSession&) = delete; - HttpSession(boost::asio::ip::tcp::socket socket, HttpRequestHandler* handler); ~HttpSession() = default; + DELETE_COPY_AND_ASSIGN(HttpSession); + const HttpRequest& request() const { return request_; } diff --git a/src/webcc/rest_async_client.cc b/src/webcc/rest_async_client.cc new file mode 100644 index 0000000..dc0ea58 --- /dev/null +++ b/src/webcc/rest_async_client.cc @@ -0,0 +1,27 @@ +#include "webcc/rest_async_client.h" + +namespace webcc { + +void RestAsyncClient::Request(const std::string& method, + const std::string& url, + const std::string& content, + HttpResponseHandler response_handler) { + response_handler_ = response_handler; + + HttpRequestPtr request(new webcc::HttpRequest()); + + request->set_method(method); + request->set_url(url); + request->SetHost(host_, port_); + + if (!content.empty()) { + request->SetContent(content); + } + + request->Build(); + + HttpAsyncClientPtr http_client(new HttpAsyncClient(io_context_)); + http_client->Request(request, response_handler_); +} + +} // namespace webcc diff --git a/src/webcc/rest_async_client.h b/src/webcc/rest_async_client.h new file mode 100644 index 0000000..5cdfba1 --- /dev/null +++ b/src/webcc/rest_async_client.h @@ -0,0 +1,60 @@ +#ifndef WEBCC_REST_ASYNC_CLIENT_H_ +#define WEBCC_REST_ASYNC_CLIENT_H_ + +#include + +#include "webcc/globals.h" +#include "webcc/http_async_client.h" + +namespace webcc { + +class RestAsyncClient { + public: + RestAsyncClient(boost::asio::io_context& io_context, + const std::string& host, const std::string& port) + : io_context_(io_context), host_(host), port_(port) { + } + + void Get(const std::string& url, + HttpResponseHandler response_handler) { + Request(kHttpGet, url, "", response_handler); + } + + void Post(const std::string& url, + const std::string& content, + HttpResponseHandler response_handler) { + Request(kHttpPost, url, content, response_handler); + } + + void Put(const std::string& url, + const std::string& content, + HttpResponseHandler response_handler) { + Request(kHttpPut, url, content, response_handler); + } + + void Patch(const std::string& url, + const std::string& content, + HttpResponseHandler response_handler) { + Request(kHttpPatch, url, content, response_handler); + } + + void Delete(const std::string& url, + HttpResponseHandler response_handler) { + Request(kHttpDelete, url, "", response_handler); + } + + private: + void Request(const std::string& method, + const std::string& url, + const std::string& content, + HttpResponseHandler response_handler); + + boost::asio::io_context& io_context_; + std::string host_; + std::string port_; + HttpResponseHandler response_handler_; +}; + +} // namespace webcc + +#endif // WEBCC_REST_ASYNC_CLIENT_H_ diff --git a/src/webcc/rest_client.cc b/src/webcc/rest_client.cc index 433a06d..c787b0c 100644 --- a/src/webcc/rest_client.cc +++ b/src/webcc/rest_client.cc @@ -8,8 +8,7 @@ namespace webcc { bool RestClient::Request(const std::string& method, const std::string& url, - const std::string& content, - HttpResponse* response) { + const std::string& content) { HttpRequest request; request.set_method(method); @@ -23,9 +22,17 @@ bool RestClient::Request(const std::string& method, request.Build(); HttpClient http_client; - Error error = http_client.Request(request, response); + http_client.set_timeout_seconds(timeout_seconds_); - return error == kNoError; + error_ = kNoError; + + if (!http_client.Request(request)) { + error_ = http_client.error(); + return false; + } + + response_ = http_client.response(); + return true; } } // namespace webcc diff --git a/src/webcc/rest_client.h b/src/webcc/rest_client.h index ab80358..3c36d7a 100644 --- a/src/webcc/rest_client.h +++ b/src/webcc/rest_client.h @@ -4,51 +4,60 @@ #include #include "webcc/globals.h" +#include "webcc/http_response.h" namespace webcc { -class HttpResponse; - class RestClient { public: RestClient(const std::string& host, const std::string& port) : host_(host), port_(port) { } - bool Get(const std::string& url, HttpResponse* response) { - return Request(kHttpGet, url, "", response); + void set_timeout_seconds(int timeout_seconds) { + timeout_seconds_ = timeout_seconds; + } + + HttpResponsePtr response() const { return response_; } + int response_status() const { return response_->status(); } + const std::string& response_content() const { return response_->content(); } + + Error error() const { return error_; } + + bool Get(const std::string& url) { + return Request(kHttpGet, url, ""); } - bool Post(const std::string& url, - const std::string& content, - HttpResponse* response) { - return Request(kHttpPost, url, content, response); + bool Post(const std::string& url, const std::string& content) { + return Request(kHttpPost, url, content); } - bool Put(const std::string& url, - const std::string& content, - HttpResponse* response) { - return Request(kHttpPut, url, content, response); + bool Put(const std::string& url, const std::string& content) { + return Request(kHttpPut, url, content); } - bool Patch(const std::string& url, - const std::string& content, - HttpResponse* response) { - return Request(kHttpPatch, url, content, response); + bool Patch(const std::string& url, const std::string& content) { + return Request(kHttpPatch, url, content); } - bool Delete(const std::string& url, HttpResponse* response) { - return Request(kHttpDelete, url, "", response); + bool Delete(const std::string& url) { + return Request(kHttpDelete, url, ""); } private: bool Request(const std::string& method, const std::string& url, - const std::string& content, - HttpResponse* response); + const std::string& content); std::string host_; std::string port_; + + // -1 means default timeout (normally 30s) will be used. + int timeout_seconds_ = -1; + + HttpResponsePtr response_; + + Error error_ = kNoError; }; } // namespace webcc diff --git a/src/webcc/rest_request_handler.h b/src/webcc/rest_request_handler.h index 281910f..8f0a411 100644 --- a/src/webcc/rest_request_handler.h +++ b/src/webcc/rest_request_handler.h @@ -12,6 +12,7 @@ namespace webcc { class RestRequestHandler : public HttpRequestHandler { public: + RestRequestHandler() = default; ~RestRequestHandler() override = default; bool Bind(RestServicePtr service, const std::string& url, bool is_regex); diff --git a/src/webcc/rest_service_manager.h b/src/webcc/rest_service_manager.h index d631589..c556aac 100644 --- a/src/webcc/rest_service_manager.h +++ b/src/webcc/rest_service_manager.h @@ -14,6 +14,8 @@ class RestServiceManager { public: RestServiceManager() = default; + DELETE_COPY_AND_ASSIGN(RestServiceManager); + // 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. @@ -59,8 +61,6 @@ class RestServiceManager { }; std::vector service_items_; - - DISALLOW_COPY_AND_ASSIGN(RestServiceManager); }; } // namespace webcc diff --git a/src/webcc/soap_client.cc b/src/webcc/soap_client.cc index 7f7a430..ae3578a 100644 --- a/src/webcc/soap_client.cc +++ b/src/webcc/soap_client.cc @@ -54,16 +54,14 @@ Error SoapClient::Call(const std::string& operation, http_client.set_timeout_seconds(timeout_seconds_); } - Error error = http_client.Request(http_request, &http_response); - - if (error != kNoError) { - return error; + if (!http_client.Request(http_request)) { + return http_client.error(); } SoapResponse soap_response; soap_response.set_result_name(result_name_); - if (!soap_response.FromXml(http_response.content())) { + if (!soap_response.FromXml(http_client.response()->content())) { return kXmlError; }