From 05782b773ae6cce02a447fa0e0e0c7ce87ab02b3 Mon Sep 17 00:00:00 2001 From: Chunting Gu Date: Wed, 13 Mar 2019 13:46:22 +0800 Subject: [PATCH] Refine client session and its examples. --- example/github_client/main.cc | 92 ++++++++++--------- example/http_client/main.cc | 164 ++++++++++++++-------------------- webcc/globals.h | 4 + webcc/http_client_pool.cc | 6 ++ webcc/http_client_session.cc | 36 ++++---- webcc/http_client_session.h | 10 +++ webcc/http_request_args.h | 13 +-- 7 files changed, 162 insertions(+), 163 deletions(-) diff --git a/example/github_client/main.cc b/example/github_client/main.cc index 834c3c7..cd0f320 100644 --- a/example/github_client/main.cc +++ b/example/github_client/main.cc @@ -1,5 +1,4 @@ #include -#include #include "json/json.h" @@ -21,9 +20,13 @@ bool kSslVerify = true; const std::size_t kBufferSize = 1500; +const std::string kUrlRoot = "https://api.github.com"; + // ----------------------------------------------------------------------------- +// JSON helper functions (based on cppjson). -static Json::Value StringToJson(const std::string& str) { +// Parse a string to JSON object. +Json::Value StringToJson(const std::string& str) { Json::Value json; Json::CharReaderBuilder builder; @@ -37,7 +40,7 @@ static Json::Value StringToJson(const std::string& str) { } // Print the JSON string in pretty format. -static void PrettyPrintJsonString(const std::string& str) { +void PrettyPrintJsonString(const std::string& str) { Json::Value json = StringToJson(str); Json::StreamWriterBuilder builder; @@ -57,48 +60,48 @@ static void PrettyPrintJsonString(const std::string& str) { #define PRINT_JSON_STRING(str) #endif // PRINT_RESPONSE -//static void PrintError(const webcc::RestSslClient& client) { -// std::cout << webcc::DescribeError(client.error()); -// if (client.timed_out()) { -// std::cout << " (timed out)"; -// } -// std::cout << std::endl; -//} -// -//// ----------------------------------------------------------------------------- -// -//// List public events. -//static void ListEvents(webcc::RestSslClient& client) { -// if (client.Get("/events")) { -// PRINT_JSON_STRING(client.response_content()); -// } else { -// PrintError(client); -// } -//} -// -//// List the followers of the given user. -//static void ListUserFollowers(webcc::RestSslClient& client, -// const std::string& user) { -// if (client.Get("/users/" + user + "/followers")) { -// PRINT_JSON_STRING(client.response_content()); -// } else { -// PrintError(client); -// } -//} +// ----------------------------------------------------------------------------- + +// List public events. +void ListEvents() { + webcc::HttpClientSession session; + session.set_ssl_verify(kSslVerify); + + try { + auto r = session.Get(kUrlRoot + "/events"); + PRINT_JSON_STRING(r->content()); + } catch (const webcc::Exception& e) { + std::cout << e.what() << std::endl; + } +} + +// List the followers of the given user. +void ListUserFollowers(const std::string& user) { + webcc::HttpClientSession session; + session.set_ssl_verify(kSslVerify); + + try { + auto r = session.Get(kUrlRoot + "/users/" + user + "/followers"); + PRINT_JSON_STRING(r->content()); + } catch (const webcc::Exception& e) { + std::cout << e.what() << std::endl; + } +} // List the followers of the current authorized user. // Header syntax: Authorization: -static void ListAuthorizedUserFollowers(webcc::HttpClientSession& session, - const std::string& auth) { - auto r = session.Request(webcc::HttpRequestArgs("GET"). - url("https://api.github.com/user/followers"). - headers({ { "Authorization", auth } }). - ssl_verify(kSslVerify).buffer_size(kBufferSize)); - - if (r) { +void ListAuthUserFollowers(const std::string& auth) { + webcc::HttpClientSession session; + session.set_ssl_verify(kSslVerify); + + try { + auto r = session.Get(kUrlRoot + "/user/followers", {}, + { "Authorization", auth }); + PRINT_JSON_STRING(r->content()); - } else { - //PrintError(client); + + } catch (const webcc::Exception& e) { + std::cout << e.what() << std::endl; } } @@ -107,10 +110,11 @@ static void ListAuthorizedUserFollowers(webcc::HttpClientSession& session, int main() { WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); - webcc::HttpClientSession session; + //ListEvents(); + + //ListUserFollowers(""); - //ListAuthorizedUserFollowers(client, "Basic c3ByaW5mYWxsQGdtYWlsLmNvbTpYaWFvTHVhbjFA"); - ListAuthorizedUserFollowers(session, "Token 1d42e2cce49929f2d24b1b6e96260003e5b3e1b0"); + //ListAuthUserFollowers("Basic "); return 0; } diff --git a/example/http_client/main.cc b/example/http_client/main.cc index 125248a..c942406 100644 --- a/example/http_client/main.cc +++ b/example/http_client/main.cc @@ -1,5 +1,4 @@ #include -#include #include "webcc/http_client_session.h" #include "webcc/logger.h" @@ -18,130 +17,99 @@ bool kSslVerify = true; // ----------------------------------------------------------------------------- -void GetBoostOrgLicense(HttpClientSession& session) { - try { - auto r = session.Request(HttpRequestArgs{ "GET" }. - url("https://www.boost.org/LICENSE_1_0.txt"). - ssl_verify(kSslVerify)); - - std::cout << r->content() << std::endl; - - } catch (const webcc::Exception& e) { - std::cout << "Exception: " << e.what() << std::endl; - } -} - -// TODO -void Test(HttpClientSession& session) { - HttpResponsePtr r; - - // --------------------------------------------------------------------------- +void ExampleBasic() { + HttpClientSession session; - r = session.Request(HttpRequestArgs{ "GET" }. - url("http://httpbin.org/get"). // moved - parameters({ "key1", "value1", "key2", "value2" }). // moved - headers({ "Accept", "application/json" }). // moved - buffer_size(1000)); + auto r = session.Request(HttpRequestArgs{"GET"} + .url("http://httpbin.org/get") + .parameters({"key1", "value1", "key2", "value2"}) + .headers({"Accept", "application/json"}) + .buffer_size(1000)); std::cout << r->content() << std::endl; +} - // --------------------------------------------------------------------------- - - // If you want to create the args object firstly, there'll be an extra call - // to its move constructor. - // - constructor: HttpRequestArgs{ "GET" } - // - move constructor: auto args = ... - - auto args = HttpRequestArgs{"GET"}. - url("http://httpbin.org/get"). - parameters({ "key1", "value1", "key2", "value2" }). - headers({ "Accept", "application/json" }). - buffer_size(1000); - - r = session.Request(std::move(args)); - - // --------------------------------------------------------------------------- - // Use pre-defined wrappers. +// 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; - r = session.Get("http://httpbin.org/get", - { "key1", "value1", "key2", "value2" }, - { "Accept", "application/json" }, - HttpRequestArgs{}.buffer_size(1000)); + auto args = HttpRequestArgs{"GET"} + .url("http://httpbin.org/get") + .parameters({"key1", "value1", "key2", "value2"}) + .headers({"Accept", "application/json"}) + .buffer_size(1000); - // --------------------------------------------------------------------------- - // HTTPS is auto-detected from the URL schema. + // Note the std::move(). + session.Request(std::move(args)); +} - try { - r = session.Post("httpt://httpbin.org/post", "{ 'key': 'value' }", true, - {"Accept", "application/json"}, - HttpRequestArgs{}.ssl_verify(false).buffer_size(1000)); +// Use pre-defined wrappers. +void ExampleWrappers() { + HttpClientSession session; - std::cout << r->status() << std::endl << r->content() << std::endl; + session.Get("http://httpbin.org/get", {"key1", "value1", "key2", "value2"}, + {"Accept", "application/json"}, + HttpRequestArgs{}.buffer_size(1000)); - } catch (const webcc::Exception& e) { - std::cout << "Exception: " << e.what() << std::endl; - } + session.Post("http://httpbin.org/post", "{ 'key': 'value' }", true, + {"Accept", "application/json"}); } -void TestKeepAlive2(HttpClientSession& session) { - try { - auto r = session.Request(webcc::HttpRequestArgs("GET"). - url("https://api.github.com/events"). - ssl_verify(false).buffer_size(1500)); - } catch (const Exception& e) { - std::cout << "Exception: " << e.what() << std::endl; - } -} - -void TestKeepAlive3(HttpClientSession& session) { - try { - auto r = session.Request(webcc::HttpRequestArgs("GET"). - url("https://www.boost.org/LICENSE_1_0.txt"). - ssl_verify(false)); +// HTTPS is auto-detected from the URL scheme. +void ExampleHttps() { + HttpClientSession session; - //std::cout << r->content() << std::endl; + auto r = session.Request(HttpRequestArgs{"GET"} + .url("https://httpbin.org/get") + .parameters({"key1", "value1", "key2", "value2"}) + .headers({"Accept", "application/json"}) + .ssl_verify(kSslVerify)); - } catch (const Exception& e) { - std::cout << "Exception: " << e.what() << std::endl; - } + std::cout << r->content() << std::endl; } -void TestKeepAlive4(HttpClientSession& session) { - try { - auto r = session.Request(webcc::HttpRequestArgs("GET"). - url("https://www.google.com"). - ssl_verify(false)); +void ExampleKeepAlive(const std::string& url) { + HttpClientSession session; - //std::cout << r->content() << std::endl; + // Keep-Alive + session.Request(webcc::HttpRequestArgs("GET").url(url). + ssl_verify(kSslVerify)); - } catch (const Exception& e) { - std::cout << "Exception: " << e.what() << std::endl; - } + // Close + session.Request(webcc::HttpRequestArgs("GET").url(url). + ssl_verify(kSslVerify). + headers({ "Connection", "Close" })); + + // Keep-Alive + session.Request(webcc::HttpRequestArgs("GET").url(url). + ssl_verify(kSslVerify)); } // ----------------------------------------------------------------------------- -void Sleep(int seconds) { - if (seconds > 0) { - LOG_INFO("Sleep %d seconds...", seconds); - std::this_thread::sleep_for(std::chrono::seconds(seconds)); - } -} - int main() { WEBCC_LOG_INIT("", LOG_CONSOLE); - HttpClientSession session; - - // TEST keep-alive. - + // Note that the exception handling is mandatory. try { - // Keep-Alive by default - session.Get("http://httpbin.org/get"); + //ExampleBasic(); + //ExampleArgs(); + //ExampleWrappers(); + //ExampleHttps(); + + // Boost.org doesn't support persistent connection so always includes + // "Connection: Close" header in the response. - session.Get("http://httpbin.org/get", {}, { "Connection", "Close" }); + // Both Google and GitHub support persistent connection but they don't like + // to include "Connection: Keep-Alive" header in the responses. - session.Get("http://httpbin.org/get"); + //ExampleKeepAlive("http://httpbin.org/get"); + //ExampleKeepAlive("https://www.boost.org/LICENSE_1_0.txt"); + //ExampleKeepAlive("https://www.google.com"); + //ExampleKeepAlive("https://api.github.com/events"); } catch (const Exception& e) { std::cout << "Exception: " << e.what() << std::endl; diff --git a/webcc/globals.h b/webcc/globals.h index 0c5a62e..48dafc2 100644 --- a/webcc/globals.h +++ b/webcc/globals.h @@ -139,6 +139,10 @@ public: return msg_.c_str(); } + Error error() const { return error_; } + + bool timeout() const { return timeout_; } + private: Error error_; diff --git a/webcc/http_client_pool.cc b/webcc/http_client_pool.cc index 7aec182..5fa6ade 100644 --- a/webcc/http_client_pool.cc +++ b/webcc/http_client_pool.cc @@ -25,10 +25,16 @@ HttpClientPtr HttpClientPool::Get(const Key& key) const { void HttpClientPool::Add(const Key& key, HttpClientPtr client) { clients_[key] = client; + + LOG_INFO("Added connection to pool (%s, %s, %s).", + key.scheme.c_str(), key.host.c_str(), key.port.c_str()); } void HttpClientPool::Remove(const Key& key) { clients_.erase(key); + + LOG_INFO("Removed connection from pool (%s, %s, %s).", + key.scheme.c_str(), key.host.c_str(), key.port.c_str()); } } // namespace webcc diff --git a/webcc/http_client_session.cc b/webcc/http_client_session.cc index 9c70acb..beb60f4 100644 --- a/webcc/http_client_session.cc +++ b/webcc/http_client_session.cc @@ -43,35 +43,39 @@ HttpResponsePtr HttpClientSession::Request(HttpRequestArgs&& args) { request.Prepare(); + // Determine SSL verify flag. + bool ssl_verify = true; + if (args.ssl_verify_) { + ssl_verify = args.ssl_verify_.value(); + } else if (ssl_verify_) { + ssl_verify = ssl_verify_.value(); + } + + bool reuse = false; const HttpClientPool::Key key{ request.url() }; - bool new_created = false; HttpClientPtr client = pool_.Get(key); if (!client) { - new_created = true; - client.reset(new HttpClient{ 0, args.ssl_verify_ }); + client.reset(new HttpClient{ 0, ssl_verify }); + reuse = false; } else { - new_created = false; + // TODO: Apply args.ssl_verify even if reuse a client. + reuse = false; LOG_VERB("Reuse an existing connection."); } - if (!client->Request(request, args.buffer_size_, new_created)) { + if (!client->Request(request, args.buffer_size_, !reuse)) { throw Exception(client->error(), client->timed_out()); } - if (new_created) { - if (!client->closed()) { - pool_.Add(key, client); - - LOG_VERB("Added connection to the pool (%s, %s, %s).", - key.scheme.c_str(), key.host.c_str(), key.port.c_str()); - } - } else { + // Update pool. + if (reuse) { if (client->closed()) { pool_.Remove(key); - - LOG_VERB("Removed connection from the pool (%s, %s, %s).", - key.scheme.c_str(), key.host.c_str(), key.port.c_str()); + } + } else { + if (!client->closed()) { + pool_.Add(key, client); } } diff --git a/webcc/http_client_session.h b/webcc/http_client_session.h index a318922..77a3c1d 100644 --- a/webcc/http_client_session.h +++ b/webcc/http_client_session.h @@ -5,6 +5,8 @@ #include #include +#include "boost/optional.hpp" + #include "webcc/http_client_pool.h" #include "webcc/http_request_args.h" #include "webcc/http_response.h" @@ -29,6 +31,10 @@ public: headers_.Add(key, value); } + void set_ssl_verify(bool ssl_verify) { + ssl_verify_.emplace(ssl_verify); + } + HttpResponsePtr Request(HttpRequestArgs&& args); HttpResponsePtr Get(const std::string& url, @@ -43,6 +49,7 @@ public: private: void InitHeaders(); +private: // E.g., "application/json". std::string content_type_; @@ -52,6 +59,9 @@ private: // Headers for each request sent from this session. HttpHeaderDict headers_; + // Verify the certificate of the peer or not. + boost::optional ssl_verify_; + // Connection pool for keep-alive. HttpClientPool pool_; }; diff --git a/webcc/http_request_args.h b/webcc/http_request_args.h index 6aa3e23..20d62de 100644 --- a/webcc/http_request_args.h +++ b/webcc/http_request_args.h @@ -5,6 +5,8 @@ #include #include +#include "boost/optional.hpp" + #include "webcc/globals.h" #include "webcc/logger.h" @@ -17,7 +19,7 @@ class HttpClientSession; class HttpRequestArgs { public: explicit HttpRequestArgs(const std::string& method = "") - : method_(method), json_(false), ssl_verify_(true), buffer_size_(0) { + : method_(method), json_(false), buffer_size_(0) { LOG_VERB("HttpRequestArgs()"); } @@ -78,7 +80,7 @@ public: } HttpRequestArgs&& ssl_verify(bool ssl_verify = true) { - ssl_verify_ = ssl_verify; + ssl_verify_.emplace(ssl_verify); return std::move(*this); } @@ -94,6 +96,7 @@ private: std::string url_; + // URL query parameters. std::vector parameters_; // Data to send in the body of the request. @@ -102,11 +105,11 @@ private: // Is the data to send a JSON string? bool json_; + // Additional request headers. std::vector headers_; - // Verify the certificate of the peer (remote server) or not. - // HTTPS only. - bool ssl_verify_; + // 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.