diff --git a/CMakeLists.txt b/CMakeLists.txt index 79d72e9..70dfe5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ endif() 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) @@ -121,17 +122,18 @@ add_subdirectory(webcc) if(WEBCC_ENABLE_EXAMPLES) add_subdirectory(example/http_client) - add_subdirectory(example/http_async_client) + # add_subdirectory(example/http_async_client) - # For including jsoncpp as "json/json.h". - include_directories(${THIRD_PARTY_DIR}/src/jsoncpp) + if(WEBCC_ENABLE_REST) + # For including jsoncpp as "json/json.h". + include_directories(${THIRD_PARTY_DIR}/src/jsoncpp) + # REST examples need jsoncpp to parse and create JSON. + add_subdirectory(${THIRD_PARTY_DIR}/src/jsoncpp) - # REST examples need jsoncpp to parse and create JSON. - add_subdirectory(${THIRD_PARTY_DIR}/src/jsoncpp) - - add_subdirectory(example/rest_book_server) - add_subdirectory(example/rest_book_client) - add_subdirectory(example/rest_book_async_client) + add_subdirectory(example/rest_book_server) + # add_subdirectory(example/rest_book_client) + # add_subdirectory(example/rest_book_async_client) + endif() if(WEBCC_ENABLE_SOAP) add_subdirectory(example/soap_calc_server) @@ -143,8 +145,11 @@ if(WEBCC_ENABLE_EXAMPLES) if(WEBCC_ENABLE_SSL) add_subdirectory(example/http_ssl_client) - add_subdirectory(example/http_ssl_async_client) - add_subdirectory(example/github_rest_client) + # add_subdirectory(example/http_ssl_async_client) + + if(WEBCC_ENABLE_REST) + # add_subdirectory(example/github_rest_client) + endif() endif() endif() diff --git a/_clang-format b/_clang-format index 0a7a576..255c975 100644 --- a/_clang-format +++ b/_clang-format @@ -1,7 +1,7 @@ --- Language: Cpp # BasedOnStyle: Google -AccessModifierOffset: -1 +AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false diff --git a/example/http_client/main.cc b/example/http_client/main.cc index 8144650..bd608e0 100644 --- a/example/http_client/main.cc +++ b/example/http_client/main.cc @@ -1,29 +1,49 @@ #include -#include "webcc/http_client.h" +#include "webcc/http_client_session.h" // TEST #include "webcc/logger.h" -static void PrintError(const webcc::HttpClient& client) { - 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); - auto request = webcc::HttpRequest::New(webcc::kHttpGet, "/get", - "httpbin.org"); + using namespace webcc; + + HttpResponsePtr r; + + 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)); + + 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)); + + r = session.Get("http://httpbin.org/get", + { "key1", "value1", "key2", "value2" }, + { "Accept", "application/json" }, + HttpRequestArgs().buffer_size(1000)); +#endif - webcc::HttpClient client; + r = session.Post("http://httpbin.org/post", "{ 'key': 'value' }", true, + { "Accept", "application/json" }, + HttpRequestArgs().buffer_size(1000)); - if (client.Request(*request)) { - std::cout << client.response_content() << std::endl; - } else { - PrintError(client); - } + std::cout << r->content() << std::endl; return 0; } diff --git a/example/http_ssl_client/main.cc b/example/http_ssl_client/main.cc index 9244c9e..63762b8 100644 --- a/example/http_ssl_client/main.cc +++ b/example/http_ssl_client/main.cc @@ -4,25 +4,21 @@ #include "webcc/logger.h" int main(int argc, char* argv[]) { - std::string host; std::string url; if (argc != 3) { - host = "www.boost.org"; - url = "/LICENSE_1_0.txt"; + url = "www.boost.org/LICENSE_1_0.txt"; } else { - host = argv[1]; - url = argv[2]; + url = argv[1]; } - std::cout << "Host: " << host << std::endl; std::cout << "URL: " << url << std::endl; std::cout << std::endl; WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); // Leave port to default value. - webcc::HttpRequest request(webcc::kHttpGet, url, host); + webcc::HttpRequest request(webcc::http::kGet, url); request.Prepare(); @@ -30,7 +26,7 @@ int main(int argc, char* argv[]) { // See HttpSslClient::Request() for more details. bool ssl_verify = false; - webcc::HttpSslClient client(2000, ssl_verify); + webcc::HttpSslClient client(ssl_verify, 2000); if (client.Request(request)) { //std::cout << client.response()->content() << std::endl; diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 3c83354..793bcda 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -1,8 +1,14 @@ # Unit test set(UT_SRCS - rest_service_manager_test.cc + url_test.cc ) +if(WEBCC_ENABLE_REST) + set(UT_SRCS ${UT_SRCS} + rest_service_manager_test.cc + ) +endif() + set(UT_TARGET_NAME webcc_unittest) add_executable(${UT_TARGET_NAME} ${UT_SRCS}) diff --git a/unittest/rest_service_manager_test.cc b/unittest/rest_service_manager_test.cc index e08d182..14a3917 100644 --- a/unittest/rest_service_manager_test.cc +++ b/unittest/rest_service_manager_test.cc @@ -1,6 +1,9 @@ -#include "webcc/rest_service_manager.h" #include "gtest/gtest.h" +#include "webcc/rest_service_manager.h" + +// ----------------------------------------------------------------------------- + class TestRestService : public webcc::RestService { public: void Handle(const webcc::RestRequest& request, @@ -9,6 +12,8 @@ class TestRestService : public webcc::RestService { } }; +// ----------------------------------------------------------------------------- + TEST(RestServiceManager, URL_RegexBasic) { webcc::RestServiceManager service_manager; diff --git a/webcc/CMakeLists.txt b/webcc/CMakeLists.txt index 567408b..dda4d58 100644 --- a/webcc/CMakeLists.txt +++ b/webcc/CMakeLists.txt @@ -15,27 +15,23 @@ include(GNUInstallDirs) set(HEADERS globals.h - http_async_client_base.h - http_async_client.h + # http_async_client_base.h + # http_async_client.h http_client_base.h http_client.h - http_session.h + http_client_session.h + http_connection.h http_message.h http_parser.h http_request.h + http_request_args.h http_request_handler.h http_request_parser.h http_response.h http_response_parser.h http_server.h - http_ssl_async_client.h + # http_ssl_async_client.h queue.h - rest_async_client.h - rest_client.h - rest_request_handler.h - rest_server.h - rest_service.h - rest_service_manager.h url.h utility.h version.h @@ -43,11 +39,12 @@ set(HEADERS set(SOURCES globals.cc - http_async_client_base.cc - http_async_client.cc + # http_async_client_base.cc + # http_async_client.cc http_client_base.cc http_client.cc - http_session.cc + http_client_session.cc + http_connection.cc http_message.cc http_parser.cc http_request.cc @@ -56,34 +53,41 @@ set(SOURCES http_response.cc http_response_parser.cc http_server.cc - http_ssl_async_client.cc + # http_ssl_async_client.cc logger.cc - rest_async_client.cc - rest_client.cc - rest_request_handler.cc - rest_service_manager.cc - rest_service.cc url.cc utility.cc ) -if(WEBCC_ENABLE_SSL) - set(HEADERS ${HEADERS} - http_ssl_client.h - # rest_ssl_async_client.h - rest_ssl_client.h +if(WEBCC_ENABLE_REST) + set(REST_HEADERS + # rest_async_client.h + # rest_client.h + rest_request_handler.h + rest_server.h + rest_service.h + rest_service_manager.h ) - - set(SOURCES ${SOURCES} - http_ssl_client.cc - # rest_ssl_async_client.cc - rest_ssl_client.cc + set(REST_SOURCES + # rest_async_client.cc + # rest_client.cc + rest_request_handler.cc + rest_service_manager.cc + rest_service.cc ) + + set(HEADERS ${HEADERS} ${REST_HEADERS}) + 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 + # soap_async_client.h soap_client.h soap_globals.h soap_message.h @@ -97,7 +101,7 @@ if(WEBCC_ENABLE_SOAP) ) set(SOAP_SOURCES - soap_async_client.cc + # soap_async_client.cc soap_client.cc soap_globals.cc soap_message.cc diff --git a/webcc/globals.cc b/webcc/globals.cc index 1db288a..ec9b726 100644 --- a/webcc/globals.cc +++ b/webcc/globals.cc @@ -1,15 +1,8 @@ #include "webcc/globals.h" -namespace webcc { - -// ----------------------------------------------------------------------------- +#include "boost/algorithm/string.hpp" -const std::string kHttpHead = "HEAD"; -const std::string kHttpGet = "GET"; -const std::string kHttpPost = "POST"; -const std::string kHttpPatch = "PATCH"; -const std::string kHttpPut = "PUT"; -const std::string kHttpDelete = "DELETE"; +namespace webcc { // ----------------------------------------------------------------------------- diff --git a/webcc/globals.h b/webcc/globals.h index 00f9e6d..657355c 100644 --- a/webcc/globals.h +++ b/webcc/globals.h @@ -1,7 +1,9 @@ #ifndef WEBCC_GLOBALS_H_ #define WEBCC_GLOBALS_H_ +#include #include +#include #include "webcc/version.h" @@ -25,6 +27,9 @@ TypeName(const TypeName&) = delete; \ TypeName& operator=(const TypeName&) = delete; +// Default user agent. +#define USER_AGENT "Webcc/" WEBCC_VERSION + namespace webcc { // ----------------------------------------------------------------------------- @@ -45,9 +50,47 @@ const int kMaxReadSeconds = 30; // when dumped/logged. const std::size_t kMaxDumpSize = 2048; +// Default ports. +const char* const kPort80 = "80"; +const char* const kPort443 = "443"; + +// Client side error codes. +enum Error { + kNoError = 0, // i.e., OK + + kHostResolveError, + kEndpointConnectError, + kHandshakeError, // HTTPS handshake + kSocketReadError, + kSocketWriteError, + + // HTTP error. + // E.g., failed to parse HTTP response (invalid content length, etc.). + kHttpError, + + // Server error. + // E.g., HTTP status 500 + SOAP Fault element. + kServerError, + + // XML parsing error. + kXmlError, +}; + +// Return a descriptive message for the given error code. +const char* DescribeError(Error error); + // HTTP headers. namespace http { +// HTTP methods (verbs) in string. +// Don't use enum to avoid converting back and forth. +const char* const kHead = "HEAD"; +const char* const kGet = "GET"; +const char* const kPost = "POST"; +const char* const kPatch = "PATCH"; +const char* const kPut = "PUT"; +const char* const kDelete = "DELETE"; + // HTTP response status. // This is not a full list. // Full list: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes @@ -94,6 +137,7 @@ const char* const kTextXml = "text/xml"; } // namespace media_types +// TODO: Rename to encodings? namespace charsets { const char* const kUtf8 = "utf-8"; @@ -102,45 +146,6 @@ const char* const kUtf8 = "utf-8"; } // namespace http -// Default ports. -const char* const kHttpPort = "80"; -const char* const kHttpSslPort = "443"; - -// HTTP methods (verbs) in string ("HEAD", "GET", etc.). -// NOTE: Don't use enum to avoid converting back and forth. -// TODO: Add enum. -extern const std::string kHttpHead; -extern const std::string kHttpGet; -extern const std::string kHttpPost; -extern const std::string kHttpPatch; -extern const std::string kHttpPut; -extern const std::string kHttpDelete; - -// Client side error codes. -enum Error { - kNoError = 0, // i.e., OK - - kHostResolveError, - kEndpointConnectError, - kHandshakeError, // HTTPS handshake - kSocketReadError, - kSocketWriteError, - - // HTTP error. - // E.g., failed to parse HTTP response (invalid content length, etc.). - kHttpError, - - // Server error. - // E.g., HTTP status 500 + SOAP Fault element. - kServerError, - - // XML parsing error. - kXmlError, -}; - -// Return a descriptive message for the given error code. -const char* DescribeError(Error error); - } // namespace webcc #endif // WEBCC_GLOBALS_H_ diff --git a/webcc/http_client.h b/webcc/http_client.h index b76736d..55eea20 100644 --- a/webcc/http_client.h +++ b/webcc/http_client.h @@ -14,7 +14,7 @@ class HttpClient : public HttpClientBase { private: Error Connect(const HttpRequest& request) final { - return DoConnect(request, kHttpPort); + return DoConnect(request, kPort80); } void SocketConnect(const Endpoints& endpoints, diff --git a/webcc/http_client_session.cc b/webcc/http_client_session.cc new file mode 100644 index 0000000..8d78009 --- /dev/null +++ b/webcc/http_client_session.cc @@ -0,0 +1,80 @@ +#include "webcc/http_client_session.h" + +#include "webcc/http_client.h" +#include "webcc/http_request.h" +#include "webcc/url.h" + +namespace webcc { + +HttpClientSession::HttpClientSession() { + InitHeaders(); +} + +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_); + + if (!args.data_.empty()) { + request.SetContent(std::move(args.data_), true); + + // TODO: charset/encoding + if (args.json_) { + request.SetContentType(http::media_types::kApplicationJson, ""); + } + } + + // Apply the session-level headers. + for (const HttpHeader& h : headers_.data()) { + request.SetHeader(h.first, h.second); + } + + // Apply the request-level headers. + // This will overwrite the session-level headers. + for (std::size_t i = 1; i < args.headers_.size(); i += 2) { + request.SetHeader(std::move(args.headers_[i - 1]), + std::move(args.headers_[i])); + } + + request.Prepare(); + + HttpClient client; + if (!client.Request(request, args.buffer_size_)) { + return HttpResponsePtr{}; + } + + return client.response(); +} + +HttpResponsePtr HttpClientSession::Get(const std::string& url, + std::vector&& parameters, + std::vector&& headers, + HttpRequestArgs&& args) { + return Request(args.method(http::kGet).url(url) + .parameters(std::move(parameters)) + .headers(std::move(headers))); +} + +HttpResponsePtr HttpClientSession::Post(const std::string& url, + std::string&& data, bool json, + std::vector&& headers, + HttpRequestArgs&& args) { + return Request(args.method(http::kPost).url(url).data(std::move(data)) + .json(json).headers(std::move(headers))); +} + +void HttpClientSession::InitHeaders() { + // NOTE: C++11 requires a space between literal and string macro. + headers_.Add(http::headers::kUserAgent, USER_AGENT); + + // TODO: Support gzip, deflate + headers_.Add(http::headers::kAcceptEncoding, "identity"); + + headers_.Add(http::headers::kAccept, "*/*"); + + // TODO: Support Keep-Alive connection. + //headers_.Add(http::headers::kConnection, "close"); +} + +} // namespace webcc diff --git a/webcc/http_client_session.h b/webcc/http_client_session.h new file mode 100644 index 0000000..4df7142 --- /dev/null +++ b/webcc/http_client_session.h @@ -0,0 +1,43 @@ +#ifndef WEBCC_HTTP_CLIENT_SESSION_H_ +#define WEBCC_HTTP_CLIENT_SESSION_H_ + +#include +#include + +#include "webcc/http_request_args.h" +#include "webcc/http_response.h" + +namespace webcc { + +class HttpClientSession { +public: + HttpClientSession(); + + ~HttpClientSession() = default; + + void AddHeader(const std::string& key, const std::string& value) { + headers_.Add(key, value); + } + + HttpResponsePtr Request(HttpRequestArgs&& args); + + HttpResponsePtr Get(const std::string& url, + std::vector&& parameters = {}, + std::vector&& headers = {}, + HttpRequestArgs&& args = HttpRequestArgs()); + + HttpResponsePtr Post(const std::string& url, + std::string&& data, bool json, + std::vector&& headers = {}, + HttpRequestArgs&& args = HttpRequestArgs()); + +private: + void InitHeaders(); + + // Headers to be sent on each request sent from this session. + HttpHeaderDict headers_; +}; + +} // namespace webcc + +#endif // WEBCC_HTTP_CLIENT_SESSION_H_ diff --git a/webcc/http_session.cc b/webcc/http_connection.cc similarity index 74% rename from webcc/http_session.cc rename to webcc/http_connection.cc index e1add89..580a75e 100644 --- a/webcc/http_session.cc +++ b/webcc/http_connection.cc @@ -1,4 +1,4 @@ -#include "webcc/http_session.h" +#include "webcc/http_connection.h" #include // for move() @@ -11,18 +11,18 @@ using boost::asio::ip::tcp; namespace webcc { -HttpSession::HttpSession(tcp::socket socket, HttpRequestHandler* handler) +HttpConnection::HttpConnection(tcp::socket socket, HttpRequestHandler* handler) : socket_(std::move(socket)), buffer_(kBufferSize), request_handler_(handler), request_parser_(&request_) { } -void HttpSession::Start() { +void HttpConnection::Start() { DoRead(); } -void HttpSession::Close() { +void HttpConnection::Close() { LOG_INFO("Close socket..."); boost::system::error_code ec; @@ -32,27 +32,27 @@ void HttpSession::Close() { } } -void HttpSession::SetResponseContent(std::string&& content, - const std::string& media_type, - const std::string& charset) { +void HttpConnection::SetResponseContent(std::string&& content, + const std::string& media_type, + const std::string& charset) { response_.SetContent(std::move(content), true); response_.SetContentType(media_type, charset); } -void HttpSession::SendResponse(http::Status status) { +void HttpConnection::SendResponse(http::Status status) { response_.set_status(status); response_.Prepare(); DoWrite(); } -void HttpSession::DoRead() { +void HttpConnection::DoRead() { socket_.async_read_some(boost::asio::buffer(buffer_), - std::bind(&HttpSession::OnRead, shared_from_this(), + std::bind(&HttpConnection::OnRead, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } -void HttpSession::OnRead(boost::system::error_code ec, std::size_t length) { +void HttpConnection::OnRead(boost::system::error_code ec, std::size_t length) { if (ec) { LOG_ERRO("Socket read error (%s).", ec.message().c_str()); if (ec != boost::asio::error::operation_aborted) { @@ -77,16 +77,16 @@ void HttpSession::OnRead(boost::system::error_code ec, std::size_t length) { LOG_VERB("HTTP request:\n%s", request_.Dump(4, "> ").c_str()); - // Enqueue this session. + // Enqueue this connection. // Some worker thread will handle it later. request_handler_->Enqueue(shared_from_this()); } -void HttpSession::DoWrite() { +void HttpConnection::DoWrite() { LOG_VERB("HTTP response:\n%s", response_.Dump(4, "> ").c_str()); boost::asio::async_write(socket_, response_.ToBuffers(), - std::bind(&HttpSession::OnWrite, shared_from_this(), + std::bind(&HttpConnection::OnWrite, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } @@ -95,7 +95,7 @@ void HttpSession::DoWrite() { // This write handler will be called from main thread (the thread calling // io_context.run), even though AsyncWrite() is invoked by worker threads. // This is ensured by Asio. -void HttpSession::OnWrite(boost::system::error_code ec, std::size_t length) { +void HttpConnection::OnWrite(boost::system::error_code ec, std::size_t length) { if (ec) { LOG_ERRO("Socket write error (%s).", ec.message().c_str()); @@ -112,7 +112,7 @@ void HttpSession::OnWrite(boost::system::error_code ec, std::size_t length) { // Socket close VS. shutdown: // https://stackoverflow.com/questions/4160347/close-vs-shutdown-socket -void HttpSession::Shutdown() { +void HttpConnection::Shutdown() { LOG_INFO("Shutdown socket..."); // Initiate graceful connection closure. diff --git a/webcc/http_session.h b/webcc/http_connection.h similarity index 72% rename from webcc/http_session.h rename to webcc/http_connection.h index 9675969..497150a 100644 --- a/webcc/http_session.h +++ b/webcc/http_connection.h @@ -1,5 +1,5 @@ -#ifndef WEBCC_HTTP_SESSION_H_ -#define WEBCC_HTTP_SESSION_H_ +#ifndef WEBCC_HTTP_CONNECTION_H_ +#define WEBCC_HTTP_CONNECTION_H_ #include #include @@ -14,18 +14,23 @@ namespace webcc { +class HttpConnection; class HttpRequestHandler; -class HttpSession : public std::enable_shared_from_this { - public: - HttpSession(boost::asio::ip::tcp::socket socket, - HttpRequestHandler* handler); +typedef std::shared_ptr HttpConnectionPtr; - ~HttpSession() = default; +class HttpConnection : public std::enable_shared_from_this { +public: + HttpConnection(boost::asio::ip::tcp::socket socket, + HttpRequestHandler* handler); - WEBCC_DELETE_COPY_ASSIGN(HttpSession); + ~HttpConnection() = default; - const HttpRequest& request() const { return request_; } + WEBCC_DELETE_COPY_ASSIGN(HttpConnection); + + const HttpRequest& request() const { + return request_; + } // Start to read and process the client request. void Start(); @@ -40,7 +45,7 @@ class HttpSession : public std::enable_shared_from_this { // Send response to client with the given status. void SendResponse(http::Status status); - private: +private: void DoRead(); void OnRead(boost::system::error_code ec, std::size_t length); @@ -69,8 +74,6 @@ class HttpSession : public std::enable_shared_from_this { HttpResponse response_; }; -typedef std::shared_ptr HttpSessionPtr; - } // namespace webcc -#endif // WEBCC_HTTP_SESSION_H_ +#endif // WEBCC_HTTP_CONNECTION_H_ diff --git a/webcc/http_message.cc b/webcc/http_message.cc index 26d25a5..6e51409 100644 --- a/webcc/http_message.cc +++ b/webcc/http_message.cc @@ -17,38 +17,53 @@ const char CRLF[] = { '\r', '\n' }; // ----------------------------------------------------------------------------- -void HttpMessage::SetHeader(const std::string& name, const std::string& value) { +std::ostream& operator<<(std::ostream& os, const HttpMessage& message) { + message.Dump(os); + return os; +} + +// ----------------------------------------------------------------------------- + +void HttpHeaderDict::Add(const std::string& key, const std::string& value) { for (HttpHeader& h : headers_) { - if (boost::iequals(h.name, name)) { - h.value = value; + if (boost::iequals(h.first, key)) { + h.second = value; return; } } - headers_.push_back({ name, value }); + headers_.push_back({ key, value }); } -void HttpMessage::SetHeader(std::string&& name, std::string&& value) { +void HttpHeaderDict::Add(std::string&& key, std::string&& value) { for (HttpHeader& h : headers_) { - if (boost::iequals(h.name, name)) { - h.value = std::move(value); + if (boost::iequals(h.first, key)) { + h.second = std::move(value); return; } } - headers_.push_back({ std::move(name), std::move(value) }); + headers_.push_back({ std::move(key), std::move(value) }); +} + +bool HttpHeaderDict::Has(const std::string& key) const { + for (const HttpHeader& h : headers_) { + if (boost::iequals(h.first, key)) { + return true; + } + } + return false; } -// NOTE: -// According to HTTP 1.1 RFC7231, the following examples are all equivalent, -// but the first is preferred for consistency: -// text/html;charset=utf-8 -// text/html;charset=UTF-8 -// Text/HTML;Charset="utf-8" -// text/html; charset="utf-8" +// ----------------------------------------------------------------------------- + // See: https://tools.ietf.org/html/rfc7231#section-3.1.1.1 void HttpMessage::SetContentType(const std::string& media_type, const std::string& charset) { - SetHeader(http::headers::kContentType, - media_type + ";charset=" + charset); + if (charset.empty()) { + SetHeader(http::headers::kContentType, media_type); + } else { + SetHeader(http::headers::kContentType, + media_type + ";charset=" + charset); + } } void HttpMessage::SetContent(std::string&& content, bool set_length) { @@ -72,10 +87,10 @@ std::vector HttpMessage::ToBuffers() const { buffers.push_back(boost::asio::buffer(start_line_)); - for (const HttpHeader& h : headers_) { - buffers.push_back(boost::asio::buffer(h.name)); + for (const HttpHeader& h : headers_.data()) { + buffers.push_back(boost::asio::buffer(h.first)); buffers.push_back(boost::asio::buffer(misc_strings::NAME_VALUE_SEPARATOR)); - buffers.push_back(boost::asio::buffer(h.value)); + buffers.push_back(boost::asio::buffer(h.second)); buffers.push_back(boost::asio::buffer(misc_strings::CRLF)); } @@ -98,8 +113,8 @@ void HttpMessage::Dump(std::ostream& os, std::size_t indent, os << indent_str << start_line_; - for (const HttpHeader& h : headers_) { - os << indent_str << h.name << ": " << h.value << std::endl; + for (const HttpHeader& h : headers_.data()) { + os << indent_str << h.first << ": " << h.second << std::endl; } os << indent_str << std::endl; @@ -144,9 +159,4 @@ std::string HttpMessage::Dump(std::size_t indent, return ss.str(); } -std::ostream& operator<<(std::ostream& os, const HttpMessage& message) { - message.Dump(os); - return os; -} - } // namespace webcc diff --git a/webcc/http_message.h b/webcc/http_message.h index 5a2d918..7ebecdc 100644 --- a/webcc/http_message.h +++ b/webcc/http_message.h @@ -12,35 +12,76 @@ namespace webcc { -struct HttpHeader { - std::string name; - std::string value; +// ----------------------------------------------------------------------------- + +typedef std::pair HttpHeader; + +class HttpMessage; + +std::ostream& operator<<(std::ostream& os, const HttpMessage& message); + +// ----------------------------------------------------------------------------- + +class HttpHeaderDict { +public: + std::size_t size() const { + return headers_.size(); + } + + void Add(const std::string& key, const std::string& value); + + void Add(std::string&& key, std::string&& value); + + bool Has(const std::string& key) const; + + const HttpHeader& Get(std::size_t i) const { + assert(i < size()); + return headers_[i]; + } + + const std::vector& data() const { + return headers_; + } + +private: + std::vector headers_; }; +// ----------------------------------------------------------------------------- + // Base class for HTTP request and response messages. class HttpMessage { - public: - HttpMessage() : content_length_(kInvalidLength) {} +public: + HttpMessage() : content_length_(kInvalidLength) { + } virtual ~HttpMessage() = default; - const std::string& start_line() const { return start_line_; } + const std::string& start_line() const { + return start_line_; + } void set_start_line(const std::string& start_line) { start_line_ = start_line; } - std::size_t content_length() const { return content_length_; } + std::size_t content_length() const { + return content_length_; + } - const std::string& content() const { return content_; } + const std::string& content() const { + return content_; + } - // TODO: Rename to AddHeader. - void SetHeader(const std::string& name, const std::string& value); + void SetHeader(const std::string& key, const std::string& value) { + headers_.Add(key, value); + } - // TODO: Remove - void SetHeader(std::string&& name, std::string&& value); + void SetHeader(std::string&& key, std::string&& value) { + headers_.Add(std::move(key), std::move(value)); + } - // E.g., "application/json; charset=utf-8" + // E.g., "text/html", "application/json; charset=utf-8", etc. void SetContentType(const std::string& media_type, const std::string& charset); @@ -51,7 +92,7 @@ class HttpMessage { // Make the message (e.g., update start line). // Must be called before ToBuffers()! - virtual void Prepare() = 0; + virtual bool Prepare() = 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 @@ -66,7 +107,7 @@ class HttpMessage { std::string Dump(std::size_t indent = 0, const std::string& prefix = "") const; - protected: +protected: void SetContentLength(std::size_t content_length) { content_length_ = content_length; SetHeader(http::headers::kContentLength, std::to_string(content_length)); @@ -77,13 +118,11 @@ class HttpMessage { std::size_t content_length_; - std::vector headers_; + HttpHeaderDict headers_; std::string content_; }; -std::ostream& operator<<(std::ostream& os, const HttpMessage& message); - } // namespace webcc #endif // WEBCC_HTTP_MESSAGE_H_ diff --git a/webcc/http_request.cc b/webcc/http_request.cc index 396f97c..a6f2cb5 100644 --- a/webcc/http_request.cc +++ b/webcc/http_request.cc @@ -1,46 +1,51 @@ #include "webcc/http_request.h" +#include "webcc/logger.h" + namespace webcc { HttpRequest::HttpRequest(const std::string& method, const std::string& url, - const std::string& host, - const std::string& port) - : method_(method), url_(url), host_(host), port_(port) { + const std::vector& parameters) + : method_(method), url_(url) { + assert(parameters.size() % 2 == 0); + for (std::size_t i = 1; i < parameters.size(); i += 2) { + url_.AddParameter(parameters[i - 1], parameters[i]); + } } -void HttpRequest::Prepare() { +bool HttpRequest::Prepare() { + if (url_.host().empty()) { + LOG_ERRO("Invalid request: host is missing."); + return false; + } + + std::string target = "/" + url_.path(); + if (!url_.query().empty()) { + target += "?"; + target += url_.query(); + } + start_line_ = method_; start_line_ += " "; - start_line_ += url_; + start_line_ += target; start_line_ += " HTTP/1.1"; start_line_ += CRLF; - if (port_.empty()) { - SetHeader(http::headers::kHost, host_); + if (url_.port().empty()) { + SetHeader(http::headers::kHost, url_.host()); } else { - SetHeader(http::headers::kHost, host_ + ":" + port_); + SetHeader(http::headers::kHost, url_.host() + ":" + url_.port()); } - // TODO: Support Keep-Alive connection. - //SetHeader(http::headers::kConnection, "close"); - - // TODO: Support gzip, deflate - SetHeader(http::headers::kAcceptEncoding, "identity"); - - // NOTE: C++11 requires a space between literal and string macro. - SetHeader(http::headers::kUserAgent, "Webcc/" WEBCC_VERSION); + return true; } -// static HttpRequestPtr HttpRequest::New(const std::string& method, const std::string& url, - const std::string& host, - const std::string& port, + const std::vector& parameters, bool prepare) { - HttpRequestPtr request{ - new HttpRequest{ method, url, host, port } - }; + HttpRequestPtr request{ new HttpRequest{ method, url, parameters } }; if (prepare) { request->Prepare(); diff --git a/webcc/http_request.h b/webcc/http_request.h index d0224fd..ed26010 100644 --- a/webcc/http_request.h +++ b/webcc/http_request.h @@ -3,8 +3,10 @@ #include #include +#include #include "webcc/http_message.h" +#include "webcc/url.h" namespace webcc { @@ -14,29 +16,34 @@ class HttpRequestParser; typedef std::shared_ptr HttpRequestPtr; class HttpRequest : public HttpMessage { - public: +public: HttpRequest() = default; - // 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 value (80 for HTTP - // or 443 for HTTPS) will be used to connect to server if it's empty. + // TODO: Move parameters HttpRequest(const std::string& method, const std::string& url, - const std::string& host, - const std::string& port = ""); + const std::vector& parameters = {}); ~HttpRequest() override = default; - const std::string& method() const { return method_; } + const std::string& method() const { + return method_; + } - const std::string& url() const { return url_; } + const Url& url() const { + return url_; + } - const std::string& host() const { return host_; } - const std::string& port() const { return port_; } + const std::string& host() const { + return url_.host(); + } + + const std::string& port() const { + return url_.port(); + } std::string port(const std::string& default_port) const { - return port_.empty() ? default_port : port_; + return port().empty() ? default_port : port(); } // Shortcut to set `Accept` header. @@ -51,30 +58,26 @@ class HttpRequest : public HttpMessage { // Prepare payload. // Compose start line, set Host header, etc. - void Prepare() override; + bool Prepare() override; + // TODO: Re-place static HttpRequestPtr New(const std::string& method, const std::string& url, - const std::string& host, - const std::string& port = "", + const std::vector& parameters = {}, bool prepare = true); - private: +private: friend class HttpRequestParser; - void set_method(const std::string& method) { method_ = method; } - void set_url(const std::string& url) { url_ = url; } + void set_method(const std::string& method) { + method_ = method; + } + void set_url(const std::string& url) { + url_.Init(url); + } - // HTTP method. std::string method_; - - // Request URL. - // A complete URL naming the requested resource, or the path component of - // the URL. - std::string url_; - - std::string host_; - std::string port_; + Url url_; }; } // namespace webcc diff --git a/webcc/http_request_args.h b/webcc/http_request_args.h new file mode 100644 index 0000000..e53434f --- /dev/null +++ b/webcc/http_request_args.h @@ -0,0 +1,140 @@ +#ifndef WEBCC_HTTP_REQUEST_ARGS_H_ +#define WEBCC_HTTP_REQUEST_ARGS_H_ + +#include +#include +#include + +#include "webcc/globals.h" +#include "webcc/logger.h" + +namespace webcc { + +class HttpClientSession; + +// Args maker for HttpClientSession. +// Use method chaining to simulate Named Parameters. +class HttpRequestArgs { +public: + explicit HttpRequestArgs(const std::string& method = "") + : method_(method), json_(false), buffer_size_(0) { + LOG_VERB("HttpRequestArgs()"); + } + + HttpRequestArgs(const HttpRequestArgs&) = default; + HttpRequestArgs& operator=(const HttpRequestArgs&) = default; + +#if WEBCC_DEFAULT_MOVE_COPY_ASSIGN + + HttpRequestArgs(HttpRequestArgs&&) = default; + HttpRequestArgs& operator=(HttpRequestArgs&&) = default; + +#else + + HttpRequestArgs(HttpRequestArgs&& rhs) + : method_(std::move(rhs.method_)), + url_(std::move(rhs.url_)), + parameters_(std::move(rhs.parameters_)), + data_(std::move(rhs.data_)), + json_(rhs.json_), + headers_(std::move(rhs.headers_)), + buffer_size_(rhs.buffer_size_) { + LOG_VERB("HttpRequestArgs(&&)"); + } + + HttpRequestArgs& operator=(HttpRequestArgs&& rhs) { + if (&rhs != this) { + method_ = std::move(rhs.method_); + url_ = std::move(rhs.url_); + parameters_ = std::move(rhs.parameters_); + data_ = std::move(rhs.data_); + json_ = rhs.json_; + headers_ = std::move(rhs.headers_); + buffer_size_ = buffer_size_; + } + LOG_VERB("HttpRequestArgs& operator=(&&)"); + return *this; + } + +#endif // WEBCC_DEFAULT_MOVE_COPY_ASSIGN + + HttpRequestArgs&& method(const std::string& method) { + method_ = method; + return std::move(*this); + } + + HttpRequestArgs&& url(const std::string& url) { + url_ = url; + return std::move(*this); + } + + HttpRequestArgs&& url(std::string&& url) { + url_ = std::move(url); + return std::move(*this); + } + + HttpRequestArgs&& parameters(const std::vector& parameters) { + parameters_ = parameters; + return std::move(*this); + } + + HttpRequestArgs&& parameters(std::vector&& parameters) { + parameters_ = std::move(parameters); + return std::move(*this); + } + + HttpRequestArgs&& data(const std::string& data) { + data_ = data; + return std::move(*this); + } + + HttpRequestArgs&& data(std::string&& data) { + data_ = std::move(data); + return std::move(*this); + } + + HttpRequestArgs&& json(bool json = true) { + json_ = json; + return std::move(*this); + } + + HttpRequestArgs&& headers(const std::vector& headers) { + headers_ = headers; + return std::move(*this); + } + + HttpRequestArgs&& headers(std::vector&& headers) { + headers_ = std::move(headers); + return std::move(*this); + } + + HttpRequestArgs&& buffer_size(std::size_t buffer_size) { + buffer_size_ = buffer_size; + return std::move(*this); + } + +private: + friend class HttpClientSession; + + std::string method_; + + std::string url_; + + std::vector parameters_; + + // Data to send in the body of the request. + std::string data_; + + // Is the data to send a JSON string? + bool json_; + + std::vector headers_; + + // Size of the buffer to read response. + // Leave it to 0 for using default value. + std::size_t buffer_size_; +}; + +} // namespace webcc + +#endif // WEBCC_HTTP_REQUEST_ARGS_H_ diff --git a/webcc/http_request_handler.cc b/webcc/http_request_handler.cc index 3e019f2..6d3cf7e 100644 --- a/webcc/http_request_handler.cc +++ b/webcc/http_request_handler.cc @@ -9,8 +9,8 @@ namespace webcc { -void HttpRequestHandler::Enqueue(HttpSessionPtr session) { - queue_.Push(session); +void HttpRequestHandler::Enqueue(HttpConnectionPtr connection) { + queue_.Push(connection); } void HttpRequestHandler::Start(std::size_t count) { @@ -24,14 +24,14 @@ void HttpRequestHandler::Start(std::size_t count) { void HttpRequestHandler::Stop() { LOG_INFO("Stopping workers..."); - // Close pending sessions. - for (HttpSessionPtr s = queue_.Pop(); s; s = queue_.Pop()) { - LOG_INFO("Closing pending session..."); + // Close pending connections. + for (HttpConnectionPtr s = queue_.Pop(); s; s = queue_.Pop()) { + LOG_INFO("Closing pending connection..."); s->Close(); } - // Enqueue a null session to trigger the first worker to stop. - queue_.Push(HttpSessionPtr()); + // Enqueue a null connection to trigger the first worker to stop. + queue_.Push(HttpConnectionPtr()); for (auto& worker : workers_) { if (worker.joinable()) { @@ -46,19 +46,19 @@ void HttpRequestHandler::WorkerRoutine() { LOG_INFO("Worker is running."); for (;;) { - HttpSessionPtr session = queue_.PopOrWait(); + HttpConnectionPtr connection = queue_.PopOrWait(); - if (!session) { + if (!connection) { LOG_INFO("Worker is going to stop."); // For stopping next worker. - queue_.Push(HttpSessionPtr()); + queue_.Push(HttpConnectionPtr()); // Stop the worker. break; } - HandleSession(session); + HandleConnection(connection); } } diff --git a/webcc/http_request_handler.h b/webcc/http_request_handler.h index 4cbd582..ac7c2cb 100644 --- a/webcc/http_request_handler.h +++ b/webcc/http_request_handler.h @@ -5,7 +5,7 @@ #include #include -#include "webcc/http_session.h" +#include "webcc/http_connection.h" #include "webcc/queue.h" #include "webcc/soap_service.h" @@ -16,28 +16,30 @@ class HttpResponse; // The common handler for all incoming requests. class HttpRequestHandler { - public: +public: HttpRequestHandler() = default; virtual ~HttpRequestHandler() = default; WEBCC_DELETE_COPY_ASSIGN(HttpRequestHandler); - // Put the session into the queue. - void Enqueue(HttpSessionPtr session); + // Put the connection into the queue. + void Enqueue(HttpConnectionPtr connection); // Start worker threads. void Start(std::size_t count); - // Close pending sessions and stop worker threads. + // Close pending connections and stop worker threads. void Stop(); - private: +private: void WorkerRoutine(); // Called by the worker routine. - virtual void HandleSession(HttpSessionPtr session) = 0; + virtual void HandleConnection(HttpConnectionPtr connection) = 0; + +private: + Queue queue_; - Queue queue_; std::vector workers_; }; diff --git a/webcc/http_response.cc b/webcc/http_response.cc index 324c047..97294a6 100644 --- a/webcc/http_response.cc +++ b/webcc/http_response.cc @@ -57,13 +57,13 @@ const std::string& ToString(int status) { } // namespace status_strings -void HttpResponse::Prepare() { +bool HttpResponse::Prepare() { start_line_ = status_strings::ToString(status_); - // NOTE: C++11 requires a space between literal and string macro. - SetHeader("Server", "Webcc/" WEBCC_VERSION); - + SetHeader("Server", USER_AGENT); SetHeader("Date", GetHttpDateTimestamp()); + + return true; } HttpResponse HttpResponse::Fault(http::Status status) { diff --git a/webcc/http_response.h b/webcc/http_response.h index f43f999..bedc717 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 Prepare() override; + bool Prepare() override; // Get a fault response when HTTP status is not OK. // TODO: Avoid copy. diff --git a/webcc/http_response_parser.cc b/webcc/http_response_parser.cc index 1e95ceb..6d6a2c8 100644 --- a/webcc/http_response_parser.cc +++ b/webcc/http_response_parser.cc @@ -12,15 +12,15 @@ HttpResponseParser::HttpResponseParser(HttpResponse* response) } bool HttpResponseParser::ParseStartLine(const std::string& line) { - std::vector splitted; - boost::split(splitted, line, boost::is_any_of(" "), boost::token_compress_on); + std::vector parts; + boost::split(parts, line, boost::is_any_of(" "), boost::token_compress_on); - if (splitted.size() < 3) { + if (parts.size() < 3) { LOG_ERRO("Invalid HTTP response status line: %s", line.c_str()); return false; } - std::string& status_str = splitted[1]; + std::string& status_str = parts[1]; try { response_->set_status(std::stoi(status_str)); diff --git a/webcc/http_server.cc b/webcc/http_server.cc index 16a5ad8..039430c 100644 --- a/webcc/http_server.cc +++ b/webcc/http_server.cc @@ -96,8 +96,8 @@ void HttpServer::DoAccept() { if (!ec) { LOG_INFO("Accepted a connection."); - std::make_shared(std::move(socket), - GetRequestHandler())->Start(); + std::make_shared(std::move(socket), + GetRequestHandler())->Start(); } DoAccept(); diff --git a/webcc/http_server.h b/webcc/http_server.h index 20e5e9e..2285d72 100644 --- a/webcc/http_server.h +++ b/webcc/http_server.h @@ -8,7 +8,7 @@ #include "boost/asio/signal_set.hpp" #include "webcc/globals.h" -#include "webcc/http_session.h" +#include "webcc/http_connection.h" namespace webcc { diff --git a/webcc/http_ssl_client.cc b/webcc/http_ssl_client.cc index 295d49a..f567138 100644 --- a/webcc/http_ssl_client.cc +++ b/webcc/http_ssl_client.cc @@ -20,7 +20,7 @@ HttpSslClient::HttpSslClient(bool ssl_verify, std::size_t buffer_size) } Error HttpSslClient::Connect(const HttpRequest& request) { - Error error = DoConnect(request, kHttpSslPort); + Error error = DoConnect(request, kPort443); if (error != kNoError) { return error; diff --git a/webcc/rest_client.h b/webcc/rest_client.h index 172d3e8..f06128c 100644 --- a/webcc/rest_client.h +++ b/webcc/rest_client.h @@ -35,22 +35,22 @@ class RestClient { // instead for the HTTP status code. inline bool Get(const std::string& url, std::size_t buffer_size = 0) { - return Request(kHttpGet, url, "", buffer_size); + return Request("GET", url, "", buffer_size); } inline bool Post(const std::string& url, std::string&& content, std::size_t buffer_size = 0) { - return Request(kHttpPost, url, std::move(content), buffer_size); + return Request("POST", url, std::move(content), buffer_size); } inline bool Put(const std::string& url, std::string&& content, std::size_t buffer_size = 0) { - return Request(kHttpPut, url, std::move(content), buffer_size); + return Request("PUT", url, std::move(content), buffer_size); } inline bool Patch(const std::string& url, std::string&& content, std::size_t buffer_size = 0) { - return Request(kHttpPatch, url, std::move(content), buffer_size); + return Request("PATCH", url, std::move(content), buffer_size); } inline bool Delete(const std::string& url, std::size_t buffer_size = 0) { diff --git a/webcc/rest_request_handler.cc b/webcc/rest_request_handler.cc index d556109..354875f 100644 --- a/webcc/rest_request_handler.cc +++ b/webcc/rest_request_handler.cc @@ -13,13 +13,14 @@ bool RestRequestHandler::Bind(RestServicePtr service, const std::string& url, return service_manager_.AddService(service, url, is_regex); } -void RestRequestHandler::HandleSession(HttpSessionPtr session) { - const HttpRequest& http_request = session->request(); +void RestRequestHandler::HandleConnection(HttpConnectionPtr connection) { + const HttpRequest& http_request = connection->request(); - Url url(http_request.url(), /*decode*/true); + // TODO + const Url& url = http_request.url(); - if (!url.IsPathValid()) { - session->SendResponse(http::Status::kBadRequest); + if (url.path().empty()) { + connection->SendResponse(http::Status::kBadRequest); return; } @@ -33,7 +34,7 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) { if (!service) { LOG_WARN("No service matches the URL path: %s", url.path().c_str()); - session->SendResponse(http::Status::kNotFound); + connection->SendResponse(http::Status::kNotFound); return; } @@ -42,13 +43,13 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) { service->Handle(rest_request, &rest_response); if (!rest_response.content.empty()) { - session->SetResponseContent(std::move(rest_response.content), - http::media_types::kApplicationJson, - http::charsets::kUtf8); + connection->SetResponseContent(std::move(rest_response.content), + http::media_types::kApplicationJson, + http::charsets::kUtf8); } // Send response back to client. - session->SendResponse(rest_response.status); + connection->SendResponse(rest_response.status); } } // namespace webcc diff --git a/webcc/rest_request_handler.h b/webcc/rest_request_handler.h index 8f0a411..a7d421f 100644 --- a/webcc/rest_request_handler.h +++ b/webcc/rest_request_handler.h @@ -11,14 +11,15 @@ namespace webcc { class RestRequestHandler : public HttpRequestHandler { - public: +public: RestRequestHandler() = default; + ~RestRequestHandler() override = default; bool Bind(RestServicePtr service, const std::string& url, bool is_regex); - private: - void HandleSession(HttpSessionPtr session) override; +private: + void HandleConnection(HttpConnectionPtr connection) override; RestServiceManager service_manager_; }; diff --git a/webcc/rest_service.cc b/webcc/rest_service.cc index 3109ed6..a191cec 100644 --- a/webcc/rest_service.cc +++ b/webcc/rest_service.cc @@ -8,9 +8,9 @@ namespace webcc { void RestListService::Handle(const RestRequest& request, RestResponse* response) { - if (request.method == kHttpGet) { + if (request.method == http::kGet) { Get(UrlQuery(request.url_query_str), response); - } else if (request.method == kHttpPost) { + } else if (request.method == http::kPost) { Post(request.content, response); } else { LOG_ERRO("RestListService doesn't support '%s' method.", @@ -22,13 +22,13 @@ void RestListService::Handle(const RestRequest& request, void RestDetailService::Handle(const RestRequest& request, RestResponse* response) { - if (request.method == kHttpGet) { + if (request.method == http::kGet) { Get(request.url_sub_matches, UrlQuery(request.url_query_str), response); - } else if (request.method == kHttpPut) { + } else if (request.method == http::kPut) { Put(request.url_sub_matches, request.content, response); - } else if (request.method == kHttpPatch) { + } else if (request.method == http::kPatch) { Patch(request.url_sub_matches, request.content, response); - } else if (request.method == kHttpDelete) { + } else if (request.method == http::kDelete) { Delete(request.url_sub_matches, response); } else { LOG_ERRO("RestDetailService doesn't support '%s' method.", diff --git a/webcc/rest_ssl_client.h b/webcc/rest_ssl_client.h index 9c61666..f03a3cd 100644 --- a/webcc/rest_ssl_client.h +++ b/webcc/rest_ssl_client.h @@ -63,7 +63,7 @@ protected: // Default: "utf-8". std::string content_charset_; - // Default headers for all session requests. + // Default headers for each request sent from this session. std::map headers_; private: @@ -96,7 +96,7 @@ public: inline bool Post(const std::string& url, std::string&& content, const SSMap& headers = {}, std::size_t buffer_size = 0) { - return Request(kHttpPost, url, std::move(content), headers, buffer_size); + return Request(Post, url, std::move(content), headers, buffer_size); } inline bool Put(const std::string& url, std::string&& content, diff --git a/webcc/soap_client.cc b/webcc/soap_client.cc index 4888b02..c0f42f7 100644 --- a/webcc/soap_client.cc +++ b/webcc/soap_client.cc @@ -48,7 +48,14 @@ bool SoapClient::Request(const std::string& operation, std::string http_content; soap_request.ToXml(format_raw_, indent_str_, &http_content); - HttpRequest http_request(kHttpPost, url_, host_, port_); + // TODO + std::string url = host_; + url += url_; + if (!port_.empty()) { + url += ":" + port_; + } + + HttpRequest http_request(http::kPost, url); http_request.SetContent(std::move(http_content), true); diff --git a/webcc/soap_parameter.h b/webcc/soap_parameter.h index 720827b..ccdda20 100644 --- a/webcc/soap_parameter.h +++ b/webcc/soap_parameter.h @@ -53,13 +53,11 @@ class SoapParameter { #else - // Use "= default" if drop the support of VS 2013. SoapParameter(SoapParameter&& rhs) : key_(std::move(rhs.key_)), value_(std::move(rhs.value_)), as_cdata_(rhs.as_cdata_) { } - // Use "= default" if drop the support of VS 2013. SoapParameter& operator=(SoapParameter&& rhs) { if (&rhs != this) { key_ = std::move(rhs.key_); diff --git a/webcc/soap_request_handler.cc b/webcc/soap_request_handler.cc index e4898a0..9dbe579 100644 --- a/webcc/soap_request_handler.cc +++ b/webcc/soap_request_handler.cc @@ -15,17 +15,17 @@ bool SoapRequestHandler::Bind(SoapServicePtr service, const std::string& url) { return true; } -void SoapRequestHandler::HandleSession(HttpSessionPtr session) { - SoapServicePtr service = GetServiceByUrl(session->request().url()); +void SoapRequestHandler::HandleConnection(HttpConnectionPtr connection) { + SoapServicePtr service = GetServiceByUrl(connection->request().url().path()); if (!service) { - session->SendResponse(http::Status::kBadRequest); + connection->SendResponse(http::Status::kBadRequest); return; } // Parse the SOAP request XML. SoapRequest soap_request; - if (!soap_request.FromXml(session->request().content())) { - session->SendResponse(http::Status::kBadRequest); + if (!soap_request.FromXml(connection->request().content())) { + connection->SendResponse(http::Status::kBadRequest); return; } @@ -40,7 +40,7 @@ void SoapRequestHandler::HandleSession(HttpSessionPtr session) { } if (!service->Handle(soap_request, &soap_response)) { - session->SendResponse(http::Status::kBadRequest); + connection->SendResponse(http::Status::kBadRequest); return; } @@ -48,16 +48,16 @@ void SoapRequestHandler::HandleSession(HttpSessionPtr session) { soap_response.ToXml(format_raw_, indent_str_, &content); if (soap_version_ == kSoapV11) { - session->SetResponseContent(std::move(content), + connection->SetResponseContent(std::move(content), http::media_types::kTextXml, http::charsets::kUtf8); } else { - session->SetResponseContent(std::move(content), + connection->SetResponseContent(std::move(content), http::media_types::kApplicationSoapXml, http::charsets::kUtf8); } - session->SendResponse(http::Status::kOK); + connection->SendResponse(http::Status::kOK); } SoapServicePtr SoapRequestHandler::GetServiceByUrl(const std::string& url) { diff --git a/webcc/soap_request_handler.h b/webcc/soap_request_handler.h index 7ef8dc4..6a3c367 100644 --- a/webcc/soap_request_handler.h +++ b/webcc/soap_request_handler.h @@ -10,15 +10,16 @@ namespace webcc { class SoapRequestHandler : public HttpRequestHandler { - public: +public: explicit SoapRequestHandler(SoapVersion soap_version) - : soap_version_(soap_version), - format_raw_(true) { + : soap_version_(soap_version), format_raw_(true) { } ~SoapRequestHandler() override = default; - void set_format_raw(bool format_raw) { format_raw_ = format_raw; } + void set_format_raw(bool format_raw) { + format_raw_ = format_raw; + } void set_indent_str(const std::string& indent_str) { indent_str_ = indent_str; @@ -26,8 +27,8 @@ class SoapRequestHandler : public HttpRequestHandler { bool Bind(SoapServicePtr service, const std::string& url); - private: - void HandleSession(HttpSessionPtr session) override; +private: + void HandleConnection(HttpConnectionPtr connection) override; SoapServicePtr GetServiceByUrl(const std::string& url); diff --git a/webcc/url.cc b/webcc/url.cc index 63e85bc..76782ae 100644 --- a/webcc/url.cc +++ b/webcc/url.cc @@ -4,6 +4,8 @@ #include #include +#include "boost/algorithm/string.hpp" + namespace webcc { // ----------------------------------------------------------------------------- @@ -244,48 +246,86 @@ UrlQuery::ConstIterator UrlQuery::Find(const std::string& key) const { // ----------------------------------------------------------------------------- Url::Url(const std::string& str, bool decode) { + Init(str, decode); +} + +void Url::Init(const std::string& str, bool decode, bool clear) { + if (clear) { + Clear(); + } + if (!decode || str.find('%') == std::string::npos) { - Init(str); + Parse(str); return; } std::string decoded; if (Decode(str, &decoded)) { - Init(decoded); + Parse(decoded); } else { - // TODO(Adam): Exception? - Init(str); + // TODO: Exception? + Parse(str); } } -bool Url::IsPathValid() const { - // URL path must be absolute. - if (path_.empty() || path_[0] != '/') { - return false; +void Url::AddParameter(const std::string& key, const std::string& value) { + if (!query_.empty()) { + query_ += "&"; } - return true; + query_ += key + "=" + value; } -std::vector Url::SplitPath(const std::string& path) { - std::vector results; - std::stringstream iss(path); - std::string s; - while (std::getline(iss, s, '/')) { - if (!s.empty()) { - results.push_back(s); - } +void Url::Parse(const std::string& str) { + std::string tmp = boost::trim_left_copy(str); + + std::size_t pos = std::string::npos; + + pos = tmp.find("://"); + if (pos != std::string::npos) { + scheme_ = tmp.substr(0, pos); + tmp = tmp.substr(pos + 3); } - return results; -} -void Url::Init(const std::string& str) { - std::size_t pos = str.find('?'); - if (pos == std::string::npos) { - path_ = str; + pos = tmp.find('/'); + if (pos != std::string::npos) { + host_ = tmp.substr(0, pos); + + tmp = tmp.substr(pos + 1); + + pos = tmp.find('?'); + if (pos != std::string::npos) { + path_ = tmp.substr(0, pos); + query_ = tmp.substr(pos + 1); + } else { + path_ = tmp; + } } else { - path_ = str.substr(0, pos); - query_ = str.substr(pos + 1); + path_ = ""; + + pos = tmp.find('?'); + if (pos != std::string::npos) { + host_ = tmp.substr(0, pos); + query_ = tmp.substr(pos + 1); + } else { + host_ = tmp; + } } + + if (!host_.empty()) { + pos = host_.find(':'); + if (pos != std::string::npos) { + port_ = host_.substr(pos + 1); + host_ = host_.substr(0, pos); + } + } +} + +void Url::Clear() { + scheme_.clear(); + host_.clear(); + port_.clear(); + path_.clear(); + query_.clear(); } } // namespace webcc diff --git a/webcc/url.h b/webcc/url.h index a0dc5f5..7b3ba71 100644 --- a/webcc/url.h +++ b/webcc/url.h @@ -2,10 +2,6 @@ #define WEBCC_URL_H_ // A simplified implementation of URL (or URI). -// The URL should start with "/". -// The parameters (separated by ";") are not supported. -// Example: -// /inventory-check.cgi?item=12731&color=blue&size=large #include #include @@ -17,7 +13,7 @@ namespace webcc { // URL query parameters. class UrlQuery { - public: +public: typedef std::pair Parameter; typedef std::vector Parameters; @@ -48,7 +44,7 @@ class UrlQuery { // E.g., "item=12731&color=blue&size=large". std::string ToString() const; - private: +private: typedef Parameters::const_iterator ConstIterator; ConstIterator Find(const std::string& key) const; @@ -58,34 +54,46 @@ class UrlQuery { // ----------------------------------------------------------------------------- class Url { - public: +public: Url() = default; - Url(const std::string& str, bool decode); - bool IsPathValid() const; + // TODO: decode/encode/encoded? + explicit Url(const std::string& str, bool decode = true); - const std::string& path() const { - return path_; + void Init(const std::string& str, bool decode = true, bool clear = true); + + const std::string& scheme() const { + return scheme_; + } + + const std::string& host() const { + return host_; } - void set_path(const std::string& path) { - path_ = path; + const std::string& port() const { + return port_; + } + + const std::string& path() const { + return path_; } const std::string& query() const { return query_; } - void set_query(const std::string& query) { - query_ = query; - } + // Add a parameter to the query string. + void AddParameter(const std::string& key, const std::string& value); - // Split a path into its hierarchical components. - static std::vector SplitPath(const std::string& path); +private: + void Parse(const std::string& str); - private: - void Init(const std::string& str); + void Clear(); + // TODO: Support auth & fragment. + std::string scheme_; + std::string host_; + std::string port_; std::string path_; std::string query_; };