From d159ce46bfa6274fed1fca2025e4904b3c1251c0 Mon Sep 17 00:00:00 2001 From: Chunting Gu Date: Thu, 11 Oct 2018 12:47:47 +0800 Subject: [PATCH] Add RestSslClient; add User-Agent header. --- CMakeLists.txt | 1 + example/http_hello_async_client/main.cc | 4 +- example/http_hello_client/main.cc | 4 +- example/http_ssl_client/main.cc | 8 +- example/rest_github_client/CMakeLists.txt | 10 ++ example/rest_github_client/main.cc | 26 +++++ webcc/CMakeLists.txt | 4 +- webcc/basic_rest_client.h | 123 ++++++++++++++++++++++ webcc/globals.cc | 1 + webcc/globals.h | 4 + webcc/http_client.cc | 1 - webcc/http_message.h | 4 +- webcc/http_request.cc | 21 ++-- webcc/http_request.h | 13 ++- webcc/http_response.cc | 5 +- webcc/http_response.h | 2 +- webcc/http_session.cc | 2 +- webcc/http_ssl_client.cc | 10 -- webcc/http_ssl_client.h | 3 + webcc/rest_async_client.cc | 4 +- webcc/rest_client.cc | 40 ------- webcc/rest_client.h | 84 +-------------- webcc/rest_ssl_client.h | 13 +++ webcc/soap_async_client.cc | 4 +- webcc/soap_client.cc | 4 +- webcc/version.h | 6 ++ 26 files changed, 232 insertions(+), 169 deletions(-) create mode 100644 example/rest_github_client/CMakeLists.txt create mode 100644 example/rest_github_client/main.cc create mode 100644 webcc/basic_rest_client.h delete mode 100644 webcc/rest_client.cc create mode 100644 webcc/rest_ssl_client.h create mode 100644 webcc/version.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 761f03e..3f91e58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,6 +160,7 @@ if(WEBCC_BUILD_EXAMPLE) if(WEBCC_ENABLE_SSL) add_subdirectory(${PROJECT_SOURCE_DIR}/example/http_ssl_client) + add_subdirectory(${PROJECT_SOURCE_DIR}/example/rest_github_client) endif() endif() diff --git a/example/http_hello_async_client/main.cc b/example/http_hello_async_client/main.cc index 32946b4..6ea1d0f 100644 --- a/example/http_hello_async_client/main.cc +++ b/example/http_hello_async_client/main.cc @@ -15,8 +15,8 @@ void Test(boost::asio::io_context& io_context) { request->set_method(webcc::kHttpGet); request->set_url("/index.html"); - request->SetHost("localhost", "8000"); - request->UpdateStartLine(); + request->set_host("localhost", "8000"); + request->Make(); webcc::HttpAsyncClientPtr client(new webcc::HttpAsyncClient(io_context)); diff --git a/example/http_hello_client/main.cc b/example/http_hello_client/main.cc index aefba4d..84aaf6d 100644 --- a/example/http_hello_client/main.cc +++ b/example/http_hello_client/main.cc @@ -12,8 +12,8 @@ void Test() { webcc::HttpRequest request; request.set_method(webcc::kHttpGet); request.set_url("/index.html"); - request.SetHost("localhost", "8000"); - request.UpdateStartLine(); + request.set_host("localhost", "8000"); + request.Make(); webcc::HttpClient client; if (client.Request(request)) { diff --git a/example/http_ssl_client/main.cc b/example/http_ssl_client/main.cc index d33f7f5..e662bb2 100644 --- a/example/http_ssl_client/main.cc +++ b/example/http_ssl_client/main.cc @@ -7,10 +7,14 @@ void Test() { webcc::HttpRequest request; request.set_method(webcc::kHttpGet); request.set_url("/LICENSE_1_0.txt"); - request.SetHost("www.boost.org", "443"); - request.UpdateStartLine(); + + // Leave port to default value. + request.set_host("www.boost.org"); + + request.Make(); webcc::HttpSslClient client; + if (client.Request(request)) { std::cout << client.response()->content() << std::endl; } else { diff --git a/example/rest_github_client/CMakeLists.txt b/example/rest_github_client/CMakeLists.txt new file mode 100644 index 0000000..f1d9fce --- /dev/null +++ b/example/rest_github_client/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(rest_github_client main.cc) + +set(SSL_LIBS ${OPENSSL_LIBRARIES}) +if(WIN32) + set(SSL_LIBS ${SSL_LIBS} crypt32) +endif() + +target_link_libraries(rest_github_client webcc ${Boost_LIBRARIES}) +target_link_libraries(rest_github_client "${CMAKE_THREAD_LIBS_INIT}") +target_link_libraries(rest_github_client ${SSL_LIBS}) diff --git a/example/rest_github_client/main.cc b/example/rest_github_client/main.cc new file mode 100644 index 0000000..09b5b5c --- /dev/null +++ b/example/rest_github_client/main.cc @@ -0,0 +1,26 @@ +#include + +#include "webcc/rest_ssl_client.h" +#include "webcc/logger.h" + +void Test() { + webcc::RestSslClient client("api.github.com"); + + if (client.Get("/events")) { + std::cout << client.response()->content() << std::endl; + } else { + std::cout << webcc::DescribeError(client.error()); + if (client.timed_out()) { + std::cout << " (timed out)"; + } + std::cout << std::endl; + } +} + +int main() { + WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); + + Test(); + + return 0; +} diff --git a/webcc/CMakeLists.txt b/webcc/CMakeLists.txt index 139ee8b..0a73031 100644 --- a/webcc/CMakeLists.txt +++ b/webcc/CMakeLists.txt @@ -9,6 +9,7 @@ endif() include(GNUInstallDirs) set(HEADERS + version.h globals.h http_async_client.h http_client.h @@ -23,6 +24,7 @@ set(HEADERS http_server.h logger.h queue.h + basic_rest_client.h rest_async_client.h rest_client.h rest_request_handler.h @@ -48,7 +50,6 @@ set(SOURCES http_server.cc logger.cc rest_async_client.cc - rest_client.cc rest_request_handler.cc rest_service_manager.cc rest_service.cc @@ -59,6 +60,7 @@ set(SOURCES if(WEBCC_ENABLE_SSL) set(HEADERS ${HEADERS} http_ssl_client.h + rest_ssl_client.h ) set(SOURCES ${SOURCES} diff --git a/webcc/basic_rest_client.h b/webcc/basic_rest_client.h new file mode 100644 index 0000000..f269abb --- /dev/null +++ b/webcc/basic_rest_client.h @@ -0,0 +1,123 @@ +#ifndef WEBCC_BASIC_REST_CLIENT_H_ +#define WEBCC_BASIC_REST_CLIENT_H_ + +#include +#include +#include // for move() + +#include "webcc/globals.h" +#include "webcc/http_request.h" +#include "webcc/http_response.h" + +namespace webcc { + +template +class BasicRestClient { + public: + // If |port| is empty, |host| will be checked to see if it contains port or + // not (separated by ':'). + explicit BasicRestClient(const std::string& host, + const std::string& port = "") + : host_(host), port_(port) { + if (port_.empty()) { + std::size_t i = host_.find_last_of(':'); + if (i != std::string::npos) { + port_ = host_.substr(i + 1); + host_ = host_.substr(0, i); + } + } + } + + ~BasicRestClient() = default; + + WEBCC_DELETE_COPY_ASSIGN(BasicRestClient); + + void SetTimeout(int seconds) { + http_client_.SetTimeout(seconds); + } + + // NOTE: + // The return value of the following methods (Get, Post, etc.) only indicates + // if the socket communication is successful or not. Check error() and + // timed_out() for more information if it's failed. Check response_status() + // instead for the HTTP status code. + + // HTTP GET request. + inline bool Get(const std::string& url) { + return Request(kHttpGet, url, ""); + } + + // HTTP POST request. + inline bool Post(const std::string& url, std::string&& content) { + return Request(kHttpPost, url, std::move(content)); + } + + // HTTP PUT request. + inline bool Put(const std::string& url, std::string&& content) { + return Request(kHttpPut, url, std::move(content)); + } + + // HTTP PATCH request. + inline bool Patch(const std::string& url, std::string&& content) { + return Request(kHttpPatch, url, std::move(content)); + } + + // HTTP DELETE request. + inline bool Delete(const std::string& url) { + return Request(kHttpDelete, url, ""); + } + + HttpResponsePtr response() const { + return http_client_.response(); + } + + int response_status() const { + assert(response()); + return response()->status(); + } + + const std::string& response_content() const { + assert(response()); + return response()->content(); + } + + bool timed_out() const { + return http_client_.timed_out(); + } + + Error error() const { + return http_client_.error(); + } + + private: + bool Request(const std::string& method, const std::string& url, + std::string&& content) { + HttpRequest http_request; + + http_request.set_method(method); + http_request.set_url(url); + http_request.set_host(host_, port_); + + if (!content.empty()) { + http_request.SetContent(std::move(content), true); + http_request.SetContentType(kAppJsonUtf8); + } + + http_request.Make(); + + if (!http_client_.Request(http_request)) { + return false; + } + + return true; + } + + std::string host_; + std::string port_; + + HttpClientType http_client_; +}; + +} // namespace webcc + +#endif // WEBCC_BASIC_REST_CLIENT_H_ diff --git a/webcc/globals.cc b/webcc/globals.cc index 7c5a3ef..170ce2b 100644 --- a/webcc/globals.cc +++ b/webcc/globals.cc @@ -9,6 +9,7 @@ namespace webcc { const std::string kHost = "Host"; const std::string kContentType = "Content-Type"; const std::string kContentLength = "Content-Length"; +const std::string kUserAgent = "User-Agent"; const std::string kAppJsonUtf8 = "application/json; charset=utf-8"; diff --git a/webcc/globals.h b/webcc/globals.h index e2a2f6b..863e5ce 100644 --- a/webcc/globals.h +++ b/webcc/globals.h @@ -3,6 +3,8 @@ #include +#include "webcc/version.h" + // ----------------------------------------------------------------------------- // Macros @@ -38,9 +40,11 @@ const std::size_t kInvalidLength = std::string::npos; // Default timeout for reading response. const int kMaxReadSeconds = 30; +// HTTP headers. extern const std::string kHost; extern const std::string kContentType; extern const std::string kContentLength; +extern const std::string kUserAgent; extern const std::string kAppJsonUtf8; diff --git a/webcc/http_client.cc b/webcc/http_client.cc index 627b0fd..dd99f7e 100644 --- a/webcc/http_client.cc +++ b/webcc/http_client.cc @@ -1,6 +1,5 @@ #include "webcc/http_client.h" -#include // for min #include #include "boost/asio/connect.hpp" diff --git a/webcc/http_message.h b/webcc/http_message.h index 08a74f3..7403d30 100644 --- a/webcc/http_message.h +++ b/webcc/http_message.h @@ -50,9 +50,9 @@ class HttpMessage { } } - // Set start line according to other informations. + // Make the message (e.g., update start line). // Must be called before ToBuffers()! - virtual void UpdateStartLine() = 0; + virtual void Make() = 0; // Convert the message into a vector of buffers. The buffers do not own the // underlying memory blocks, therefore the message object must remain valid diff --git a/webcc/http_request.cc b/webcc/http_request.cc index cdfd017..ce72c2b 100644 --- a/webcc/http_request.cc +++ b/webcc/http_request.cc @@ -2,23 +2,20 @@ namespace webcc { -void HttpRequest::SetHost(const std::string& host, const std::string& port) { - host_ = host; - port_ = port; - - if (port.empty()) { - SetHeader(kHost, host); - } else { - SetHeader(kHost, host + ":" + port); - } -} - -void HttpRequest::UpdateStartLine() { +void HttpRequest::Make() { start_line_ = method_; start_line_ += " "; start_line_ += url_; start_line_ += " HTTP/1.1"; start_line_ += CRLF; + + if (port_.empty()) { + SetHeader(kHost, host_); + } else { + SetHeader(kHost, host_ + ":" + port_); + } + + SetHeader(kUserAgent, "Webcc/"WEBCC_VERSION); } } // namespace webcc diff --git a/webcc/http_request.h b/webcc/http_request.h index c500919..5dc02d8 100644 --- a/webcc/http_request.h +++ b/webcc/http_request.h @@ -29,12 +29,15 @@ class HttpRequest : public HttpMessage { // Set host name and port number. // The |host| is a descriptive name (e.g., www.google.com) or a numeric IP // address (127.0.0.1). - // The |port| is a numeric number (e.g., 9000), the default (80 for HTTP and - // 443 for HTTPS) will be used if it's empty. - void SetHost(const std::string& host, const std::string& port); + // The |port| is a numeric number (e.g., 9000). The default value (80 for HTTP + // or 443 for HTTPS) will be used to connect to server if it's empty. + void set_host(const std::string& host, const std::string& port = "") { + host_ = host; + port_ = port; + } - // Set start line according to HTTP method, URL, etc. - void UpdateStartLine() override; + // Compose start line, set Host header, etc. + void Make() override; private: // HTTP method. diff --git a/webcc/http_response.cc b/webcc/http_response.cc index ba8bd81..aec347a 100644 --- a/webcc/http_response.cc +++ b/webcc/http_response.cc @@ -55,7 +55,7 @@ const std::string& ToString(int status) { } // namespace status_strings -void HttpResponse::UpdateStartLine() { +void HttpResponse::Make() { start_line_ = status_strings::ToString(status_); } @@ -64,7 +64,8 @@ HttpResponse HttpResponse::Fault(HttpStatus::Enum status) { HttpResponse response; response.set_status(status); - response.UpdateStartLine(); + + response.Make(); return response; } diff --git a/webcc/http_response.h b/webcc/http_response.h index a335630..fdc91b6 100644 --- a/webcc/http_response.h +++ b/webcc/http_response.h @@ -19,7 +19,7 @@ class HttpResponse : public HttpMessage { void set_status(int status) { status_ = status; } // Set start line according to status code. - void UpdateStartLine() override; + void Make() override; // Get a fault response when HTTP status is not OK. // TODO: Avoid copy. diff --git a/webcc/http_session.cc b/webcc/http_session.cc index 5eac5d4..d5c3022 100644 --- a/webcc/http_session.cc +++ b/webcc/http_session.cc @@ -40,7 +40,7 @@ void HttpSession::SetResponseContent(std::string&& content, void HttpSession::SendResponse(HttpStatus::Enum status) { response_.set_status(status); - response_.UpdateStartLine(); + response_.Make(); DoWrite(); } diff --git a/webcc/http_ssl_client.cc b/webcc/http_ssl_client.cc index bd0574e..49df839 100644 --- a/webcc/http_ssl_client.cc +++ b/webcc/http_ssl_client.cc @@ -6,8 +6,6 @@ #include "boost/asio/read.hpp" #include "boost/asio/write.hpp" #include "boost/date_time/posix_time/posix_time.hpp" -#include "boost/lambda/bind.hpp" -#include "boost/lambda/lambda.hpp" #include "webcc/logger.h" #include "webcc/utility.h" @@ -93,14 +91,6 @@ Error HttpSslClient::Connect(const HttpRequest& request) { LOG_VERB("Socket connected."); - // The deadline actor may have had a chance to run and close our socket, even - // though the connect operation notionally succeeded. - if (stopped_) { - // |timed_out_| should be true in this case. - LOG_ERRO("Socket connect timed out."); - return kEndpointConnectError; - } - return kNoError; } diff --git a/webcc/http_ssl_client.h b/webcc/http_ssl_client.h index 95de97c..38dc8cf 100644 --- a/webcc/http_ssl_client.h +++ b/webcc/http_ssl_client.h @@ -18,6 +18,9 @@ namespace webcc { +// HTTP SSL (a.k.a., HTTPS) client session in synchronous mode. +// A request will not return until the response is received or timeout occurs. +// Don't use the same HttpClient object in multiple threads. class HttpSslClient { public: HttpSslClient(); diff --git a/webcc/rest_async_client.cc b/webcc/rest_async_client.cc index ac97c2d..048deb2 100644 --- a/webcc/rest_async_client.cc +++ b/webcc/rest_async_client.cc @@ -16,14 +16,14 @@ void RestAsyncClient::Request(const std::string& method, request->set_method(method); request->set_url(url); - request->SetHost(host_, port_); + request->set_host(host_, port_); if (!content.empty()) { request->SetContent(std::move(content), true); request->SetContentType(kAppJsonUtf8); } - request->UpdateStartLine(); + request->Make(); HttpAsyncClientPtr http_client(new HttpAsyncClient(io_context_)); diff --git a/webcc/rest_client.cc b/webcc/rest_client.cc deleted file mode 100644 index db8f599..0000000 --- a/webcc/rest_client.cc +++ /dev/null @@ -1,40 +0,0 @@ -#include "webcc/rest_client.h" - -#include "webcc/http_request.h" - -namespace webcc { - -RestClient::RestClient(const std::string& host, const std::string& port) - : host_(host), port_(port) { - if (port_.empty()) { - std::size_t i = host_.find_last_of(':'); - if (i != std::string::npos) { - port_ = host_.substr(i + 1); - host_ = host_.substr(0, i); - } - } -} - -bool RestClient::Request(const std::string& method, const std::string& url, - std::string&& content) { - HttpRequest http_request; - - http_request.set_method(method); - http_request.set_url(url); - http_request.SetHost(host_, port_); - - if (!content.empty()) { - http_request.SetContent(std::move(content), true); - http_request.SetContentType(kAppJsonUtf8); - } - - http_request.UpdateStartLine(); - - if (!http_client_.Request(http_request)) { - return false; - } - - return true; -} - -} // namespace webcc diff --git a/webcc/rest_client.h b/webcc/rest_client.h index 65b2c33..b125ba8 100644 --- a/webcc/rest_client.h +++ b/webcc/rest_client.h @@ -1,92 +1,12 @@ #ifndef WEBCC_REST_CLIENT_H_ #define WEBCC_REST_CLIENT_H_ -#include -#include -#include // for move() - -#include "webcc/globals.h" +#include "webcc/basic_rest_client.h" #include "webcc/http_client.h" -#include "webcc/http_response.h" namespace webcc { -class RestClient { - public: - // If |port| is empty, |host| will be checked to see if it contains port or - // not (separated by ':'). - explicit RestClient(const std::string& host, const std::string& port = ""); - - ~RestClient() = default; - - WEBCC_DELETE_COPY_ASSIGN(RestClient); - - void SetTimeout(int seconds) { - http_client_.SetTimeout(seconds); - } - - // NOTE: - // The return value of the following methods (Get, Post, etc.) only indicates - // if the socket communication is successful or not. Check error() and - // timed_out() for more information if it's failed. Check response_status() - // instead for the HTTP status code. - - // HTTP GET request. - inline bool Get(const std::string& url) { - return Request(kHttpGet, url, ""); - } - - // HTTP POST request. - inline bool Post(const std::string& url, std::string&& content) { - return Request(kHttpPost, url, std::move(content)); - } - - // HTTP PUT request. - inline bool Put(const std::string& url, std::string&& content) { - return Request(kHttpPut, url, std::move(content)); - } - - // HTTP PATCH request. - inline bool Patch(const std::string& url, std::string&& content) { - return Request(kHttpPatch, url, std::move(content)); - } - - // HTTP DELETE request. - inline bool Delete(const std::string& url) { - return Request(kHttpDelete, url, ""); - } - - HttpResponsePtr response() const { - return http_client_.response(); - } - - int response_status() const { - assert(response()); - return response()->status(); - } - - const std::string& response_content() const { - assert(response()); - return response()->content(); - } - - bool timed_out() const { - return http_client_.timed_out(); - } - - Error error() const { - return http_client_.error(); - } - - private: - bool Request(const std::string& method, const std::string& url, - std::string&& content); - - std::string host_; - std::string port_; - - HttpClient http_client_; -}; +typedef BasicRestClient RestClient; } // namespace webcc diff --git a/webcc/rest_ssl_client.h b/webcc/rest_ssl_client.h new file mode 100644 index 0000000..4420f61 --- /dev/null +++ b/webcc/rest_ssl_client.h @@ -0,0 +1,13 @@ +#ifndef WEBCC_REST_SSL_CLIENT_H_ +#define WEBCC_REST_SSL_CLIENT_H_ + +#include "webcc/basic_rest_client.h" +#include "webcc/http_ssl_client.h" + +namespace webcc { + +typedef BasicRestClient RestSslClient; + +} // namespace webcc + +#endif // WEBCC_REST_SSL_CLIENT_H_ diff --git a/webcc/soap_async_client.cc b/webcc/soap_async_client.cc index df22969..1753716 100644 --- a/webcc/soap_async_client.cc +++ b/webcc/soap_async_client.cc @@ -66,9 +66,9 @@ void SoapAsyncClient::Request(const std::string& operation, http_request->SetContentType(kAppSoapXmlUtf8); } - http_request->SetHost(host_, port_); + http_request->set_host(host_, port_); http_request->SetHeader(kSoapAction, operation); - http_request->UpdateStartLine(); + http_request->Make(); HttpAsyncClientPtr http_client(new HttpAsyncClient(io_context_)); diff --git a/webcc/soap_client.cc b/webcc/soap_client.cc index 8f6d13e..90d30e8 100644 --- a/webcc/soap_client.cc +++ b/webcc/soap_client.cc @@ -65,9 +65,9 @@ bool SoapClient::Request(const std::string& operation, http_request.SetContentType(kAppSoapXmlUtf8); } - http_request.SetHost(host_, port_); + http_request.set_host(host_, port_); http_request.SetHeader(kSoapAction, operation); - http_request.UpdateStartLine(); + http_request.Make(); if (!http_client_.Request(http_request)) { error_ = http_client_.error(); diff --git a/webcc/version.h b/webcc/version.h new file mode 100644 index 0000000..6ad5b8a --- /dev/null +++ b/webcc/version.h @@ -0,0 +1,6 @@ +#ifndef WEBCC_VERSION_H_ +#define WEBCC_VERSION_H_ + +#define WEBCC_VERSION "0.1.0" + +#endif // WEBCC_VERSION_H_