From 9ecec5de0bc5d9525c60385eaca5255a0ce20d28 Mon Sep 17 00:00:00 2001 From: Chunting Gu Date: Mon, 11 Feb 2019 14:56:45 +0800 Subject: [PATCH] Add a base class for HttpClient and HttpSslClient to eliminate duplicate code. --- webcc/CMakeLists.txt | 2 + webcc/http_client.cc | 226 ++----------------------------------- webcc/http_client.h | 73 +++--------- webcc/http_client_base.cc | 232 ++++++++++++++++++++++++++++++++++++++ webcc/http_client_base.h | 103 +++++++++++++++++ webcc/http_ssl_client.cc | 222 ++++-------------------------------- webcc/http_ssl_client.h | 73 +++--------- 7 files changed, 394 insertions(+), 537 deletions(-) create mode 100644 webcc/http_client_base.cc create mode 100644 webcc/http_client_base.h diff --git a/webcc/CMakeLists.txt b/webcc/CMakeLists.txt index ab71377..a365204 100644 --- a/webcc/CMakeLists.txt +++ b/webcc/CMakeLists.txt @@ -16,6 +16,7 @@ include(GNUInstallDirs) set(HEADERS globals.h http_async_client.h + http_client_base.h http_client.h http_session.h http_message.h @@ -42,6 +43,7 @@ set(HEADERS set(SOURCES globals.cc http_async_client.cc + http_client_base.cc http_client.cc http_session.cc http_message.cc diff --git a/webcc/http_client.cc b/webcc/http_client.cc index a7e3af6..4647382 100644 --- a/webcc/http_client.cc +++ b/webcc/http_client.cc @@ -1,234 +1,32 @@ #include "webcc/http_client.h" -#include - #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" -#include "webcc/utility.h" - -using boost::asio::ip::tcp; namespace webcc { HttpClient::HttpClient(std::size_t buffer_size) - : socket_(io_context_), - buffer_(buffer_size == 0 ? kBufferSize : buffer_size), - deadline_(io_context_), - timeout_seconds_(kMaxReadSeconds), - stopped_(false), - timed_out_(false), - error_(kNoError) { -} - -void HttpClient::SetTimeout(int seconds) { - if (seconds > 0) { - timeout_seconds_ = seconds; - } -} - -bool HttpClient::Request(const HttpRequest& request, std::size_t buffer_size) { - io_context_.restart(); - - response_.reset(new HttpResponse()); - response_parser_.reset(new HttpResponseParser(response_.get())); - - stopped_ = false; - timed_out_ = false; - error_ = kNoError; - - BufferResizer buffer_resizer(&buffer_, buffer_size); - - if ((error_ = Connect(request)) != kNoError) { - return false; - } - - if ((error_ = SendReqeust(request)) != kNoError) { - return false; - } - - if ((error_ = ReadResponse()) != kNoError) { - return false; - } - - return true; -} - -Error HttpClient::Connect(const HttpRequest& request) { - tcp::resolver resolver(io_context_); - - std::string port = request.port(kHttpPort); - - boost::system::error_code ec; - auto endpoints = resolver.resolve(tcp::v4(), request.host(), port, ec); - - if (ec) { - LOG_ERRO("Host resolve error (%s): %s, %s.", ec.message().c_str(), - request.host().c_str(), port.c_str()); - return kHostResolveError; - } - - LOG_VERB("Connect to server..."); - - // Use sync API directly since we don't need timeout control. - boost::asio::connect(socket_, endpoints, ec); - - // Determine whether a connection was successfully established. - if (ec) { - LOG_ERRO("Socket connect error (%s).", ec.message().c_str()); - Stop(); - return kEndpointConnectError; - } - - LOG_VERB("Socket connected."); - - return kNoError; -} - -Error HttpClient::SendReqeust(const HttpRequest& request) { - LOG_VERB("HTTP request:\n%s", request.Dump(4, "> ").c_str()); - - // 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::system::error_code ec; - - // Use sync API directly since we don't need timeout control. - boost::asio::write(socket_, request.ToBuffers(), ec); - - if (ec) { - LOG_ERRO("Socket write error (%s).", ec.message().c_str()); - Stop(); - return kSocketWriteError; - } - - LOG_INFO("Request sent."); - - return kNoError; -} - -Error HttpClient::ReadResponse() { - LOG_VERB("Read response (timeout: %ds)...", timeout_seconds_); - - deadline_.expires_from_now(boost::posix_time::seconds(timeout_seconds_)); - DoWaitDeadline(); - - Error error = kNoError; - DoReadResponse(&error); - - if (error == kNoError) { - LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str()); - } - - return error; + : HttpClientBase(buffer_size), socket_(io_context_) { } -void HttpClient::DoReadResponse(Error* error) { - boost::system::error_code ec = boost::asio::error::would_block; - - // ReadHandler: void(boost::system::error_code, std::size_t) - socket_.async_read_some( - boost::asio::buffer(buffer_), - [this, &ec, error](boost::system::error_code inner_ec, - std::size_t length) { - ec = inner_ec; - - LOG_VERB("Socket async read handler."); - - if (ec || length == 0) { - Stop(); - *error = kSocketReadError; - LOG_ERRO("Socket read error (%s).", ec.message().c_str()); - return; - } - - LOG_INFO("Read data, length: %u.", length); - - // Parse the response piece just read. - if (!response_parser_->Parse(buffer_.data(), length)) { - Stop(); - *error = kHttpError; - LOG_ERRO("Failed to parse HTTP response."); - return; - } - - if (response_parser_->finished()) { - // Stop trying to read once all content has been received, because - // some servers will block extra call to read_some(). - Stop(); - - LOG_INFO("Finished to read and parse HTTP response."); - LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str()); - - return; - } - - if (!stopped_) { - DoReadResponse(error); - } - }); - - // Block until the asynchronous operation has completed. - do { - io_context_.run_one(); - } while (ec == boost::asio::error::would_block); +void HttpClient::SocketConnect(const Endpoints& endpoints, + boost::system::error_code* ec) { + boost::asio::connect(socket_, endpoints, *ec); } -void HttpClient::DoWaitDeadline() { - deadline_.async_wait(std::bind(&HttpClient::OnDeadline, this, - std::placeholders::_1)); +void HttpClient::SocketWrite(const HttpRequest& request, + boost::system::error_code* ec) { + boost::asio::write(socket_, request.ToBuffers(), *ec); } -void HttpClient::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 HttpClient::SocketAsyncReadSome(std::vector& buffer, + ReadHandler handler) { + socket_.async_read_some(boost::asio::buffer(buffer), handler); } -void HttpClient::Stop() { - 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 HttpClient::SocketClose(boost::system::error_code* ec) { + socket_.close(*ec); } } // namespace webcc diff --git a/webcc/http_client.h b/webcc/http_client.h index 5fd90ec..b86be21 100644 --- a/webcc/http_client.h +++ b/webcc/http_client.h @@ -1,85 +1,38 @@ #ifndef WEBCC_HTTP_CLIENT_H_ #define WEBCC_HTTP_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_client_base.h" namespace webcc { -// HTTP client session in synchronous mode. +// 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. -class HttpClient { +class HttpClient : public HttpClientBase { public: - // 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); - // Set the timeout seconds for reading response. - // The |seconds| is only effective when greater than 0. - void SetTimeout(int seconds); - - // Connect to server, send request, wait until response is received. - // 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_; } - - bool timed_out() const { return timed_out_; } - - Error error() const { return error_; } - private: - Error Connect(const HttpRequest& request); - - Error SendReqeust(const HttpRequest& request); + Error Connect(const HttpRequest& request) final { + return DoConnect(request, kHttpPort); + } - Error ReadResponse(); + void SocketConnect(const Endpoints& endpoints, + boost::system::error_code* ec) final; - void DoReadResponse(Error* error); + void SocketWrite(const HttpRequest& request, + boost::system::error_code* ec) final; - void DoWaitDeadline(); - void OnDeadline(boost::system::error_code ec); + void SocketAsyncReadSome(std::vector& buffer, + ReadHandler handler) final; - void Stop(); + void SocketClose(boost::system::error_code* ec) final; - boost::asio::io_context io_context_; boost::asio::ip::tcp::socket socket_; - - std::vector buffer_; - - 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. - bool timed_out_; - - Error error_; }; } // namespace webcc diff --git a/webcc/http_client_base.cc b/webcc/http_client_base.cc new file mode 100644 index 0000000..f5435bf --- /dev/null +++ b/webcc/http_client_base.cc @@ -0,0 +1,232 @@ +#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" +#include "webcc/utility.h" + +using boost::asio::ip::tcp; + +namespace webcc { + +HttpClientBase::HttpClientBase(std::size_t buffer_size) + : buffer_(buffer_size == 0 ? kBufferSize : buffer_size), + deadline_(io_context_), + timeout_seconds_(kMaxReadSeconds), + stopped_(false), + timed_out_(false), + error_(kNoError) { +} + +void HttpClientBase::SetTimeout(int seconds) { + if (seconds > 0) { + timeout_seconds_ = seconds; + } +} + +bool HttpClientBase::Request(const HttpRequest& request, + std::size_t buffer_size) { + io_context_.restart(); + + response_.reset(new HttpResponse()); + response_parser_.reset(new HttpResponseParser(response_.get())); + + stopped_ = false; + timed_out_ = false; + error_ = kNoError; + + BufferResizer buffer_resizer(&buffer_, buffer_size); + + if ((error_ = Connect(request)) != kNoError) { + return false; + } + + if ((error_ = SendReqeust(request)) != kNoError) { + return false; + } + + if ((error_ = ReadResponse()) != kNoError) { + return false; + } + + return true; +} + +Error HttpClientBase::DoConnect(const HttpRequest& request, + const std::string& default_port) { + tcp::resolver resolver(io_context_); + + std::string port = request.port(default_port); + + boost::system::error_code ec; + auto endpoints = resolver.resolve(tcp::v4(), request.host(), port, ec); + + if (ec) { + LOG_ERRO("Host resolve error (%s): %s, %s.", ec.message().c_str(), + request.host().c_str(), port.c_str()); + return kHostResolveError; + } + + LOG_VERB("Connect to server..."); + + // Use sync API directly since we don't need timeout control. + SocketConnect(endpoints, &ec); + + // Determine whether a connection was successfully established. + if (ec) { + LOG_ERRO("Socket connect error (%s).", ec.message().c_str()); + Stop(); + return kEndpointConnectError; + } + + LOG_VERB("Socket connected."); + + return kNoError; +} + +Error HttpClientBase::SendReqeust(const HttpRequest& request) { + LOG_VERB("HTTP request:\n%s", request.Dump(4, "> ").c_str()); + + // 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::system::error_code ec; + + // Use sync API directly since we don't need timeout control. + SocketWrite(request, &ec); + + if (ec) { + LOG_ERRO("Socket write error (%s).", ec.message().c_str()); + Stop(); + return kSocketWriteError; + } + + LOG_INFO("Request sent."); + + return kNoError; +} + +Error HttpClientBase::ReadResponse() { + LOG_VERB("Read response (timeout: %ds)...", timeout_seconds_); + + deadline_.expires_from_now(boost::posix_time::seconds(timeout_seconds_)); + DoWaitDeadline(); + + Error error = kNoError; + DoReadResponse(&error); + + if (error == kNoError) { + LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str()); + } + + return error; +} + +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) { + ec = inner_ec; + + LOG_VERB("Socket async read handler."); + + if (ec || length == 0) { + Stop(); + *error = kSocketReadError; + LOG_ERRO("Socket read error (%s).", ec.message().c_str()); + return; + } + + LOG_INFO("Read data, length: %u.", length); + + // Parse the response piece just read. + if (!response_parser_->Parse(buffer_.data(), length)) { + Stop(); + *error = kHttpError; + LOG_ERRO("Failed to parse HTTP response."); + return; + } + + if (response_parser_->finished()) { + // Stop trying to read once all content has been received, because + // some servers will block extra call to read_some(). + Stop(); + + LOG_INFO("Finished to read and parse HTTP response."); + + return; + } + + if (!stopped_) { + DoReadResponse(error); + } + }; + + SocketAsyncReadSome(buffer_, read_handler); + + // Block until the asynchronous operation has completed. + do { + io_context_.run_one(); + } while (ec == boost::asio::error::would_block); +} + +void HttpClientBase::DoWaitDeadline() { + deadline_.async_wait( + std::bind(&HttpClientBase::OnDeadline, this, std::placeholders::_1)); +} + +void HttpClientBase::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 HttpClientBase::Stop() { + 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_client_base.h b/webcc/http_client_base.h new file mode 100644 index 0000000..74d8e87 --- /dev/null +++ b/webcc/http_client_base.h @@ -0,0 +1,103 @@ +#ifndef WEBCC_HTTP_CLIENT_BASE_H_ +#define WEBCC_HTTP_CLIENT_BASE_H_ + +#include +#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 { + +class HttpClientBase { + public: + // The |buffer_size| is the bytes of the buffer for reading response. + // 0 means default value (e.g., 1024) will be used. + explicit HttpClientBase(std::size_t buffer_size = 0); + + ~HttpClientBase() = default; + + WEBCC_DELETE_COPY_ASSIGN(HttpClientBase); + + // Set the timeout seconds for reading response. + // The |seconds| is only effective when greater than 0. + void SetTimeout(int seconds); + + // Connect to server, send request, wait until response is received. + // 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_; } + + bool timed_out() const { return timed_out_; } + + Error error() const { return error_; } + + protected: + typedef boost::asio::ip::tcp::resolver::results_type Endpoints; + + typedef std::function + ReadHandler; + + Error DoConnect(const HttpRequest& request, const std::string& default_port); + + boost::asio::io_context io_context_; + + private: + Error SendReqeust(const HttpRequest& request); + + Error ReadResponse(); + + void DoReadResponse(Error* error); + + void DoWaitDeadline(); + void OnDeadline(boost::system::error_code ec); + + 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 SocketClose(boost::system::error_code* ec) = 0; + + std::vector buffer_; + + 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. + bool timed_out_; + + Error error_; +}; + +} // namespace webcc + +#endif // WEBCC_HTTP_CLIENT_BASE_H_ diff --git a/webcc/http_ssl_client.cc b/webcc/http_ssl_client.cc index 7aa65e9..c661fbb 100644 --- a/webcc/http_ssl_client.cc +++ b/webcc/http_ssl_client.cc @@ -1,101 +1,32 @@ #include "webcc/http_ssl_client.h" -#include - #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" -#include "webcc/utility.h" -using boost::asio::ip::tcp; namespace ssl = boost::asio::ssl; namespace webcc { HttpSslClient::HttpSslClient(std::size_t buffer_size, bool ssl_verify) - : ssl_context_(ssl::context::sslv23), + : HttpClientBase(buffer_size), + ssl_context_(ssl::context::sslv23), ssl_socket_(io_context_, ssl_context_), - buffer_(buffer_size == 0 ? kBufferSize : buffer_size), - deadline_(io_context_), - ssl_verify_(ssl_verify), - timeout_seconds_(kMaxReadSeconds), - stopped_(false), - timed_out_(false), - error_(kNoError) { + ssl_verify_(ssl_verify) { // Use the default paths for finding CA certificates. ssl_context_.set_default_verify_paths(); } -void HttpSslClient::SetTimeout(int seconds) { - if (seconds > 0) { - timeout_seconds_ = seconds; - } -} - -bool HttpSslClient::Request(const HttpRequest& request, - std::size_t buffer_size) { - io_context_.restart(); - - response_.reset(new HttpResponse()); - response_parser_.reset(new HttpResponseParser(response_.get())); - - stopped_ = false; - timed_out_ = false; - error_ = kNoError; - - BufferResizer buffer_resizer(&buffer_, buffer_size); - - if ((error_ = Connect(request)) != kNoError) { - return false; - } - - if ((error_ = Handshake(request.host())) != kNoError) { - return false; - } - - if ((error_ = SendReqeust(request)) != kNoError) { - return false; - } - - if ((error_ = ReadResponse()) != kNoError) { - return false; - } - - return true; -} - Error HttpSslClient::Connect(const HttpRequest& request) { - tcp::resolver resolver(io_context_); - - std::string port = request.port(kHttpSslPort); - - boost::system::error_code ec; - auto endpoints = resolver.resolve(tcp::v4(), request.host(), port, ec); - - if (ec) { - LOG_ERRO("Can't resolve host (%s): %s, %s", ec.message().c_str(), - request.host().c_str(), port.c_str()); - return kHostResolveError; - } - - LOG_VERB("Connect to server..."); - - // Use sync API directly since we don't need timeout control. - boost::asio::connect(ssl_socket_.lowest_layer(), endpoints, ec); - - // Determine whether a connection was successfully established. - if (ec) { - LOG_ERRO("Socket connect error (%s).", ec.message().c_str()); - Stop(); - return kEndpointConnectError; + Error error = DoConnect(request, kHttpSslPort); + + if (error != kNoError) { + return error; } - LOG_VERB("Socket connected."); - - return kNoError; + return Handshake(request.host()); } // NOTE: Don't check timeout. It doesn't make much sense. @@ -120,138 +51,23 @@ Error HttpSslClient::Handshake(const std::string& host) { return kNoError; } -Error HttpSslClient::SendReqeust(const HttpRequest& request) { - LOG_VERB("HTTP request:\n%s", request.Dump(4, "> ").c_str()); - - // 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::system::error_code ec; - - // Use sync API directly since we don't need timeout control. - boost::asio::write(ssl_socket_, request.ToBuffers(), ec); - - if (ec) { - LOG_ERRO("Socket write error (%s).", ec.message().c_str()); - Stop(); - return kSocketWriteError; - } - - LOG_INFO("Request sent."); - - return kNoError; -} - -Error HttpSslClient::ReadResponse() { - LOG_VERB("Read response (timeout: %ds)...", timeout_seconds_); - - deadline_.expires_from_now(boost::posix_time::seconds(timeout_seconds_)); - DoWaitDeadline(); - - Error error = kNoError; - DoReadResponse(&error); - - if (error == kNoError) { - LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str()); - } - - return error; -} - -void HttpSslClient::DoReadResponse(Error* error) { - boost::system::error_code ec = boost::asio::error::would_block; - - // ReadHandler: void(boost::system::error_code, std::size_t) - ssl_socket_.async_read_some( - boost::asio::buffer(buffer_), - [this, &ec, error](boost::system::error_code inner_ec, - std::size_t length) { - ec = inner_ec; - - LOG_VERB("Socket async read handler."); - - if (ec || length == 0) { - Stop(); - *error = kSocketReadError; - LOG_ERRO("Socket read error (%s).", ec.message().c_str()); - return; - } - - LOG_INFO("Read data, length: %u.", length); - - // Parse the response piece just read. - if (!response_parser_->Parse(buffer_.data(), length)) { - Stop(); - *error = kHttpError; - LOG_ERRO("Failed to parse HTTP response."); - return; - } - - if (response_parser_->finished()) { - // Stop trying to read once all content has been received, - // because some servers will block extra call to read_some(). - Stop(); - LOG_INFO("Finished to read and parse HTTP response."); - return; - } - - if (!stopped_) { - DoReadResponse(error); - } - }); - - // Block until the asynchronous operation has completed. - do { - io_context_.run_one(); - } while (ec == boost::asio::error::would_block); +void HttpSslClient::SocketConnect(const Endpoints& endpoints, + boost::system::error_code* ec) { + boost::asio::connect(ssl_socket_.lowest_layer(), endpoints, *ec); } -void HttpSslClient::DoWaitDeadline() { - deadline_.async_wait(std::bind(&HttpSslClient::OnDeadline, this, - std::placeholders::_1)); +void HttpSslClient::SocketWrite(const HttpRequest& request, + boost::system::error_code* ec) { + boost::asio::write(ssl_socket_, request.ToBuffers(), *ec); } -void HttpSslClient::OnDeadline(boost::system::error_code ec) { - if (stopped_) { - return; - } - - LOG_VERB("OnDeadline."); - - 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 HttpSslClient::SocketAsyncReadSome(std::vector& buffer, + ReadHandler handler) { + ssl_socket_.async_read_some(boost::asio::buffer(buffer), handler); } -void HttpSslClient::Stop() { - if (stopped_) { - return; - } - - stopped_ = true; - - LOG_INFO("Close socket..."); - - boost::system::error_code ec; - ssl_socket_.lowest_layer().close(ec); - if (ec) { - LOG_ERRO("Socket close error (%s).", ec.message().c_str()); - } - - LOG_INFO("Cancel deadline timer..."); - deadline_.cancel(); +void HttpSslClient::SocketClose(boost::system::error_code* ec) { + ssl_socket_.lowest_layer().close(*ec); } } // namespace webcc diff --git a/webcc/http_ssl_client.h b/webcc/http_ssl_client.h index 003f73d..f333c85 100644 --- a/webcc/http_ssl_client.h +++ b/webcc/http_ssl_client.h @@ -1,30 +1,17 @@ #ifndef WEBCC_HTTP_SSL_CLIENT_H_ #define WEBCC_HTTP_SSL_CLIENT_H_ -#include -#include -#include -#include - -#include "boost/asio/deadline_timer.hpp" -#include "boost/asio/io_context.hpp" -#include "boost/asio/ip/tcp.hpp" -#include "boost/asio/ssl.hpp" +#include "webcc/http_client_base.h" -#include "webcc/globals.h" -#include "webcc/http_request.h" -#include "webcc/http_response.h" -#include "webcc/http_response_parser.h" +#include "boost/asio/ssl.hpp" 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 HttpClient object in multiple threads. -class HttpSslClient { +// Don't use the same HttpSslClient object in multiple threads. +class HttpSslClient : public HttpClientBase { public: - // 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. @@ -34,62 +21,28 @@ class HttpSslClient { WEBCC_DELETE_COPY_ASSIGN(HttpSslClient); - // Set the timeout seconds for reading response. - // The |seconds| is only effective when greater than 0. - void SetTimeout(int seconds); - - // Connect to server, send request, wait until response is received. - // 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_; } - - bool timed_out() const { return timed_out_; } - - Error error() const { return error_; } - private: - Error Connect(const HttpRequest& request); - Error Handshake(const std::string& host); - Error SendReqeust(const HttpRequest& request); + // Override to do handshake after connected. + Error Connect(const HttpRequest& request) final; - Error ReadResponse(); + void SocketConnect(const Endpoints& endpoints, + boost::system::error_code* ec) final; - void DoReadResponse(Error* error); + void SocketWrite(const HttpRequest& request, + boost::system::error_code* ec) final; - void DoWaitDeadline(); - void OnDeadline(boost::system::error_code ec); + void SocketAsyncReadSome(std::vector& buffer, + ReadHandler handler) final; - void Stop(); - - boost::asio::io_context io_context_; + void SocketClose(boost::system::error_code* ec) final; boost::asio::ssl::context ssl_context_; boost::asio::ssl::stream ssl_socket_; - std::vector buffer_; - - HttpResponsePtr response_; - std::unique_ptr response_parser_; - - boost::asio::deadline_timer deadline_; - // Verify the certificate of the peer (remote server) or not. bool ssl_verify_; - - // Maximum seconds to wait before the client cancels the operation. - // Only for receiving response from server. - int timeout_seconds_; - - bool stopped_; - - // If the error was caused by timeout or not. - bool timed_out_; - - Error error_; }; } // namespace webcc