From 3b7f4ecacfb050fce1908016069fe0d5f1efe05c Mon Sep 17 00:00:00 2001 From: Chunting Gu Date: Fri, 22 Mar 2019 16:11:01 +0800 Subject: [PATCH] Add shortcuts for GET, POST, etc. to HttpClientSession. --- README.md | 6 +-- examples/github_client.cc | 20 +++---- examples/http_client.cc | 47 +++++++++------- examples/rest_book_client.cc | 19 ++----- examples/rest_book_server.cc | 4 +- webcc/globals.h | 21 +++++--- webcc/http_client.cc | 4 +- webcc/http_client.h | 5 +- webcc/http_client_session.cc | 102 +++++++++++++++++++++++++++-------- webcc/http_client_session.h | 25 ++++++++- webcc/http_request.h | 18 +++++-- webcc/http_request_builder.h | 27 ++++------ webcc/rest_service.cc | 12 ++--- webcc/soap_client.cc | 3 +- 14 files changed, 198 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index 201bafc..06fae3a 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,7 @@ int main() { try { webcc::HttpClientSession session; - auto r = session.Request(webcc::HttpRequestBuilder{}.Get(). - url("http://httpbin.org/get")()); + auto r = session.Get("http://httpbin.org/get"); std::cout << r->content() << std::endl; @@ -40,8 +39,7 @@ int main() { List GitHub public events: ```cpp - auto r = session.Request(webcc::HttpRequestBuilder{}.Get(). - url("https://api.github.com/events")()); + auto r = session.Get("https://api.github.com/events"); // Parse r->content() to JSON object... ``` diff --git a/examples/github_client.cc b/examples/github_client.cc index fe8779a..41a187f 100644 --- a/examples/github_client.cc +++ b/examples/github_client.cc @@ -18,8 +18,6 @@ bool kSslVerify = false; bool kSslVerify = true; #endif -const std::size_t kBufferSize = 1500; - const std::string kUrlRoot = "https://api.github.com"; // ----------------------------------------------------------------------------- @@ -65,8 +63,7 @@ void PrettyPrintJsonString(const std::string& str) { // List public events. void ListEvents(webcc::HttpClientSession& session) { try { - auto r = session.Request(webcc::HttpRequestBuilder{}.Get(). - url(kUrlRoot + "/events")()); + auto r = session.Get(kUrlRoot + "/events"); PRINT_JSON_STRING(r->content()); @@ -81,8 +78,7 @@ void ListEvents(webcc::HttpClientSession& session) { void ListUserFollowers(webcc::HttpClientSession& session, const std::string& user) { try { - auto r = session.Request(webcc::HttpRequestBuilder{}.Get(). - url(kUrlRoot + "/users/" + user + "/followers")()); + auto r = session.Get(kUrlRoot + "/users/" + user + "/followers"); PRINT_JSON_STRING(r->content()); @@ -99,9 +95,8 @@ void ListUserFollowers(webcc::HttpClientSession& session, void ListAuthUserFollowers(webcc::HttpClientSession& session, const std::string& auth) { try { - auto r = session.Request(webcc::HttpRequestBuilder{}.Get(). - url(kUrlRoot + "/user/followers"). - header("Authorization", auth)()); + auto r = session.Get(kUrlRoot + "/user/followers", {}, + {"Authorization", auth}); PRINT_JSON_STRING(r->content()); @@ -116,11 +111,8 @@ void CreateAuthorization(webcc::HttpClientSession& session, std::string data = "{'note': 'Webcc test', 'scopes': ['public_repo', 'repo', 'repo:status', 'user']}"; - auto r = session.Request(webcc::HttpRequestBuilder{}.Post(). - url(kUrlRoot + "/authorizations"). - data(std::move(data)). - json(true). - header("Authorization", auth)()); + auto r = session.Post(kUrlRoot + "/authorizations", std::move(data), true, + {"Authorization", auth}); std::cout << r->content() << std::endl; diff --git a/examples/http_client.cc b/examples/http_client.cc index 94ad862..9f4d55d 100644 --- a/examples/http_client.cc +++ b/examples/http_client.cc @@ -20,13 +20,25 @@ bool kSslVerify = true; void ExampleBasic() { 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)()); + + 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; +} + +// Use predefined shortcuts. +void ExampleShortcut() { + webcc::HttpClientSession session; + + auto r = session.Get("http://httpbin.org/get", + {"key1", "value1", "key2", "value2"}, + {"Accept", "application/json"}); std::cout << r->content() << std::endl; } @@ -36,10 +48,11 @@ void ExampleHttps() { webcc::HttpClientSession session; session.set_ssl_verify(kSslVerify); - auto r = session.Request(webcc::HttpRequestBuilder{}.Get(). - url("https://httpbin.org/get"). - parameter("key1", "value1"). - header("Accept", "application/json")()); + 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; } @@ -61,25 +74,23 @@ void ExampleKeepAlive(const std::string& url) { session.set_ssl_verify(kSslVerify); // Keep-Alive - session.Request(webcc::HttpRequestBuilder{}.Get().url(url)()); + session.Get(url); // Close - session.Request(webcc::HttpRequestBuilder{}.Get().url(url).keep_alive(false)()); + session.Get(url, {}, {"Connection", "Close"}); // Keep-Alive - session.Request(webcc::HttpRequestBuilder{}.Get().url(url)()); + session.Get(url); } void ExampleCompression() { HttpClientSession session; - auto r = session.Request(webcc::HttpRequestBuilder{}. - Get().url("http://httpbin.org/gzip")()); + auto r = session.Get("http://httpbin.org/gzip"); std::cout << r->content() << std::endl; - r = session.Request(webcc::HttpRequestBuilder{}. - Get().url("http://httpbin.org/deflate")()); + r = session.Get("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 2768eb3..eca6791 100644 --- a/examples/rest_book_client.cc +++ b/examples/rest_book_client.cc @@ -55,8 +55,7 @@ public: bool ListBooks(std::list* books) { try { - auto r = session_.Request(webcc::HttpRequestBuilder{}.Get(). - url(url_ + "/books")()); + auto r = session_.Get(url_ + "/books"); if (!CheckStatus(r, webcc::http::Status::kOK)) { // Response HTTP status error. @@ -87,10 +86,7 @@ public: req_json["price"] = price; try { - auto r = session_.Request(webcc::HttpRequestBuilder{}.Post(). - url(url_ + "/books"). - data(JsonToString(req_json)). - json(true)()); + auto r = session_.Post(url_ + "/books", JsonToString(req_json), true); if (!CheckStatus(r, webcc::http::Status::kCreated)) { return false; @@ -118,8 +114,7 @@ public: bool GetBook(const std::string& id, Book* book) { try { - auto r = session_.Request(webcc::HttpRequestBuilder{}.Get(). - url(url_ + "/books/" + id)()); + auto r = session_.Get(url_ + "/books/" + id); if (!CheckStatus(r, webcc::http::Status::kOK)) { return false; @@ -140,10 +135,7 @@ public: json["price"] = price; try { - auto r = session_.Request(webcc::HttpRequestBuilder{}.Put(). - url(url_ + "/books/" + id). - data(JsonToString(json)). - json(true)()); + auto r = session_.Put(url_ + "/books/" + id, JsonToString(json), true); if (!CheckStatus(r, webcc::http::Status::kOK)) { return false; @@ -159,8 +151,7 @@ public: bool DeleteBook(const std::string& id) { try { - auto r = session_.Request(webcc::HttpRequestBuilder{}.Delete(). - url(url_ + "/books/" + id)()); + auto r = session_.Delete(url_ + "/books/" + id); if (!CheckStatus(r, webcc::http::Status::kOK)) { return false; diff --git a/examples/rest_book_server.cc b/examples/rest_book_server.cc index 5b32d1e..72f6bd3 100644 --- a/examples/rest_book_server.cc +++ b/examples/rest_book_server.cc @@ -40,7 +40,7 @@ public: : sleep_seconds_(sleep_seconds) { } -public: +protected: // Get a list of books based on query parameters. void Get(const webcc::UrlQuery& query, webcc::RestResponse* response) final; @@ -64,7 +64,7 @@ public: : sleep_seconds_(sleep_seconds) { } -public: +protected: // Get the detailed information of a book. void Get(const webcc::UrlMatches& url_matches, const webcc::UrlQuery& query, diff --git a/webcc/globals.h b/webcc/globals.h index b927069..86fda3f 100644 --- a/webcc/globals.h +++ b/webcc/globals.h @@ -39,14 +39,22 @@ const std::size_t kGzipThreshold = 1400; // HTTP headers. namespace http { +namespace methods { + // HTTP methods (verbs) in string. // Don't use enum to avoid converting back and forth. -const char* const kHead = "HEAD"; -const char* const kGet = "GET"; -const char* const kPost = "POST"; -const char* const kPatch = "PATCH"; -const char* const kPut = "PUT"; -const char* const kDelete = "DELETE"; + +const char* const kGet = "GET"; +const char* const kHead = "HEAD"; +const char* const kPost = "POST"; +const char* const kPut = "PUT"; +const char* const kDelete = "DELETE"; +const char* const kConnect = "CONNECT"; +const char* const kOptions = "OPTIONS"; +const char* const kTrace = "TRACE"; +const char* const kPatch = "PATCH"; + +} // namespace methods // HTTP response status. // This is not a full list. @@ -69,6 +77,7 @@ namespace headers { // NOTE: Field names are case-insensitive. // See https://stackoverflow.com/a/5259004 for more details. + const char* const kHost = "Host"; const char* const kDate = "Date"; const char* const kContentType = "Content-Type"; diff --git a/webcc/http_client.cc b/webcc/http_client.cc index fe347a2..ac75ea2 100644 --- a/webcc/http_client.cc +++ b/webcc/http_client.cc @@ -9,9 +9,9 @@ using boost::asio::ip::tcp; namespace webcc { -HttpClient::HttpClient(bool ssl_verify) +HttpClient::HttpClient() : timer_(io_context_), - ssl_verify_(ssl_verify), + ssl_verify_(true), timeout_(kMaxReadSeconds), closed_(false), timer_canceled_(false), diff --git a/webcc/http_client.h b/webcc/http_client.h index 16a3de3..a16dc39 100644 --- a/webcc/http_client.h +++ b/webcc/http_client.h @@ -27,7 +27,7 @@ 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); + HttpClient(); virtual ~HttpClient() = default; @@ -94,8 +94,7 @@ private: // Verify the certificate of the peer or not (for HTTPS). bool ssl_verify_; - // Maximum seconds to wait before the client cancels the operation. - // Only for reading response from server. + // Timeout (seconds) for receiving response. int timeout_; // Connection closed. diff --git a/webcc/http_client_session.cc b/webcc/http_client_session.cc index 812da85..c81ba9e 100644 --- a/webcc/http_client_session.cc +++ b/webcc/http_client_session.cc @@ -5,25 +5,6 @@ namespace webcc { -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; -//} - -} // namespace - -// ----------------------------------------------------------------------------- - HttpClientSession::HttpClientSession() { InitHeaders(); } @@ -47,6 +28,83 @@ HttpResponsePtr HttpClientSession::Request(HttpRequestPtr request) { return Send(request); } +static void SetHeaders(const std::vector& headers, + HttpRequestBuilder* builder) { + assert(headers.size() % 2 == 0); + + for (std::size_t i = 1; i < headers.size(); i += 2) { + builder->header(headers[i - 1], headers[i]); + } +} + +HttpResponsePtr HttpClientSession::Get(const std::string& url, + const std::vector& parameters, + const std::vector& headers) { + HttpRequestBuilder builder{http::methods::kGet}; + builder.url(url); + + assert(parameters.size() % 2 == 0); + for (std::size_t i = 1; i < parameters.size(); i += 2) { + builder.parameter(parameters[i - 1], parameters[i]); + } + + SetHeaders(headers, &builder); + + return Request(builder()); +} + +HttpResponsePtr HttpClientSession::Post(const std::string& url, + std::string&& data, bool json, + const std::vector& headers) { + HttpRequestBuilder builder{http::methods::kPost}; + builder.url(url); + + SetHeaders(headers, &builder); + + builder.data(std::move(data)); + builder.json(json); + + return Request(builder()); +} + +HttpResponsePtr HttpClientSession::Put(const std::string& url, + std::string&& data, bool json, + const std::vector& headers) { + HttpRequestBuilder builder{http::methods::kPut}; + builder.url(url); + + SetHeaders(headers, &builder); + + builder.data(std::move(data)); + builder.json(json); + + return Request(builder()); +} + +HttpResponsePtr HttpClientSession::Delete(const std::string& url, + const std::vector& headers) { + HttpRequestBuilder builder{http::methods::kDelete}; + builder.url(url); + + SetHeaders(headers, &builder); + + return Request(builder()); +} + +HttpResponsePtr HttpClientSession::Patch(const std::string& url, + std::string&& data, bool json, + const std::vector& headers) { + HttpRequestBuilder builder{http::methods::kPatch}; + builder.url(url); + + SetHeaders(headers, &builder); + + builder.data(std::move(data)); + builder.json(json); + + return Request(builder()); +} + void HttpClientSession::InitHeaders() { using namespace http::headers; @@ -92,14 +150,14 @@ HttpResponsePtr HttpClientSession::Send(HttpRequestPtr request) { HttpClientPtr client = pool_.Get(key); if (!client) { - client.reset(new HttpClient{ssl_verify_}); + client.reset(new HttpClient{}); reuse = false; } else { - client->set_ssl_verify(ssl_verify_); - reuse = true; LOG_VERB("Reuse an existing connection."); + reuse = true; } + client->set_ssl_verify(ssl_verify_); client->set_timeout(timeout_); bool ok = client->Request(request, !reuse); diff --git a/webcc/http_client_session.h b/webcc/http_client_session.h index 35706e2..4cad2c0 100644 --- a/webcc/http_client_session.h +++ b/webcc/http_client_session.h @@ -41,12 +41,35 @@ public: } } - void AddHeader(const std::string& key, const std::string& value) { + void SetHeader(const std::string& key, const std::string& value) { headers_.Set(key, value); } + // Send a request. + // Please use HttpRequestBuilder to build the request. HttpResponsePtr Request(HttpRequestPtr request); + // Shortcut for GET request. + HttpResponsePtr Get(const std::string& url, + const std::vector& parameters = {}, + const std::vector& headers = {}); + + // Shortcut for POST request. + HttpResponsePtr Post(const std::string& url, std::string&& data, bool json, + const std::vector& headers = {}); + + // Shortcut for PUT request. + HttpResponsePtr Put(const std::string& url, std::string&& data, bool json, + const std::vector& headers = {}); + + // Shortcut for DELETE request. + HttpResponsePtr Delete(const std::string& url, + const std::vector& headers = {}); + + // Shortcut for PATCH request. + HttpResponsePtr Patch(const std::string& url, std::string&& data, bool json, + const std::vector& headers = {}); + private: void InitHeaders(); diff --git a/webcc/http_request.h b/webcc/http_request.h index 48daa7f..b1c5635 100644 --- a/webcc/http_request.h +++ b/webcc/http_request.h @@ -11,7 +11,6 @@ namespace webcc { class HttpRequest; - typedef std::shared_ptr HttpRequestPtr; class HttpRequest : public HttpMessage { @@ -37,6 +36,14 @@ public: url_.AddParameter(key, value); } + void set_buffer_size(std::size_t buffer_size) { + buffer_size_ = buffer_size; + } + + void set_ssl_verify(bool ssl_verify) { + ssl_verify_ = ssl_verify; + } + const std::string& method() const { return method_; } @@ -61,8 +68,8 @@ public: return buffer_size_; } - void set_buffer_size(std::size_t buffer_size) { - buffer_size_ = buffer_size; + bool ssl_verify() const { + return ssl_verify_; } // Prepare payload. @@ -73,6 +80,11 @@ private: std::string method_; Url url_; + // Verify the certificate of the peer or not (for HTTPS). + bool ssl_verify_ = true; + + // The size of the buffer for reading response. + // 0 means default value will be used. std::size_t buffer_size_ = 0; }; diff --git a/webcc/http_request_builder.h b/webcc/http_request_builder.h index 1994884..120834a 100644 --- a/webcc/http_request_builder.h +++ b/webcc/http_request_builder.h @@ -4,26 +4,25 @@ #include #include -#include "boost/optional.hpp" - #include "webcc/http_request.h" namespace webcc { -// TODO: Add timeout() - -// HTTP request builder. class HttpRequestBuilder { public: - HttpRequestBuilder() = default; + explicit HttpRequestBuilder(const std::string& method = "") + : method_(method) { + } // 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& Get() { return method(http::methods::kGet); } + HttpRequestBuilder& Head() { return method(http::methods::kHead); } + HttpRequestBuilder& Post() { return method(http::methods::kPost); } + HttpRequestBuilder& Put() { return method(http::methods::kPut); } + HttpRequestBuilder& Delete() { return method(http::methods::kDelete); } + HttpRequestBuilder& Patch() { return method(http::methods::kPatch); } HttpRequestBuilder& method(const std::string& method) { method_ = method; @@ -69,11 +68,6 @@ public: 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; @@ -107,9 +101,6 @@ private: // 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; diff --git a/webcc/rest_service.cc b/webcc/rest_service.cc index 1ccb47a..99159b2 100644 --- a/webcc/rest_service.cc +++ b/webcc/rest_service.cc @@ -8,9 +8,9 @@ namespace webcc { void RestListService::Handle(const RestRequest& request, RestResponse* response) { - if (request.method == http::kGet) { + if (request.method == http::methods::kGet) { Get(UrlQuery(request.url_query_str), response); - } else if (request.method == http::kPost) { + } else if (request.method == http::methods::kPost) { Post(request.content, response); } else { LOG_ERRO("RestListService doesn't support '%s' method.", @@ -22,13 +22,13 @@ void RestListService::Handle(const RestRequest& request, void RestDetailService::Handle(const RestRequest& request, RestResponse* response) { - if (request.method == http::kGet) { + if (request.method == http::methods::kGet) { Get(request.url_matches, UrlQuery(request.url_query_str), response); - } else if (request.method == http::kPut) { + } else if (request.method == http::methods::kPut) { Put(request.url_matches, request.content, response); - } else if (request.method == http::kPatch) { + } else if (request.method == http::methods::kPatch) { Patch(request.url_matches, request.content, response); - } else if (request.method == http::kDelete) { + } else if (request.method == http::methods::kDelete) { Delete(request.url_matches, response); } else { LOG_ERRO("RestDetailService doesn't support '%s' method.", diff --git a/webcc/soap_client.cc b/webcc/soap_client.cc index 5460474..847c225 100644 --- a/webcc/soap_client.cc +++ b/webcc/soap_client.cc @@ -13,7 +13,6 @@ namespace webcc { SoapClient::SoapClient(const std::string& url, SoapVersion soap_version) : url_(url), soap_version_(soap_version), - http_client_(true), format_raw_(true), error_(kNoError) { } @@ -48,7 +47,7 @@ bool SoapClient::Request(const std::string& operation, std::string http_content; soap_request.ToXml(format_raw_, indent_str_, &http_content); - auto http_request = std::make_shared(http::kPost, url_); + auto http_request = std::make_shared(http::methods::kPost, url_); http_request->SetContent(std::move(http_content), true);