From 3e60c76da1d3d33124bff251a4f27d3a1c80099b Mon Sep 17 00:00:00 2001 From: Chunting Gu Date: Mon, 11 Feb 2019 12:46:27 +0800 Subject: [PATCH] Allow to set buffer size for client; don't auto adjust reading buffer size; refine the global definitions. --- example/github_rest_client/main.cc | 41 ++++++- example/http_bin_client/main.cc | 5 +- example/http_hello_async_client/main.cc | 6 +- example/http_hello_client/main.cc | 6 +- example/http_ssl_client/main.cc | 11 +- example/rest_book_async_client/main.cc | 18 +-- example/rest_book_client/main.cc | 12 +- example/rest_book_server/services.cc | 24 ++-- example/soap_book_client/book_client.cc | 2 +- example/soap_calc_client/main.cc | 2 +- example/soap_calc_client_parasoft/main.cc | 2 +- unittest/rest_service_manager_test.cc | 2 +- webcc/CMakeLists.txt | 2 + webcc/globals.cc | 13 -- webcc/globals.h | 84 ++++++++----- webcc/http_async_client.cc | 140 +++++++++++++--------- webcc/http_async_client.h | 32 +++-- webcc/http_client.cc | 31 +++-- webcc/http_client.h | 12 +- webcc/http_message.cc | 21 ++++ webcc/http_message.h | 14 +-- webcc/http_parser.cc | 4 +- webcc/http_parser.h | 1 - webcc/http_request.cc | 19 ++- webcc/http_request.h | 29 +++-- webcc/http_response.cc | 24 ++-- webcc/http_response.h | 4 +- webcc/http_session.cc | 9 +- webcc/http_session.h | 6 +- webcc/http_ssl_client.cc | 17 +-- webcc/http_ssl_client.h | 11 +- webcc/rest_async_client.cc | 31 ++--- webcc/rest_async_client.h | 35 +++--- webcc/rest_client.cc | 28 ++--- webcc/rest_client.h | 30 +++-- webcc/rest_request_handler.cc | 7 +- webcc/rest_service.h | 2 +- webcc/rest_ssl_client.cc | 27 ++--- webcc/rest_ssl_client.h | 29 +++-- webcc/soap_async_client.cc | 59 +++++---- webcc/soap_async_client.h | 17 +-- webcc/soap_client.cc | 36 +++--- webcc/soap_client.h | 11 +- webcc/soap_request_handler.cc | 16 ++- webcc/soap_service.h | 2 +- webcc/utility.cc | 22 ++-- webcc/utility.h | 24 +++- 47 files changed, 558 insertions(+), 422 deletions(-) diff --git a/example/github_rest_client/main.cc b/example/github_rest_client/main.cc index 3c06dbd..2920e6f 100644 --- a/example/github_rest_client/main.cc +++ b/example/github_rest_client/main.cc @@ -5,6 +5,11 @@ #include "webcc/rest_ssl_client.h" #include "webcc/logger.h" +const bool kSslVerify = false; + +#define PRINT_CONTENT 0 + + static Json::Value StringToJson(const std::string& str) { Json::Value json; @@ -18,10 +23,11 @@ static Json::Value StringToJson(const std::string& str) { return json; } -void Test() { - webcc::RestSslClient client("api.github.com"); +void ListPublicEvents() { + webcc::RestSslClient client("api.github.com", "", kSslVerify); if (client.Get("/events")) { +#if PRINT_CONTENT Json::Value json = StringToJson(client.response()->content()); // Pretty print the JSON. @@ -33,6 +39,33 @@ void Test() { writer->write(json, &std::cout); std::cout << std::endl; +#endif // PRINT_CONTENT + } else { + std::cout << webcc::DescribeError(client.error()); + if (client.timed_out()) { + std::cout << " (timed out)"; + } + std::cout << std::endl; + } +} + +void ListUserFollowers() { + webcc::RestSslClient client("api.github.com", "", kSslVerify); + + if (client.Get("/users/sprinfall/followers")) { +#if PRINT_CONTENT + Json::Value json = StringToJson(client.response()->content()); + + // Pretty print the JSON. + + Json::StreamWriterBuilder builder; + builder["indentation"] = " "; + + std::unique_ptr writer(builder.newStreamWriter()); + writer->write(json, &std::cout); + + std::cout << std::endl; +#endif // PRINT_CONTENT } else { std::cout << webcc::DescribeError(client.error()); if (client.timed_out()) { @@ -45,7 +78,9 @@ void Test() { int main() { WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); - Test(); + //ListPublicEvents(); + + ListUserFollowers(); return 0; } diff --git a/example/http_bin_client/main.cc b/example/http_bin_client/main.cc index 64319bb..51be995 100644 --- a/example/http_bin_client/main.cc +++ b/example/http_bin_client/main.cc @@ -37,10 +37,7 @@ #include "webcc/logger.h" void Test() { - webcc::HttpRequest request; - request.set_method(webcc::kHttpGet); - request.set_url("/get"); - request.set_host("httpbin.org"/*, "80"*/); + webcc::HttpRequest request(webcc::kHttpGet, "/get", "httpbin.org"/*, "80"*/); request.Make(); webcc::HttpClient client; diff --git a/example/http_hello_async_client/main.cc b/example/http_hello_async_client/main.cc index 6ea1d0f..30a3491 100644 --- a/example/http_hello_async_client/main.cc +++ b/example/http_hello_async_client/main.cc @@ -11,11 +11,9 @@ // The default port number should be 8000. void Test(boost::asio::io_context& io_context) { - webcc::HttpRequestPtr request(new webcc::HttpRequest()); + webcc::HttpRequestPtr request = std::make_shared( + webcc::kHttpGet, "/index.html", "localhost", "8000"); - request->set_method(webcc::kHttpGet); - request->set_url("/index.html"); - request->set_host("localhost", "8000"); request->Make(); webcc::HttpAsyncClientPtr client(new webcc::HttpAsyncClient(io_context)); diff --git a/example/http_hello_client/main.cc b/example/http_hello_client/main.cc index 84aaf6d..85e2023 100644 --- a/example/http_hello_client/main.cc +++ b/example/http_hello_client/main.cc @@ -9,10 +9,8 @@ // The default port number should be 8000. void Test() { - webcc::HttpRequest request; - request.set_method(webcc::kHttpGet); - request.set_url("/index.html"); - request.set_host("localhost", "8000"); + webcc::HttpRequest request(webcc::kHttpGet, "/index.html", "localhost", + "8000"); request.Make(); webcc::HttpClient client; diff --git a/example/http_ssl_client/main.cc b/example/http_ssl_client/main.cc index a9b67d7..f739f11 100644 --- a/example/http_ssl_client/main.cc +++ b/example/http_ssl_client/main.cc @@ -21,20 +21,19 @@ int main(int argc, char* argv[]) { WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); - webcc::HttpRequest request; - request.set_method(webcc::kHttpGet); - request.set_url(url); - request.set_host(host); // Leave port to default value. + // Leave port to default value. + webcc::HttpRequest request(webcc::kHttpGet, url, host); + request.Make(); // Verify the certificate of the peer or not. // See HttpSslClient::Request() for more details. bool ssl_verify = false; - webcc::HttpSslClient client(ssl_verify); + webcc::HttpSslClient client(2000, ssl_verify); if (client.Request(request)) { - std::cout << client.response()->content() << std::endl; + //std::cout << client.response()->content() << std::endl; } else { std::cout << webcc::DescribeError(client.error()); if (client.timed_out()) { diff --git a/example/rest_book_async_client/main.cc b/example/rest_book_async_client/main.cc index e7fa6c8..9bb1e45 100644 --- a/example/rest_book_async_client/main.cc +++ b/example/rest_book_async_client/main.cc @@ -16,7 +16,7 @@ class BookClientBase { const std::string& host, const std::string& port, int timeout_seconds) : rest_client_(io_context, host, port) { - rest_client_.set_timeout_seconds(timeout_seconds); + rest_client_.SetTimeout(timeout_seconds); } virtual ~BookClientBase() = default; @@ -58,10 +58,10 @@ class BookListClient : public BookClientBase { : BookClientBase(io_context, host, port, timeout_seconds) { } - void ListBooks(webcc::HttpResponseHandler handler) { + void ListBooks(webcc::HttpResponseCallback callback) { std::cout << "ListBooks" << std::endl; - rest_client_.Get("/books", handler); + rest_client_.Get("/books", callback); } void CreateBook(const std::string& title, double price, @@ -96,7 +96,7 @@ class BookDetailClient : public BookClientBase { : BookClientBase(io_context, host, port, timeout_seconds) { } - void GetBook(const std::string& id, webcc::HttpResponseHandler handler) { + void GetBook(const std::string& id, webcc::HttpResponseCallback callback) { std::cout << "GetBook: " << id << std::endl; auto rsp_callback = [](webcc::HttpResponsePtr response) { @@ -105,13 +105,13 @@ class BookDetailClient : public BookClientBase { //id_callback(rsp_json["id"].asString()); }; - rest_client_.Get("/books/" + id, handler); + rest_client_.Get("/books/" + id, callback); } void UpdateBook(const std::string& id, const std::string& title, double price, - webcc::HttpResponseHandler handler) { + webcc::HttpResponseCallback callback) { std::cout << "UpdateBook: " << id << " " << title << " " << price << std::endl; @@ -120,13 +120,13 @@ class BookDetailClient : public BookClientBase { json["title"] = title; json["price"] = price; - rest_client_.Put("/books/" + id, JsonToString(json), handler); + rest_client_.Put("/books/" + id, JsonToString(json), callback); } - void DeleteBook(const std::string& id, webcc::HttpResponseHandler handler) { + void DeleteBook(const std::string& id, webcc::HttpResponseCallback callback) { std::cout << "DeleteBook: " << id << std::endl; - rest_client_.Delete("/books/" + id, handler); + rest_client_.Delete("/books/" + id, callback); } }; diff --git a/example/rest_book_client/main.cc b/example/rest_book_client/main.cc index 01e4078..b2e37c8 100644 --- a/example/rest_book_client/main.cc +++ b/example/rest_book_client/main.cc @@ -40,7 +40,7 @@ class BookClientBase { } // Check HTTP response status. - bool CheckStatus(webcc::HttpStatus::Enum expected_status) { + bool CheckStatus(webcc::http::Status expected_status) { int status = rest_client_.response_status(); if (status != expected_status) { LOG_ERRO("HTTP status error (actual: %d, expected: %d).", @@ -69,7 +69,7 @@ class BookListClient : public BookClientBase { return false; } - if (!CheckStatus(webcc::HttpStatus::kOK)) { + if (!CheckStatus(webcc::http::Status::kOK)) { // Response HTTP status error. return false; } @@ -97,7 +97,7 @@ class BookListClient : public BookClientBase { return false; } - if (!CheckStatus(webcc::HttpStatus::kCreated)) { + if (!CheckStatus(webcc::http::Status::kCreated)) { return false; } @@ -123,7 +123,7 @@ class BookDetailClient : public BookClientBase { return false; } - if (!CheckStatus(webcc::HttpStatus::kOK)) { + if (!CheckStatus(webcc::http::Status::kOK)) { return false; } @@ -141,7 +141,7 @@ class BookDetailClient : public BookClientBase { return false; } - if (!CheckStatus(webcc::HttpStatus::kOK)) { + if (!CheckStatus(webcc::http::Status::kOK)) { return false; } @@ -154,7 +154,7 @@ class BookDetailClient : public BookClientBase { return false; } - if (!CheckStatus(webcc::HttpStatus::kOK)) { + if (!CheckStatus(webcc::http::Status::kOK)) { return false; } diff --git a/example/rest_book_server/services.cc b/example/rest_book_server/services.cc index a68cc71..6cc1fe1 100644 --- a/example/rest_book_server/services.cc +++ b/example/rest_book_server/services.cc @@ -35,7 +35,7 @@ void BookListService::Get(const webcc::UrlQuery& /*query*/, } response->content = JsonToString(json); - response->status = webcc::HttpStatus::kOK; + response->status = webcc::http::Status::kOK; } void BookListService::Post(const std::string& request_content, @@ -50,10 +50,10 @@ void BookListService::Post(const std::string& request_content, json["id"] = id; response->content = JsonToString(json); - response->status = webcc::HttpStatus::kCreated; + response->status = webcc::http::Status::kCreated; } else { // Invalid JSON - response->status = webcc::HttpStatus::kBadRequest; + response->status = webcc::http::Status::kBadRequest; } } @@ -67,7 +67,7 @@ void BookDetailService::Get(const webcc::UrlSubMatches& url_sub_matches, if (url_sub_matches.size() != 1) { // Using kNotFound means the resource specified by the URL cannot be found. // kBadRequest could be another choice. - response->status = webcc::HttpStatus::kNotFound; + response->status = webcc::http::Status::kNotFound; return; } @@ -75,12 +75,12 @@ void BookDetailService::Get(const webcc::UrlSubMatches& url_sub_matches, const Book& book = g_book_store.GetBook(book_id); if (book.IsNull()) { - response->status = webcc::HttpStatus::kNotFound; + response->status = webcc::http::Status::kNotFound; return; } response->content = BookToJsonString(book); - response->status = webcc::HttpStatus::kOK; + response->status = webcc::http::Status::kOK; } void BookDetailService::Put(const webcc::UrlSubMatches& url_sub_matches, @@ -89,7 +89,7 @@ void BookDetailService::Put(const webcc::UrlSubMatches& url_sub_matches, Sleep(sleep_seconds_); if (url_sub_matches.size() != 1) { - response->status = webcc::HttpStatus::kNotFound; + response->status = webcc::http::Status::kNotFound; return; } @@ -97,14 +97,14 @@ void BookDetailService::Put(const webcc::UrlSubMatches& url_sub_matches, Book book; if (!JsonStringToBook(request_content, &book)) { - response->status = webcc::HttpStatus::kBadRequest; + response->status = webcc::http::Status::kBadRequest; return; } book.id = book_id; g_book_store.UpdateBook(book); - response->status = webcc::HttpStatus::kOK; + response->status = webcc::http::Status::kOK; } void BookDetailService::Delete(const webcc::UrlSubMatches& url_sub_matches, @@ -112,16 +112,16 @@ void BookDetailService::Delete(const webcc::UrlSubMatches& url_sub_matches, Sleep(sleep_seconds_); if (url_sub_matches.size() != 1) { - response->status = webcc::HttpStatus::kNotFound; + response->status = webcc::http::Status::kNotFound; return; } const std::string& book_id = url_sub_matches[0]; if (!g_book_store.DeleteBook(book_id)) { - response->status = webcc::HttpStatus::kNotFound; + response->status = webcc::http::Status::kNotFound; return; } - response->status = webcc::HttpStatus::kOK; + response->status = webcc::http::Status::kOK; } diff --git a/example/soap_book_client/book_client.cc b/example/soap_book_client/book_client.cc index 716a022..39a32a5 100644 --- a/example/soap_book_client/book_client.cc +++ b/example/soap_book_client/book_client.cc @@ -104,7 +104,7 @@ bool BookClient::Call1(const std::string& operation, bool BookClient::Call(const std::string& operation, std::vector&& parameters, std::string* result_str) { - if (!soap_client_.Request(operation, std::move(parameters), kResult, + if (!soap_client_.Request(operation, std::move(parameters), kResult, 0, result_str)) { PrintError(); return false; diff --git a/example/soap_calc_client/main.cc b/example/soap_calc_client/main.cc index 2603b76..70f0df5 100644 --- a/example/soap_calc_client/main.cc +++ b/example/soap_calc_client/main.cc @@ -55,7 +55,7 @@ class CalcClient { }; std::string result_str; - if (!soap_client_.Request(operation, std::move(parameters), kResultName, + if (!soap_client_.Request(operation, std::move(parameters), kResultName, 0, &result_str)) { PrintError(); return false; diff --git a/example/soap_calc_client_parasoft/main.cc b/example/soap_calc_client_parasoft/main.cc index 6c38d66..055d3b9 100644 --- a/example/soap_calc_client_parasoft/main.cc +++ b/example/soap_calc_client_parasoft/main.cc @@ -56,7 +56,7 @@ class CalcClient { }; std::string result_str; - if (!soap_client_.Request(operation, std::move(parameters), kResultName, + if (!soap_client_.Request(operation, std::move(parameters), kResultName, 0, &result_str)) { PrintError(); return false; diff --git a/unittest/rest_service_manager_test.cc b/unittest/rest_service_manager_test.cc index 4a5cd13..e08d182 100644 --- a/unittest/rest_service_manager_test.cc +++ b/unittest/rest_service_manager_test.cc @@ -5,7 +5,7 @@ class TestRestService : public webcc::RestService { public: void Handle(const webcc::RestRequest& request, webcc::RestResponse* response) final { - response->status = webcc::HttpStatus::kOK; + response->status = webcc::http::Status::kOK; } }; diff --git a/webcc/CMakeLists.txt b/webcc/CMakeLists.txt index af684e3..ab71377 100644 --- a/webcc/CMakeLists.txt +++ b/webcc/CMakeLists.txt @@ -65,11 +65,13 @@ set(SOURCES if(WEBCC_ENABLE_SSL) set(HEADERS ${HEADERS} http_ssl_client.h + # rest_ssl_async_client.h rest_ssl_client.h ) set(SOURCES ${SOURCES} http_ssl_client.cc + # rest_ssl_async_client.cc rest_ssl_client.cc ) endif() diff --git a/webcc/globals.cc b/webcc/globals.cc index 60e85c1..1db288a 100644 --- a/webcc/globals.cc +++ b/webcc/globals.cc @@ -4,19 +4,6 @@ namespace webcc { // ----------------------------------------------------------------------------- -// NOTE: Field names are case-insensitive. -// See https://stackoverflow.com/a/5259004 for more details. -const std::string kHost = "Host"; -const std::string kContentType = "Content-Type"; -const std::string kContentLength = "Content-Length"; -const std::string kTransferEncoding = "Transfer-Encoding"; -const std::string kUserAgent = "User-Agent"; - -const std::string kAppJsonUtf8 = "application/json; charset=utf-8"; - -const std::string kHttpPort = "80"; -const std::string kHttpSslPort = "443"; - const std::string kHttpHead = "HEAD"; const std::string kHttpGet = "GET"; const std::string kHttpPost = "POST"; diff --git a/webcc/globals.h b/webcc/globals.h index 9aee7b5..00f9e6d 100644 --- a/webcc/globals.h +++ b/webcc/globals.h @@ -46,20 +46,69 @@ const int kMaxReadSeconds = 30; const std::size_t kMaxDumpSize = 2048; // HTTP headers. -extern const std::string kHost; -extern const std::string kContentType; -extern const std::string kContentLength; -extern const std::string kTransferEncoding; -extern const std::string kUserAgent; +namespace http { -extern const std::string kAppJsonUtf8; +// 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. +enum Status { + kOK = 200, + kCreated = 201, + kAccepted = 202, + kNoContent = 204, + kNotModified = 304, + kBadRequest = 400, + kNotFound = 404, + kInternalServerError = 500, + kNotImplemented = 501, + kServiceUnavailable = 503, +}; + +namespace headers { + +// NOTE: Field names are case-insensitive. +// See https://stackoverflow.com/a/5259004 for more details. +const char* const kHost = "Host"; +const char* const kContentType = "Content-Type"; +const char* const kContentLength = "Content-Length"; +const char* const kConnection = "Connection"; +const char* const kTransferEncoding = "Transfer-Encoding"; +const char* const kAccept = "Accept"; +const char* const kAcceptEncoding = "Accept-Encoding"; +const char* const kUserAgent = "User-Agent"; + +} // namespace headers + +namespace media_types { + +// NOTE: +// According to www.w3.org when placing SOAP messages in HTTP bodies, the HTTP +// Content-type header must be chosen as "application/soap+xml" [RFC 3902]. +// But in practice, many web servers cannot understand it. +// See: https://www.w3.org/TR/2007/REC-soap12-part0-20070427/#L26854 + +const char* const kApplicationJson = "application/json"; +const char* const kApplicationSoapXml = "application/soap+xml"; +const char* const kTextXml = "text/xml"; + +} // namespace media_types + +namespace charsets { + +const char* const kUtf8 = "utf-8"; + +} // namespace charsets + +} // namespace http // Default ports. -extern const std::string kHttpPort; -extern const std::string kHttpSslPort; +const char* const kHttpPort = "80"; +const char* const kHttpSslPort = "443"; // HTTP methods (verbs) in string ("HEAD", "GET", etc.). // NOTE: Don't use enum to avoid converting back and forth. +// TODO: Add enum. extern const std::string kHttpHead; extern const std::string kHttpGet; extern const std::string kHttpPost; @@ -67,25 +116,6 @@ 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, - kInternalServerError = 500, - kNotImplemented = 501, - kServiceUnavailable = 503, - }; -}; - // Client side error codes. enum Error { kNoError = 0, // i.e., OK diff --git a/webcc/http_async_client.cc b/webcc/http_async_client.cc index c5ff836..6c5ed9d 100644 --- a/webcc/http_async_client.cc +++ b/webcc/http_async_client.cc @@ -9,20 +9,27 @@ namespace webcc { -HttpAsyncClient::HttpAsyncClient(boost::asio::io_context& io_context) +HttpAsyncClient::HttpAsyncClient(boost::asio::io_context& io_context, + std::size_t buffer_size) : socket_(io_context), resolver_(io_context), - buffer_(kBufferSize), + buffer_(buffer_size == 0 ? kBufferSize : buffer_size), deadline_(io_context), timeout_seconds_(kMaxReadSeconds), stopped_(false), timed_out_(false) { } +void HttpAsyncClient::SetTimeout(int seconds) { + if (seconds > 0) { + timeout_seconds_ = seconds; + } +} + void HttpAsyncClient::Request(std::shared_ptr request, - HttpResponseHandler response_handler) { + HttpResponseCallback response_callback) { assert(request); - assert(response_handler); + assert(response_callback); response_.reset(new HttpResponse()); response_parser_.reset(new HttpResponseParser(response_.get())); @@ -33,7 +40,7 @@ void HttpAsyncClient::Request(std::shared_ptr request, LOG_VERB("HTTP request:\n%s", request->Dump(4, "> ").c_str()); request_ = request; - response_handler_ = response_handler; + response_callback_ = response_callback; resolver_.async_resolve(tcp::v4(), request->host(), request->port(kHttpPort), std::bind(&HttpAsyncClient::OnResolve, @@ -43,31 +50,20 @@ void HttpAsyncClient::Request(std::shared_ptr request, } void HttpAsyncClient::Stop() { - if (stopped_) { - return; - } - - stopped_ = true; - - LOG_INFO("Close socket..."); + LOG_INFO("The user asks to cancel the request."); - boost::system::error_code ec; - socket_.close(ec); - if (ec) { - LOG_ERRO("Socket close error (%s).", ec.message().c_str()); - } - - LOG_INFO("Cancel deadline timer..."); - deadline_.cancel(); + DoStop(); } void HttpAsyncClient::OnResolve(boost::system::error_code ec, tcp::resolver::results_type endpoints) { if (ec) { - LOG_ERRO("Can't resolve host (%s): %s, %s", ec.message().c_str(), + LOG_ERRO("Host resolve error (%s): %s, %s.", ec.message().c_str(), request_->host().c_str(), request_->port().c_str()); - response_handler_(response_, kHostResolveError, timed_out_); + response_callback_(response_, kHostResolveError, timed_out_); } else { + LOG_VERB("Connect to server..."); + // ConnectHandler: void(boost::system::error_code, tcp::endpoint) boost::asio::async_connect(socket_, endpoints, std::bind(&HttpAsyncClient::OnConnect, @@ -81,19 +77,19 @@ void HttpAsyncClient::OnConnect(boost::system::error_code ec, tcp::endpoint endpoint) { if (ec) { LOG_ERRO("Socket connect error (%s).", ec.message().c_str()); - Stop(); - response_handler_(response_, kEndpointConnectError, timed_out_); + DoStop(); + response_callback_(response_, kEndpointConnectError, timed_out_); return; } LOG_VERB("Socket connected."); - // The deadline actor may have had a chance to run and close our socket, even - // though the connect operation notionally succeeded. + // Even though the connect operation notionally succeeded, the user could + // have stopped the operation by calling Stop(). And if we started the + // deadline timer, it could also be stopped due to timeout. if (stopped_) { - // |timed_out_| should be true in this case. - LOG_ERRO("Socket connect timed out."); - response_handler_(response_, kEndpointConnectError, timed_out_); + // TODO: Use some other error. + response_callback_(response_, kEndpointConnectError, timed_out_); return; } @@ -102,9 +98,10 @@ void HttpAsyncClient::OnConnect(boost::system::error_code ec, } void HttpAsyncClient::DoWrite() { - if (stopped_) { - return; - } + // NOTE: + // It doesn't make much sense to set a timeout for socket write. + // I find that it's almost impossible to simulate a situation in the server + // side to test this timeout. boost::asio::async_write(socket_, request_->ToBuffers(), @@ -115,13 +112,19 @@ void HttpAsyncClient::DoWrite() { void HttpAsyncClient::OnWrite(boost::system::error_code ec) { if (stopped_) { + // TODO: Use some other error. + response_callback_(response_, kSocketWriteError, timed_out_); return; } if (ec) { - Stop(); - response_handler_(response_, kSocketWriteError, timed_out_); + LOG_ERRO("Socket write error (%s).", ec.message().c_str()); + DoStop(); + response_callback_(response_, kSocketWriteError, timed_out_); } else { + LOG_INFO("Request sent."); + LOG_VERB("Read response (timeout: %ds)...", timeout_seconds_); + deadline_.expires_from_now(boost::posix_time::seconds(timeout_seconds_)); DoWaitDeadline(); @@ -142,36 +145,30 @@ void HttpAsyncClient::OnRead(boost::system::error_code ec, LOG_VERB("Socket async read handler."); if (ec || length == 0) { - Stop(); + DoStop(); LOG_ERRO("Socket read error (%s).", ec.message().c_str()); - response_handler_(response_, kSocketReadError, timed_out_); + response_callback_(response_, kSocketReadError, timed_out_); return; } - LOG_INFO("Read data, length: %d.", length); - - bool content_length_parsed = response_parser_->content_length_parsed(); + LOG_INFO("Read data, length: %u.", length); // Parse the response piece just read. // If the content has been fully received, |finished()| will be true. if (!response_parser_->Parse(buffer_.data(), length)) { - Stop(); + DoStop(); LOG_ERRO("Failed to parse HTTP response."); - response_handler_(response_, kHttpError, timed_out_); + response_callback_(response_, kHttpError, timed_out_); return; } - if (!content_length_parsed && - response_parser_->content_length_parsed()) { - // Content length just has been parsed. - AdjustBufferSize(response_parser_->content_length(), &buffer_); - } - if (response_parser_->finished()) { + DoStop(); + LOG_INFO("Finished to read and parse HTTP response."); LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str()); - Stop(); - response_handler_(response_, kNoError, timed_out_); + + response_callback_(response_, kNoError, timed_out_); return; } @@ -186,17 +183,50 @@ void HttpAsyncClient::DoWaitDeadline() { } void HttpAsyncClient::OnDeadline(boost::system::error_code ec) { - LOG_VERB("Deadline handler."); + if (stopped_) { + return; + } - if (ec == boost::asio::error::operation_aborted) { - LOG_VERB("Deadline timer canceled."); + LOG_VERB("OnDeadline."); + + // NOTE: Can't check this: + // if (ec == boost::asio::error::operation_aborted) { + // LOG_VERB("Deadline timer canceled."); + // return; + // } + + if (deadline_.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. + LOG_WARN("HTTP client timed out."); + timed_out_ = true; + Stop(); return; } - LOG_WARN("HTTP client timed out."); - timed_out_ = true; + // Put the actor back to sleep. + DoWaitDeadline(); +} - Stop(); +void HttpAsyncClient::DoStop() { + if (stopped_) { + return; + } + + stopped_ = true; + + LOG_INFO("Close socket..."); + + boost::system::error_code ec; + socket_.close(ec); + if (ec) { + LOG_ERRO("Socket close error (%s).", ec.message().c_str()); + } + + LOG_INFO("Cancel deadline timer..."); + deadline_.cancel(); } } // namespace webcc diff --git a/webcc/http_async_client.h b/webcc/http_async_client.h index 3ff63e3..f9a1812 100644 --- a/webcc/http_async_client.h +++ b/webcc/http_async_client.h @@ -16,8 +16,8 @@ namespace webcc { -// Response handler/callback. -typedef std::function HttpResponseHandler; +// Response callback. +typedef std::function HttpResponseCallback; // HTTP client session in asynchronous mode. // A request will return without waiting for the response, the callback handler @@ -25,21 +25,22 @@ typedef std::function HttpResponseHandler; // Don't use the same HttpAsyncClient object in multiple threads. class HttpAsyncClient : public std::enable_shared_from_this { public: - explicit HttpAsyncClient(boost::asio::io_context& io_context); + // The |buffer_size| is the bytes of the buffer for reading response. + // 0 means default value (e.g., 1024) will be used. + explicit HttpAsyncClient(boost::asio::io_context& io_context, + std::size_t buffer_size = 0); WEBCC_DELETE_COPY_ASSIGN(HttpAsyncClient); - void set_timeout_seconds(int timeout_seconds) { - timeout_seconds_ = timeout_seconds; - } + // Set the timeout seconds for reading response. + // The |seconds| is only effective when greater than 0. + void SetTimeout(int seconds); // Asynchronously connect to the server, send the request, read the response, - // and call the |response_handler| when all these finish. - void Request(HttpRequestPtr request, HttpResponseHandler response_handler); + // and call the |response_callback| when all these finish. + void Request(HttpRequestPtr request, HttpResponseCallback response_callback); - // Terminate all the actors to shut down the connection. It may be called by - // the user of the client class, or by the class itself in response to - // graceful termination or an unrecoverable error. + // Called by the user to cancel the request. void Stop(); private: @@ -59,6 +60,9 @@ class HttpAsyncClient : public std::enable_shared_from_this { void DoWaitDeadline(); void OnDeadline(boost::system::error_code ec); + // Terminate all the actors to shut down the connection. + void DoStop(); + tcp::resolver resolver_; tcp::socket socket_; @@ -67,7 +71,7 @@ class HttpAsyncClient : public std::enable_shared_from_this { HttpResponsePtr response_; std::unique_ptr response_parser_; - HttpResponseHandler response_handler_; + HttpResponseCallback response_callback_; // Timer for the timeout control. boost::asio::deadline_timer deadline_; @@ -76,7 +80,11 @@ class HttpAsyncClient : public std::enable_shared_from_this { // Only for receiving response from server. int timeout_seconds_; + // Request stopped due to timeout or socket error. bool stopped_; + + // If the error was caused by timeout or not. + // Will be passed to the response handler/callback. bool timed_out_; }; diff --git a/webcc/http_client.cc b/webcc/http_client.cc index dd99f7e..a7e3af6 100644 --- a/webcc/http_client.cc +++ b/webcc/http_client.cc @@ -14,9 +14,9 @@ using boost::asio::ip::tcp; namespace webcc { -HttpClient::HttpClient() +HttpClient::HttpClient(std::size_t buffer_size) : socket_(io_context_), - buffer_(kBufferSize), + buffer_(buffer_size == 0 ? kBufferSize : buffer_size), deadline_(io_context_), timeout_seconds_(kMaxReadSeconds), stopped_(false), @@ -30,7 +30,7 @@ void HttpClient::SetTimeout(int seconds) { } } -bool HttpClient::Request(const HttpRequest& request) { +bool HttpClient::Request(const HttpRequest& request, std::size_t buffer_size) { io_context_.restart(); response_.reset(new HttpResponse()); @@ -40,6 +40,8 @@ bool HttpClient::Request(const HttpRequest& request) { timed_out_ = false; error_ = kNoError; + BufferResizer buffer_resizer(&buffer_, buffer_size); + if ((error_ = Connect(request)) != kNoError) { return false; } @@ -147,8 +149,6 @@ void HttpClient::DoReadResponse(Error* error) { LOG_INFO("Read data, length: %u.", length); - bool content_length_parsed = response_parser_->content_length_parsed(); - // Parse the response piece just read. if (!response_parser_->Parse(buffer_.data(), length)) { Stop(); @@ -157,17 +157,14 @@ void HttpClient::DoReadResponse(Error* error) { return; } - if (!content_length_parsed && - response_parser_->content_length_parsed()) { - // Content length just has been parsed. - AdjustBufferSize(response_parser_->content_length(), &buffer_); - } - if (response_parser_->finished()) { - // Stop trying to read once all content has been received, - // because some servers will block extra call to read_some(). + // Stop trying to read once all content has been received, because + // some servers will block extra call to read_some(). Stop(); + LOG_INFO("Finished to read and parse HTTP response."); + LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str()); + return; } @@ -195,10 +192,10 @@ void HttpClient::OnDeadline(boost::system::error_code ec) { LOG_VERB("OnDeadline."); // NOTE: Can't check this: - // if (ec == boost::asio::error::operation_aborted) { - // LOG_VERB("Deadline timer canceled."); - // return; - // } + // if (ec == boost::asio::error::operation_aborted) { + // LOG_VERB("Deadline timer canceled."); + // return; + // } if (deadline_.expires_at() <= boost::asio::deadline_timer::traits_type::now()) { diff --git a/webcc/http_client.h b/webcc/http_client.h index 2a15136..5fd90ec 100644 --- a/webcc/http_client.h +++ b/webcc/http_client.h @@ -21,7 +21,10 @@ namespace webcc { // Don't use the same HttpClient object in multiple threads. class HttpClient { public: - HttpClient(); + // The |buffer_size| is the bytes of the buffer for reading response. + // 0 means default value (e.g., 1024) will be used. + explicit HttpClient(std::size_t buffer_size = 0); + ~HttpClient() = default; WEBCC_DELETE_COPY_ASSIGN(HttpClient); @@ -31,7 +34,9 @@ class HttpClient { void SetTimeout(int seconds); // Connect to server, send request, wait until response is received. - bool Request(const HttpRequest& request); + // Set |buffer_size| to non-zero to use a different buffer size for this + // specific request. + bool Request(const HttpRequest& request, std::size_t buffer_size = 0); HttpResponsePtr response() const { return response_; } @@ -54,7 +59,6 @@ class HttpClient { void Stop(); boost::asio::io_context io_context_; - boost::asio::ip::tcp::socket socket_; std::vector buffer_; @@ -62,12 +66,14 @@ class HttpClient { HttpResponsePtr response_; std::unique_ptr response_parser_; + // Timer for the timeout control. boost::asio::deadline_timer deadline_; // Maximum seconds to wait before the client cancels the operation. // Only for reading response from server. int timeout_seconds_; + // Request stopped due to timeout or socket error. bool stopped_; // If the error was caused by timeout or not. diff --git a/webcc/http_message.cc b/webcc/http_message.cc index f9a0cdf..1f1d947 100644 --- a/webcc/http_message.cc +++ b/webcc/http_message.cc @@ -37,6 +37,27 @@ void HttpMessage::SetHeader(std::string&& name, std::string&& value) { headers_.push_back({ std::move(name), std::move(value) }); } +// NOTE: +// According to HTTP 1.1 RFC7231, the following examples are all equivalent, +// but the first is preferred for consistency: +// text/html;charset=utf-8 +// text/html;charset=UTF-8 +// Text/HTML;Charset="utf-8" +// text/html; charset="utf-8" +// See: https://tools.ietf.org/html/rfc7231#section-3.1.1.1 +void HttpMessage::SetContentType(const std::string& media_type, + const std::string& charset) { + SetHeader(http::headers::kContentType, + media_type + ";charset=" + charset); +} + +void HttpMessage::SetContent(std::string&& content, bool set_length) { + content_ = std::move(content); + if (set_length) { + SetContentLength(content_.size()); + } +} + // ATTENTION: The buffers don't hold the memory! std::vector HttpMessage::ToBuffers() const { assert(!start_line_.empty()); diff --git a/webcc/http_message.h b/webcc/http_message.h index 7403d30..12925b0 100644 --- a/webcc/http_message.h +++ b/webcc/http_message.h @@ -39,16 +39,10 @@ class HttpMessage { void SetHeader(std::string&& name, std::string&& value); // E.g., "application/json; charset=utf-8" - void SetContentType(const std::string& content_type) { - SetHeader(kContentType, content_type); - } + void SetContentType(const std::string& media_type, + const std::string& charset); - void SetContent(std::string&& content, bool set_length) { - content_ = std::move(content); - if (set_length) { - SetContentLength(content_.size()); - } - } + void SetContent(std::string&& content, bool set_length); // Make the message (e.g., update start line). // Must be called before ToBuffers()! @@ -70,7 +64,7 @@ class HttpMessage { protected: void SetContentLength(std::size_t content_length) { content_length_ = content_length; - SetHeader(kContentLength, std::to_string(content_length)); + SetHeader(http::headers::kContentLength, std::to_string(content_length)); } // Start line with trailing CRLF. diff --git a/webcc/http_parser.cc b/webcc/http_parser.cc index d6ec7d1..a3abb0a 100644 --- a/webcc/http_parser.cc +++ b/webcc/http_parser.cc @@ -132,7 +132,7 @@ bool HttpParser::ParseHeaderLine(const std::string& line) { do { if (!chunked_ && !content_length_parsed_) { - if (boost::iequals(name, kContentLength)) { + if (boost::iequals(name, http::headers::kContentLength)) { content_length_parsed_ = true; if (!StringToSizeT(value, 10, &content_length_)) { @@ -156,7 +156,7 @@ bool HttpParser::ParseHeaderLine(const std::string& line) { // TODO: Replace `!chunked_` with . if (!chunked_ && !content_length_parsed_) { - if (boost::iequals(name, kTransferEncoding)) { + if (boost::iequals(name, http::headers::kTransferEncoding)) { if (value == "chunked") { // The content is chunked. chunked_ = true; diff --git a/webcc/http_parser.h b/webcc/http_parser.h index bdd1524..9cb4791 100644 --- a/webcc/http_parser.h +++ b/webcc/http_parser.h @@ -20,7 +20,6 @@ class HttpParser { bool finished() const { return finished_; } - bool content_length_parsed() const { return content_length_parsed_; } std::size_t content_length() const { return content_length_; } bool Parse(const char* data, std::size_t length); diff --git a/webcc/http_request.cc b/webcc/http_request.cc index 95a279c..6051d68 100644 --- a/webcc/http_request.cc +++ b/webcc/http_request.cc @@ -2,6 +2,13 @@ namespace webcc { +HttpRequest::HttpRequest(const std::string& method, + const std::string& url, + const std::string& host, + const std::string& port) + : method_(method), url_(url), host_(host), port_(port) { +} + void HttpRequest::Make() { start_line_ = method_; start_line_ += " "; @@ -10,13 +17,19 @@ void HttpRequest::Make() { start_line_ += CRLF; if (port_.empty()) { - SetHeader(kHost, host_); + SetHeader(http::headers::kHost, host_); } else { - SetHeader(kHost, host_ + ":" + port_); + SetHeader(http::headers::kHost, host_ + ":" + port_); } + // TODO: Support Keep-Alive connection. + //SetHeader(http::headers::kConnection, "close"); + + // TODO: Support gzip, deflate + SetHeader(http::headers::kAcceptEncoding, "identity"); + // NOTE: C++11 requires a space between literal and string macro. - SetHeader(kUserAgent, "Webcc/" WEBCC_VERSION); + SetHeader(http::headers::kUserAgent, "Webcc/" WEBCC_VERSION); } } // namespace webcc diff --git a/webcc/http_request.h b/webcc/http_request.h index d1378c7..81690fe 100644 --- a/webcc/http_request.h +++ b/webcc/http_request.h @@ -8,16 +8,26 @@ namespace webcc { +class HttpRequestParser; + class HttpRequest : public HttpMessage { public: HttpRequest() = default; + + // The |host| is a descriptive name (e.g., www.google.com) or a numeric IP + // address (127.0.0.1). + // The |port| is a numeric number (e.g., 9000). The default value (80 for HTTP + // or 443 for HTTPS) will be used to connect to server if it's empty. + HttpRequest(const std::string& method, + const std::string& url, + const std::string& host, + const std::string& port = ""); + ~HttpRequest() override = default; const std::string& method() const { return method_; } - void set_method(const std::string& method) { method_ = method; } const std::string& url() const { return url_; } - void set_url(const std::string& url) { url_ = url; } const std::string& host() const { return host_; } const std::string& port() const { return port_; } @@ -26,20 +36,15 @@ class HttpRequest : public HttpMessage { return port_.empty() ? default_port : port_; } - // Set host name and port number. - // The |host| is a descriptive name (e.g., www.google.com) or a numeric IP - // address (127.0.0.1). - // The |port| is a numeric number (e.g., 9000). The default value (80 for HTTP - // or 443 for HTTPS) will be used to connect to server if it's empty. - void set_host(const std::string& host, const std::string& port = "") { - host_ = host; - port_ = port; - } - // Compose start line, set Host header, etc. void Make() override; private: + friend class HttpRequestParser; + + void set_method(const std::string& method) { method_ = method; } + void set_url(const std::string& url) { url_ = url; } + // HTTP method. std::string method_; diff --git a/webcc/http_response.cc b/webcc/http_response.cc index 71aecf3..6441afb 100644 --- a/webcc/http_response.cc +++ b/webcc/http_response.cc @@ -20,34 +20,34 @@ const std::string SERVICE_UNAVAILABLE = "HTTP/1.1 503 Service Unavailable\r\n"; const std::string& ToString(int status) { switch (status) { - case HttpStatus::kOK: + case http::Status::kOK: return OK; - case HttpStatus::kCreated: + case http::Status::kCreated: return CREATED; - case HttpStatus::kAccepted: + case http::Status::kAccepted: return ACCEPTED; - case HttpStatus::kNoContent: + case http::Status::kNoContent: return NO_CONTENT; - case HttpStatus::kNotModified: + case http::Status::kNotModified: return NOT_MODIFIED; - case HttpStatus::kBadRequest: + case http::Status::kBadRequest: return BAD_REQUEST; - case HttpStatus::kNotFound: + case http::Status::kNotFound: return NOT_FOUND; - case HttpStatus::kInternalServerError: + case http::Status::kInternalServerError: return INTERNAL_SERVER_ERROR; - case HttpStatus::kNotImplemented: + case http::Status::kNotImplemented: return NOT_IMPLEMENTED; - case HttpStatus::kServiceUnavailable: + case http::Status::kServiceUnavailable: return SERVICE_UNAVAILABLE; default: @@ -66,8 +66,8 @@ void HttpResponse::Make() { SetHeader("Date", GetHttpDateTimestamp()); } -HttpResponse HttpResponse::Fault(HttpStatus::Enum status) { - assert(status != HttpStatus::kOK); +HttpResponse HttpResponse::Fault(http::Status status) { + assert(status != http::Status::kOK); HttpResponse response; response.set_status(status); diff --git a/webcc/http_response.h b/webcc/http_response.h index fdc91b6..810faf4 100644 --- a/webcc/http_response.h +++ b/webcc/http_response.h @@ -10,7 +10,7 @@ namespace webcc { class HttpResponse : public HttpMessage { public: - HttpResponse() : status_(HttpStatus::kOK) {} + HttpResponse() : status_(http::Status::kOK) {} ~HttpResponse() override = default; @@ -23,7 +23,7 @@ class HttpResponse : public HttpMessage { // Get a fault response when HTTP status is not OK. // TODO: Avoid copy. - static HttpResponse Fault(HttpStatus::Enum status); + static HttpResponse Fault(http::Status status); private: int status_; diff --git a/webcc/http_session.cc b/webcc/http_session.cc index d5c3022..b1bc478 100644 --- a/webcc/http_session.cc +++ b/webcc/http_session.cc @@ -33,12 +33,13 @@ void HttpSession::Close() { } void HttpSession::SetResponseContent(std::string&& content, - const std::string& type) { + const std::string& media_type, + const std::string& charset) { response_.SetContent(std::move(content), true); - response_.SetContentType(type); + response_.SetContentType(media_type, charset); } -void HttpSession::SendResponse(HttpStatus::Enum status) { +void HttpSession::SendResponse(http::Status status) { response_.set_status(status); response_.Make(); DoWrite(); @@ -63,7 +64,7 @@ void HttpSession::OnRead(boost::system::error_code ec, std::size_t length) { if (!request_parser_.Parse(buffer_.data(), length)) { // Bad request. LOG_ERRO("Failed to parse HTTP request."); - response_ = HttpResponse::Fault(HttpStatus::kBadRequest); + response_ = HttpResponse::Fault(http::Status::kBadRequest); DoWrite(); return; } diff --git a/webcc/http_session.h b/webcc/http_session.h index 245abb9..9675969 100644 --- a/webcc/http_session.h +++ b/webcc/http_session.h @@ -33,10 +33,12 @@ class HttpSession : public std::enable_shared_from_this { // Close the socket. void Close(); - void SetResponseContent(std::string&& content, const std::string& type); + void SetResponseContent(std::string&& content, + const std::string& media_type, + const std::string& charset); // Send response to client with the given status. - void SendResponse(HttpStatus::Enum status); + void SendResponse(http::Status status); private: void DoRead(); diff --git a/webcc/http_ssl_client.cc b/webcc/http_ssl_client.cc index 5f3c728..7aa65e9 100644 --- a/webcc/http_ssl_client.cc +++ b/webcc/http_ssl_client.cc @@ -15,10 +15,10 @@ namespace ssl = boost::asio::ssl; namespace webcc { -HttpSslClient::HttpSslClient(bool ssl_verify) +HttpSslClient::HttpSslClient(std::size_t buffer_size, bool ssl_verify) : ssl_context_(ssl::context::sslv23), ssl_socket_(io_context_, ssl_context_), - buffer_(kBufferSize), + buffer_(buffer_size == 0 ? kBufferSize : buffer_size), deadline_(io_context_), ssl_verify_(ssl_verify), timeout_seconds_(kMaxReadSeconds), @@ -35,7 +35,8 @@ void HttpSslClient::SetTimeout(int seconds) { } } -bool HttpSslClient::Request(const HttpRequest& request) { +bool HttpSslClient::Request(const HttpRequest& request, + std::size_t buffer_size) { io_context_.restart(); response_.reset(new HttpResponse()); @@ -45,6 +46,8 @@ bool HttpSslClient::Request(const HttpRequest& request) { timed_out_ = false; error_ = kNoError; + BufferResizer buffer_resizer(&buffer_, buffer_size); + if ((error_ = Connect(request)) != kNoError) { return false; } @@ -178,8 +181,6 @@ void HttpSslClient::DoReadResponse(Error* error) { LOG_INFO("Read data, length: %u.", length); - bool content_length_parsed = response_parser_->content_length_parsed(); - // Parse the response piece just read. if (!response_parser_->Parse(buffer_.data(), length)) { Stop(); @@ -188,12 +189,6 @@ void HttpSslClient::DoReadResponse(Error* error) { return; } - if (!content_length_parsed && - response_parser_->content_length_parsed()) { - // Content length just has been parsed. - AdjustBufferSize(response_parser_->content_length(), &buffer_); - } - if (response_parser_->finished()) { // Stop trying to read once all content has been received, // because some servers will block extra call to read_some(). diff --git a/webcc/http_ssl_client.h b/webcc/http_ssl_client.h index 6d1d269..003f73d 100644 --- a/webcc/http_ssl_client.h +++ b/webcc/http_ssl_client.h @@ -23,11 +23,12 @@ namespace webcc { // Don't use the same HttpClient object in multiple threads. class HttpSslClient { public: - // NOTE: - // SSL verification (ssl_verify=true) needs CA certificates to be found + // The |buffer_size| is the bytes of the buffer for reading response. + // 0 means default value (e.g., 1024) will be used. + // SSL verification (|ssl_verify|) needs CA certificates to be found // in the default verify paths of OpenSSL. On Windows, it means you need to // set environment variable SSL_CERT_FILE properly. - HttpSslClient(bool ssl_verify = true); + explicit HttpSslClient(std::size_t buffer_size = 0, bool ssl_verify = true); ~HttpSslClient() = default; @@ -38,7 +39,9 @@ class HttpSslClient { void SetTimeout(int seconds); // Connect to server, send request, wait until response is received. - bool Request(const HttpRequest& request); + // Set |buffer_size| to non-zero to use a different buffer size for this + // specific request. + bool Request(const HttpRequest& request, std::size_t buffer_size = 0); HttpResponsePtr response() const { return response_; } diff --git a/webcc/rest_async_client.cc b/webcc/rest_async_client.cc index 048deb2..a792c68 100644 --- a/webcc/rest_async_client.cc +++ b/webcc/rest_async_client.cc @@ -4,34 +4,37 @@ namespace webcc { RestAsyncClient::RestAsyncClient(boost::asio::io_context& io_context, const std::string& host, - const std::string& port) - : io_context_(io_context), host_(host), port_(port), timeout_seconds_(0) { + const std::string& port, + std::size_t buffer_size) + : io_context_(io_context), + host_(host), port_(port), + timeout_seconds_(0), + buffer_size_(buffer_size) { } void RestAsyncClient::Request(const std::string& method, const std::string& url, std::string&& content, - HttpResponseHandler response_handler) { - HttpRequestPtr request(new webcc::HttpRequest()); - - request->set_method(method); - request->set_url(url); - request->set_host(host_, port_); + HttpResponseCallback callback) { + HttpRequestPtr http_request(new HttpRequest(method, url, host_, port_)); if (!content.empty()) { - request->SetContent(std::move(content), true); - request->SetContentType(kAppJsonUtf8); + http_request->SetContent(std::move(content), true); + http_request->SetContentType(http::media_types::kApplicationJson, + http::charsets::kUtf8); } - request->Make(); + http_request->Make(); - HttpAsyncClientPtr http_client(new HttpAsyncClient(io_context_)); + HttpAsyncClientPtr http_async_client{ + new HttpAsyncClient(io_context_, buffer_size_) + }; if (timeout_seconds_ > 0) { - http_client->set_timeout_seconds(timeout_seconds_); + http_async_client->SetTimeout(timeout_seconds_); } - http_client->Request(request, response_handler); + http_async_client->Request(http_request, callback); } } // namespace webcc diff --git a/webcc/rest_async_client.h b/webcc/rest_async_client.h index b3525e8..7eb1701 100644 --- a/webcc/rest_async_client.h +++ b/webcc/rest_async_client.h @@ -10,41 +10,40 @@ namespace webcc { class RestAsyncClient { public: - RestAsyncClient(boost::asio::io_context& io_context, // NOLINT - const std::string& host, const std::string& port); + RestAsyncClient(boost::asio::io_context& io_context, + const std::string& host, const std::string& port, + std::size_t buffer_size = 0); - void set_timeout_seconds(int timeout_seconds) { - timeout_seconds_ = timeout_seconds; + void SetTimeout(int seconds) { + timeout_seconds_ = seconds; } - void Get(const std::string& url, - HttpResponseHandler response_handler) { - Request(kHttpGet, url, "", response_handler); + void Get(const std::string& url, HttpResponseCallback callback) { + Request(kHttpGet, url, "", callback); } void Post(const std::string& url, std::string&& content, - HttpResponseHandler response_handler) { - Request(kHttpPost, url, std::move(content), response_handler); + HttpResponseCallback callback) { + Request(kHttpPost, url, std::move(content), callback); } void Put(const std::string& url, std::string&& content, - HttpResponseHandler response_handler) { - Request(kHttpPut, url, std::move(content), response_handler); + HttpResponseCallback callback) { + Request(kHttpPut, url, std::move(content), callback); } void Patch(const std::string& url, std::string&& content, - HttpResponseHandler response_handler) { - Request(kHttpPatch, url, std::move(content), response_handler); + HttpResponseCallback callback) { + Request(kHttpPatch, url, std::move(content), callback); } - void Delete(const std::string& url, - HttpResponseHandler response_handler) { - Request(kHttpDelete, url, "", response_handler); + void Delete(const std::string& url, HttpResponseCallback callback) { + Request(kHttpDelete, url, "", callback); } private: void Request(const std::string& method, const std::string& url, - std::string&& content, HttpResponseHandler response_handler); + std::string&& content, HttpResponseCallback callback); boost::asio::io_context& io_context_; @@ -53,6 +52,8 @@ class RestAsyncClient { // Timeout in seconds; only effective when > 0. int timeout_seconds_; + + std::size_t buffer_size_; }; } // namespace webcc diff --git a/webcc/rest_client.cc b/webcc/rest_client.cc index a93c117..8a78dc2 100644 --- a/webcc/rest_client.cc +++ b/webcc/rest_client.cc @@ -1,34 +1,30 @@ #include "webcc/rest_client.h" +#include "webcc/utility.h" namespace webcc { -RestClient::RestClient(const std::string& host, const std::string& port) - : host_(host), port_(port) { - if (port_.empty()) { - std::size_t i = host_.find_last_of(':'); - if (i != std::string::npos) { - port_ = host_.substr(i + 1); - host_ = host_.substr(0, i); - } - } +RestClient::RestClient(const std::string& host, const std::string& port, + std::size_t buffer_size) + : host_(host), port_(port), http_client_(buffer_size) { + AdjustHostPort(host_, port_); } bool RestClient::Request(const std::string& method, const std::string& url, - std::string&& content) { - HttpRequest http_request; + std::string&& content, std::size_t buffer_size) { + HttpRequest http_request(method, url, host_, port_); - http_request.set_method(method); - http_request.set_url(url); - http_request.set_host(host_, port_); + http_request.SetHeader(http::headers::kAccept, + http::media_types::kApplicationJson); if (!content.empty()) { http_request.SetContent(std::move(content), true); - http_request.SetContentType(kAppJsonUtf8); + http_request.SetContentType(http::media_types::kApplicationJson, + http::charsets::kUtf8); } http_request.Make(); - if (!http_client_.Request(http_request)) { + if (!http_client_.Request(http_request, buffer_size)) { return false; } diff --git a/webcc/rest_client.h b/webcc/rest_client.h index 7b5afc5..3545168 100644 --- a/webcc/rest_client.h +++ b/webcc/rest_client.h @@ -17,7 +17,8 @@ class RestClient { // If |port| is empty, |host| will be checked to see if it contains port or // not (separated by ':'). explicit RestClient(const std::string& host, - const std::string& port = ""); + const std::string& port = "", + std::size_t buffer_size = 0); ~RestClient() = default; @@ -33,24 +34,27 @@ class RestClient { // timed_out() for more information if it's failed. Check response_status() // instead for the HTTP status code. - inline bool Get(const std::string& url) { - return Request(kHttpGet, url, ""); + inline bool Get(const std::string& url, std::size_t buffer_size = 0) { + return Request(kHttpGet, url, "", buffer_size); } - inline bool Post(const std::string& url, std::string&& content) { - return Request(kHttpPost, url, std::move(content)); + inline bool Post(const std::string& url, std::string&& content, + std::size_t buffer_size = 0) { + return Request(kHttpPost, url, std::move(content), buffer_size); } - inline bool Put(const std::string& url, std::string&& content) { - return Request(kHttpPut, url, std::move(content)); + inline bool Put(const std::string& url, std::string&& content, + std::size_t buffer_size = 0) { + return Request(kHttpPut, url, std::move(content), buffer_size); } - inline bool Patch(const std::string& url, std::string&& content) { - return Request(kHttpPatch, url, std::move(content)); + inline bool Patch(const std::string& url, std::string&& content, + std::size_t buffer_size = 0) { + return Request(kHttpPatch, url, std::move(content), buffer_size); } - inline bool Delete(const std::string& url) { - return Request(kHttpDelete, url, ""); + inline bool Delete(const std::string& url, std::size_t buffer_size = 0) { + return Request(kHttpDelete, url, "", buffer_size); } HttpResponsePtr response() const { @@ -76,8 +80,8 @@ class RestClient { } private: - bool Request(const std::string& method, const std::string& url, - std::string&& content); + bool Request(const std::string& method, const std::string& url, + std::string&& content, std::size_t buffer_size); std::string host_; std::string port_; diff --git a/webcc/rest_request_handler.cc b/webcc/rest_request_handler.cc index 613d761..8171030 100644 --- a/webcc/rest_request_handler.cc +++ b/webcc/rest_request_handler.cc @@ -19,7 +19,7 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) { Url url(http_request.url(), /*decode*/true); if (!url.IsPathValid()) { - session->SendResponse(HttpStatus::kBadRequest); + session->SendResponse(http::Status::kBadRequest); return; } @@ -33,7 +33,7 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) { if (!service) { LOG_WARN("No service matches the URL path: %s", url.path().c_str()); - session->SendResponse(HttpStatus::kNotFound); + session->SendResponse(http::Status::kNotFound); return; } @@ -42,7 +42,8 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) { if (!rest_response.content.empty()) { session->SetResponseContent(std::move(rest_response.content), - kAppJsonUtf8); + http::media_types::kApplicationJson, + http::charsets::kUtf8); } // Send response back to client. diff --git a/webcc/rest_service.h b/webcc/rest_service.h index f62942d..7a276e3 100644 --- a/webcc/rest_service.h +++ b/webcc/rest_service.h @@ -38,7 +38,7 @@ struct RestRequest { }; struct RestResponse { - HttpStatus::Enum status; + http::Status status; std::string content; }; diff --git a/webcc/rest_ssl_client.cc b/webcc/rest_ssl_client.cc index 91f4519..3343ab2 100644 --- a/webcc/rest_ssl_client.cc +++ b/webcc/rest_ssl_client.cc @@ -1,35 +1,28 @@ #include "webcc/rest_ssl_client.h" +#include "webcc/utility.h" namespace webcc { RestSslClient::RestSslClient(const std::string& host, const std::string& port, - bool ssl_verify) - : host_(host), port_(port), http_client_(ssl_verify) { - if (port_.empty()) { - std::size_t i = host_.find_last_of(':'); - if (i != std::string::npos) { - port_ = host_.substr(i + 1); - host_ = host_.substr(0, i); - } - } + std::size_t buffer_size, bool ssl_verify) + : host_(host), port_(port), + http_client_(buffer_size, ssl_verify) { + AdjustHostPort(host_, port_); } bool RestSslClient::Request(const std::string& method, const std::string& url, - std::string&& content) { - HttpRequest http_request; - - http_request.set_method(method); - http_request.set_url(url); - http_request.set_host(host_, port_); + std::string&& content, std::size_t buffer_size) { + HttpRequest http_request(method, url, host_, port_); if (!content.empty()) { http_request.SetContent(std::move(content), true); - http_request.SetContentType(kAppJsonUtf8); + http_request.SetContentType(http::media_types::kApplicationJson, + http::charsets::kUtf8); } http_request.Make(); - if (!http_client_.Request(http_request)) { + if (!http_client_.Request(http_request, buffer_size)) { return false; } diff --git a/webcc/rest_ssl_client.h b/webcc/rest_ssl_client.h index 559592d..7201555 100644 --- a/webcc/rest_ssl_client.h +++ b/webcc/rest_ssl_client.h @@ -18,6 +18,7 @@ class RestSslClient { // not (separated by ':'). explicit RestSslClient(const std::string& host, const std::string& port = "", + std::size_t buffer_size = 0, bool ssl_verify = true); ~RestSslClient() = default; @@ -34,24 +35,27 @@ class RestSslClient { // timed_out() for more information if it's failed. Check response_status() // instead for the HTTP status code. - inline bool Get(const std::string& url) { - return Request(kHttpGet, url, ""); + inline bool Get(const std::string& url, std::size_t buffer_size = 0) { + return Request(kHttpGet, url, "", buffer_size); } - inline bool Post(const std::string& url, std::string&& content) { - return Request(kHttpPost, url, std::move(content)); + inline bool Post(const std::string& url, std::string&& content, + std::size_t buffer_size = 0) { + return Request(kHttpPost, url, std::move(content), buffer_size); } - inline bool Put(const std::string& url, std::string&& content) { - return Request(kHttpPut, url, std::move(content)); + inline bool Put(const std::string& url, std::string&& content, + std::size_t buffer_size = 0) { + return Request(kHttpPut, url, std::move(content), buffer_size); } - inline bool Patch(const std::string& url, std::string&& content) { - return Request(kHttpPatch, url, std::move(content)); + inline bool Patch(const std::string& url, std::string&& content, + std::size_t buffer_size = 0) { + return Request(kHttpPatch, url, std::move(content), buffer_size); } - inline bool Delete(const std::string& url) { - return Request(kHttpDelete, url, ""); + inline bool Delete(const std::string& url, std::size_t buffer_size = 0) { + return Request(kHttpDelete, url, "", buffer_size); } HttpResponsePtr response() const { @@ -77,9 +81,8 @@ class RestSslClient { } private: - bool Request(const std::string& method, - const std::string& url, - std::string&& content); + bool Request(const std::string& method, const std::string& url, + std::string&& content, std::size_t buffer_size); std::string host_; std::string port_; diff --git a/webcc/soap_async_client.cc b/webcc/soap_async_client.cc index 1753716..d61f29d 100644 --- a/webcc/soap_async_client.cc +++ b/webcc/soap_async_client.cc @@ -3,33 +3,29 @@ #include #include // for move() -#include "webcc/http_async_client.h" #include "webcc/soap_globals.h" #include "webcc/soap_request.h" #include "webcc/soap_response.h" +#include "webcc/utility.h" namespace webcc { SoapAsyncClient::SoapAsyncClient(boost::asio::io_context& io_context, const std::string& host, const std::string& port, - SoapVersion soap_version) + SoapVersion soap_version, + std::size_t buffer_size) : io_context_(io_context), host_(host), port_(port), soap_version_(soap_version), + buffer_size_(buffer_size), format_raw_(true), timeout_seconds_(0) { - if (port_.empty()) { - std::size_t i = host_.find_last_of(':'); - if (i != std::string::npos) { - port_ = host_.substr(i + 1); - host_ = host_.substr(0, i); - } - } + AdjustHostPort(host_, port_); } void SoapAsyncClient::Request(const std::string& operation, std::vector&& parameters, - SoapResponseHandler soap_response_handler) { + SoapResponseCallback soap_response_callback) { assert(service_ns_.IsValid()); assert(!url_.empty() && !host_.empty()); assert(!result_name_.empty()); @@ -54,50 +50,53 @@ void SoapAsyncClient::Request(const std::string& operation, std::string http_content; soap_request.ToXml(format_raw_, indent_str_, &http_content); - HttpRequestPtr http_request; + HttpRequestPtr http_request(new HttpRequest(kHttpPost, url_, host_, port_)); - http_request->set_method(kHttpPost); - http_request->set_url(url_); http_request->SetContent(std::move(http_content), true); if (soap_version_ == kSoapV11) { - http_request->SetContentType(kTextXmlUtf8); + http_request->SetContentType(http::media_types::kTextXml, + http::charsets::kUtf8); } else { - http_request->SetContentType(kAppSoapXmlUtf8); + http_request->SetContentType(http::media_types::kApplicationSoapXml, + http::charsets::kUtf8); } - http_request->set_host(host_, port_); http_request->SetHeader(kSoapAction, operation); http_request->Make(); - HttpAsyncClientPtr http_client(new HttpAsyncClient(io_context_)); + HttpAsyncClientPtr http_async_client{ + new HttpAsyncClient(io_context_, buffer_size_) + }; if (timeout_seconds_ > 0) { - http_client->set_timeout_seconds(timeout_seconds_); + http_async_client->SetTimeout(timeout_seconds_); } - auto http_response_handler = std::bind(&SoapAsyncClient::ResponseHandler, - this, soap_response_handler, - std::placeholders::_1, - std::placeholders::_2, - std::placeholders::_3); + auto http_response_callback = std::bind(&SoapAsyncClient::OnHttpResponse, + this, soap_response_callback, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3); - http_client->Request(http_request, http_response_handler); + http_async_client->Request(http_request, http_response_callback); } -void SoapAsyncClient::ResponseHandler(SoapResponseHandler soap_response_handler, - HttpResponsePtr http_response, - Error error, bool timed_out) { +void SoapAsyncClient::OnHttpResponse(SoapResponseCallback soap_response_callback, + HttpResponsePtr http_response, + Error error, bool timed_out) { if (error != kNoError) { - soap_response_handler("", error, timed_out); + soap_response_callback("", error, timed_out); } else { SoapResponse soap_response; + // TODO //soap_response.set_result_name(result_name_); if (!soap_response.FromXml(http_response->content())) { - soap_response_handler("", kXmlError, false); + soap_response_callback("", kXmlError, false); } else { - //soap_response_handler(soap_response.result_moved(), kNoError, false); + // TODO + //soap_response_callback(soap_response.result_moved(), kNoError, false); } } } diff --git a/webcc/soap_async_client.h b/webcc/soap_async_client.h index 2419da1..c514464 100644 --- a/webcc/soap_async_client.h +++ b/webcc/soap_async_client.h @@ -11,8 +11,8 @@ namespace webcc { -// Response handler/callback. -typedef std::function SoapResponseHandler; +// Response callback. +typedef std::function SoapResponseCallback; class SoapAsyncClient { public: @@ -20,7 +20,8 @@ class SoapAsyncClient { // not (separated by ':'). SoapAsyncClient(boost::asio::io_context& io_context, // NOLINT const std::string& host, const std::string& port = "", - SoapVersion soap_version = kSoapV12); + SoapVersion soap_version = kSoapV12, + std::size_t buffer_size = 0); ~SoapAsyncClient() = default; @@ -48,12 +49,12 @@ class SoapAsyncClient { void Request(const std::string& operation, std::vector&& parameters, - SoapResponseHandler soap_response_handler); + SoapResponseCallback soap_response_callback); private: - void ResponseHandler(SoapResponseHandler soap_response_handler, - HttpResponsePtr http_response, - Error error, bool timed_out); + void OnHttpResponse(SoapResponseCallback soap_response_callback, + HttpResponsePtr http_response, + Error error, bool timed_out); boost::asio::io_context& io_context_; @@ -62,6 +63,8 @@ class SoapAsyncClient { SoapVersion soap_version_; + std::size_t buffer_size_; + // Request URL. std::string url_; diff --git a/webcc/soap_client.cc b/webcc/soap_client.cc index 90d30e8..4128e7c 100644 --- a/webcc/soap_client.cc +++ b/webcc/soap_client.cc @@ -6,27 +6,22 @@ #include "boost/algorithm/string.hpp" #include "webcc/soap_request.h" -#include "webcc/soap_response.h" +#include "webcc/utility.h" namespace webcc { SoapClient::SoapClient(const std::string& host, const std::string& port, - SoapVersion soap_version) - : host_(host), port_(port), - soap_version_(soap_version), + SoapVersion soap_version, std::size_t buffer_size) + : host_(host), port_(port), soap_version_(soap_version), + http_client_(buffer_size), format_raw_(true), error_(kNoError) { - if (port_.empty()) { - std::size_t i = host_.find_last_of(':'); - if (i != std::string::npos) { - port_ = host_.substr(i + 1); - host_ = host_.substr(0, i); - } - } + AdjustHostPort(host_, port_); } bool SoapClient::Request(const std::string& operation, std::vector&& parameters, - SoapResponse::Parser parser) { + SoapResponse::Parser parser, + std::size_t buffer_size) { assert(service_ns_.IsValid()); assert(!url_.empty() && !host_.empty()); @@ -53,23 +48,23 @@ bool SoapClient::Request(const std::string& operation, std::string http_content; soap_request.ToXml(format_raw_, indent_str_, &http_content); - HttpRequest http_request; + HttpRequest http_request(kHttpPost, url_, host_, port_); - http_request.set_method(kHttpPost); - http_request.set_url(url_); http_request.SetContent(std::move(http_content), true); if (soap_version_ == kSoapV11) { - http_request.SetContentType(kTextXmlUtf8); + http_request.SetContentType(http::media_types::kTextXml, + http::charsets::kUtf8); } else { - http_request.SetContentType(kAppSoapXmlUtf8); + http_request.SetContentType(http::media_types::kApplicationSoapXml, + http::charsets::kUtf8); } - http_request.set_host(host_, port_); http_request.SetHeader(kSoapAction, operation); + http_request.Make(); - if (!http_client_.Request(http_request)) { + if (!http_client_.Request(http_request, buffer_size)) { error_ = http_client_.error(); return false; } @@ -95,6 +90,7 @@ bool SoapClient::Request(const std::string& operation, bool SoapClient::Request(const std::string& operation, std::vector&& parameters, const std::string& result_name, + std::size_t buffer_size, std::string* result) { auto parser = [result, &result_name](pugi::xml_node xnode) { if (boost::iequals(soap_xml::GetNameNoPrefix(xnode), result_name)) { @@ -103,7 +99,7 @@ bool SoapClient::Request(const std::string& operation, return false; // Stop next call. }; - return Request(operation, std::move(parameters), parser); + return Request(operation, std::move(parameters), parser, buffer_size); } } // namespace webcc diff --git a/webcc/soap_client.h b/webcc/soap_client.h index 61ba05c..8445b8a 100644 --- a/webcc/soap_client.h +++ b/webcc/soap_client.h @@ -16,7 +16,8 @@ class SoapClient { // If |port| is empty, |host| will be checked to see if it contains port or // not (separated by ':'). explicit SoapClient(const std::string& host, const std::string& port = "", - SoapVersion soap_version = kSoapV12); + SoapVersion soap_version = kSoapV12, + std::size_t buffer_size = 0); ~SoapClient() = default; @@ -42,7 +43,8 @@ class SoapClient { bool Request(const std::string& operation, std::vector&& parameters, - SoapResponse::Parser parser); + SoapResponse::Parser parser, + std::size_t buffer_size = 0); // Shortcut for responses with single result node. // The name of the single result node is specified by |result_name|. @@ -50,6 +52,7 @@ class SoapClient { bool Request(const std::string& operation, std::vector&& parameters, const std::string& result_name, + std::size_t buffer_size, // Pass 0 for using default size. std::string* result); // HTTP status code (200, 500, etc.) in the response. @@ -72,6 +75,8 @@ class SoapClient { SoapVersion soap_version_; + HttpClient http_client_; + // Request URL. std::string url_; @@ -85,8 +90,6 @@ class SoapClient { // Applicable when |format_raw_| is false. std::string indent_str_; - HttpClient http_client_; - Error error_; std::shared_ptr fault_; diff --git a/webcc/soap_request_handler.cc b/webcc/soap_request_handler.cc index 3f52e94..e4898a0 100644 --- a/webcc/soap_request_handler.cc +++ b/webcc/soap_request_handler.cc @@ -18,14 +18,14 @@ bool SoapRequestHandler::Bind(SoapServicePtr service, const std::string& url) { void SoapRequestHandler::HandleSession(HttpSessionPtr session) { SoapServicePtr service = GetServiceByUrl(session->request().url()); if (!service) { - session->SendResponse(HttpStatus::kBadRequest); + session->SendResponse(http::Status::kBadRequest); return; } // Parse the SOAP request XML. SoapRequest soap_request; if (!soap_request.FromXml(session->request().content())) { - session->SendResponse(HttpStatus::kBadRequest); + session->SendResponse(http::Status::kBadRequest); return; } @@ -40,7 +40,7 @@ void SoapRequestHandler::HandleSession(HttpSessionPtr session) { } if (!service->Handle(soap_request, &soap_response)) { - session->SendResponse(HttpStatus::kBadRequest); + session->SendResponse(http::Status::kBadRequest); return; } @@ -48,12 +48,16 @@ void SoapRequestHandler::HandleSession(HttpSessionPtr session) { soap_response.ToXml(format_raw_, indent_str_, &content); if (soap_version_ == kSoapV11) { - session->SetResponseContent(std::move(content), kTextXmlUtf8); + session->SetResponseContent(std::move(content), + http::media_types::kTextXml, + http::charsets::kUtf8); } else { - session->SetResponseContent(std::move(content), kAppSoapXmlUtf8); + session->SetResponseContent(std::move(content), + http::media_types::kApplicationSoapXml, + http::charsets::kUtf8); } - session->SendResponse(HttpStatus::kOK); + session->SendResponse(http::Status::kOK); } SoapServicePtr SoapRequestHandler::GetServiceByUrl(const std::string& url) { diff --git a/webcc/soap_service.h b/webcc/soap_service.h index 4a8b64f..adcde22 100644 --- a/webcc/soap_service.h +++ b/webcc/soap_service.h @@ -20,7 +20,7 @@ class SoapService { SoapResponse* soap_response) = 0; protected: - HttpStatus::Enum http_status_ = HttpStatus::kOK; + http::Status http_status_ = http::Status::kOK; }; typedef std::shared_ptr SoapServicePtr; diff --git a/webcc/utility.cc b/webcc/utility.cc index 1c8d676..6a262a3 100644 --- a/webcc/utility.cc +++ b/webcc/utility.cc @@ -12,21 +12,13 @@ using tcp = boost::asio::ip::tcp; namespace webcc { -void AdjustBufferSize(std::size_t content_length, std::vector* buffer) { - const std::size_t kMaxTimes = 10; - - // According to test, a client never read more than 200000 bytes a time. - // So it doesn't make sense to set any larger size, e.g., 1MB. - const std::size_t kMaxBufferSize = 200000; - - LOG_INFO("Adjust buffer size according to content length."); - - std::size_t min_buffer_size = content_length / kMaxTimes; - if (min_buffer_size > buffer->size()) { - buffer->resize(std::min(min_buffer_size, kMaxBufferSize)); - LOG_INFO("Resize read buffer to %u.", buffer->size()); - } else { - LOG_INFO("Keep the current buffer size: %u.", buffer->size()); +void AdjustHostPort(std::string& host, std::string& port) { + if (port.empty()) { + std::size_t i = host.find_last_of(':'); + if (i != std::string::npos) { + port = host.substr(i + 1); + host = host.substr(0, i); + } } } diff --git a/webcc/utility.h b/webcc/utility.h index ec643e4..024f5ec 100644 --- a/webcc/utility.h +++ b/webcc/utility.h @@ -9,9 +9,8 @@ namespace webcc { -// Adjust buffer size according to content length. -// This is to avoid reading too many times. -void AdjustBufferSize(std::size_t content_length, std::vector* buffer); +// If |port| is empty, try to extract it from |host| (separated by ':'). +void AdjustHostPort(std::string& host, std::string& port); void PrintEndpoint(std::ostream& ostream, const boost::asio::ip::tcp::endpoint& endpoint); @@ -27,6 +26,25 @@ std::string EndpointToString(const boost::asio::ip::tcp::endpoint& endpoint); // See: https://tools.ietf.org/html/rfc7231#section-7.1.1.2 std::string GetHttpDateTimestamp(); +// Resize a buffer in ctor and restore its original size in dtor. +struct BufferResizer { + BufferResizer(std::vector* buffer, std::size_t new_size) + : buffer_(buffer), old_size_(buffer->size()) { + if (new_size != 0 && new_size != old_size_) { + buffer_->resize(new_size); + } + } + + ~BufferResizer() { + if (buffer_->size() != old_size_) { + buffer_->resize(old_size_); + } + } + + std::vector* buffer_; + std::size_t old_size_; +}; + } // namespace webcc #endif // WEBCC_UTILITY_H_