diff --git a/autotest/client_autotest.cc b/autotest/client_autotest.cc index 62a89fa..2df578d 100644 --- a/autotest/client_autotest.cc +++ b/autotest/client_autotest.cc @@ -44,31 +44,9 @@ TEST(ClientTest, Head) { EXPECT_EQ(webcc::Status::kOK, r->status()); EXPECT_EQ("OK", r->reason()); - EXPECT_EQ("", r->data()); - - } catch (const webcc::Error& error) { - std::cerr << error << std::endl; - } -} - -// Force Accept-Encoding to be "identity" so that HttpBin.org will include -// a Content-Length header in the response. -// This tests that the response with Content-Length while no body could be -// correctly parsed. -TEST(ClientTest, Head_AcceptEncodingIdentity) { - webcc::ClientSession session; - - try { - auto r = session.Send(webcc::RequestBuilder{}. - Head("http://httpbin.org/get"). - Header("Accept-Encoding", "identity") - ()); - - EXPECT_EQ(webcc::Status::kOK, r->status()); - EXPECT_EQ("OK", r->reason()); - EXPECT_TRUE(r->HasHeader(webcc::headers::kContentLength)); + // The response of HTTP HEAD method has no body. EXPECT_EQ("", r->data()); } catch (const webcc::Error& error) { @@ -78,30 +56,6 @@ TEST(ClientTest, Head_AcceptEncodingIdentity) { // ----------------------------------------------------------------------------- -static void AssertGet(webcc::ResponsePtr r) { - EXPECT_EQ(webcc::Status::kOK, r->status()); - EXPECT_EQ("OK", r->reason()); - - Json::Value json = StringToJson(r->data()); - - Json::Value args = json["args"]; - - EXPECT_EQ(2, args.size()); - EXPECT_EQ("value1", args["key1"].asString()); - EXPECT_EQ("value2", args["key2"].asString()); - - Json::Value headers = json["headers"]; - - EXPECT_EQ("application/json", headers["Accept"].asString()); - EXPECT_EQ("httpbin.org", headers["Host"].asString()); - -#if WEBCC_ENABLE_GZIP - EXPECT_EQ("gzip, deflate", headers["Accept-Encoding"].asString()); -#else - EXPECT_EQ("identity", headers["Accept-Encoding"].asString()); -#endif // WEBCC_ENABLE_GZIP -} - TEST(ClientTest, Get) { webcc::ClientSession session; @@ -112,7 +66,22 @@ TEST(ClientTest, Get) { Header("Accept", "application/json") ()); - AssertGet(r); + EXPECT_EQ(webcc::Status::kOK, r->status()); + EXPECT_EQ("OK", r->reason()); + + Json::Value json = StringToJson(r->data()); + + Json::Value args = json["args"]; + + EXPECT_EQ(2, args.size()); + EXPECT_EQ("value1", args["key1"].asString()); + EXPECT_EQ("value2", args["key2"].asString()); + + Json::Value headers = json["headers"]; + + EXPECT_EQ("application/json", headers["Accept"].asString()); + EXPECT_EQ("identity", headers["Accept-Encoding"].asString()); + EXPECT_EQ("httpbin.org", headers["Host"].asString()); } catch (const webcc::Error& error) { std::cerr << error << std::endl; @@ -152,10 +121,25 @@ TEST(ClientTest, Get_SSL) { auto r = session.Send(webcc::RequestBuilder{}. Get("https://httpbin.org/get"). Query("key1", "value1").Query("key2", "value2"). - Header("Accept", "application/json") + Accept("application/json") ()); - AssertGet(r); + EXPECT_EQ(webcc::Status::kOK, r->status()); + EXPECT_EQ("OK", r->reason()); + + Json::Value json = StringToJson(r->data()); + + Json::Value args = json["args"]; + + EXPECT_EQ(2, args.size()); + EXPECT_EQ("value1", args["key1"].asString()); + EXPECT_EQ("value2", args["key2"].asString()); + + Json::Value headers = json["headers"]; + + EXPECT_EQ("application/json", headers["Accept"].asString()); + EXPECT_EQ("identity", headers["Accept-Encoding"].asString()); + EXPECT_EQ("httpbin.org", headers["Host"].asString()); } catch (const webcc::Error& error) { std::cerr << error << std::endl; @@ -248,9 +232,11 @@ TEST(ClientTest, Get_Jpeg_Stream_NoMove) { // ----------------------------------------------------------------------------- #if WEBCC_ENABLE_GZIP + // Test Gzip compressed response. TEST(ClientTest, Get_Gzip) { webcc::ClientSession session; + session.AcceptGzip(); try { auto r = session.Send(webcc::RequestBuilder{}. @@ -265,12 +251,11 @@ TEST(ClientTest, Get_Gzip) { std::cerr << error << std::endl; } } -#endif // WEBCC_ENABLE_GZIP -#if WEBCC_ENABLE_GZIP // Test Deflate compressed response. TEST(ClientTest, Get_Deflate) { webcc::ClientSession session; + session.AcceptGzip(); try { auto r = session.Send(webcc::RequestBuilder{}. @@ -285,6 +270,7 @@ TEST(ClientTest, Get_Deflate) { std::cerr << error << std::endl; } } + #endif // WEBCC_ENABLE_GZIP // ----------------------------------------------------------------------------- diff --git a/examples/github_client.cc b/examples/github_client.cc index 1d90848..e2ca213 100644 --- a/examples/github_client.cc +++ b/examples/github_client.cc @@ -128,6 +128,10 @@ int main() { webcc::ClientSession session; +#if WEBCC_ENABLE_GZIP + session.AcceptGzip(); +#endif + ListEvents(session); //ListUserFollowers(session, "sprinfall"); diff --git a/webcc/client_session.cc b/webcc/client_session.cc index febb2f8..4f3552b 100644 --- a/webcc/client_session.cc +++ b/webcc/client_session.cc @@ -19,6 +19,40 @@ void ClientSession::Accept(const std::string& content_types) { } } +#if WEBCC_ENABLE_GZIP + +// 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": +// "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. +// (https://www.zlib.net/zlib_faq.html#faq39) + +void ClientSession::AcceptGzip(bool gzip) { + if (gzip) { + headers_.Set(headers::kAcceptEncoding, "gzip, deflate"); + } else { + headers_.Set(headers::kAcceptEncoding, "identity"); + } +} + +#endif // WEBCC_ENABLE_GZIP + void ClientSession::Auth(const std::string& type, const std::string& credentials) { headers_.Set(headers::kAuthorization, type + " " + credentials); @@ -54,41 +88,15 @@ ResponsePtr ClientSession::Send(RequestPtr request, bool stream) { } void ClientSession::InitHeaders() { - using namespace headers; - - headers_.Set(kUserAgent, utility::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": - // "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. - // (https://www.zlib.net/zlib_faq.html#faq39) - - headers_.Set(kAccept, "*/*"); + headers_.Set(headers::kUserAgent, utility::UserAgent()); -#if WEBCC_ENABLE_GZIP - headers_.Set(kAcceptEncoding, "gzip, deflate"); -#else - headers_.Set(kAcceptEncoding, "identity"); -#endif // WEBCC_ENABLE_GZIP + headers_.Set(headers::kAccept, "*/*"); - headers_.Set(kConnection, "Keep-Alive"); + // Accept-Encoding is always default to "identity", even if GZIP is enabled. + // Please overwrite with AcceptGzip(). + headers_.Set(headers::kAcceptEncoding, "identity"); + + headers_.Set(headers::kConnection, "Keep-Alive"); } ResponsePtr ClientSession::DoSend(RequestPtr request, bool stream) { @@ -141,9 +149,11 @@ ResponsePtr ClientSession::DoSend(RequestPtr request, bool stream) { } auto response = client->response(); + // The client object might be cached in the pool. // Reset to make sure it won't keep a reference to the response object. client->Reset(); + return response; } diff --git a/webcc/client_session.h b/webcc/client_session.h index 8e28baa..b420c36 100644 --- a/webcc/client_session.h +++ b/webcc/client_session.h @@ -51,6 +51,13 @@ public: // Set content types to accept. void Accept(const std::string& content_types); +#if WEBCC_ENABLE_GZIP + + // Accept Gzip compressed response data or not. + void AcceptGzip(bool gzip = true); + +#endif // WEBCC_ENABLE_GZIP + // Set authorization. void Auth(const std::string& type, const std::string& credentials); diff --git a/webcc/request_builder.cc b/webcc/request_builder.cc index 15af692..2d4096f 100644 --- a/webcc/request_builder.cc +++ b/webcc/request_builder.cc @@ -54,6 +54,19 @@ RequestPtr RequestBuilder::operator()() { return request; } +#if WEBCC_ENABLE_GZIP + +// Accept Gzip compressed response data or not. +RequestBuilder& RequestBuilder::AcceptGzip(bool gzip) { + if (gzip) { + return Header(headers::kAcceptEncoding, "gzip, deflate"); + } else { + return Header(headers::kAcceptEncoding, "identity"); + } +} + +#endif // WEBCC_ENABLE_GZIP + RequestBuilder& RequestBuilder::File(const bfs::path& path, bool infer_media_type, std::size_t chunk_size) { diff --git a/webcc/request_builder.h b/webcc/request_builder.h index fa745ec..609c518 100644 --- a/webcc/request_builder.h +++ b/webcc/request_builder.h @@ -124,6 +124,13 @@ public: return Header(headers::kAccept, content_types); } +#if WEBCC_ENABLE_GZIP + + // Accept Gzip compressed response data or not. + RequestBuilder& AcceptGzip(bool gzip = true); + +#endif // WEBCC_ENABLE_GZIP + RequestBuilder& Body(const std::string& data) { body_.reset(new StringBody{ data, false }); return *this; @@ -173,10 +180,17 @@ public: RequestBuilder& Date(); #if WEBCC_ENABLE_GZIP + + // Compress the body data (only for string body). + // 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 RequestBuilder& Gzip(bool gzip = true) { gzip_ = gzip; return *this; } + #endif // WEBCC_ENABLE_GZIP private: @@ -207,11 +221,6 @@ private: bool keep_alive_ = true; #if WEBCC_ENABLE_GZIP - // Compress the body data (only for string body). - // 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; #endif // WEBCC_ENABLE_GZIP };