#include #include "gtest/gtest.h" #include "boost/algorithm/string.hpp" #include "boost/filesystem/fstream.hpp" #include "boost/filesystem/operations.hpp" #include "json/json.h" #include "webcc/client_session.h" #include "webcc/logger.h" namespace bfs = boost::filesystem; // ----------------------------------------------------------------------------- // JSON helper functions (based on jsoncpp). // Parse a string to JSON object. static Json::Value StringToJson(const std::string& str) { Json::Value json; Json::CharReaderBuilder builder; std::stringstream stream(str); std::string errors; if (!Json::parseFromStream(builder, stream, &json, &errors)) { std::cerr << errors << std::endl; } return json; } // ----------------------------------------------------------------------------- TEST(ClientTest, Head) { webcc::ClientSession session; try { auto r = session.Request(webcc::RequestBuilder{}. Head("http://httpbin.org/get") ()); 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; } } TEST(ClientTest, Head_Shortcut) { webcc::ClientSession session; try { auto r = session.Head("http://httpbin.org/get"); 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.Request(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)); EXPECT_EQ("", r->data()); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } } // ----------------------------------------------------------------------------- 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; try { auto r = session.Request(webcc::RequestBuilder{}. Get("http://httpbin.org/get"). Query("key1", "value1"). Query("key2", "value2"). Header("Accept", "application/json") ()); AssertGet(r); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } } TEST(ClientTest, Get_Shortcut) { webcc::ClientSession session; try { auto r = session.Get("http://httpbin.org/get", { "key1", "value1", "key2", "value2" }, { "Accept", "application/json" }); AssertGet(r); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } } #if WEBCC_ENABLE_SSL TEST(ClientTest, Get_SSL) { webcc::ClientSession session; try { // HTTPS is auto-detected from the URL scheme. auto r = session.Request(webcc::RequestBuilder{}. Get("https://httpbin.org/get"). Query("key1", "value1"). Query("key2", "value2"). Header("Accept", "application/json") ()); AssertGet(r); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } } #endif // WEBCC_ENABLE_SSL // Get a JPEG image (without streaming). TEST(ClientTest, Get_Jpeg_NoStream) { webcc::ClientSession session; try { auto r = session.Request(webcc::RequestBuilder{}. Get("http://httpbin.org/image/jpeg") ()); // TODO: Verify the response is a valid JPEG image. //std::ofstream ofs(, std::ios::binary); //ofs << r->data(); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } } TEST(ClientTest, Get_Jpeg_Stream) { webcc::ClientSession session; try { auto r = session.Request(webcc::RequestBuilder{}. Get("http://httpbin.org/image/jpeg") (), true); auto file_body = r->file_body(); EXPECT_TRUE(!!file_body); EXPECT_TRUE(!file_body->path().empty()); // Backup the path of the temp file. const webcc::Path ori_path = file_body->path(); const webcc::Path new_path("./wolf.jpeg"); bool moved = file_body->Move(new_path); EXPECT_TRUE(moved); EXPECT_TRUE(boost::filesystem::exists(new_path)); // The file in the original path should not exist any more. EXPECT_TRUE(!boost::filesystem::exists(ori_path)); // After move, the original path should be reset. EXPECT_TRUE(file_body->path().empty()); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } } // Test whether the streamed file will be deleted or not at the end if it's // not moved to another path by the user. TEST(ClientTest, Get_Jpeg_Stream_NoMove) { webcc::ClientSession session; try { webcc::Path ori_path; { auto r = session.Request(webcc::RequestBuilder{}. Get("http://httpbin.org/image/jpeg") (), true); auto file_body = r->file_body(); EXPECT_TRUE(!!file_body); EXPECT_TRUE(!file_body->path().empty()); // Backup the path of the temp file. ori_path = file_body->path(); } // The temp file should be deleted. EXPECT_TRUE(!boost::filesystem::exists(ori_path)); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } } // ----------------------------------------------------------------------------- #if WEBCC_ENABLE_GZIP // Test Gzip compressed response. TEST(ClientTest, Get_Gzip) { webcc::ClientSession session; try { auto r = session.Request(webcc::RequestBuilder{}. Get("http://httpbin.org/gzip") ()); Json::Value json = StringToJson(r->data()); EXPECT_EQ(true, json["gzipped"].asBool()); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } } #endif // WEBCC_ENABLE_GZIP #if WEBCC_ENABLE_GZIP // Test Deflate compressed response. TEST(ClientTest, Get_Deflate) { webcc::ClientSession session; try { auto r = session.Request(webcc::RequestBuilder{}. Get("http://httpbin.org/deflate") ()); Json::Value json = StringToJson(r->data()); EXPECT_EQ(true, json["deflated"].asBool()); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } } #endif // WEBCC_ENABLE_GZIP // ----------------------------------------------------------------------------- TEST(ClientTest, Post) { webcc::ClientSession session; try { const std::string data = "{'name'='Adam', 'age'=20}"; auto r = session.Request(webcc::RequestBuilder{}. Post("http://httpbin.org/post"). Body(data).Json() ()); EXPECT_EQ(webcc::Status::kOK, r->status()); EXPECT_EQ("OK", r->reason()); Json::Value json = StringToJson(r->data()); EXPECT_EQ(data, json["data"].asString()); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } } TEST(ClientTest, Post_Shortcut) { webcc::ClientSession session; try { const std::string data = "{'name'='Adam', 'age'=20}"; auto r = session.Post("http://httpbin.org/post", std::string(data), true); EXPECT_EQ(webcc::Status::kOK, r->status()); EXPECT_EQ("OK", r->reason()); Json::Value json = StringToJson(r->data()); EXPECT_EQ(data, json["data"].asString()); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } } static bfs::path GenerateTempFile(const std::string& data) { try { bfs::path path = bfs::temp_directory_path() / bfs::unique_path(); bfs::ofstream ofs; ofs.open(path, std::ios::binary); if (ofs.fail()) { return bfs::path{}; } ofs << data; return path; } catch (const bfs::filesystem_error&) { return bfs::path{}; } } TEST(ClientTest, Post_FileBody) { webcc::ClientSession session; const std::string data = "{'name'='Adam', 'age'=20}"; auto path = GenerateTempFile(data); if (path.empty()) { return; } try { auto r = session.Request(webcc::RequestBuilder{}. Post("http://httpbin.org/post"). File(path) // Use the file as body ()); EXPECT_EQ(webcc::Status::kOK, r->status()); EXPECT_EQ("OK", r->reason()); Json::Value json = StringToJson(r->data()); EXPECT_EQ(data, json["data"].asString()); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } // Remove the temp file. boost::system::error_code ec; bfs::remove(path, ec); } #if WEBCC_ENABLE_GZIP TEST(ClientTest, Post_Gzip_SmallData) { webcc::ClientSession session; try { // This data is too small to be compressed. const std::string data = "{'name'='Adam', 'age'=20}"; // This doesn't really compress the body! auto r = session.Request(webcc::RequestBuilder{}. Post("http://httpbin.org/post"). Body(data).Json(). Gzip() ()); //Json::Value json = StringToJson(r->data()); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } } #endif // WEBCC_ENABLE_GZIP #if (WEBCC_ENABLE_GZIP && WEBCC_ENABLE_SSL) // NOTE: Most servers don't support compressed requests! TEST(ClientTest, Post_Gzip) { webcc::ClientSession session; try { // Use Boost.org home page as the POST data. auto r1 = session.Get("https://www.boost.org/"); const std::string& data = r1->data(); auto r2 = session.Request(webcc::RequestBuilder{}. Post("http://httpbin.org/post"). Body(data).Gzip() ()); EXPECT_EQ(webcc::Status::kOK, r2->status()); EXPECT_EQ("OK", r2->reason()); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } } #endif // (WEBCC_ENABLE_GZIP && WEBCC_ENABLE_SSL) // ----------------------------------------------------------------------------- // Test persistent (keep-alive) connections. // // NOTE: // Boost.org doesn't support persistent connection and always includes // "Connection: Close" header in the response. // Both Google and GitHub support persistent connection but they don't like // to include "Connection: Keep-Alive" header in the responses. // URLs: // "http://httpbin.org/get"; // "https://www.boost.org/LICENSE_1_0.txt"; // "https://www.google.com"; // "https://api.github.com/events"; // TEST(ClientTest, KeepAlive) { webcc::ClientSession session; const std::string url = "http://httpbin.org/get"; try { // Keep-Alive by default. auto r = session.Request(webcc::RequestBuilder{}.Get(url)()); using boost::iequals; EXPECT_TRUE(iequals(r->GetHeader("Connection"), "Keep-alive")); // Close by setting Connection header directly. r = session.Request(webcc::RequestBuilder{}. Get(url). Header("Connection", "Close") ()); EXPECT_TRUE(iequals(r->GetHeader("Connection"), "Close")); // Close by using request builder. r = session.Request(webcc::RequestBuilder{}. Get(url). KeepAlive(false) ()); EXPECT_TRUE(iequals(r->GetHeader("Connection"), "Close")); // Keep-Alive explicitly by using request builder. r = session.Request(webcc::RequestBuilder{}. Get(url). KeepAlive(true) ()); EXPECT_TRUE(iequals(r->GetHeader("Connection"), "Keep-alive")); } catch (const webcc::Error& error) { std::cerr << error << std::endl; } } // ----------------------------------------------------------------------------- int main(int argc, char* argv[]) { // Set webcc::LOG_CONSOLE to enable logging. WEBCC_LOG_INIT("", 0); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }