From ffa07949268d661f0ebc4cc484b2a2e37d6e83a7 Mon Sep 17 00:00:00 2001 From: Chunting Gu Date: Wed, 6 Mar 2019 13:46:19 +0800 Subject: [PATCH] Handle both HTTP & HTTPS in HttpClientSession; remove macro WEBCC_ENABLE_SSL. --- CMakeLists.txt | 36 ++++++---- example/github_rest_client/CMakeLists.txt | 15 +--- example/http_client/CMakeLists.txt | 4 +- example/http_client/main.cc | 49 +++++++------ example/http_ssl_client/CMakeLists.txt | 10 +-- unittest/url_test.cc | 83 +++++++++++++++++++++++ webcc/CMakeLists.txt | 9 +-- webcc/http_client_session.cc | 24 +++++-- webcc/http_request_args.h | 13 +++- 9 files changed, 171 insertions(+), 72 deletions(-) create mode 100644 unittest/url_test.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 70dfe5b..1b1742c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,6 @@ project(webcc) option(WEBCC_ENABLE_REST "Enable REST support?" ON) option(WEBCC_ENABLE_SOAP "Enable SOAP support (need pugixml)?" ON) -option(WEBCC_ENABLE_SSL "Enable SSL/HTTPS support (need OpenSSL)?" OFF) option(WEBCC_ENABLE_UNITTEST "Build unit test?" ON) option(WEBCC_ENABLE_EXAMPLES "Build examples?" ON) @@ -85,14 +84,12 @@ if(Boost_FOUND) message(STATUS ${Boost_LIBRARIES}) endif() -if(WEBCC_ENABLE_SSL) - set(OPENSSL_USE_STATIC_LIBS ON) - set(OPENSSL_MSVC_STATIC_RT ON) - find_package(OpenSSL) - if(OPENSSL_FOUND) - include_directories(${OPENSSL_INCLUDE_DIR}) - message(STATUS "OpenSSL libs: " ${OPENSSL_LIBRARIES}) - endif() +set(OPENSSL_USE_STATIC_LIBS ON) +set(OPENSSL_MSVC_STATIC_RT ON) +find_package(OpenSSL) +if(OPENSSL_FOUND) + include_directories(${OPENSSL_INCLUDE_DIR}) + message(STATUS "OpenSSL libs: " ${OPENSSL_LIBRARIES}) endif() include_directories( @@ -121,6 +118,17 @@ endif() add_subdirectory(webcc) if(WEBCC_ENABLE_EXAMPLES) + # Common libraries to link for examples. + set(EXAMPLE_COMMON_LIBS webcc ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} + "${CMAKE_THREAD_LIBS_INIT}") + if(WIN32) + set(EXAMPLE_COMMON_LIBS ${EXAMPLE_COMMON_LIBS} crypt32) + endif() + if(UNIX) + # Add `-ldl` for Linux to avoid "undefined reference to `dlopen'". + set(EXAMPLE_COMMON_LIBS ${EXAMPLE_COMMON_LIBS} ${CMAKE_DL_LIBS}) + endif() + add_subdirectory(example/http_client) # add_subdirectory(example/http_async_client) @@ -143,13 +151,11 @@ if(WEBCC_ENABLE_EXAMPLES) add_subdirectory(example/soap_book_client) endif() - if(WEBCC_ENABLE_SSL) - add_subdirectory(example/http_ssl_client) - # add_subdirectory(example/http_ssl_async_client) + add_subdirectory(example/http_ssl_client) + # add_subdirectory(example/http_ssl_async_client) - if(WEBCC_ENABLE_REST) - # add_subdirectory(example/github_rest_client) - endif() + if(WEBCC_ENABLE_REST) + # add_subdirectory(example/github_rest_client) endif() endif() diff --git a/example/github_rest_client/CMakeLists.txt b/example/github_rest_client/CMakeLists.txt index de6477d..837856a 100644 --- a/example/github_rest_client/CMakeLists.txt +++ b/example/github_rest_client/CMakeLists.txt @@ -1,15 +1,2 @@ -set(LIBS webcc jsoncpp ${Boost_LIBRARIES} "${CMAKE_THREAD_LIBS_INIT}") - -set(LIBS ${LIBS} ${OPENSSL_LIBRARIES}) -if(WIN32) - set(LIBS ${LIBS} crypt32) -endif() - -if(UNIX) - # Add `-ldl` for Linux to avoid "undefined reference to `dlopen'". - set(LIBS ${LIBS} ${CMAKE_DL_LIBS}) -endif() - add_executable(github_rest_client main.cc) - -target_link_libraries(github_rest_client ${LIBS}) +target_link_libraries(github_rest_client ${EXAMPLE_COMMON_LIBS} jsoncpp) diff --git a/example/http_client/CMakeLists.txt b/example/http_client/CMakeLists.txt index 723ef64..0907baa 100644 --- a/example/http_client/CMakeLists.txt +++ b/example/http_client/CMakeLists.txt @@ -1,4 +1,2 @@ add_executable(http_client main.cc) - -target_link_libraries(http_client webcc ${Boost_LIBRARIES}) -target_link_libraries(http_client "${CMAKE_THREAD_LIBS_INIT}") +target_link_libraries(http_client ${EXAMPLE_COMMON_LIBS}) diff --git a/example/http_client/main.cc b/example/http_client/main.cc index bd608e0..4ab2310 100644 --- a/example/http_client/main.cc +++ b/example/http_client/main.cc @@ -1,6 +1,6 @@ #include -#include "webcc/http_client_session.h" // TEST +#include "webcc/http_client_session.h" #include "webcc/logger.h" int main() { @@ -12,38 +12,49 @@ int main() { HttpClientSession session; -#if 0 - 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)); + // --------------------------------------------------------------------------- + + 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)); 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); + // - 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. + r = session.Get("http://httpbin.org/get", { "key1", "value1", "key2", "value2" }, { "Accept", "application/json" }, - HttpRequestArgs().buffer_size(1000)); -#endif + HttpRequestArgs{}.buffer_size(1000)); + + // --------------------------------------------------------------------------- + // HTTPS is auto-detected from the URL schema. - r = session.Post("http://httpbin.org/post", "{ 'key': 'value' }", true, + r = session.Post("https://httpbin.org/post", "{ 'key': 'value' }", true, { "Accept", "application/json" }, - HttpRequestArgs().buffer_size(1000)); + HttpRequestArgs{}.ssl_verify(false).buffer_size(1000)); - std::cout << r->content() << std::endl; + if (r) { + std::cout << r->content() << std::endl; + } return 0; } diff --git a/example/http_ssl_client/CMakeLists.txt b/example/http_ssl_client/CMakeLists.txt index b9b4247..5b78664 100644 --- a/example/http_ssl_client/CMakeLists.txt +++ b/example/http_ssl_client/CMakeLists.txt @@ -1,10 +1,2 @@ add_executable(http_ssl_client main.cc) - -set(SSL_LIBS ${OPENSSL_LIBRARIES}) -if(WIN32) - set(SSL_LIBS ${SSL_LIBS} crypt32) -endif() - -target_link_libraries(http_ssl_client webcc ${Boost_LIBRARIES}) -target_link_libraries(http_ssl_client "${CMAKE_THREAD_LIBS_INIT}") -target_link_libraries(http_ssl_client ${SSL_LIBS}) +target_link_libraries(http_ssl_client ${EXAMPLE_COMMON_LIBS}) diff --git a/unittest/url_test.cc b/unittest/url_test.cc new file mode 100644 index 0000000..7fa60bf --- /dev/null +++ b/unittest/url_test.cc @@ -0,0 +1,83 @@ +#include "gtest/gtest.h" + +#include "webcc/url.h" + +TEST(Url, Basic) { + webcc::Url url("http://example.com/path", false); + + EXPECT_EQ("http", url.scheme()); + EXPECT_EQ("example.com", url.host()); + EXPECT_EQ("", url.port()); + EXPECT_EQ("path", url.path()); + EXPECT_EQ("", url.query()); +} + +TEST(Url, NoPath) { + webcc::Url url("http://example.com", false); + + EXPECT_EQ("http", url.scheme()); + EXPECT_EQ("example.com", url.host()); + EXPECT_EQ("", url.port()); + EXPECT_EQ("", url.path()); + EXPECT_EQ("", url.query()); +} + +TEST(Url, NoPath2) { + webcc::Url url("http://example.com/", false); + + EXPECT_EQ("http", url.scheme()); + EXPECT_EQ("example.com", url.host()); + EXPECT_EQ("", url.port()); + EXPECT_EQ("", url.path()); + EXPECT_EQ("", url.query()); +} + +TEST(Url, NoPath3) { + webcc::Url url("http://example.com?key=value", false); + + EXPECT_EQ("http", url.scheme()); + EXPECT_EQ("example.com", url.host()); + EXPECT_EQ("", url.port()); + EXPECT_EQ("", url.path()); + EXPECT_EQ("key=value", url.query()); +} + +TEST(Url, NoPath4) { + webcc::Url url("http://example.com/?key=value", false); + + EXPECT_EQ("http", url.scheme()); + EXPECT_EQ("example.com", url.host()); + EXPECT_EQ("", url.port()); + EXPECT_EQ("", url.path()); + EXPECT_EQ("key=value", url.query()); +} + +TEST(Url, NoScheme) { + webcc::Url url("/path/to", false); + + EXPECT_EQ("", url.scheme()); + EXPECT_EQ("", url.host()); + EXPECT_EQ("", url.port()); + EXPECT_EQ("path/to", url.path()); + EXPECT_EQ("", url.query()); +} + +TEST(Url, NoScheme2) { + webcc::Url url("/path/to?key=value", false); + + EXPECT_EQ("", url.scheme()); + EXPECT_EQ("", url.host()); + EXPECT_EQ("", url.port()); + EXPECT_EQ("path/to", url.path()); + EXPECT_EQ("key=value", url.query()); +} + +TEST(Url, Full) { + webcc::Url url("https://localhost:3000/path/to?key=value", false); + + EXPECT_EQ("https", url.scheme()); + EXPECT_EQ("localhost", url.host()); + EXPECT_EQ("3000", url.port()); + EXPECT_EQ("path/to", url.path()); + EXPECT_EQ("key=value", url.query()); +} diff --git a/webcc/CMakeLists.txt b/webcc/CMakeLists.txt index dda4d58..2b6c1db 100644 --- a/webcc/CMakeLists.txt +++ b/webcc/CMakeLists.txt @@ -30,6 +30,7 @@ set(HEADERS http_response.h http_response_parser.h http_server.h + http_ssl_client.h # http_ssl_async_client.h queue.h url.h @@ -53,6 +54,7 @@ set(SOURCES http_response.cc http_response_parser.cc http_server.cc + http_ssl_client.cc # http_ssl_async_client.cc logger.cc url.cc @@ -61,7 +63,7 @@ set(SOURCES if(WEBCC_ENABLE_REST) set(REST_HEADERS - # rest_async_client.h + # rest_async_client.h # rest_client.h rest_request_handler.h rest_server.h @@ -80,11 +82,6 @@ if(WEBCC_ENABLE_REST) set(SOURCES ${SOURCES} ${REST_SOURCES}) endif() -if(WEBCC_ENABLE_SSL) - set(HEADERS ${HEADERS} http_ssl_client.h) - set(SOURCES ${SOURCES} http_ssl_client.cc) -endif() - if(WEBCC_ENABLE_SOAP) set(SOAP_HEADERS # soap_async_client.h diff --git a/webcc/http_client_session.cc b/webcc/http_client_session.cc index 8d78009..afa0622 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/http_client.h" -#include "webcc/http_request.h" +#include "webcc/http_ssl_client.h" #include "webcc/url.h" namespace webcc { @@ -14,7 +14,7 @@ HttpResponsePtr HttpClientSession::Request(HttpRequestArgs&& args) { assert(args.parameters_.size() % 2 == 0); assert(args.headers_.size() % 2 == 0); - HttpRequest request(args.method_, args.url_, args.parameters_); + HttpRequest request{ args.method_, args.url_, args.parameters_ }; if (!args.data_.empty()) { request.SetContent(std::move(args.data_), true); @@ -39,12 +39,26 @@ HttpResponsePtr HttpClientSession::Request(HttpRequestArgs&& args) { request.Prepare(); - HttpClient client; - if (!client.Request(request, args.buffer_size_)) { + // TODO: + + std::shared_ptr impl; + + if (request.url().scheme() == "http") { + impl.reset(new HttpClient); + } else if (request.url().scheme() == "https") { + impl.reset(new HttpSslClient{args.ssl_verify_}); + } else { return HttpResponsePtr{}; } - return client.response(); + if (impl) { + if (!impl->Request(request, args.buffer_size_)) { + return HttpResponsePtr{}; + } + return impl->response(); + } + + return HttpResponsePtr{}; } HttpResponsePtr HttpClientSession::Get(const std::string& url, diff --git a/webcc/http_request_args.h b/webcc/http_request_args.h index e53434f..64181d3 100644 --- a/webcc/http_request_args.h +++ b/webcc/http_request_args.h @@ -17,7 +17,7 @@ class HttpClientSession; class HttpRequestArgs { public: explicit HttpRequestArgs(const std::string& method = "") - : method_(method), json_(false), buffer_size_(0) { + : method_(method), json_(false), ssl_verify_(true), buffer_size_(0) { LOG_VERB("HttpRequestArgs()"); } @@ -38,6 +38,7 @@ public: data_(std::move(rhs.data_)), json_(rhs.json_), headers_(std::move(rhs.headers_)), + ssl_verify_(rhs.ssl_verify_), buffer_size_(rhs.buffer_size_) { LOG_VERB("HttpRequestArgs(&&)"); } @@ -50,6 +51,7 @@ public: data_ = std::move(rhs.data_); json_ = rhs.json_; headers_ = std::move(rhs.headers_); + ssl_verify_ = rhs.ssl_verify_; buffer_size_ = buffer_size_; } LOG_VERB("HttpRequestArgs& operator=(&&)"); @@ -108,6 +110,11 @@ public: return std::move(*this); } + HttpRequestArgs&& ssl_verify(bool ssl_verify = true) { + ssl_verify_ = ssl_verify; + return std::move(*this); + } + HttpRequestArgs&& buffer_size(std::size_t buffer_size) { buffer_size_ = buffer_size; return std::move(*this); @@ -130,6 +137,10 @@ private: std::vector headers_; + // Verify the certificate of the peer (remote server) or not. + // HTTPS only. + bool ssl_verify_; + // Size of the buffer to read response. // Leave it to 0 for using default value. std::size_t buffer_size_;