From da1dbbb0736e6e328b12236e3a489881e118f4fc Mon Sep 17 00:00:00 2001 From: Chunting Gu Date: Fri, 22 Mar 2019 10:17:27 +0800 Subject: [PATCH] Add HttpRequestBuilder to replace HttpRequestArgs --- examples/github_client.cc | 23 ++-- examples/http_client.cc | 74 +++++-------- examples/rest_book_client.cc | 19 +++- webcc/CMakeLists.txt | 3 +- webcc/http_client.cc | 36 ++++--- webcc/http_client.h | 28 +---- webcc/http_client_session.cc | 199 +++++++++++------------------------ webcc/http_client_session.h | 39 ++----- webcc/http_message.cc | 8 +- webcc/http_message.h | 16 +-- webcc/http_request.h | 10 ++ webcc/http_request_args.h | 123 ---------------------- webcc/http_request_builder.h | 123 ++++++++++++++++++++++ webcc/soap_client.cc | 31 +++--- webcc/soap_client.h | 3 +- 15 files changed, 313 insertions(+), 422 deletions(-) delete mode 100644 webcc/http_request_args.h create mode 100644 webcc/http_request_builder.h diff --git a/examples/github_client.cc b/examples/github_client.cc index 59d11dc..fe8779a 100644 --- a/examples/github_client.cc +++ b/examples/github_client.cc @@ -65,8 +65,11 @@ void PrettyPrintJsonString(const std::string& str) { // List public events. void ListEvents(webcc::HttpClientSession& session) { try { - auto r = session.Get(kUrlRoot + "/events"); + auto r = session.Request(webcc::HttpRequestBuilder{}.Get(). + url(kUrlRoot + "/events")()); + PRINT_JSON_STRING(r->content()); + } catch (const webcc::Exception& e) { std::cout << e.what() << std::endl; } @@ -78,8 +81,11 @@ void ListEvents(webcc::HttpClientSession& session) { void ListUserFollowers(webcc::HttpClientSession& session, const std::string& user) { try { - auto r = session.Get(kUrlRoot + "/users/" + user + "/followers"); + auto r = session.Request(webcc::HttpRequestBuilder{}.Get(). + url(kUrlRoot + "/users/" + user + "/followers")()); + PRINT_JSON_STRING(r->content()); + } catch (const webcc::Exception& e) { std::cout << e.what() << std::endl; } @@ -93,8 +99,9 @@ void ListUserFollowers(webcc::HttpClientSession& session, void ListAuthUserFollowers(webcc::HttpClientSession& session, const std::string& auth) { try { - auto r = session.Get(kUrlRoot + "/user/followers", {}, - { "Authorization", auth }); + auto r = session.Request(webcc::HttpRequestBuilder{}.Get(). + url(kUrlRoot + "/user/followers"). + header("Authorization", auth)()); PRINT_JSON_STRING(r->content()); @@ -109,8 +116,11 @@ void CreateAuthorization(webcc::HttpClientSession& session, std::string data = "{'note': 'Webcc test', 'scopes': ['public_repo', 'repo', 'repo:status', 'user']}"; - auto r = session.Post(kUrlRoot + "/authorizations", std::move(data), true, - {"Authorization", auth}); + auto r = session.Request(webcc::HttpRequestBuilder{}.Post(). + url(kUrlRoot + "/authorizations"). + data(std::move(data)). + json(true). + header("Authorization", auth)()); std::cout << r->content() << std::endl; @@ -125,7 +135,6 @@ int main() { WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); webcc::HttpClientSession session; - session.set_ssl_verify(kSslVerify); ListEvents(session); diff --git a/examples/http_client.cc b/examples/http_client.cc index ad191f3..94ad862 100644 --- a/examples/http_client.cc +++ b/examples/http_client.cc @@ -19,55 +19,27 @@ bool kSslVerify = true; // Examples void ExampleBasic() { - HttpClientSession session; - - auto r = session.Request(HttpRequestArgs{"GET"} - .url("http://httpbin.org/get") - .parameters({"key1", "value1", "key2", "value2"}) - .headers({"Accept", "application/json"}) - .buffer_size(1000)); + webcc::HttpClientSession session; + + auto r = session.Request(webcc::HttpRequestBuilder{}.Get(). + url("http://httpbin.org/get"). + parameter("key1", "value1"). + parameter("key2", "value2"). + header("Accept", "application/json"). + buffer(1000)()); std::cout << r->content() << std::endl; } -// If you want to create the args object firstly, there might be an extra -// call to its move constructor (maybe only for MSVC). -// - constructor: HttpRequestArgs{ "GET" } -// - move constructor: auto args = ... -void ExampleArgs() { - HttpClientSession session; - - auto args = HttpRequestArgs{"GET"} - .url("http://httpbin.org/get") - .parameters({"key1", "value1", "key2", "value2"}) - .headers({"Accept", "application/json"}) - .buffer_size(1000); - - // Note the std::move(). - session.Request(std::move(args)); -} - -// Use pre-defined wrappers. -void ExampleWrappers() { - HttpClientSession session; - - session.Get("http://httpbin.org/get", {"key1", "value1", "key2", "value2"}, - {"Accept", "application/json"}, - HttpRequestArgs{}.buffer_size(1000)); - - session.Post("http://httpbin.org/post", "{'key': 'value'}", true, - {"Accept", "application/json"}); -} - // HTTPS is auto-detected from the URL scheme. void ExampleHttps() { - HttpClientSession session; + webcc::HttpClientSession session; + session.set_ssl_verify(kSslVerify); - auto r = session.Request(HttpRequestArgs{"GET"} - .url("https://httpbin.org/get") - .parameters({"key1", "value1", "key2", "value2"}) - .headers({"Accept", "application/json"}) - .ssl_verify(kSslVerify)); + auto r = session.Request(webcc::HttpRequestBuilder{}.Get(). + url("https://httpbin.org/get"). + parameter("key1", "value1"). + header("Accept", "application/json")()); std::cout << r->content() << std::endl; } @@ -86,27 +58,29 @@ void ExampleHttps() { // void ExampleKeepAlive(const std::string& url) { HttpClientSession session; + session.set_ssl_verify(kSslVerify); // Keep-Alive - session.Request(webcc::HttpRequestArgs("GET").url(url). - ssl_verify(kSslVerify)); + session.Request(webcc::HttpRequestBuilder{}.Get().url(url)()); // Close - session.Request(webcc::HttpRequestArgs("GET").url(url). - ssl_verify(kSslVerify).keep_alive(false)); + session.Request(webcc::HttpRequestBuilder{}.Get().url(url).keep_alive(false)()); // Keep-Alive - session.Request(webcc::HttpRequestArgs("GET").url(url). - ssl_verify(kSslVerify)); + session.Request(webcc::HttpRequestBuilder{}.Get().url(url)()); } void ExampleCompression() { HttpClientSession session; - auto r = session.Get("http://httpbin.org/gzip"); + auto r = session.Request(webcc::HttpRequestBuilder{}. + Get().url("http://httpbin.org/gzip")()); + std::cout << r->content() << std::endl; - r = session.Get("http://httpbin.org/deflate"); + r = session.Request(webcc::HttpRequestBuilder{}. + Get().url("http://httpbin.org/deflate")()); + std::cout << r->content() << std::endl; } diff --git a/examples/rest_book_client.cc b/examples/rest_book_client.cc index eca6791..2768eb3 100644 --- a/examples/rest_book_client.cc +++ b/examples/rest_book_client.cc @@ -55,7 +55,8 @@ public: bool ListBooks(std::list* books) { try { - auto r = session_.Get(url_ + "/books"); + auto r = session_.Request(webcc::HttpRequestBuilder{}.Get(). + url(url_ + "/books")()); if (!CheckStatus(r, webcc::http::Status::kOK)) { // Response HTTP status error. @@ -86,7 +87,10 @@ public: req_json["price"] = price; try { - auto r = session_.Post(url_ + "/books", JsonToString(req_json), true); + auto r = session_.Request(webcc::HttpRequestBuilder{}.Post(). + url(url_ + "/books"). + data(JsonToString(req_json)). + json(true)()); if (!CheckStatus(r, webcc::http::Status::kCreated)) { return false; @@ -114,7 +118,8 @@ public: bool GetBook(const std::string& id, Book* book) { try { - auto r = session_.Get(url_ + "/books/" + id); + auto r = session_.Request(webcc::HttpRequestBuilder{}.Get(). + url(url_ + "/books/" + id)()); if (!CheckStatus(r, webcc::http::Status::kOK)) { return false; @@ -135,7 +140,10 @@ public: json["price"] = price; try { - auto r = session_.Put(url_ + "/books/" + id, JsonToString(json), true); + auto r = session_.Request(webcc::HttpRequestBuilder{}.Put(). + url(url_ + "/books/" + id). + data(JsonToString(json)). + json(true)()); if (!CheckStatus(r, webcc::http::Status::kOK)) { return false; @@ -151,7 +159,8 @@ public: bool DeleteBook(const std::string& id) { try { - auto r = session_.Delete(url_ + "/books/" + id); + auto r = session_.Request(webcc::HttpRequestBuilder{}.Delete(). + url(url_ + "/books/" + id)()); if (!CheckStatus(r, webcc::http::Status::kOK)) { return false; diff --git a/webcc/CMakeLists.txt b/webcc/CMakeLists.txt index a9473eb..85ca38b 100644 --- a/webcc/CMakeLists.txt +++ b/webcc/CMakeLists.txt @@ -22,7 +22,7 @@ set(HEADERS http_message.h http_parser.h http_request.h - http_request_args.h + http_request_builder.h http_request_handler.h http_request_parser.h http_response.h @@ -49,6 +49,7 @@ set(SOURCES http_message.cc http_parser.cc http_request.cc + http_request_builder.cc http_request_handler.cc http_request_parser.cc http_response.cc diff --git a/webcc/http_client.cc b/webcc/http_client.cc index f5e9811..fe347a2 100644 --- a/webcc/http_client.cc +++ b/webcc/http_client.cc @@ -9,10 +9,9 @@ using boost::asio::ip::tcp; namespace webcc { -HttpClient::HttpClient(bool ssl_verify, std::size_t buffer_size) +HttpClient::HttpClient(bool ssl_verify) : timer_(io_context_), ssl_verify_(ssl_verify), - buffer_size_(buffer_size == 0 ? kBufferSize : buffer_size), timeout_(kMaxReadSeconds), closed_(false), timer_canceled_(false), @@ -20,7 +19,7 @@ HttpClient::HttpClient(bool ssl_verify, std::size_t buffer_size) error_(kNoError) { } -bool HttpClient::Request(const HttpRequest& request, bool connect) { +bool HttpClient::Request(HttpRequestPtr request, bool connect) { io_context_.restart(); response_.reset(new HttpResponse()); @@ -31,9 +30,14 @@ bool HttpClient::Request(const HttpRequest& request, bool connect) { timed_out_ = false; error_ = kNoError; - if (buffer_.size() != buffer_size_) { - LOG_VERB("Resize buffer: %u -> %u.", buffer_.size(), buffer_size_); - buffer_.resize(buffer_size_); + std::size_t buffer_size = request->buffer_size(); + if (buffer_size == 0) { + buffer_size = kBufferSize; + } + + if (buffer_.size() != buffer_size) { + LOG_VERB("Resize buffer: %u -> %u.", buffer_.size(), buffer_size); + buffer_.resize(buffer_size); } if (connect) { @@ -71,8 +75,8 @@ void HttpClient::Close() { } } -Error HttpClient::Connect(const HttpRequest& request) { - if (request.url().scheme() == "https") { +Error HttpClient::Connect(HttpRequestPtr request) { + if (request->url().scheme() == "https") { socket_.reset(new HttpSslSocket{ io_context_, ssl_verify_ }); return DoConnect(request, kPort443); } else { @@ -81,25 +85,25 @@ Error HttpClient::Connect(const HttpRequest& request) { } } -Error HttpClient::DoConnect(const HttpRequest& request, +Error HttpClient::DoConnect(HttpRequestPtr request, const std::string& default_port) { tcp::resolver resolver(io_context_); - std::string port = request.port(default_port); + std::string port = request->port(default_port); boost::system::error_code ec; - auto endpoints = resolver.resolve(tcp::v4(), request.host(), port, 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()); + 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. - socket_->Connect(request.host(), endpoints, &ec); + socket_->Connect(request->host(), endpoints, &ec); // Determine whether a connection was successfully established. if (ec) { @@ -113,8 +117,8 @@ Error HttpClient::DoConnect(const HttpRequest& request, return kNoError; } -Error HttpClient::WriteReqeust(const HttpRequest& request) { - LOG_VERB("HTTP request:\n%s", request.Dump(4, "> ").c_str()); +Error HttpClient::WriteReqeust(HttpRequestPtr 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. @@ -124,7 +128,7 @@ Error HttpClient::WriteReqeust(const HttpRequest& request) { boost::system::error_code ec; // Use sync API directly since we don't need timeout control. - socket_->Write(request, &ec); + socket_->Write(*request, &ec); if (ec) { LOG_ERRO("Socket write error (%s).", ec.message().c_str()); diff --git a/webcc/http_client.h b/webcc/http_client.h index 5d1cdba..16a3de3 100644 --- a/webcc/http_client.h +++ b/webcc/http_client.h @@ -27,17 +27,13 @@ typedef std::shared_ptr HttpClientPtr; // Please don't use the same client object in multiple threads. class HttpClient { public: - explicit HttpClient(bool ssl_verify = true, std::size_t buffer_size = 0); + explicit HttpClient(bool ssl_verify = true); virtual ~HttpClient() = default; HttpClient(const HttpClient&) = delete; HttpClient& operator=(const HttpClient&) = delete; - void set_buffer_size(std::size_t buffer_size) { - buffer_size_ = (buffer_size == 0 ? kBufferSize : buffer_size); - } - void set_ssl_verify(bool ssl_verify) { ssl_verify_ = ssl_verify; } @@ -50,23 +46,13 @@ public: } // Connect to server, send request, wait until response is received. - bool Request(const HttpRequest& request, bool connect = true); + bool Request(HttpRequestPtr request, bool connect = true); // Close the socket. void Close(); 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 closed() const { return closed_; } bool timed_out() const { return timed_out_; } @@ -74,11 +60,11 @@ public: Error error() const { return error_; } private: - Error Connect(const HttpRequest& request); + Error Connect(HttpRequestPtr request); - Error DoConnect(const HttpRequest& request, const std::string& default_port); + Error DoConnect(HttpRequestPtr request, const std::string& default_port); - Error WriteReqeust(const HttpRequest& request); + Error WriteReqeust(HttpRequestPtr request); Error ReadResponse(); @@ -108,10 +94,6 @@ private: // Verify the certificate of the peer or not (for HTTPS). bool ssl_verify_; - // The size of the buffer for reading response. - // Set 0 for using default value (e.g., 1024). - std::size_t buffer_size_; - // Maximum seconds to wait before the client cancels the operation. // Only for reading response from server. int timeout_; diff --git a/webcc/http_client_session.cc b/webcc/http_client_session.cc index 0279423..812da85 100644 --- a/webcc/http_client_session.cc +++ b/webcc/http_client_session.cc @@ -1,7 +1,7 @@ #include "webcc/http_client_session.h" +#include "webcc/logger.h" #include "webcc/url.h" -#include "webcc/zlib_wrapper.h" namespace webcc { @@ -10,25 +10,15 @@ namespace { // ----------------------------------------------------------------------------- // Helper functions. -bool GetSslVerify(const boost::optional& session_ssl_verify, - const boost::optional& request_ssl_verify) { - if (request_ssl_verify) { - return request_ssl_verify.value(); - } else if (session_ssl_verify) { - return session_ssl_verify.value(); - } - return true; -} - -std::size_t GetBufferSize(std::size_t session_buffer_size, - std::size_t request_buffer_size) { - if (request_buffer_size != 0) { - return request_buffer_size; - } else if (session_buffer_size != 0) { - return session_buffer_size; - } - return 0; -} +//bool GetSslVerify(const boost::optional& session_ssl_verify, +// const boost::optional& request_ssl_verify) { +// if (request_ssl_verify) { +// return request_ssl_verify.value(); +// } else if (session_ssl_verify) { +// return session_ssl_verify.value(); +// } +// return true; +//} } // namespace @@ -38,72 +28,74 @@ HttpClientSession::HttpClientSession() { InitHeaders(); } -HttpResponsePtr HttpClientSession::Request(HttpRequestArgs&& args) { - assert(args.parameters_.size() % 2 == 0); - assert(args.headers_.size() % 2 == 0); +HttpResponsePtr HttpClientSession::Request(HttpRequestPtr request) { + assert(request); - HttpRequest request{args.method_, args.url_}; - - for (std::size_t i = 1; i < args.parameters_.size(); i += 2) { - request.AddParameter(args.parameters_[i - 1], args.parameters_[i]); + for (const auto& h : headers_.data()) { + if (!request->HaveHeader(h.first)) { + request->SetHeader(h.first, h.second); + } } - // Apply the session-level headers. - for (const HttpHeader& h : headers_.data()) { - request.SetHeader(h.first, h.second); + if (!content_type_.empty() && + !request->HaveHeader(http::headers::kContentType)) { + request->SetContentType(content_type_, charset_); } - // Apply the request-level headers. - // This will overwrite the session-level headers. - for (std::size_t i = 1; i < args.headers_.size(); i += 2) { - request.SetHeader(std::move(args.headers_[i - 1]), - std::move(args.headers_[i])); - } + request->Prepare(); - // No keep-alive? - if (!args.keep_alive_) { - request.SetHeader(http::headers::kConnection, "Close"); - } + return Send(request); +} - if (!args.data_.empty()) { - if (gzip_ && args.data_.size() > kGzipThreshold) { - std::string compressed; - if (Compress(args.data_, &compressed)) { - request.SetContent(std::move(compressed), true); - request.SetHeader(http::headers::kContentEncoding, "gzip"); - } else { - LOG_WARN("Cannot compress the content data!"); - request.SetContent(std::move(args.data_), true); - } - } else { - request.SetContent(std::move(args.data_), true); - } +void HttpClientSession::InitHeaders() { + using namespace http::headers; - // TODO: Request-level charset. - if (args.json_) { - request.SetContentType(http::media_types::kApplicationJson, charset_); - } else if (!content_type_.empty()) { - request.SetContentType(content_type_, charset_); - } - } + headers_.Set(kUserAgent, http::UserAgent()); - request.Prepare(); + // Content-Encoding Tokens: + // (https://en.wikipedia.org/wiki/HTTP_compression) + // * compress 每 UNIX "compress" program method (historic; deprecated in most + // applications and replaced by gzip or deflate); + // * deflate 每 compression based on the deflate algorithm, a combination of + // the LZ77 algorithm and Huffman coding, wrapped inside the + // zlib data format; + // * gzip 每 GNU zip format. Uses the deflate algorithm for compression, + // but the data format and the checksum algorithm differ from + // the "deflate" content-encoding. This method is the most + // broadly supported as of March 2011. + // * identity 每 No transformation is used. This is the default value for + // content coding. + // + // A note about "deflate": + // (https://www.zlib.net/zlib_faq.html#faq39) + // "gzip" is the gzip format, and "deflate" is the zlib format. They should + // probably have called the second one "zlib" instead to avoid confusion with + // the raw deflate compressed data format. + // Simply put, "deflate" is not recommended for HTTP 1.1 encoding. + // + headers_.Set(kAcceptEncoding, "gzip, deflate"); + + headers_.Set(kAccept, "*/*"); - bool ssl_verify = GetSslVerify(ssl_verify_, args.ssl_verify_); - std::size_t buffer_size = GetBufferSize(buffer_size_, args.buffer_size_); + headers_.Set(kConnection, "Keep-Alive"); +} - const HttpClientPool::Key key{request.url()}; +HttpResponsePtr HttpClientSession::Send(HttpRequestPtr request) { + if (request->buffer_size() == 0) { + request->set_buffer_size(buffer_size_); + } + + const HttpClientPool::Key key{request->url()}; // Reuse a connection or not. bool reuse = false; HttpClientPtr client = pool_.Get(key); if (!client) { - client.reset(new HttpClient{ssl_verify, buffer_size}); + client.reset(new HttpClient{ssl_verify_}); reuse = false; } else { - client->set_buffer_size(buffer_size); - client->set_ssl_verify(ssl_verify); + client->set_ssl_verify(ssl_verify_); reuse = true; LOG_VERB("Reuse an existing connection."); } @@ -143,77 +135,4 @@ HttpResponsePtr HttpClientSession::Request(HttpRequestArgs&& args) { return client->response(); } -HttpResponsePtr HttpClientSession::Get(const std::string& url, - std::vector&& parameters, - std::vector&& headers, - HttpRequestArgs&& args) { - return Request(args.method(http::kGet) - .url(url) - .parameters(std::move(parameters)) - .headers(std::move(headers))); -} - -HttpResponsePtr HttpClientSession::Post(const std::string& url, - std::string&& data, bool json, - std::vector&& headers, - HttpRequestArgs&& args) { - return Request(args.method(http::kPost) - .url(url) - .data(std::move(data)) - .json(json) - .headers(std::move(headers))); -} - -HttpResponsePtr HttpClientSession::Put(const std::string& url, - std::string&& data, bool json, - std::vector&& headers, - HttpRequestArgs&& args) { - return Request(args.method(http::kPut) - .url(url) - .data(std::move(data)) - .json(json) - .headers(std::move(headers))); -} - -HttpResponsePtr HttpClientSession::Delete(const std::string& url, - std::vector&& headers, - HttpRequestArgs&& args) { - return Request(args.method(http::kDelete) - .url(url) - .headers(std::move(headers))); -} - -void HttpClientSession::InitHeaders() { - using namespace http::headers; - - headers_.Add(kUserAgent, http::UserAgent()); - - // Content-Encoding Tokens: - // (https://en.wikipedia.org/wiki/HTTP_compression) - // * compress 每 UNIX "compress" program method (historic; deprecated in most - // applications and replaced by gzip or deflate); - // * deflate 每 compression based on the deflate algorithm, a combination of - // the LZ77 algorithm and Huffman coding, wrapped inside the - // zlib data format; - // * gzip 每 GNU zip format. Uses the deflate algorithm for compression, - // but the data format and the checksum algorithm differ from - // the "deflate" content-encoding. This method is the most - // broadly supported as of March 2011. - // * identity 每 No transformation is used. This is the default value for - // content coding. - // - // A note about "deflate": - // (https://www.zlib.net/zlib_faq.html#faq39) - // "gzip" is the gzip format, and "deflate" is the zlib format. They should - // probably have called the second one "zlib" instead to avoid confusion with - // the raw deflate compressed data format. - // Simply put, "deflate" is not recommended for HTTP 1.1 encoding. - // - headers_.Add(kAcceptEncoding, "gzip, deflate"); - - headers_.Add(kAccept, "*/*"); - - headers_.Add(kConnection, "Keep-Alive"); -} - } // namespace webcc diff --git a/webcc/http_client_session.h b/webcc/http_client_session.h index 53c18aa..35706e2 100644 --- a/webcc/http_client_session.h +++ b/webcc/http_client_session.h @@ -8,7 +8,7 @@ #include "boost/optional.hpp" #include "webcc/http_client_pool.h" -#include "webcc/http_request_args.h" +#include "webcc/http_request_builder.h" #include "webcc/http_response.h" namespace webcc { @@ -28,7 +28,7 @@ public: } void set_ssl_verify(bool ssl_verify) { - ssl_verify_.emplace(ssl_verify); + ssl_verify_ = ssl_verify; } void set_buffer_size(std::size_t buffer_size) { @@ -41,36 +41,17 @@ public: } } - void set_gzip(bool gzip) { - gzip_ = gzip; - } - void AddHeader(const std::string& key, const std::string& value) { - headers_.Add(key, value); + headers_.Set(key, value); } - HttpResponsePtr Request(HttpRequestArgs&& args); - - HttpResponsePtr Get(const std::string& url, - std::vector&& parameters = {}, - std::vector&& headers = {}, - HttpRequestArgs&& args = HttpRequestArgs()); - - HttpResponsePtr Post(const std::string& url, std::string&& data, bool json, - std::vector&& headers = {}, - HttpRequestArgs&& args = HttpRequestArgs()); - - HttpResponsePtr Put(const std::string& url, std::string&& data, bool json, - std::vector&& headers = {}, - HttpRequestArgs&& args = HttpRequestArgs()); - - HttpResponsePtr Delete(const std::string& url, - std::vector&& headers = {}, - HttpRequestArgs&& args = HttpRequestArgs()); + HttpResponsePtr Request(HttpRequestPtr request); private: void InitHeaders(); + HttpResponsePtr Send(HttpRequestPtr request); + private: // E.g., "application/json". std::string content_type_; @@ -82,7 +63,7 @@ private: HttpHeaderDict headers_; // Verify the certificate of the peer or not. - boost::optional ssl_verify_; + bool ssl_verify_ = true; // The size of the buffer for reading response. // 0 means default value will be used. @@ -91,12 +72,6 @@ private: // Timeout in seconds for receiving response. int timeout_ = 0; - // Compress the request content. - // NOTE: Most servers don't support compressed requests. - // Even the requests module from Python doesn't have a built-in support. - // See: https://github.com/kennethreitz/requests/issues/1753 - bool gzip_ = false; - // Connection pool for keep-alive. HttpClientPool pool_; }; diff --git a/webcc/http_message.cc b/webcc/http_message.cc index 2c23007..d1f9fce 100644 --- a/webcc/http_message.cc +++ b/webcc/http_message.cc @@ -24,7 +24,7 @@ std::ostream& operator<<(std::ostream& os, const HttpMessage& message) { // ----------------------------------------------------------------------------- -void HttpHeaderDict::Add(const std::string& key, const std::string& value) { +void HttpHeaderDict::Set(const std::string& key, const std::string& value) { auto it = Find(key); if (it != headers_.end()) { it->second = value; @@ -33,7 +33,7 @@ void HttpHeaderDict::Add(const std::string& key, const std::string& value) { } } -void HttpHeaderDict::Add(std::string&& key, std::string&& value) { +void HttpHeaderDict::Set(std::string&& key, std::string&& value) { auto it = Find(key); if (it != headers_.end()) { it->second = std::move(value); @@ -42,6 +42,10 @@ void HttpHeaderDict::Add(std::string&& key, std::string&& value) { } } +bool HttpHeaderDict::Have(const std::string& key) const { + return const_cast(this)->Find(key) != headers_.end(); +} + const std::string& HttpHeaderDict::Get(const std::string& key, bool* existed) const { auto it = const_cast(this)->Find(key); diff --git a/webcc/http_message.h b/webcc/http_message.h index 1fdaf96..54e83c0 100644 --- a/webcc/http_message.h +++ b/webcc/http_message.h @@ -31,13 +31,11 @@ public: return headers_; } - void Add(const std::string& key, const std::string& value); + void Set(const std::string& key, const std::string& value); - void Add(std::string&& key, std::string&& value); + void Set(std::string&& key, std::string&& value); - bool Has(const std::string& key) const { - return const_cast(this)->Find(key) != headers_.end(); - } + bool Have(const std::string& key) const; // Get header by index. const HttpHeader& Get(std::size_t index) const { @@ -85,11 +83,11 @@ public: bool IsConnectionKeepAlive() const; void SetHeader(const std::string& key, const std::string& value) { - headers_.Add(key, value); + headers_.Set(key, value); } void SetHeader(std::string&& key, std::string&& value) { - headers_.Add(std::move(key), std::move(value)); + headers_.Set(std::move(key), std::move(value)); } const std::string& GetHeader(const std::string& key, @@ -97,6 +95,10 @@ public: return headers_.Get(key, existed); } + bool HaveHeader(const std::string& key) const { + return headers_.Have(key); + } + http::ContentEncoding GetContentEncoding() const; // Return true if header Accept-Encoding contains "gzip". diff --git a/webcc/http_request.h b/webcc/http_request.h index e8eb712..48daa7f 100644 --- a/webcc/http_request.h +++ b/webcc/http_request.h @@ -57,6 +57,14 @@ public: return port().empty() ? default_port : port(); } + std::size_t buffer_size() const { + return buffer_size_; + } + + void set_buffer_size(std::size_t buffer_size) { + buffer_size_ = buffer_size; + } + // Prepare payload. // Compose start line, set Host header, etc. bool Prepare() final; @@ -64,6 +72,8 @@ public: private: std::string method_; Url url_; + + std::size_t buffer_size_ = 0; }; } // namespace webcc diff --git a/webcc/http_request_args.h b/webcc/http_request_args.h deleted file mode 100644 index f32f879..0000000 --- a/webcc/http_request_args.h +++ /dev/null @@ -1,123 +0,0 @@ -#ifndef WEBCC_HTTP_REQUEST_ARGS_H_ -#define WEBCC_HTTP_REQUEST_ARGS_H_ - -#include -#include -#include - -#include "boost/optional.hpp" - -#include "webcc/globals.h" -#include "webcc/logger.h" - -namespace webcc { - -class HttpClientSession; - -// Args maker for HttpClientSession. -// Use method chaining to simulate Named Parameters. -class HttpRequestArgs { -public: - explicit HttpRequestArgs(const std::string& method = "") - : method_(method), json_(false), buffer_size_(0), keep_alive_(true) { - } - - HttpRequestArgs(const HttpRequestArgs&) = default; - HttpRequestArgs& operator=(const HttpRequestArgs&) = default; - - HttpRequestArgs(HttpRequestArgs&&) = default; - HttpRequestArgs& operator=(HttpRequestArgs&&) = default; - - HttpRequestArgs&& method(const std::string& method) { - method_ = method; - return std::move(*this); - } - - HttpRequestArgs&& url(const std::string& url) { - url_ = url; - return std::move(*this); - } - - HttpRequestArgs&& parameters(const std::vector& parameters) { - parameters_ = parameters; - return std::move(*this); - } - - HttpRequestArgs&& parameters(std::vector&& parameters) { - parameters_ = std::move(parameters); - return std::move(*this); - } - - HttpRequestArgs&& data(const std::string& data) { - data_ = data; - return std::move(*this); - } - - HttpRequestArgs&& data(std::string&& data) { - data_ = std::move(data); - return std::move(*this); - } - - HttpRequestArgs&& json(bool json = true) { - json_ = json; - return std::move(*this); - } - - HttpRequestArgs&& headers(const std::vector& headers) { - headers_ = headers; - return std::move(*this); - } - - HttpRequestArgs&& headers(std::vector&& headers) { - headers_ = std::move(headers); - return std::move(*this); - } - - HttpRequestArgs&& ssl_verify(bool ssl_verify = true) { - ssl_verify_.emplace(ssl_verify); - return std::move(*this); - } - - HttpRequestArgs&& buffer_size(std::size_t buffer_size) { - buffer_size_ = buffer_size; - return std::move(*this); - } - - HttpRequestArgs&& keep_alive(bool keep_alive) { - keep_alive_ = keep_alive; - return std::move(*this); - } - -private: - friend class HttpClientSession; - - std::string method_; - - std::string url_; - - // URL query parameters. - std::vector parameters_; - - // Data to send in the body of the request. - std::string data_; - - // Is the data to send a JSON string? - bool json_; - - // Additional request headers. - std::vector headers_; - - // Verify the certificate of the peer or not. - boost::optional ssl_verify_; - - // Size of the buffer to read response. - // Leave it to 0 for using default value. - std::size_t buffer_size_; - - // Persistent connection. - bool keep_alive_; -}; - -} // namespace webcc - -#endif // WEBCC_HTTP_REQUEST_ARGS_H_ diff --git a/webcc/http_request_builder.h b/webcc/http_request_builder.h new file mode 100644 index 0000000..1994884 --- /dev/null +++ b/webcc/http_request_builder.h @@ -0,0 +1,123 @@ +#ifndef WEBCC_HTTP_REQUEST_BUILDER_H_ +#define WEBCC_HTTP_REQUEST_BUILDER_H_ + +#include +#include + +#include "boost/optional.hpp" + +#include "webcc/http_request.h" + +namespace webcc { + +// TODO: Add timeout() + +// HTTP request builder. +class HttpRequestBuilder { +public: + HttpRequestBuilder() = default; + + // Build the request. + HttpRequestPtr operator()(); + + HttpRequestBuilder& Get() { return method(http::kGet); } + HttpRequestBuilder& Post() { return method(http::kPost); } + HttpRequestBuilder& Put() { return method(http::kPut); } + HttpRequestBuilder& Delete() { return method(http::kDelete); } + + HttpRequestBuilder& method(const std::string& method) { + method_ = method; + return *this; + } + + HttpRequestBuilder& url(const std::string& url) { + url_ = url; + return *this; + } + + HttpRequestBuilder& parameter(const std::string& key, + const std::string& value) { + parameters_.push_back(key); + parameters_.push_back(value); + return *this; + } + + HttpRequestBuilder& data(const std::string& data) { + data_ = data; + return *this; + } + + HttpRequestBuilder& data(std::string&& data) { + data_ = std::move(data); + return *this; + } + + HttpRequestBuilder& json(bool json) { + json_ = json; + return *this; + } + + HttpRequestBuilder& gzip(bool gzip) { + gzip_ = gzip; + return *this; + } + + HttpRequestBuilder& header(const std::string& key, + const std::string& value) { + headers_.push_back(key); + headers_.push_back(value); + return *this; + } + + HttpRequestBuilder& ssl_verify(bool ssl_verify = true) { + ssl_verify_.emplace(ssl_verify); + return *this; + } + + HttpRequestBuilder& buffer(std::size_t buffer) { + buffer_ = buffer; + return *this; + } + + HttpRequestBuilder& keep_alive(bool keep_alive) { + keep_alive_ = keep_alive; + return *this; + } + +private: + std::string method_; + + std::string url_; + + // URL query parameters. + std::vector parameters_; + + // Data to send in the body of the request. + std::string data_; + + // Is the data to send a JSON string? + bool json_ = false; + + // Compress the request content. + // NOTE: Most servers don't support compressed requests. + // Even the requests module from Python doesn't have a built-in support. + // See: https://github.com/kennethreitz/requests/issues/1753 + bool gzip_ = false; + + // Additional request headers. + std::vector headers_; + + // Verify the certificate of the peer or not. + boost::optional ssl_verify_; + + // Size of the buffer to read response. + // Leave it to 0 for using default value. + std::size_t buffer_ = 0; + + // Persistent connection. + bool keep_alive_ = true; +}; + +} // namespace webcc + +#endif // WEBCC_HTTP_REQUEST_BUILDER_H_ diff --git a/webcc/soap_client.cc b/webcc/soap_client.cc index e10d531..5460474 100644 --- a/webcc/soap_client.cc +++ b/webcc/soap_client.cc @@ -10,11 +10,12 @@ namespace webcc { -SoapClient::SoapClient(const std::string& url, SoapVersion soap_version, - std::size_t buffer_size) - : url_(url), soap_version_(soap_version), - http_client_(buffer_size), - format_raw_(true), error_(kNoError) { +SoapClient::SoapClient(const std::string& url, SoapVersion soap_version) + : url_(url), + soap_version_(soap_version), + http_client_(true), + format_raw_(true), + error_(kNoError) { } bool SoapClient::Request(const std::string& operation, @@ -47,23 +48,25 @@ bool SoapClient::Request(const std::string& operation, std::string http_content; soap_request.ToXml(format_raw_, indent_str_, &http_content); - HttpRequest http_request(http::kPost, url_); + auto http_request = std::make_shared(http::kPost, url_); - http_request.SetContent(std::move(http_content), true); + http_request->SetContent(std::move(http_content), true); if (soap_version_ == kSoapV11) { - http_request.SetContentType(http::media_types::kTextXml, - http::charsets::kUtf8); + http_request->SetContentType(http::media_types::kTextXml, + http::charsets::kUtf8); } else { - http_request.SetContentType(http::media_types::kApplicationSoapXml, - http::charsets::kUtf8); + http_request->SetContentType(http::media_types::kApplicationSoapXml, + http::charsets::kUtf8); } - http_request.SetHeader(kSoapAction, operation); + http_request->SetHeader(kSoapAction, operation); - http_request.Prepare(); + http_request->set_buffer_size(buffer_size); - if (!http_client_.Request(http_request, buffer_size)) { + http_request->Prepare(); + + if (!http_client_.Request(http_request, true)) { error_ = http_client_.error(); return false; } diff --git a/webcc/soap_client.h b/webcc/soap_client.h index 43e9ad3..8414661 100644 --- a/webcc/soap_client.h +++ b/webcc/soap_client.h @@ -14,8 +14,7 @@ namespace webcc { class SoapClient { public: explicit SoapClient(const std::string& url, - SoapVersion soap_version = kSoapV12, - std::size_t buffer_size = 0); + SoapVersion soap_version = kSoapV12); ~SoapClient() = default;