diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e01453..79d72e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,8 +120,8 @@ endif() add_subdirectory(webcc) if(WEBCC_ENABLE_EXAMPLES) - add_subdirectory(example/http_hello_client) - add_subdirectory(example/http_hello_async_client) + add_subdirectory(example/http_client) + add_subdirectory(example/http_async_client) # For including jsoncpp as "json/json.h". include_directories(${THIRD_PARTY_DIR}/src/jsoncpp) @@ -143,10 +143,9 @@ if(WEBCC_ENABLE_EXAMPLES) if(WEBCC_ENABLE_SSL) add_subdirectory(example/http_ssl_client) + add_subdirectory(example/http_ssl_async_client) add_subdirectory(example/github_rest_client) endif() - - add_subdirectory(example/http_bin_client) endif() if(WEBCC_ENABLE_UNITTEST) diff --git a/example/HttpBin.md b/example/HttpBin.md new file mode 100644 index 0000000..766bdfe --- /dev/null +++ b/example/HttpBin.md @@ -0,0 +1,36 @@ +HttpBin (http://httpbin.org/) client example. + +You request to different endpoints, and it returns information about what was in the request. + +E.g., request: +```plain +GET /get HTTP/1.1 +Host: httpbin.org:80 +User-Agent: Webcc/0.1.0 + +``` + +Response: +```plain +HTTP/1.1 200 OK +Connection: keep-alive +Server: gunicorn/19.9.0 +Content-Type: application/json +Content-Length: 191 +Access-Control-Allow-Origin: * +Access-Control-Allow-Credentials: true +Via: 1.1 vegur + +{ + "args": {}, + "headers": { + "Connection": "close", + "Host": "httpbin.org", + "User-Agent": "Webcc/0.1.0" + }, + "origin": "198.55.94.81", + "url": "http://httpbin.org/get" +} +``` + +As you can see, the request information is returned in JSON format. 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..d8995f8 --- /dev/null +++ b/example/http_async_client/main.cc @@ -0,0 +1,49 @@ +#include + +#include "boost/asio/io_context.hpp" + +#include "webcc/http_async_client.h" +#include "webcc/logger.h" + +// TODO: The program blocks during read response. +// Only HttpBin.org has this issue. + +static void Test(boost::asio::io_context& io_context) { + auto request = webcc::HttpRequest::Make(webcc::kHttpGet, "/get", + "httpbin.org"); + + webcc::HttpAsyncClientPtr client{ + new webcc::HttpAsyncClient(io_context) + }; + + client->SetTimeout(3); + + // Response callback. + auto callback = [](webcc::HttpResponsePtr response, webcc::Error error, + bool timed_out) { + if (error == webcc::kNoError) { + std::cout << response->content() << std::endl; + } else { + std::cout << DescribeError(error); + if (timed_out) { + std::cout << " (timed out)"; + } + std::cout << std::endl; + } + }; + + client->Request(request, callback); +} + +int main() { + WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); + + boost::asio::io_context io_context; + + //Test(io_context); + Test(io_context); + + io_context.run(); + + return 0; +} diff --git a/example/http_bin_client/CMakeLists.txt b/example/http_bin_client/CMakeLists.txt deleted file mode 100644 index 3fc5d95..0000000 --- a/example/http_bin_client/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_executable(http_bin_client main.cc) - -target_link_libraries(http_bin_client webcc ${Boost_LIBRARIES}) -target_link_libraries(http_bin_client "${CMAKE_THREAD_LIBS_INIT}") diff --git a/example/http_bin_client/main.cc b/example/http_bin_client/main.cc deleted file mode 100644 index 51be995..0000000 --- a/example/http_bin_client/main.cc +++ /dev/null @@ -1,61 +0,0 @@ -// HttpBin (http://httpbin.org/) client example. -// -// You request to different endpoints, and it returns information about what -// was in the request. -// -// E.g., request: -// > GET /get HTTP/1.1 -// > Host: httpbin.org:80 -// > User-Agent: Webcc/0.1.0 -// > -// Response: -// > HTTP/1.1 200 OK -// > Connection: keep-alive -// > Server: gunicorn/19.9.0 -// > Content-Type: application/json -// > Content-Length: 191 -// > Access-Control-Allow-Origin: * -// > Access-Control-Allow-Credentials: true -// > Via: 1.1 vegur -// > -// > { -// > "args": {}, -// > "headers": { -// > "Connection": "close", -// > "Host": "httpbin.org", -// > "User-Agent": "Webcc/0.1.0" -// > }, -// > "origin": "198.55.94.81", -// > "url": "http://httpbin.org/get" -// > } -// > -// As you can see, the request information is returned in JSON format. - -#include - -#include "webcc/http_client.h" -#include "webcc/logger.h" - -void Test() { - webcc::HttpRequest request(webcc::kHttpGet, "/get", "httpbin.org"/*, "80"*/); - request.Make(); - - webcc::HttpClient client; - if (client.Request(request)) { - std::cout << client.response()->content() << std::endl; - } else { - std::cout << webcc::DescribeError(client.error()); - if (client.timed_out()) { - std::cout << " (timed out)"; - } - std::cout << std::endl; - } -} - -int main() { - WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); - - Test(); - - 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..55de951 --- /dev/null +++ b/example/http_client/main.cc @@ -0,0 +1,29 @@ +#include + +#include "webcc/http_client.h" +#include "webcc/logger.h" + +static void PrintError(const webcc::HttpClient& client) { + std::cout << webcc::DescribeError(client.error()); + if (client.timed_out()) { + std::cout << " (timed out)"; + } + std::cout << std::endl; +} + +int main() { + WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); + + auto request = webcc::HttpRequest::Make(webcc::kHttpGet, "/get", + "httpbin.org"); + + webcc::HttpClient client; + + if (client.Request(*request)) { + std::cout << client.response_content() << std::endl; + } else { + PrintError(client); + } + + return 0; +} diff --git a/example/http_hello_async_client/CMakeLists.txt b/example/http_hello_async_client/CMakeLists.txt deleted file mode 100644 index a797ab4..0000000 --- a/example/http_hello_async_client/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_executable(http_hello_async_client main.cc) - -target_link_libraries(http_hello_async_client webcc ${Boost_LIBRARIES}) -target_link_libraries(http_hello_async_client "${CMAKE_THREAD_LIBS_INIT}") diff --git a/example/http_hello_async_client/main.cc b/example/http_hello_async_client/main.cc deleted file mode 100644 index 30a3491..0000000 --- a/example/http_hello_async_client/main.cc +++ /dev/null @@ -1,50 +0,0 @@ -#include - -#include "boost/asio/io_context.hpp" - -#include "webcc/http_async_client.h" -#include "webcc/logger.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& io_context) { - webcc::HttpRequestPtr request = std::make_shared( - webcc::kHttpGet, "/index.html", "localhost", "8000"); - - request->Make(); - - webcc::HttpAsyncClientPtr client(new webcc::HttpAsyncClient(io_context)); - - // Response handler. - auto handler = [](webcc::HttpResponsePtr response, webcc::Error error, - bool timed_out) { - if (error == webcc::kNoError) { - std::cout << response->content() << std::endl; - } else { - std::cout << webcc::DescribeError(error); - if (timed_out) { - std::cout << " (timed out)"; - } - std::cout << std::endl; - } - }; - - client->Request(request, handler); -} - -int main() { - WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); - - boost::asio::io_context io_context; - - Test(io_context); - Test(io_context); - Test(io_context); - - io_context.run(); - - return 0; -} diff --git a/example/http_hello_client/CMakeLists.txt b/example/http_hello_client/CMakeLists.txt deleted file mode 100644 index 9f0bf4c..0000000 --- a/example/http_hello_client/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_executable(http_hello_client main.cc) - -target_link_libraries(http_hello_client webcc ${Boost_LIBRARIES}) -target_link_libraries(http_hello_client "${CMAKE_THREAD_LIBS_INIT}") diff --git a/example/http_hello_client/main.cc b/example/http_hello_client/main.cc deleted file mode 100644 index 85e2023..0000000 --- a/example/http_hello_client/main.cc +++ /dev/null @@ -1,36 +0,0 @@ -#include - -#include "webcc/http_client.h" -#include "webcc/logger.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(webcc::kHttpGet, "/index.html", "localhost", - "8000"); - request.Make(); - - webcc::HttpClient client; - if (client.Request(request)) { - std::cout << client.response()->content() << std::endl; - } else { - std::cout << webcc::DescribeError(client.error()); - if (client.timed_out()) { - std::cout << " (timed out)"; - } - std::cout << std::endl; - } -} - -int main() { - WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); - - Test(); - Test(); - Test(); - - return 0; -} diff --git a/example/http_ssl_async_client/CMakeLists.txt b/example/http_ssl_async_client/CMakeLists.txt new file mode 100644 index 0000000..e85b0e7 --- /dev/null +++ b/example/http_ssl_async_client/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(http_ssl_async_client main.cc) + +# TODO +set(SSL_LIBS ${OPENSSL_LIBRARIES}) +if(WIN32) + set(SSL_LIBS ${SSL_LIBS} crypt32) +endif() + +target_link_libraries(http_ssl_async_client webcc ${Boost_LIBRARIES}) +target_link_libraries(http_ssl_async_client "${CMAKE_THREAD_LIBS_INIT}") +target_link_libraries(http_ssl_async_client ${SSL_LIBS}) diff --git a/example/http_ssl_async_client/main.cc b/example/http_ssl_async_client/main.cc new file mode 100644 index 0000000..70daa16 --- /dev/null +++ b/example/http_ssl_async_client/main.cc @@ -0,0 +1,58 @@ +#include + +#include "boost/asio/io_context.hpp" + +#include "webcc/http_ssl_async_client.h" +#include "webcc/logger.h" + +int main(int argc, char* argv[]) { + std::string host; + std::string url; + + if (argc != 3) { + host = "www.boost.org"; + url = "/LICENSE_1_0.txt"; + } else { + host = argv[1]; + url = argv[2]; + } + + std::cout << "Host: " << host << std::endl; + std::cout << "URL: " << url << std::endl; + std::cout << std::endl; + + WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); + + boost::asio::io_context io_context; + + // Leave port to default value. + auto request = webcc::HttpRequest::Make(webcc::kHttpGet, url, host); + + // Verify the certificate of the peer or not. + // See HttpSslClient::Request() for more details. + bool ssl_verify = false; + + webcc::HttpSslAsyncClientPtr client{ + new webcc::HttpSslAsyncClient{ io_context, 2000, ssl_verify } + }; + + // Response callback. + auto callback = [](webcc::HttpResponsePtr response, webcc::Error error, + bool timed_out) { + if (error == webcc::kNoError) { + std::cout << response->content() << std::endl; + } else { + std::cout << DescribeError(error); + if (timed_out) { + std::cout << " (timed out)"; + } + std::cout << std::endl; + } + }; + + client->Request(request, callback); + + io_context.run(); + + return 0; +} diff --git a/example/http_ssl_client/main.cc b/example/http_ssl_client/main.cc index f739f11..9244c9e 100644 --- a/example/http_ssl_client/main.cc +++ b/example/http_ssl_client/main.cc @@ -24,7 +24,7 @@ int main(int argc, char* argv[]) { // Leave port to default value. webcc::HttpRequest request(webcc::kHttpGet, url, host); - request.Make(); + request.Prepare(); // Verify the certificate of the peer or not. // See HttpSslClient::Request() for more details. diff --git a/webcc/CMakeLists.txt b/webcc/CMakeLists.txt index a365204..567408b 100644 --- a/webcc/CMakeLists.txt +++ b/webcc/CMakeLists.txt @@ -15,6 +15,7 @@ include(GNUInstallDirs) set(HEADERS globals.h + http_async_client_base.h http_async_client.h http_client_base.h http_client.h @@ -27,7 +28,7 @@ set(HEADERS http_response.h http_response_parser.h http_server.h - logger.h + http_ssl_async_client.h queue.h rest_async_client.h rest_client.h @@ -42,6 +43,7 @@ set(HEADERS set(SOURCES globals.cc + http_async_client_base.cc http_async_client.cc http_client_base.cc http_client.cc @@ -54,6 +56,7 @@ set(SOURCES http_response.cc http_response_parser.cc http_server.cc + http_ssl_async_client.cc logger.cc rest_async_client.cc rest_client.cc diff --git a/webcc/http_async_client.cc b/webcc/http_async_client.cc index 6c5ed9d..06b3d7f 100644 --- a/webcc/http_async_client.cc +++ b/webcc/http_async_client.cc @@ -11,222 +11,25 @@ namespace webcc { HttpAsyncClient::HttpAsyncClient(boost::asio::io_context& io_context, std::size_t buffer_size) - : socket_(io_context), - resolver_(io_context), - buffer_(buffer_size == 0 ? kBufferSize : buffer_size), - deadline_(io_context), - timeout_seconds_(kMaxReadSeconds), - stopped_(false), - timed_out_(false) { + : HttpAsyncClientBase(io_context, buffer_size), + socket_(io_context) { } -void HttpAsyncClient::SetTimeout(int seconds) { - if (seconds > 0) { - timeout_seconds_ = seconds; - } +void HttpAsyncClient::SocketAsyncConnect(const Endpoints& endpoints, + ConnectHandler&& handler) { + boost::asio::async_connect(socket_, endpoints, std::move(handler)); } -void HttpAsyncClient::Request(std::shared_ptr request, - HttpResponseCallback response_callback) { - assert(request); - assert(response_callback); - - response_.reset(new HttpResponse()); - response_parser_.reset(new HttpResponseParser(response_.get())); - - stopped_ = false; - timed_out_ = false; - - LOG_VERB("HTTP request:\n%s", request->Dump(4, "> ").c_str()); - - request_ = request; - response_callback_ = response_callback; - - resolver_.async_resolve(tcp::v4(), request->host(), request->port(kHttpPort), - std::bind(&HttpAsyncClient::OnResolve, - shared_from_this(), - std::placeholders::_1, - std::placeholders::_2)); -} - -void HttpAsyncClient::Stop() { - LOG_INFO("The user asks to cancel the request."); - - DoStop(); +void HttpAsyncClient::SocketAsyncWrite(WriteHandler&& handler) { + boost::asio::async_write(socket_, request_->ToBuffers(), std::move(handler)); } -void HttpAsyncClient::OnResolve(boost::system::error_code ec, - tcp::resolver::results_type endpoints) { - if (ec) { - LOG_ERRO("Host resolve error (%s): %s, %s.", ec.message().c_str(), - request_->host().c_str(), request_->port().c_str()); - 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, - shared_from_this(), - std::placeholders::_1, - std::placeholders::_2)); - } +void HttpAsyncClient::SocketAsyncReadSome(ReadHandler&& handler) { + socket_.async_read_some(boost::asio::buffer(buffer_), std::move(handler)); } -void HttpAsyncClient::OnConnect(boost::system::error_code ec, - tcp::endpoint endpoint) { - if (ec) { - LOG_ERRO("Socket connect error (%s).", ec.message().c_str()); - DoStop(); - response_callback_(response_, kEndpointConnectError, timed_out_); - return; - } - - LOG_VERB("Socket connected."); - - // 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_) { - // TODO: Use some other error. - response_callback_(response_, kEndpointConnectError, timed_out_); - return; - } - - // Connection established. - DoWrite(); -} - -void HttpAsyncClient::DoWrite() { - // 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(), - std::bind(&HttpAsyncClient::OnWrite, - shared_from_this(), - std::placeholders::_1)); -} - -void HttpAsyncClient::OnWrite(boost::system::error_code ec) { - if (stopped_) { - // TODO: Use some other error. - response_callback_(response_, kSocketWriteError, timed_out_); - return; - } - - if (ec) { - 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(); - - DoRead(); - } -} - -void HttpAsyncClient::DoRead() { - socket_.async_read_some(boost::asio::buffer(buffer_), - std::bind(&HttpAsyncClient::OnRead, - shared_from_this(), - std::placeholders::_1, - std::placeholders::_2)); -} - -void HttpAsyncClient::OnRead(boost::system::error_code ec, - std::size_t length) { - LOG_VERB("Socket async read handler."); - - if (ec || length == 0) { - DoStop(); - LOG_ERRO("Socket read error (%s).", ec.message().c_str()); - response_callback_(response_, kSocketReadError, timed_out_); - return; - } - - 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)) { - DoStop(); - LOG_ERRO("Failed to parse HTTP response."); - response_callback_(response_, kHttpError, timed_out_); - return; - } - - 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()); - - response_callback_(response_, kNoError, timed_out_); - return; - } - - if (!stopped_) { - DoRead(); - } -} - -void HttpAsyncClient::DoWaitDeadline() { - deadline_.async_wait(std::bind(&HttpAsyncClient::OnDeadline, - shared_from_this(), std::placeholders::_1)); -} - -void HttpAsyncClient::OnDeadline(boost::system::error_code ec) { - if (stopped_) { - return; - } - - 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; - } - - // Put the actor back to sleep. - DoWaitDeadline(); -} - -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(); +void HttpAsyncClient::SocketClose(boost::system::error_code* ec) { + socket_.close(*ec); } } // namespace webcc diff --git a/webcc/http_async_client.h b/webcc/http_async_client.h index f9a1812..d30dbbc 100644 --- a/webcc/http_async_client.h +++ b/webcc/http_async_client.h @@ -1,91 +1,37 @@ #ifndef WEBCC_HTTP_ASYNC_CLIENT_H_ #define WEBCC_HTTP_ASYNC_CLIENT_H_ -#include -#include -#include - -#include "boost/asio/deadline_timer.hpp" -#include "boost/asio/io_context.hpp" -#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" +#include "webcc/http_async_client_base.h" namespace webcc { -// Response callback. -typedef std::function HttpResponseCallback; - -// HTTP client session in asynchronous mode. -// A request will return without waiting for the response, the callback handler -// will be invoked when the response is received or timeout occurs. -// Don't use the same HttpAsyncClient object in multiple threads. -class HttpAsyncClient : public std::enable_shared_from_this { +// HTTP asynchronous client. +class HttpAsyncClient : public HttpAsyncClientBase { public: - // 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); - - // 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_callback| when all these finish. - void Request(HttpRequestPtr request, HttpResponseCallback response_callback); - - // Called by the user to cancel the request. - void Stop(); + ~HttpAsyncClient() = default; private: - using tcp = boost::asio::ip::tcp; - - void OnResolve(boost::system::error_code ec, - tcp::resolver::results_type results); + void Resolve() final { + DoResolve(kHttpPort); + } - void OnConnect(boost::system::error_code ec, tcp::endpoint endpoint); + void OnConnected() final { + DoWrite(); + } - void DoWrite(); - void OnWrite(boost::system::error_code ec); + void SocketAsyncConnect(const Endpoints& endpoints, + ConnectHandler&& handler) final; - void DoRead(); - void OnRead(boost::system::error_code ec, std::size_t length); + void SocketAsyncWrite(WriteHandler&& handler) final; - void DoWaitDeadline(); - void OnDeadline(boost::system::error_code ec); + void SocketAsyncReadSome(ReadHandler&& handler) final; - // Terminate all the actors to shut down the connection. - void DoStop(); + void SocketClose(boost::system::error_code* ec) final; - tcp::resolver resolver_; tcp::socket socket_; - - std::shared_ptr request_; - std::vector buffer_; - - HttpResponsePtr response_; - std::unique_ptr response_parser_; - HttpResponseCallback response_callback_; - - // Timer for the timeout control. - boost::asio::deadline_timer deadline_; - - // Maximum seconds to wait before the client cancels the operation. - // 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_; }; typedef std::shared_ptr HttpAsyncClientPtr; diff --git a/webcc/http_async_client_base.cc b/webcc/http_async_client_base.cc new file mode 100644 index 0000000..3ffc677 --- /dev/null +++ b/webcc/http_async_client_base.cc @@ -0,0 +1,236 @@ +#include "webcc/http_async_client_base.h" + +#include "boost/asio/connect.hpp" +#include "boost/asio/read.hpp" +#include "boost/asio/write.hpp" + +#include "webcc/logger.h" +#include "webcc/utility.h" + +namespace webcc { + +HttpAsyncClientBase::HttpAsyncClientBase(boost::asio::io_context& io_context, + std::size_t buffer_size) + : resolver_(io_context), + buffer_(buffer_size == 0 ? kBufferSize : buffer_size), + deadline_(io_context), + timeout_seconds_(kMaxReadSeconds), + stopped_(false), + timed_out_(false) { +} + +void HttpAsyncClientBase::SetTimeout(int seconds) { + if (seconds > 0) { + timeout_seconds_ = seconds; + } +} + +void HttpAsyncClientBase::Request(std::shared_ptr request, + HttpResponseCallback response_callback) { + assert(request); + assert(response_callback); + + response_.reset(new HttpResponse()); + response_parser_.reset(new HttpResponseParser(response_.get())); + + stopped_ = false; + timed_out_ = false; + + LOG_VERB("HTTP request:\n%s", request->Dump(4, "> ").c_str()); + + request_ = request; + response_callback_ = response_callback; + + DoResolve(kHttpSslPort); +} + +void HttpAsyncClientBase::Stop() { + LOG_INFO("The user asks to cancel the request."); + + DoStop(); +} + +void HttpAsyncClientBase::DoResolve(const std::string& default_port) { + auto handler = std::bind(&HttpAsyncClientBase::OnResolve, shared_from_this(), + std::placeholders::_1, std::placeholders::_2); + + resolver_.async_resolve(tcp::v4(), request_->host(), + request_->port(default_port), handler); +} + +void HttpAsyncClientBase::OnResolve(boost::system::error_code ec, + tcp::resolver::results_type endpoints) { + if (ec) { + LOG_ERRO("Host resolve error (%s): %s, %s.", ec.message().c_str(), + request_->host().c_str(), request_->port().c_str()); + + response_callback_(response_, kHostResolveError, timed_out_); + } else { + LOG_VERB("Host resolved."); + + DoConnect(endpoints); + } +} + +void HttpAsyncClientBase::DoConnect(const Endpoints& endpoints) { + auto handler = std::bind(&HttpAsyncClientBase::OnConnect, + shared_from_this(), + std::placeholders::_1, std::placeholders::_2); + + SocketAsyncConnect(endpoints, std::move(handler)); +} + +void HttpAsyncClientBase::OnConnect(boost::system::error_code ec, + tcp::endpoint endpoint) { + if (ec) { + LOG_ERRO("Socket connect error (%s).", ec.message().c_str()); + DoStop(); + response_callback_(response_, kEndpointConnectError, timed_out_); + return; + } + + LOG_VERB("Socket connected."); + + // 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_) { + // TODO: Use some other error. + response_callback_(response_, kEndpointConnectError, timed_out_); + return; + } + + // Connection established. + OnConnected(); +} + +void HttpAsyncClientBase::DoWrite() { + // 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. + + SocketAsyncWrite(std::bind(&HttpAsyncClientBase::OnWrite, shared_from_this(), + std::placeholders::_1, std::placeholders::_2)); +} + +void HttpAsyncClientBase::OnWrite(boost::system::error_code ec, + std::size_t /*length*/) { + if (stopped_) { + // TODO: Use some other error. + response_callback_(response_, kSocketWriteError, timed_out_); + return; + } + + if (ec) { + 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(); + + DoRead(); + } +} + +void HttpAsyncClientBase::DoRead() { + auto handler = std::bind(&HttpAsyncClientBase::OnRead, shared_from_this(), + std::placeholders::_1, std::placeholders::_2); + SocketAsyncReadSome(std::move(handler)); +} + +void HttpAsyncClientBase::OnRead(boost::system::error_code ec, + std::size_t length) { + LOG_VERB("Socket async read handler."); + + if (ec || length == 0) { + DoStop(); + LOG_ERRO("Socket read error (%s).", ec.message().c_str()); + response_callback_(response_, kSocketReadError, timed_out_); + return; + } + + 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)) { + DoStop(); + LOG_ERRO("Failed to parse HTTP response."); + response_callback_(response_, kHttpError, timed_out_); + return; + } + + 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()); + + response_callback_(response_, kNoError, timed_out_); + return; + } + + if (!stopped_) { + DoRead(); + } +} + +void HttpAsyncClientBase::DoWaitDeadline() { + deadline_.async_wait(std::bind(&HttpAsyncClientBase::OnDeadline, + shared_from_this(), std::placeholders::_1)); +} + +void HttpAsyncClientBase::OnDeadline(boost::system::error_code ec) { + if (stopped_) { + return; + } + + 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; + } + + // Put the actor back to sleep. + DoWaitDeadline(); +} + +void HttpAsyncClientBase::DoStop() { + if (stopped_) { + return; + } + + stopped_ = true; + + LOG_INFO("Close socket..."); + + boost::system::error_code ec; + SocketClose(&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_base.h b/webcc/http_async_client_base.h new file mode 100644 index 0000000..a0224b3 --- /dev/null +++ b/webcc/http_async_client_base.h @@ -0,0 +1,128 @@ +#ifndef WEBCC_HTTP_ASYNC_CLIENT_BASE_H_ +#define WEBCC_HTTP_ASYNC_CLIENT_BASE_H_ + +#include +#include +#include + +#include "boost/asio/deadline_timer.hpp" +#include "boost/asio/io_context.hpp" +#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 { + +// Response callback. +typedef std::function HttpResponseCallback; + +// HTTP client session in asynchronous mode. +// A request will return without waiting for the response, the callback handler +// will be invoked when the response is received or timeout occurs. +// Don't use the same HttpAsyncClient object in multiple threads. +class HttpAsyncClientBase + : public std::enable_shared_from_this { + public: + // The |buffer_size| is the bytes of the buffer for reading response. + // 0 means default value (e.g., 1024) will be used. + explicit HttpAsyncClientBase(boost::asio::io_context& io_context, + std::size_t buffer_size = 0); + + WEBCC_DELETE_COPY_ASSIGN(HttpAsyncClientBase); + + // 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_callback| when all these finish. + void Request(HttpRequestPtr request, HttpResponseCallback response_callback); + + // Called by the user to cancel the request. + void Stop(); + + protected: + using tcp = boost::asio::ip::tcp; + + typedef tcp::resolver::results_type Endpoints; + + typedef std::function + ReadHandler; + + typedef std::function + ConnectHandler; + + typedef std::function + WriteHandler; + + // To enable_shared_from_this for both parent and derived. + // See https://stackoverflow.com/q/657155/6825348 + template + std::shared_ptr shared_from_base() { + return std::static_pointer_cast(shared_from_this()); + } + + protected: + virtual void Resolve() = 0; + + void DoResolve(const std::string& default_port); + void OnResolve(boost::system::error_code ec, + tcp::resolver::results_type results); + + void DoConnect(const Endpoints& endpoints); + void OnConnect(boost::system::error_code ec, tcp::endpoint endpoint); + + virtual void OnConnected() = 0; + + void DoWrite(); + void OnWrite(boost::system::error_code ec, std::size_t length); + + void DoRead(); + void OnRead(boost::system::error_code ec, std::size_t length); + + void DoWaitDeadline(); + void OnDeadline(boost::system::error_code ec); + + // Terminate all the actors to shut down the connection. + void DoStop(); + + virtual void SocketAsyncConnect(const Endpoints& endpoints, + ConnectHandler&& handler) = 0; + + virtual void SocketAsyncWrite(WriteHandler&& handler) = 0; + + virtual void SocketAsyncReadSome(ReadHandler&& handler) = 0; + + virtual void SocketClose(boost::system::error_code* ec) = 0; + + tcp::resolver resolver_; + + HttpRequestPtr request_; + + std::vector buffer_; + + HttpResponsePtr response_; + std::unique_ptr response_parser_; + HttpResponseCallback response_callback_; + + // Timer for the timeout control. + boost::asio::deadline_timer deadline_; + + // Maximum seconds to wait before the client cancels the operation. + // 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_; +}; + +} // namespace webcc + +#endif // WEBCC_HTTP_ASYNC_CLIENT_BASE_H_ diff --git a/webcc/http_client.cc b/webcc/http_client.cc index 4647382..72a3fa6 100644 --- a/webcc/http_client.cc +++ b/webcc/http_client.cc @@ -20,9 +20,8 @@ void HttpClient::SocketWrite(const HttpRequest& request, boost::asio::write(socket_, request.ToBuffers(), *ec); } -void HttpClient::SocketAsyncReadSome(std::vector& buffer, - ReadHandler handler) { - socket_.async_read_some(boost::asio::buffer(buffer), handler); +void HttpClient::SocketAsyncReadSome(ReadHandler&& handler) { + socket_.async_read_some(boost::asio::buffer(buffer_), std::move(handler)); } void HttpClient::SocketClose(boost::system::error_code* ec) { diff --git a/webcc/http_client.h b/webcc/http_client.h index b86be21..b76736d 100644 --- a/webcc/http_client.h +++ b/webcc/http_client.h @@ -5,17 +5,13 @@ namespace webcc { -// HTTP client in synchronous mode. -// A request will not return until the response is received or timeout occurs. -// Don't use the same HttpClient object in multiple threads. +// HTTP synchronous client. class HttpClient : public HttpClientBase { public: explicit HttpClient(std::size_t buffer_size = 0); ~HttpClient() = default; - WEBCC_DELETE_COPY_ASSIGN(HttpClient); - private: Error Connect(const HttpRequest& request) final { return DoConnect(request, kHttpPort); @@ -27,8 +23,7 @@ class HttpClient : public HttpClientBase { void SocketWrite(const HttpRequest& request, boost::system::error_code* ec) final; - void SocketAsyncReadSome(std::vector& buffer, - ReadHandler handler) final; + void SocketAsyncReadSome(ReadHandler&& handler) final; void SocketClose(boost::system::error_code* ec) final; diff --git a/webcc/http_client_base.cc b/webcc/http_client_base.cc index f5435bf..8060bbf 100644 --- a/webcc/http_client_base.cc +++ b/webcc/http_client_base.cc @@ -1,8 +1,5 @@ #include "webcc/http_client_base.h" -#include "boost/asio/connect.hpp" -#include "boost/asio/read.hpp" -#include "boost/asio/write.hpp" #include "boost/date_time/posix_time/posix_time.hpp" #include "webcc/logger.h" @@ -44,7 +41,7 @@ bool HttpClientBase::Request(const HttpRequest& request, return false; } - if ((error_ = SendReqeust(request)) != kNoError) { + if ((error_ = WriteReqeust(request)) != kNoError) { return false; } @@ -87,7 +84,7 @@ Error HttpClientBase::DoConnect(const HttpRequest& request, return kNoError; } -Error HttpClientBase::SendReqeust(const HttpRequest& request) { +Error HttpClientBase::WriteReqeust(const HttpRequest& request) { LOG_VERB("HTTP request:\n%s", request.Dump(4, "> ").c_str()); // NOTE: @@ -130,8 +127,8 @@ Error HttpClientBase::ReadResponse() { void HttpClientBase::DoReadResponse(Error* error) { boost::system::error_code ec = boost::asio::error::would_block; - auto read_handler = [this, &ec, error](boost::system::error_code inner_ec, - std::size_t length) { + auto handler = [this, &ec, error](boost::system::error_code inner_ec, + std::size_t length) { ec = inner_ec; LOG_VERB("Socket async read handler."); @@ -168,7 +165,7 @@ void HttpClientBase::DoReadResponse(Error* error) { } }; - SocketAsyncReadSome(buffer_, read_handler); + SocketAsyncReadSome(std::move(handler)); // Block until the asynchronous operation has completed. do { diff --git a/webcc/http_client_base.h b/webcc/http_client_base.h index 74d8e87..a7f0ff7 100644 --- a/webcc/http_client_base.h +++ b/webcc/http_client_base.h @@ -17,6 +17,10 @@ namespace webcc { +// The base class of synchronous HTTP clients. +// In synchronous mode, a request won't return until the response is received +// or timeout occurs. +// Please don't use the same client object in multiple threads. class HttpClientBase { public: // The |buffer_size| is the bytes of the buffer for reading response. @@ -38,6 +42,16 @@ class HttpClientBase { HttpResponsePtr response() const { return response_; } + int response_status() const { + assert(response_); + return response_->status(); + } + + const std::string& response_content() const { + assert(response_); + return response_->content(); + } + bool timed_out() const { return timed_out_; } Error error() const { return error_; } @@ -48,12 +62,11 @@ class HttpClientBase { typedef std::function ReadHandler; - Error DoConnect(const HttpRequest& request, const std::string& default_port); + virtual Error Connect(const HttpRequest& request) = 0; - boost::asio::io_context io_context_; + Error DoConnect(const HttpRequest& request, const std::string& default_port); - private: - Error SendReqeust(const HttpRequest& request); + Error WriteReqeust(const HttpRequest& request); Error ReadResponse(); @@ -64,19 +77,18 @@ class HttpClientBase { void Stop(); - virtual Error Connect(const HttpRequest& request) = 0; - virtual void SocketConnect(const Endpoints& endpoints, boost::system::error_code* ec) = 0; virtual void SocketWrite(const HttpRequest& request, boost::system::error_code* ec) = 0; - virtual void SocketAsyncReadSome(std::vector& buffer, - ReadHandler handler) = 0; + virtual void SocketAsyncReadSome(ReadHandler&& handler) = 0; virtual void SocketClose(boost::system::error_code* ec) = 0; + boost::asio::io_context io_context_; + std::vector buffer_; HttpResponsePtr response_; diff --git a/webcc/http_message.h b/webcc/http_message.h index 12925b0..f1d4fbe 100644 --- a/webcc/http_message.h +++ b/webcc/http_message.h @@ -46,7 +46,7 @@ class HttpMessage { // Make the message (e.g., update start line). // Must be called before ToBuffers()! - virtual void Make() = 0; + virtual void Prepare() = 0; // Convert the message into a vector of buffers. The buffers do not own the // underlying memory blocks, therefore the message object must remain valid diff --git a/webcc/http_request.cc b/webcc/http_request.cc index 6051d68..09fff75 100644 --- a/webcc/http_request.cc +++ b/webcc/http_request.cc @@ -9,7 +9,7 @@ HttpRequest::HttpRequest(const std::string& method, : method_(method), url_(url), host_(host), port_(port) { } -void HttpRequest::Make() { +void HttpRequest::Prepare() { start_line_ = method_; start_line_ += " "; start_line_ += url_; @@ -32,4 +32,21 @@ void HttpRequest::Make() { SetHeader(http::headers::kUserAgent, "Webcc/" WEBCC_VERSION); } +// static +HttpRequestPtr HttpRequest::Make(const std::string& method, + const std::string& url, + const std::string& host, + const std::string& port, + bool prepare) { + HttpRequestPtr request{ + new HttpRequest{ method, url, host, port } + }; + + if (prepare) { + request->Prepare(); + } + + return request; +} + } // namespace webcc diff --git a/webcc/http_request.h b/webcc/http_request.h index 81690fe..b3d52fe 100644 --- a/webcc/http_request.h +++ b/webcc/http_request.h @@ -8,8 +8,11 @@ namespace webcc { +class HttpRequest; class HttpRequestParser; +typedef std::shared_ptr HttpRequestPtr; + class HttpRequest : public HttpMessage { public: HttpRequest() = default; @@ -36,8 +39,15 @@ class HttpRequest : public HttpMessage { return port_.empty() ? default_port : port_; } + // Prepare payload. // Compose start line, set Host header, etc. - void Make() override; + void Prepare() override; + + static HttpRequestPtr Make(const std::string& method, + const std::string& url, + const std::string& host, + const std::string& port = "", + bool prepare = true); private: friend class HttpRequestParser; @@ -57,8 +67,6 @@ class HttpRequest : public HttpMessage { std::string port_; }; -typedef std::shared_ptr HttpRequestPtr; - } // namespace webcc #endif // WEBCC_HTTP_REQUEST_H_ diff --git a/webcc/http_response.cc b/webcc/http_response.cc index 6441afb..324c047 100644 --- a/webcc/http_response.cc +++ b/webcc/http_response.cc @@ -57,7 +57,7 @@ const std::string& ToString(int status) { } // namespace status_strings -void HttpResponse::Make() { +void HttpResponse::Prepare() { start_line_ = status_strings::ToString(status_); // NOTE: C++11 requires a space between literal and string macro. @@ -72,7 +72,7 @@ HttpResponse HttpResponse::Fault(http::Status status) { HttpResponse response; response.set_status(status); - response.Make(); + response.Prepare(); return response; } diff --git a/webcc/http_response.h b/webcc/http_response.h index 810faf4..f43f999 100644 --- a/webcc/http_response.h +++ b/webcc/http_response.h @@ -19,7 +19,7 @@ class HttpResponse : public HttpMessage { void set_status(int status) { status_ = status; } // Set start line according to status code. - void Make() override; + void Prepare() override; // Get a fault response when HTTP status is not OK. // TODO: Avoid copy. diff --git a/webcc/http_session.cc b/webcc/http_session.cc index b1bc478..e1add89 100644 --- a/webcc/http_session.cc +++ b/webcc/http_session.cc @@ -41,7 +41,7 @@ void HttpSession::SetResponseContent(std::string&& content, void HttpSession::SendResponse(http::Status status) { response_.set_status(status); - response_.Make(); + response_.Prepare(); DoWrite(); } diff --git a/webcc/http_ssl_async_client.cc b/webcc/http_ssl_async_client.cc new file mode 100644 index 0000000..2bfc3fb --- /dev/null +++ b/webcc/http_ssl_async_client.cc @@ -0,0 +1,71 @@ +#include "webcc/http_ssl_async_client.h" + +#include "boost/asio/connect.hpp" + +#include "webcc/logger.h" + +using tcp = boost::asio::ip::tcp; +namespace ssl = boost::asio::ssl; + +namespace webcc { + +HttpSslAsyncClient::HttpSslAsyncClient(boost::asio::io_context& io_context, + std::size_t buffer_size, + bool ssl_verify) + : HttpAsyncClientBase(io_context, buffer_size), + ssl_context_(ssl::context::sslv23), + ssl_socket_(io_context, ssl_context_), + ssl_verify_(ssl_verify) { + // Use the default paths for finding CA certificates. + ssl_context_.set_default_verify_paths(); +} + +void HttpSslAsyncClient::Resolve() { + DoResolve(kHttpSslPort); +} + +void HttpSslAsyncClient::DoHandshake() { + if (ssl_verify_) { + ssl_socket_.set_verify_mode(ssl::verify_peer); + } else { + ssl_socket_.set_verify_mode(ssl::verify_none); + } + + ssl_socket_.set_verify_callback(ssl::rfc2818_verification(request_->host())); + + ssl_socket_.async_handshake(ssl::stream_base::client, + std::bind(&HttpSslAsyncClient::OnHandshake, + shared_from_this(), + std::placeholders::_1)); +} + +void HttpSslAsyncClient::OnHandshake(boost::system::error_code ec) { + if (ec) { + LOG_ERRO("Handshake error (%s).", ec.message().c_str()); + response_callback_(response_, kHandshakeError, false); + return; + } + + DoWrite(); +} + +void HttpSslAsyncClient::SocketAsyncConnect(const Endpoints& endpoints, + ConnectHandler&& handler) { + boost::asio::async_connect(ssl_socket_.lowest_layer(), endpoints, + std::move(handler)); +} + +void HttpSslAsyncClient::SocketAsyncWrite(WriteHandler&& handler) { + boost::asio::async_write(ssl_socket_, request_->ToBuffers(), + std::move(handler)); +} + +void HttpSslAsyncClient::SocketAsyncReadSome(ReadHandler&& handler) { + ssl_socket_.async_read_some(boost::asio::buffer(buffer_), std::move(handler)); +} + +void HttpSslAsyncClient::SocketClose(boost::system::error_code* ec) { + ssl_socket_.lowest_layer().close(*ec); +} + +} // namespace webcc diff --git a/webcc/http_ssl_async_client.h b/webcc/http_ssl_async_client.h new file mode 100644 index 0000000..c35c9c7 --- /dev/null +++ b/webcc/http_ssl_async_client.h @@ -0,0 +1,58 @@ +#ifndef WEBCC_HTTP_SSL_ASYNC_CLIENT_H_ +#define WEBCC_HTTP_SSL_ASYNC_CLIENT_H_ + +#include "webcc/http_async_client_base.h" + +#include "boost/asio/ssl.hpp" + +namespace webcc { + +// HTTP SSL (a.k.a., HTTPS) asynchronous client. +class HttpSslAsyncClient : public HttpAsyncClientBase { + public: + // 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. + explicit HttpSslAsyncClient(boost::asio::io_context& io_context, + std::size_t buffer_size = 0, + bool ssl_verify = true); + + ~HttpSslAsyncClient() = default; + + // See https://stackoverflow.com/q/657155/6825348 + std::shared_ptr shared_from_this() { + return shared_from_base(); + } + + private: + void Resolve() final; + + // Override to do handshake after connected. + void OnConnected() final { + DoHandshake(); + } + + void DoHandshake(); + void OnHandshake(boost::system::error_code ec); + + void SocketAsyncConnect(const Endpoints& endpoints, + ConnectHandler&& handler) final; + + void SocketAsyncWrite(WriteHandler&& handler) final; + + void SocketAsyncReadSome(ReadHandler&& handler) final; + + void SocketClose(boost::system::error_code* ec) final; + + boost::asio::ssl::context ssl_context_; + boost::asio::ssl::stream ssl_socket_; + + // Verify the certificate of the peer (remote server) or not. + bool ssl_verify_; +}; + +typedef std::shared_ptr HttpSslAsyncClientPtr; + +} // namespace webcc + +#endif // WEBCC_HTTP_SSL_ASYNC_CLIENT_H_ diff --git a/webcc/http_ssl_client.cc b/webcc/http_ssl_client.cc index c661fbb..a5384a7 100644 --- a/webcc/http_ssl_client.cc +++ b/webcc/http_ssl_client.cc @@ -61,9 +61,8 @@ void HttpSslClient::SocketWrite(const HttpRequest& request, boost::asio::write(ssl_socket_, request.ToBuffers(), *ec); } -void HttpSslClient::SocketAsyncReadSome(std::vector& buffer, - ReadHandler handler) { - ssl_socket_.async_read_some(boost::asio::buffer(buffer), handler); +void HttpSslClient::SocketAsyncReadSome(ReadHandler&& handler) { + ssl_socket_.async_read_some(boost::asio::buffer(buffer_), std::move(handler)); } void HttpSslClient::SocketClose(boost::system::error_code* ec) { diff --git a/webcc/http_ssl_client.h b/webcc/http_ssl_client.h index f333c85..5482661 100644 --- a/webcc/http_ssl_client.h +++ b/webcc/http_ssl_client.h @@ -7,9 +7,7 @@ namespace webcc { -// HTTP SSL (a.k.a., HTTPS) client session in synchronous mode. -// A request will not return until the response is received or timeout occurs. -// Don't use the same HttpSslClient object in multiple threads. +// HTTP SSL (a.k.a., HTTPS) synchronous client. class HttpSslClient : public HttpClientBase { public: // SSL verification (|ssl_verify|) needs CA certificates to be found @@ -19,8 +17,6 @@ class HttpSslClient : public HttpClientBase { ~HttpSslClient() = default; - WEBCC_DELETE_COPY_ASSIGN(HttpSslClient); - private: Error Handshake(const std::string& host); @@ -33,8 +29,7 @@ class HttpSslClient : public HttpClientBase { void SocketWrite(const HttpRequest& request, boost::system::error_code* ec) final; - void SocketAsyncReadSome(std::vector& buffer, - ReadHandler handler) final; + void SocketAsyncReadSome(ReadHandler&& handler) final; void SocketClose(boost::system::error_code* ec) final; diff --git a/webcc/logger.cc b/webcc/logger.cc index d5062dd..bd5bab7 100644 --- a/webcc/logger.cc +++ b/webcc/logger.cc @@ -168,7 +168,7 @@ void LogWrite(int level, const char* file, int line, const char* format, ...) { va_list args; va_start(args, format); - fprintf(stderr, "%s, %s, %7s, %24s, %4d, ", + fprintf(stderr, "%s, %s, %7s, %25s, %4d, ", timestamp.c_str(), kLevelNames[level], thread_id.c_str(), file, line); diff --git a/webcc/rest_async_client.cc b/webcc/rest_async_client.cc index a792c68..0c99d6f 100644 --- a/webcc/rest_async_client.cc +++ b/webcc/rest_async_client.cc @@ -24,7 +24,7 @@ void RestAsyncClient::Request(const std::string& method, http::charsets::kUtf8); } - http_request->Make(); + http_request->Prepare(); HttpAsyncClientPtr http_async_client{ new HttpAsyncClient(io_context_, buffer_size_) diff --git a/webcc/rest_client.cc b/webcc/rest_client.cc index 8a78dc2..0188f0d 100644 --- a/webcc/rest_client.cc +++ b/webcc/rest_client.cc @@ -22,7 +22,7 @@ bool RestClient::Request(const std::string& method, const std::string& url, http::charsets::kUtf8); } - http_request.Make(); + http_request.Prepare(); if (!http_client_.Request(http_request, buffer_size)) { return false; diff --git a/webcc/rest_client.h b/webcc/rest_client.h index 3545168..172d3e8 100644 --- a/webcc/rest_client.h +++ b/webcc/rest_client.h @@ -16,9 +16,9 @@ class RestClient { public: // 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 = "", - std::size_t buffer_size = 0); + explicit RestClient(const std::string& host, + const std::string& port = "", + std::size_t buffer_size = 0); ~RestClient() = default; diff --git a/webcc/rest_ssl_client.cc b/webcc/rest_ssl_client.cc index 3343ab2..9e1eed0 100644 --- a/webcc/rest_ssl_client.cc +++ b/webcc/rest_ssl_client.cc @@ -20,7 +20,7 @@ bool RestSslClient::Request(const std::string& method, const std::string& url, http::charsets::kUtf8); } - http_request.Make(); + http_request.Prepare(); if (!http_client_.Request(http_request, buffer_size)) { return false; diff --git a/webcc/soap_async_client.cc b/webcc/soap_async_client.cc index d61f29d..34b87e4 100644 --- a/webcc/soap_async_client.cc +++ b/webcc/soap_async_client.cc @@ -63,7 +63,7 @@ void SoapAsyncClient::Request(const std::string& operation, } http_request->SetHeader(kSoapAction, operation); - http_request->Make(); + http_request->Prepare(); HttpAsyncClientPtr http_async_client{ new HttpAsyncClient(io_context_, buffer_size_) diff --git a/webcc/soap_client.cc b/webcc/soap_client.cc index 4128e7c..4888b02 100644 --- a/webcc/soap_client.cc +++ b/webcc/soap_client.cc @@ -62,7 +62,7 @@ bool SoapClient::Request(const std::string& operation, http_request.SetHeader(kSoapAction, operation); - http_request.Make(); + http_request.Prepare(); if (!http_client_.Request(http_request, buffer_size)) { error_ = http_client_.error(); diff --git a/webcc/utility.cc b/webcc/utility.cc index 6a262a3..f26e1d2 100644 --- a/webcc/utility.cc +++ b/webcc/utility.cc @@ -22,8 +22,7 @@ void AdjustHostPort(std::string& host, std::string& port) { } } -void PrintEndpoint(std::ostream& ostream, - const boost::asio::ip::tcp::endpoint& endpoint) { +void PrintEndpoint(std::ostream& ostream, const TcpEndpoint& endpoint) { ostream << endpoint; if (endpoint.protocol() == tcp::v4()) { ostream << ", v4"; @@ -32,8 +31,7 @@ void PrintEndpoint(std::ostream& ostream, } } -void PrintEndpoints(std::ostream& ostream, - const tcp::resolver::results_type& endpoints) { +void PrintEndpoints(std::ostream& ostream, const TcpEndpoints& endpoints) { ostream << "Endpoints: " << endpoints.size() << std::endl; tcp::resolver::results_type::iterator it = endpoints.begin(); for (; it != endpoints.end(); ++it) { @@ -43,7 +41,7 @@ void PrintEndpoints(std::ostream& ostream, } } -std::string EndpointToString(const boost::asio::ip::tcp::endpoint& endpoint) { +std::string EndpointToString(const TcpEndpoint& endpoint) { std::stringstream ss; PrintEndpoint(ss, endpoint); return ss.str(); diff --git a/webcc/utility.h b/webcc/utility.h index 024f5ec..46e0009 100644 --- a/webcc/utility.h +++ b/webcc/utility.h @@ -12,14 +12,14 @@ namespace webcc { // 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); +typedef boost::asio::ip::tcp::endpoint TcpEndpoint; +typedef boost::asio::ip::tcp::resolver::results_type TcpEndpoints; -void PrintEndpoints( - std::ostream& ostream, - const boost::asio::ip::tcp::resolver::results_type& endpoints); +void PrintEndpoint(std::ostream& ostream, const TcpEndpoint& endpoint); -std::string EndpointToString(const boost::asio::ip::tcp::endpoint& endpoint); +void PrintEndpoints(std::ostream& ostream, const TcpEndpoints& endpoints); + +std::string EndpointToString(const TcpEndpoint& endpoint); // Get the timestamp for HTTP Date header field. // E.g., Wed, 21 Oct 2015 07:28:00 GMT