diff --git a/example/rest_book_client/main.cc b/example/rest_book_client/main.cc index d6e6c15..04f95eb 100644 --- a/example/rest_book_client/main.cc +++ b/example/rest_book_client/main.cc @@ -1,78 +1,50 @@ #include #include "boost/algorithm/string.hpp" -#include "json/json.h" // jsoncpp +#include "json/json.h" #include "webcc/logger.h" #include "webcc/http_client.h" #include "webcc/http_request.h" #include "webcc/http_response.h" +#include "webcc/rest_client.h" //////////////////////////////////////////////////////////////////////////////// -class BookClientBase { -public: - BookClientBase(const std::string& host, const std::string& port) - : host_(host), port_(port) { - } - - bool Request(const std::string& method, - const std::string& url, - const std::string& content, - webcc::HttpResponse* http_response) { - webcc::HttpRequest http_request; - - http_request.set_method(method); - http_request.set_url(url); - http_request.SetHost(host_, port_); - if (!content.empty()) { // TODO - http_request.SetContent(content); - } - http_request.Build(); - - webcc::HttpClient http_client; - webcc::Error error = http_client.MakeRequest(http_request, http_response); - - return error == webcc::kNoError; - } - -protected: - std::string host_; - std::string port_; -}; +// Write a JSON object to string. +std::string JsonToString(const Json::Value& json) { + Json::StreamWriterBuilder builder; + return Json::writeString(builder, json); +} //////////////////////////////////////////////////////////////////////////////// -class BookListClient : public BookClientBase { +class BookListClient { public: BookListClient(const std::string& host, const std::string& port) - : BookClientBase(host, port) { + : rest_client_(host, port) { } bool ListBooks() { webcc::HttpResponse http_response; - if (!Request(webcc::kHttpGet, "/books", "", &http_response)) { + if (!rest_client_.Get("/books", &http_response)) { return false; } std::cout << "result:\n" << http_response.content() << std::endl; - return true; } bool CreateBook(const std::string& id, const std::string& title, double price) { - Json::Value root(Json::objectValue); - root["id"] = id; - root["title"] = title; - root["price"] = price; - - Json::StreamWriterBuilder builder; - std::string book_json = Json::writeString(builder, root); + Json::Value json(Json::objectValue); + json["id"] = id; + json["title"] = title; + json["price"] = price; webcc::HttpResponse http_response; - if (!Request(webcc::kHttpPost, "/books", book_json, &http_response)) { + if (!rest_client_.Post("/books", JsonToString(json), &http_response)) { return false; } @@ -80,58 +52,58 @@ public: return true; } + +private: + webcc::RestClient rest_client_; }; //////////////////////////////////////////////////////////////////////////////// -class BookDetailClient : public BookClientBase { +class BookDetailClient { public: BookDetailClient(const std::string& host, const std::string& port) - : BookClientBase(host, port) { + : rest_client_(host, port) { } bool GetBook(const std::string& id) { webcc::HttpResponse http_response; - if (!Request(webcc::kHttpGet, "/book/" + id, "", &http_response)) { + if (!rest_client_.Get("/book/" + id, &http_response)) { return false; } std::cout << http_response.content() << std::endl; - return true; } bool UpdateBook(const std::string& id, const std::string& title, double price) { - Json::Value root(Json::objectValue); - // root["id"] = id; // NOTE: ID is already in the URL. - root["title"] = title; - root["price"] = price; - - Json::StreamWriterBuilder builder; - std::string book_json = Json::writeString(builder, root); + // NOTE: ID is already in the URL. + Json::Value json(Json::objectValue); + json["title"] = title; + json["price"] = price; webcc::HttpResponse http_response; - if (!Request(webcc::kHttpPost, "/book/" + id, book_json, &http_response)) { + if (!rest_client_.Post("/book/" + id, JsonToString(json), &http_response)) { return false; } std::cout << http_response.status() << std::endl; - return true; } bool DeleteBook(const std::string& id) { webcc::HttpResponse http_response; - if (!Request(webcc::kHttpDelete, "/book/" + id, "", &http_response)) { + if (!rest_client_.Delete("/book/" + id, &http_response)) { return false; } - std::cout << http_response.content() << std::endl; - + std::cout << http_response.status() << std::endl; return true; } + +private: + webcc::RestClient rest_client_; }; //////////////////////////////////////////////////////////////////////////////// @@ -147,13 +119,8 @@ void Help(const char* argv0) { std::string GetUserInput() { char input[256]; - // std::size_t length = 0; - // do { - std::cout << ">> "; - std::cin.getline(input, 256); - // length = strlen(input); - // } while (length == 0); - + std::cout << ">> "; + std::cin.getline(input, 256); return input; } diff --git a/example/rest_book_server/book_services.cc b/example/rest_book_server/book_services.cc index ed05116..be4795e 100644 --- a/example/rest_book_server/book_services.cc +++ b/example/rest_book_server/book_services.cc @@ -144,6 +144,7 @@ bool BookListService::Post(const std::string& request_content, //////////////////////////////////////////////////////////////////////////////// bool BookDetailService::Get(const std::vector& url_sub_matches, + const webcc::UrlQuery& query, std::string* response_content) { if (url_sub_matches.size() != 1) { return false; diff --git a/example/rest_book_server/book_services.h b/example/rest_book_server/book_services.h index 0e74ca5..061dc56 100644 --- a/example/rest_book_server/book_services.h +++ b/example/rest_book_server/book_services.h @@ -32,6 +32,7 @@ class BookListService : public webcc::RestListService { class BookDetailService : public webcc::RestDetailService { protected: bool Get(const std::vector& url_sub_matches, + const webcc::UrlQuery& query, std::string* response_content) final; bool Patch(const std::vector& url_sub_matches, diff --git a/src/webcc/CMakeLists.txt b/src/webcc/CMakeLists.txt index b16d897..71a1847 100644 --- a/src/webcc/CMakeLists.txt +++ b/src/webcc/CMakeLists.txt @@ -2,8 +2,8 @@ add_definitions(-DBOOST_ASIO_NO_DEPRECATED) set(SRCS - common.cc - common.h + globals.cc + globals.h http_client.cc http_client.h http_message.cc @@ -27,7 +27,12 @@ set(SRCS logger.cc logger.h queue.h - rest_server.cc + rest_client.cc + rest_client.h + rest_request_handler.cc + rest_request_handler.h + rest_service_manager.cc + rest_service_manager.h rest_server.h rest_service.cc rest_service.h diff --git a/src/webcc/common.cc b/src/webcc/globals.cc similarity index 93% rename from src/webcc/common.cc rename to src/webcc/globals.cc index dfd219c..ce0d9e1 100644 --- a/src/webcc/common.cc +++ b/src/webcc/globals.cc @@ -1,4 +1,4 @@ -#include "webcc/common.h" +#include "webcc/globals.h" namespace webcc { @@ -63,13 +63,6 @@ const char* GetErrorMessage(Error error) { //////////////////////////////////////////////////////////////////////////////// -const SoapNamespace kSoapEnvNamespace{ - "soap", - "http://schemas.xmlsoap.org/soap/envelope/" -}; - -//////////////////////////////////////////////////////////////////////////////// - Parameter::Parameter(const std::string& key, const char* value) : key_(key), value_(value) { } @@ -110,4 +103,8 @@ Parameter& Parameter::operator=(Parameter&& rhs) { return *this; } +std::string Parameter::ToString() const { + return key_ + "=" + value_; +} + } // namespace webcc diff --git a/src/webcc/common.h b/src/webcc/globals.h similarity index 59% rename from src/webcc/common.h rename to src/webcc/globals.h index c5086b2..5431cc1 100644 --- a/src/webcc/common.h +++ b/src/webcc/globals.h @@ -1,17 +1,47 @@ -#ifndef WEBCC_COMMON_H_ -#define WEBCC_COMMON_H_ - -// Common definitions. +#ifndef WEBCC_GLOBALS_H_ +#define WEBCC_GLOBALS_H_ #include #include namespace webcc { -//////////////////////////////////////////////////////////////////////////////// +// ----------------------------------------------------------------------------- +// Macros + +// Explicitly declare the assignment operator as deleted. +#define DISALLOW_ASSIGN(TypeName) TypeName& operator=(const TypeName&) = delete; + +// Explicitly declare the copy constructor and assignment operator as deleted. +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + DISALLOW_ASSIGN(TypeName) + +// Explicitly declare all implicit constructors as deleted, namely the +// default constructor, copy constructor and operator= functions. +// This is especially useful for classes containing only static methods. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// Disallow copying a type, but provide default construction, move construction +// and move assignment. Especially useful for move-only structs. +#define MOVE_ONLY_WITH_DEFAULT_CONSTRUCTORS(TypeName) \ + TypeName() = default; \ + MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(TypeName) + +// Disallow copying a type, and only provide move construction and move +// assignment. Especially useful for move-only structs. +#define MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(TypeName) \ + TypeName(TypeName&&) = default; \ + TypeName& operator=(TypeName&&) = default; \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// ----------------------------------------------------------------------------- +// Constants // Buffer size for sending HTTP request and receiving HTTP response. -// TODO: Configurable for client and server separately. +// TODO: Configurable const std::size_t kBufferSize = 1024; const std::size_t kInvalidLength = static_cast(-1); @@ -24,8 +54,6 @@ extern const std::string kHost; extern const std::string kTextXmlUtf8; extern const std::string kTextJsonUtf8; -//////////////////////////////////////////////////////////////////////////////// - // HTTP methods (verbs) in string ("HEAD", "GET", etc.). // NOTE: Don't use enum to avoid converting back and forth. extern const std::string kHttpHead; @@ -54,11 +82,9 @@ struct HttpStatus { }; }; -//////////////////////////////////////////////////////////////////////////////// - // Error codes. enum Error { - kNoError = 0, // OK + kNoError = 0, kHostResolveError, kEndpointConnectError, @@ -83,31 +109,14 @@ enum Error { // Return a descriptive message for the given error code. const char* GetErrorMessage(Error error); -//////////////////////////////////////////////////////////////////////////////// - -// XML namespace name/url pair. -// E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" } -class SoapNamespace { -public: - std::string name; - std::string url; - - bool IsValid() const { - return !name.empty() && !url.empty(); - } -}; - -// CSoap's default namespace for SOAP Envelope. -extern const SoapNamespace kSoapEnvNamespace; - -//////////////////////////////////////////////////////////////////////////////// +// ----------------------------------------------------------------------------- // Key-value parameter. class Parameter { public: Parameter() = default; - Parameter(const Parameter& rhs) = default; - Parameter& operator=(const Parameter& rhs) = default; + Parameter(const Parameter&) = default; + Parameter& operator=(const Parameter&) = default; Parameter(const std::string& key, const char* value); Parameter(const std::string& key, const std::string& value); @@ -138,6 +147,9 @@ public: return value_.c_str(); } + // Return "key=value" string. + std::string ToString() const; + private: std::string key_; std::string value_; @@ -145,4 +157,4 @@ private: } // namespace webcc -#endif // WEBCC_COMMON_H_ +#endif // WEBCC_GLOBALS_H_ diff --git a/src/webcc/http_async_client.cc b/src/webcc/http_async_client.cc new file mode 100644 index 0000000..3f26649 --- /dev/null +++ b/src/webcc/http_async_client.cc @@ -0,0 +1,169 @@ +#include "webcc/http_async_client.h" + +#if WEBCC_DEBUG_OUTPUT +#include +#endif + +#if 0 +#include "boost/asio.hpp" +#else +#include "boost/asio/connect.hpp" +//#include "boost/asio/ip/tcp.hpp" +#include "boost/asio/read.hpp" +#include "boost/asio/write.hpp" +#endif + +#include "webcc/http_response_parser.h" +#include "webcc/http_request.h" +#include "webcc/http_response.h" + +using boost::asio::ip::tcp; + +namespace webcc { + +HttpAsyncClient::HttpAsyncClient(boost::asio::io_context& io_context) + : socket_(io_context) { + + resolver_.reset(new tcp::resolver(io_context)); + + response_.reset(new HttpResponse()); + parser_.reset(new HttpResponseParser(response_.get())); +} + +Error HttpAsyncClient::SendRequest(std::shared_ptr request, + HttpResponseHandler response_handler) { + request_ = request; + + std::string port = request->port(); + if (port.empty()) { + port = "80"; + } + + auto handler = std::bind(&HttpAsyncClient::HandleResolve, + this, + std::placeholders::_1, + std::placeholders::_2); + + resolver_->async_resolve(tcp::v4(), request->host(), port, handler); + + return kNoError; +} + +void HttpAsyncClient::HandleResolve(boost::system::error_code ec, + tcp::resolver::results_type results) { + if (ec) { + std::cerr << "Resolve: " << ec.message() << std::endl; + // return kHostResolveError; + } else { + endpoints_ = results; + DoConnect(endpoints_.begin()); + } +} + +void HttpAsyncClient::DoConnect(tcp::resolver::results_type::iterator endpoint_it) { + if (endpoint_it != endpoints_.end()) { + socket_.async_connect(endpoint_it->endpoint(), + std::bind(&HttpAsyncClient::HandleConnect, + this, + std::placeholders::_1, + endpoint_it)); + } +} + +void HttpAsyncClient::HandleConnect(boost::system::error_code ec, + tcp::resolver::results_type::iterator endpoint_it) { + if (ec) { + // Will be here if the end point is v6. + std::cout << "Connect error: " << ec.message() << std::endl; + // return kEndpointConnectError; + + socket_.close(); + + // Try the next available endpoint. + DoConnect(++endpoint_it); + } else { + DoWrite(); + } +} + +// Send HTTP request. +void HttpAsyncClient::DoWrite() { + boost::asio::async_write(socket_, + request_->ToBuffers(), + std::bind(&HttpAsyncClient::HandleWrite, + this, + std::placeholders::_1)); +} + +void HttpAsyncClient::HandleWrite(boost::system::error_code ec) { + if (ec) { + //return kSocketWriteError; + return; + } + + DoRead(); +} + +void HttpAsyncClient::DoRead() { + socket_.async_read_some(boost::asio::buffer(buffer_), + std::bind(&HttpAsyncClient::HandleRead, + this, + std::placeholders::_1, + std::placeholders::_2)); +} + +void HttpAsyncClient::HandleRead(boost::system::error_code ec, + std::size_t length) { + if (ec || length == 0) { + //return kSocketReadError; + return; + } + +#if WEBCC_DEBUG_OUTPUT + // NOTE: the content XML might not be well formated. + std::cout.write(buffer_.data(), length); +#endif + + // Parse the response piece just read. + // If the content has been fully received, next time flag "finished_" + // will be set. + Error error = parser_->Parse(buffer_.data(), length); + + if (error != kNoError) { + //return error; + } + + if (parser_->finished()) { + return; + } + + // Read and parse HTTP response. + + // NOTE: + // We must stop trying to read once all content has been received, + // because some servers will block extra call to read_some(). + //while (!parser_.finished()) { + // size_t length = socket_.read_some(boost::asio::buffer(buffer_), ec); + + //if (length == 0 || ec) { + // return kSocketReadError; + //} + + // Parse the response piece just read. + // If the content has been fully received, next time flag "finished_" + // will be set. + //Error error = parser_.Parse(buffer_.data(), length); + + //if (error != kNoError) { + // return error; + //} + //} + +//#if WEBCC_DEBUG_OUTPUT +// std::cout << std::endl; +// std::cout << "--- RESPONSE (PARSED) ---" << std::endl; +// std::cout << *response << std::endl; +//#endif +} + +} // namespace webcc diff --git a/src/webcc/http_async_client.h b/src/webcc/http_async_client.h new file mode 100644 index 0000000..00004bf --- /dev/null +++ b/src/webcc/http_async_client.h @@ -0,0 +1,62 @@ +#ifndef WEBCC_HTTP_ASYNC_CLIENT_H_ +#define WEBCC_HTTP_ASYNC_CLIENT_H_ + +#include + +#include "boost/smart_ptr/scoped_ptr.hpp" + +#include "boost/asio/io_context.hpp" +#include "boost/asio/ip/tcp.hpp" + +#include "webcc/common.h" +#include "webcc/http_response_parser.h" + +namespace webcc { + +class HttpRequest; +class HttpResponse; + +typedef void(*HttpResponseHandler)(std::shared_ptr); + +class HttpAsyncClient { +public: + HttpAsyncClient(boost::asio::io_context& io_context); + + Error SendRequest(std::shared_ptr request, + HttpResponseHandler response_handler); + +private: + void HandleResolve(boost::system::error_code ec, + boost::asio::ip::tcp::resolver::results_type results); + + void DoConnect(boost::asio::ip::tcp::resolver::results_type::iterator endpoint_it); + + void HandleConnect(boost::system::error_code ec, + boost::asio::ip::tcp::resolver::results_type::iterator endpoint_it); + + void DoWrite(); + + void HandleWrite(boost::system::error_code ec); + + void DoRead(); + + void HandleRead(boost::system::error_code ec, std::size_t length); + +private: + boost::asio::ip::tcp::socket socket_; + + std::shared_ptr request_; + + std::unique_ptr resolver_; + boost::asio::ip::tcp::resolver::results_type endpoints_; + + std::array buffer_; + + std::unique_ptr parser_; + + std::shared_ptr response_; +}; + +} // namespace webcc + +#endif // WEBCC_HTTP_ASYNC_CLIENT_H_ diff --git a/src/webcc/http_client.cc b/src/webcc/http_client.cc index 4645afa..089c80f 100644 --- a/src/webcc/http_client.cc +++ b/src/webcc/http_client.cc @@ -3,14 +3,9 @@ #include "boost/date_time/posix_time/posix_time.hpp" #include "boost/lambda/bind.hpp" #include "boost/lambda/lambda.hpp" - -#if 0 -#include "boost/asio.hpp" -#else #include "boost/asio/connect.hpp" #include "boost/asio/read.hpp" #include "boost/asio/write.hpp" -#endif #include "webcc/logger.h" #include "webcc/http_request.h" diff --git a/src/webcc/http_client.h b/src/webcc/http_client.h index 22be370..8bd6bf8 100644 --- a/src/webcc/http_client.h +++ b/src/webcc/http_client.h @@ -5,10 +5,10 @@ #include #include "boost/asio/deadline_timer.hpp" -#include "boost/asio/ip/tcp.hpp" #include "boost/asio/io_context.hpp" +#include "boost/asio/ip/tcp.hpp" -#include "webcc/common.h" +#include "webcc/globals.h" #include "webcc/http_response_parser.h" namespace webcc { diff --git a/src/webcc/http_message.h b/src/webcc/http_message.h index db4e315..fbb9f79 100644 --- a/src/webcc/http_message.h +++ b/src/webcc/http_message.h @@ -3,7 +3,8 @@ #include #include -#include "webcc/common.h" + +#include "webcc/globals.h" namespace webcc { @@ -16,9 +17,6 @@ public: // Base class for HTTP request and response messages. class HttpMessage { public: - HttpMessage() = default; - HttpMessage(const HttpMessage&) = default; - HttpMessage& operator=(const HttpMessage&) = default; virtual ~HttpMessage() = default; const std::string& start_line() const { diff --git a/src/webcc/http_parser.h b/src/webcc/http_parser.h index bbab09b..89271cd 100644 --- a/src/webcc/http_parser.h +++ b/src/webcc/http_parser.h @@ -2,7 +2,8 @@ #define WEBCC_HTTP_PARSER_H_ #include -#include "webcc/common.h" + +#include "webcc/globals.h" namespace webcc { @@ -15,9 +16,6 @@ public: virtual ~HttpParser() = default; - HttpParser(const HttpParser&) = delete; - HttpParser& operator=(const HttpParser&) = delete; - bool finished() const { return finished_; } @@ -53,6 +51,8 @@ protected: bool content_length_parsed_; bool header_parsed_; bool finished_; + + DISALLOW_COPY_AND_ASSIGN(HttpParser); }; } // namespace webcc diff --git a/src/webcc/http_request.h b/src/webcc/http_request.h index 1891b35..00efb66 100644 --- a/src/webcc/http_request.h +++ b/src/webcc/http_request.h @@ -2,7 +2,9 @@ #define WEBCC_HTTP_REQUEST_H_ #include + #include "boost/asio/buffer.hpp" // for const_buffer + #include "webcc/http_message.h" namespace webcc { @@ -12,10 +14,6 @@ class HttpRequest; std::ostream& operator<<(std::ostream& os, const HttpRequest& request); class HttpRequest : public HttpMessage { - - friend std::ostream& operator<<(std::ostream& os, - const HttpRequest& request); - public: HttpRequest() = default; HttpRequest(const HttpRequest&) = default; @@ -47,8 +45,9 @@ public: return port_; } - // \param host Descriptive host name or numeric IP address. - // \param port Numeric port number, "80" will be used if it's empty. + // Set host name and port number. + // The |host| is a descriptive name or a numeric IP address. The |port| is + // a numeric number (e.g., "9000") and "80" will be used if it's empty. void SetHost(const std::string& host, const std::string& port); // Compose start line, etc. @@ -64,6 +63,8 @@ public: std::string Dump() const; private: + friend std::ostream& operator<<(std::ostream& os, const HttpRequest& request); + // HTTP method. std::string method_; diff --git a/src/webcc/http_request_handler.cc b/src/webcc/http_request_handler.cc index b0fbce5..e1a04ee 100644 --- a/src/webcc/http_request_handler.cc +++ b/src/webcc/http_request_handler.cc @@ -2,10 +2,10 @@ #include -#include "webcc/logger.h" -#include "webcc/common.h" +#include "webcc/globals.h" #include "webcc/http_request.h" #include "webcc/http_response.h" +#include "webcc/logger.h" namespace webcc { diff --git a/src/webcc/http_request_handler.h b/src/webcc/http_request_handler.h index f85bde6..491546f 100644 --- a/src/webcc/http_request_handler.h +++ b/src/webcc/http_request_handler.h @@ -18,13 +18,8 @@ class HttpResponse; // The common handler for all incoming requests. class HttpRequestHandler { public: - HttpRequestHandler(const HttpRequestHandler&) = delete; - HttpRequestHandler& operator=(const HttpRequestHandler&) = delete; - HttpRequestHandler() = default; - - virtual ~HttpRequestHandler() { - } + virtual ~HttpRequestHandler() = default; // Put the session into the queue. void Enqueue(HttpSessionPtr session); @@ -41,9 +36,10 @@ private: // Called by the worker routine. virtual void HandleSession(HttpSessionPtr session) = 0; -private: Queue queue_; boost::thread_group workers_; + + DISALLOW_COPY_AND_ASSIGN(HttpRequestHandler); }; } // namespace webcc diff --git a/src/webcc/http_request_parser.h b/src/webcc/http_request_parser.h index 4feb69b..3cd5807 100644 --- a/src/webcc/http_request_parser.h +++ b/src/webcc/http_request_parser.h @@ -16,7 +16,6 @@ public: private: Error ParseStartLine(const std::string& line) override; -private: HttpRequest* request_; }; diff --git a/src/webcc/http_response.cc b/src/webcc/http_response.cc index c74f321..68cdba9 100644 --- a/src/webcc/http_response.cc +++ b/src/webcc/http_response.cc @@ -1,4 +1,5 @@ #include "webcc/http_response.h" + #include #include diff --git a/src/webcc/http_response.h b/src/webcc/http_response.h index df1d736..95b6c7c 100644 --- a/src/webcc/http_response.h +++ b/src/webcc/http_response.h @@ -2,7 +2,9 @@ #define WEBCC_HTTP_RESPONSE_H_ #include + #include "boost/asio/buffer.hpp" // for const_buffer + #include "webcc/http_message.h" namespace webcc { @@ -12,9 +14,6 @@ class HttpResponse; std::ostream& operator<<(std::ostream& os, const HttpResponse& response); class HttpResponse : public HttpMessage { - friend std::ostream& operator<<(std::ostream& os, - const HttpResponse& response); - public: HttpResponse() = default; ~HttpResponse() override = default; @@ -39,6 +38,9 @@ public: static HttpResponse Fault(HttpStatus::Enum status); private: + friend std::ostream& operator<<(std::ostream& os, + const HttpResponse& response); + int status_ = HttpStatus::kOK; }; diff --git a/src/webcc/http_response_parser.cc b/src/webcc/http_response_parser.cc index b3c7419..c6ae836 100644 --- a/src/webcc/http_response_parser.cc +++ b/src/webcc/http_response_parser.cc @@ -1,5 +1,7 @@ #include "webcc/http_response_parser.h" + #include "boost/lexical_cast.hpp" + #include "webcc/http_response.h" namespace webcc { diff --git a/src/webcc/http_server.cc b/src/webcc/http_server.cc index b71281f..f9488f2 100644 --- a/src/webcc/http_server.cc +++ b/src/webcc/http_server.cc @@ -1,9 +1,9 @@ #include "webcc/http_server.h" -#include +#include -#include "webcc/logger.h" #include "webcc/http_request_handler.h" +#include "webcc/logger.h" #include "webcc/soap_service.h" #include "webcc/utility.h" diff --git a/src/webcc/http_server.h b/src/webcc/http_server.h index fa47ef7..07241ff 100644 --- a/src/webcc/http_server.h +++ b/src/webcc/http_server.h @@ -4,12 +4,11 @@ #include #include -#include "boost/scoped_ptr.hpp" -#include "boost/thread/thread.hpp" - #include "boost/asio/io_context.hpp" -#include "boost/asio/signal_set.hpp" #include "boost/asio/ip/tcp.hpp" +#include "boost/asio/signal_set.hpp" +#include "boost/scoped_ptr.hpp" +#include "boost/thread/thread.hpp" #include "webcc/http_session.h" diff --git a/src/webcc/http_session.cc b/src/webcc/http_session.cc index 2a5fdb7..8f4dc30 100644 --- a/src/webcc/http_session.cc +++ b/src/webcc/http_session.cc @@ -5,8 +5,8 @@ #include "boost/asio/write.hpp" #include "boost/date_time/posix_time/posix_time.hpp" -#include "webcc/logger.h" #include "webcc/http_request_handler.h" +#include "webcc/logger.h" namespace webcc { diff --git a/src/webcc/http_session.h b/src/webcc/http_session.h index 6136c22..6e3ccba 100644 --- a/src/webcc/http_session.h +++ b/src/webcc/http_session.h @@ -6,7 +6,7 @@ #include "boost/asio/ip/tcp.hpp" // for ip::tcp::socket -#include "webcc/common.h" +#include "webcc/globals.h" #include "webcc/http_request.h" #include "webcc/http_request_parser.h" #include "webcc/http_response.h" diff --git a/src/webcc/queue.h b/src/webcc/queue.h index 057f6de..a61a0d8 100644 --- a/src/webcc/queue.h +++ b/src/webcc/queue.h @@ -15,11 +15,11 @@ namespace webcc { template class Queue { public: - Queue(const Queue& rhs) = delete; - Queue& operator=(const Queue& rhs) = delete; - Queue() = default; + Queue(const Queue&) = delete; + Queue& operator=(const Queue&) = delete; + T PopOrWait() { boost::unique_lock lock(mutex_); diff --git a/src/webcc/rest_client.cc b/src/webcc/rest_client.cc new file mode 100644 index 0000000..a6440b9 --- /dev/null +++ b/src/webcc/rest_client.cc @@ -0,0 +1,31 @@ +#include "webcc/rest_client.h" + +#include "webcc/http_client.h" +#include "webcc/http_request.h" +#include "webcc/http_response.h" + +namespace webcc { + +bool RestClient::Request(const std::string& method, + const std::string& url, + const std::string& content, + HttpResponse* response) { + HttpRequest request; + + request.set_method(method); + request.set_url(url); + request.SetHost(host_, port_); + + if (!content.empty()) { + request.SetContent(content); + } + + request.Build(); + + HttpClient http_client; + Error error = http_client.MakeRequest(request, response); + + return error == kNoError; +} + +} // namespace webcc diff --git a/src/webcc/rest_client.h b/src/webcc/rest_client.h new file mode 100644 index 0000000..5186a2a --- /dev/null +++ b/src/webcc/rest_client.h @@ -0,0 +1,56 @@ +#ifndef WEBCC_REST_CLIENT_H_ +#define WEBCC_REST_CLIENT_H_ + +#include + +#include "webcc/globals.h" + +namespace webcc { + +class HttpResponse; + +class RestClient { +public: + RestClient(const std::string& host, const std::string& port) + : host_(host), port_(port) { + } + + bool Get(const std::string& url, HttpResponse* response) { + return Request(kHttpGet, url, "", response); + } + + bool Post(const std::string& url, + const std::string& content, + HttpResponse* response) { + return Request(kHttpPost, url, content, response); + } + + bool Put(const std::string& url, + const std::string& content, + HttpResponse* response) { + return Request(kHttpPut, url, content, response); + } + + bool Patch(const std::string& url, + const std::string& content, + HttpResponse* response) { + return Request(kHttpPatch, url, content, response); + } + + bool Delete(const std::string& url, HttpResponse* response) { + return Request(kHttpDelete, url, "", response); + } + +private: + bool Request(const std::string& method, + const std::string& url, + const std::string& content, + HttpResponse* response); + + std::string host_; + std::string port_; +}; + +} // namespace webcc + +#endif // WEBCC_REST_CLIENT_H_ diff --git a/src/webcc/rest_request_handler.cc b/src/webcc/rest_request_handler.cc new file mode 100644 index 0000000..31e0192 --- /dev/null +++ b/src/webcc/rest_request_handler.cc @@ -0,0 +1,50 @@ +#include "webcc/rest_request_handler.h" + +#include "webcc/logger.h" +#include "webcc/url.h" + +namespace webcc { + +bool RestRequestHandler::RegisterService(RestServicePtr service, + const std::string& url) { + return service_manager_.AddService(service, url); +} + +void RestRequestHandler::HandleSession(HttpSessionPtr session) { + Url url(session->request().url(), true); + + if (!url.IsValid()) { + session->SendResponse(HttpStatus::kBadRequest); + return; + } + + std::vector sub_matches; + RestServicePtr service = service_manager_.GetService(url.path(), + &sub_matches); + if (!service) { + LOG_WARN("No service matches the URL: %s", url.path().c_str()); + session->SendResponse(HttpStatus::kBadRequest); + return; + } + + // TODO: Only for GET? + UrlQuery query; + Url::SplitQuery(url.query(), &query); + + std::string content; + bool ok = service->Handle(session->request().method(), + sub_matches, + query, + session->request().content(), + &content); + if (!ok) { + // TODO: Could be other than kBadRequest. + session->SendResponse(HttpStatus::kBadRequest); + return; + } + + session->SetResponseContent(std::move(content), kTextJsonUtf8); + session->SendResponse(HttpStatus::kOK); +} + +} // namespace webcc diff --git a/src/webcc/rest_request_handler.h b/src/webcc/rest_request_handler.h new file mode 100644 index 0000000..1e617a0 --- /dev/null +++ b/src/webcc/rest_request_handler.h @@ -0,0 +1,29 @@ +#ifndef WEBCC_REST_REQUEST_HANDLER_H_ +#define WEBCC_REST_REQUEST_HANDLER_H_ + +// HTTP server handling REST requests. + +#include "webcc/http_request_handler.h" +#include "webcc/rest_service_manager.h" + +namespace webcc { + +class RestRequestHandler : public HttpRequestHandler { +public: + ~RestRequestHandler() override = default; + + // Register a REST service to the given URL path. + // The URL should start with "/" and could be a regular expression or not. + // E.g., "/instances". "/instances/(\\d+)" + bool RegisterService(RestServicePtr service, const std::string& url); + +private: + void HandleSession(HttpSessionPtr session) override; + +private: + RestServiceManager service_manager_; +}; + +} // namespace webcc + +#endif // WEBCC_REST_REQUEST_HANDLER_H_ diff --git a/src/webcc/rest_server.cc b/src/webcc/rest_server.cc deleted file mode 100644 index 61c558e..0000000 --- a/src/webcc/rest_server.cc +++ /dev/null @@ -1,114 +0,0 @@ -#include "webcc/rest_server.h" - -#include "webcc/logger.h" -#include "webcc/url.h" - -namespace webcc { - -//////////////////////////////////////////////////////////////////////////////// - -bool RestServiceManager::AddService(RestServicePtr service, - const std::string& url) { - assert(service); - - ServiceItem item(service, url); - - std::regex::flag_type flags = std::regex::ECMAScript | std::regex::icase; - - try { - // Compile the regex. - item.url_regex.assign(url, flags); - - service_items_.push_back(item); - - return true; - - } catch (std::regex_error& e) { - LOG_ERRO("URL is not a valid regular expression: %s", e.what()); - } - - return false; -} - -RestServicePtr RestServiceManager::GetService( - const std::string& url, - std::vector* sub_matches) { - assert(sub_matches != NULL); - - for (ServiceItem& item : service_items_) { - std::smatch match; - - if (std::regex_match(url, match, item.url_regex)) { - // Any sub-matches? - // NOTE: Start from 1 because match[0] is the whole string itself. - for (size_t i = 1; i < match.size(); ++i) { - sub_matches->push_back(match[i].str()); - } - - return item.service; - } - } - - return RestServicePtr(); -} - -//////////////////////////////////////////////////////////////////////////////// - -bool RestRequestHandler::RegisterService(RestServicePtr service, - const std::string& url) { - return service_manager_.AddService(service, url); -} - -void RestRequestHandler::HandleSession(HttpSessionPtr session) { - Url url(session->request().url()); - - if (!url.IsValid()) { - session->SendResponse(HttpStatus::kBadRequest); - return; - } - - std::vector sub_matches; - RestServicePtr service = service_manager_.GetService(url.path(), &sub_matches); - if (!service) { - LOG_WARN("No service matches the URL: %s", url.path().c_str()); - session->SendResponse(HttpStatus::kBadRequest); - return; - } - - // TODO: Only for GET? - UrlQuery query; - Url::SplitQuery(url.query(), &query); - - std::string content; - bool ok = service->Handle(session->request().method(), - sub_matches, - query, - session->request().content(), - &content); - if (!ok) { - // TODO: Could be other than kBadRequest. - session->SendResponse(HttpStatus::kBadRequest); - return; - } - - session->SetResponseContent(std::move(content), kTextJsonUtf8); - session->SendResponse(HttpStatus::kOK); -} - -//////////////////////////////////////////////////////////////////////////////// - -RestServer::RestServer(unsigned short port, std::size_t workers) - : HttpServer(port, workers) - , request_handler_(new RestRequestHandler()) { -} - -RestServer::~RestServer() { - delete request_handler_; -} - -bool RestServer::RegisterService(RestServicePtr service, - const std::string& url) { - return request_handler_->RegisterService(service, url); -} - -} // namespace webcc diff --git a/src/webcc/rest_server.h b/src/webcc/rest_server.h index 112ab0c..bbdfeba 100644 --- a/src/webcc/rest_server.h +++ b/src/webcc/rest_server.h @@ -3,106 +3,36 @@ // HTTP server handling REST requests. -#include -#include -#include - -#include "webcc/http_request_handler.h" #include "webcc/http_server.h" +#include "webcc/rest_request_handler.h" #include "webcc/rest_service.h" namespace webcc { -class Url; - -//////////////////////////////////////////////////////////////////////////////// - -class RestServiceManager { -public: - RestServiceManager() = default; - RestServiceManager(const RestServiceManager&) = delete; - RestServiceManager& operator=(const RestServiceManager&) = delete; - - // Add a service and bind it with the given URL. - // The URL should start with "/" and could be a regular expression or not. - // E.g., "/instances". "/instances/(\\d+)" - bool AddService(RestServicePtr service, const std::string& url); - - // Parameter 'sub_matches' is only available when the URL bound to the - // service is a regular expression and has sub-expressions. - // E.g., the URL bound to the service is "/instances/(\\d+)", now match - // "/instances/12345" against it, you will get one sub-match of "12345". - RestServicePtr GetService(const std::string& url, - std::vector* sub_matches); - -private: - class ServiceItem { - public: - ServiceItem(RestServicePtr _service, const std::string& _url) - : service(_service), url(_url) { - } - - ServiceItem(const ServiceItem& rhs) = default; - ServiceItem& operator=(const ServiceItem& rhs) = default; - - ServiceItem(ServiceItem&& rhs) - : url(std::move(rhs.url)) - , url_regex(std::move(rhs.url_regex)) - , service(rhs.service) { // No move - } - - RestServicePtr service; - - // URL string, e.g., "/instances/(\\d+)". - std::string url; - - // Compiled regex for URL string. - std::regex url_regex; - }; - - std::vector service_items_; -}; - -//////////////////////////////////////////////////////////////////////////////// - -class RestRequestHandler : public HttpRequestHandler { -public: - RestRequestHandler() = default; - - // Register a REST service to the given URL path. - // The URL should start with "/" and could be a regular expression or not. - // E.g., "/instances". "/instances/(\\d+)" - bool RegisterService(RestServicePtr service, const std::string& url); - -private: - void HandleSession(HttpSessionPtr session) override; - -private: - RestServiceManager service_manager_; -}; - -//////////////////////////////////////////////////////////////////////////////// - class RestServer : public HttpServer { public: - RestServer(unsigned short port, std::size_t workers); + RestServer(unsigned short port, std::size_t workers) + : HttpServer(port, workers) { + } - ~RestServer() override; + ~RestServer() override = default; // Register a REST service to the given URL path. // The URL should start with "/" and could be a regular expression or not. // E.g., "/instances". "/instances/(\\d+)" // NOTE: Registering to the same URL multiple times is allowed, but only the // last one takes effect. - bool RegisterService(RestServicePtr service, const std::string& url); + bool RegisterService(RestServicePtr service, const std::string& url) { + return request_handler_.RegisterService(service, url); + } private: HttpRequestHandler* GetRequestHandler() override { - return request_handler_; + return &request_handler_; } private: - RestRequestHandler* request_handler_; + RestRequestHandler request_handler_; }; } // namespace webcc diff --git a/src/webcc/rest_service.cc b/src/webcc/rest_service.cc index 1704513..4817443 100644 --- a/src/webcc/rest_service.cc +++ b/src/webcc/rest_service.cc @@ -1,4 +1,5 @@ #include "webcc/rest_service.h" + #include "webcc/logger.h" namespace webcc { @@ -29,7 +30,7 @@ bool RestDetailService::Handle(const std::string& http_method, const std::string& request_content, std::string* response_content) { if (http_method == kHttpGet) { - return Get(url_sub_matches, response_content); + return Get(url_sub_matches, query, response_content); } if (http_method == kHttpPut) { diff --git a/src/webcc/rest_service.h b/src/webcc/rest_service.h index 1f193d5..39e483f 100644 --- a/src/webcc/rest_service.h +++ b/src/webcc/rest_service.h @@ -13,7 +13,7 @@ #include #include -#include "webcc/common.h" +#include "webcc/globals.h" namespace webcc { @@ -28,12 +28,9 @@ public: } // Handle REST request, output the response. - // \param http_method GET, POST, etc. - // \param url_sub_matches The regex sub-matches in the URL, - // usually resource ID. - // \param query Query parameters in the URL. - // \param request_content Request JSON. - // \param response_content Output response JSON. + // The regex sub-matches of the URL (usually resource IDs) were stored in + // |url_sub_matches|. The |query| part of the URL is normally only for GET + // request. Both the request and response contents are JSON strings. virtual bool Handle(const std::string& http_method, const std::vector& url_sub_matches, const UrlQuery& query, @@ -79,6 +76,7 @@ class RestDetailService : public RestService { protected: virtual bool Get(const std::vector& url_sub_matches, + const UrlQuery& query, std::string* response_content) { return false; } diff --git a/src/webcc/rest_service_manager.cc b/src/webcc/rest_service_manager.cc new file mode 100644 index 0000000..ad80dd9 --- /dev/null +++ b/src/webcc/rest_service_manager.cc @@ -0,0 +1,55 @@ +#include "webcc/rest_service_manager.h" + +#include + +#include "webcc/logger.h" + +namespace webcc { + +bool RestServiceManager::AddService(RestServicePtr service, + const std::string& url) { + assert(service); + + ServiceItem item(service, url); + + std::regex::flag_type flags = std::regex::ECMAScript | std::regex::icase; + + try { + // Compile the regex. + item.url_regex.assign(url, flags); + + service_items_.push_back(item); + + return true; + + } catch (std::regex_error& e) { + LOG_ERRO("URL is not a valid regular expression: %s", e.what()); + } + + return false; +} + +RestServicePtr RestServiceManager::GetService( + const std::string& url, + std::vector* sub_matches) { + + assert(sub_matches != NULL); + + for (ServiceItem& item : service_items_) { + std::smatch match; + + if (std::regex_match(url, match, item.url_regex)) { + // Any sub-matches? + // NOTE: Start from 1 because match[0] is the whole string itself. + for (size_t i = 1; i < match.size(); ++i) { + sub_matches->push_back(match[i].str()); + } + + return item.service; + } + } + + return RestServicePtr(); +} + +} // namespace webcc diff --git a/src/webcc/rest_service_manager.h b/src/webcc/rest_service_manager.h new file mode 100644 index 0000000..2bb896e --- /dev/null +++ b/src/webcc/rest_service_manager.h @@ -0,0 +1,59 @@ +#ifndef WEBCC_REST_SERVICE_MANAGER_H_ +#define WEBCC_REST_SERVICE_MANAGER_H_ + +#include +#include + +#include "webcc/rest_service.h" + +namespace webcc { + +class RestServiceManager { +public: + RestServiceManager() = default; + + // Add a service and bind it with the given URL. + // The |url| should start with "/" and could be a regular expression or not. + // E.g., "/instances". "/instances/(\\d+)" + bool AddService(RestServicePtr service, const std::string& url); + + // The |sub_matches| is only available when the |url| bound to the + // service is a regular expression and has sub-expressions. + // E.g., the URL bound to the service is "/instances/(\\d+)", now match + // "/instances/12345" against it, you will get one sub-match of "12345". + RestServicePtr GetService(const std::string& url, + std::vector* sub_matches); + +private: + class ServiceItem { + public: + ServiceItem(RestServicePtr _service, const std::string& _url) + : service(_service), url(_url) { + } + + ServiceItem(const ServiceItem& rhs) = default; + ServiceItem& operator=(const ServiceItem& rhs) = default; + + ServiceItem(ServiceItem&& rhs) + : url(std::move(rhs.url)), + url_regex(std::move(rhs.url_regex)), + service(rhs.service) { // No move + } + + RestServicePtr service; + + // URL string, e.g., "/instances/(\\d+)". + std::string url; + + // Compiled regex for URL string. + std::regex url_regex; + }; + + std::vector service_items_; + + DISALLOW_COPY_AND_ASSIGN(RestServiceManager); +}; + +} // namespace webcc + +#endif // WEBCC_REST_SERVICE_MANAGER_H_ diff --git a/src/webcc/soap_client.h b/src/webcc/soap_client.h index 1b98bee..28217a5 100644 --- a/src/webcc/soap_client.h +++ b/src/webcc/soap_client.h @@ -4,7 +4,8 @@ #include #include -#include "webcc/common.h" +#include "webcc/globals.h" +#include "webcc/soap_message.h" namespace webcc { diff --git a/src/webcc/soap_message.cc b/src/webcc/soap_message.cc index bfb7fd9..a003112 100644 --- a/src/webcc/soap_message.cc +++ b/src/webcc/soap_message.cc @@ -1,10 +1,16 @@ #include "webcc/soap_message.h" #include + #include "webcc/soap_xml.h" namespace webcc { +const SoapNamespace kSoapEnvNamespace{ + "soap", + "http://schemas.xmlsoap.org/soap/envelope/" +}; + void SoapMessage::ToXml(std::string* xml_string) { assert(soapenv_ns_.IsValid() && service_ns_.IsValid() && diff --git a/src/webcc/soap_message.h b/src/webcc/soap_message.h index 23b25a1..6a581b2 100644 --- a/src/webcc/soap_message.h +++ b/src/webcc/soap_message.h @@ -2,14 +2,33 @@ #define WEBCC_SOAP_MESSAGE_H_ #include + #include "pugixml/pugixml.hpp" -#include "webcc/common.h" + +#include "webcc/globals.h" namespace webcc { +// XML namespace name/url pair. +// E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" } +class SoapNamespace { +public: + std::string name; + std::string url; + + bool IsValid() const { + return !name.empty() && !url.empty(); + } +}; + +// CSoap's default namespace for SOAP Envelope. +extern const SoapNamespace kSoapEnvNamespace; + // Base class for SOAP request and response. class SoapMessage { public: + virtual ~SoapMessage() {} + // E.g., set as kSoapEnvNamespace. void set_soapenv_ns(const SoapNamespace& soapenv_ns) { soapenv_ns_ = soapenv_ns; @@ -34,8 +53,6 @@ public: bool FromXml(const std::string& xml_string); protected: - SoapMessage() = default; - // Convert to SOAP body XML. virtual void ToXmlBody(pugi::xml_node xbody) = 0; diff --git a/src/webcc/soap_request.cc b/src/webcc/soap_request.cc index e209175..ea7332b 100644 --- a/src/webcc/soap_request.cc +++ b/src/webcc/soap_request.cc @@ -1,4 +1,5 @@ #include "webcc/soap_request.h" + #include "webcc/soap_xml.h" namespace webcc { diff --git a/src/webcc/soap_request.h b/src/webcc/soap_request.h index ccfd20a..7fca9cd 100644 --- a/src/webcc/soap_request.h +++ b/src/webcc/soap_request.h @@ -2,6 +2,7 @@ #define WEBCC_SOAP_REQUEST_H_ #include + #include "webcc/soap_message.h" namespace webcc { diff --git a/src/webcc/soap_server.cc b/src/webcc/soap_request_handler.cc similarity index 71% rename from src/webcc/soap_server.cc rename to src/webcc/soap_request_handler.cc index 7cfd917..3ef4345 100644 --- a/src/webcc/soap_server.cc +++ b/src/webcc/soap_request_handler.cc @@ -1,4 +1,4 @@ -#include "webcc/soap_server.h" +#include "webcc/soap_request_handler.h" #include "webcc/logger.h" #include "webcc/soap_request.h" @@ -6,8 +6,6 @@ namespace webcc { -//////////////////////////////////////////////////////////////////////////////// - bool SoapRequestHandler::RegisterService(SoapServicePtr service, const std::string& url) { assert(service); @@ -55,20 +53,4 @@ SoapServicePtr SoapRequestHandler::GetServiceByUrl(const std::string& url) { return SoapServicePtr(); } -//////////////////////////////////////////////////////////////////////////////// - -SoapServer::SoapServer(unsigned short port, std::size_t workers) - : HttpServer(port, workers) - , request_handler_(new SoapRequestHandler()) { -} - -SoapServer::~SoapServer() { - delete request_handler_; -} - -bool SoapServer::RegisterService(SoapServicePtr service, - const std::string& url) { - return request_handler_->RegisterService(service, url); -} - } // namespace webcc diff --git a/src/webcc/soap_request_handler.h b/src/webcc/soap_request_handler.h new file mode 100644 index 0000000..e42ac1f --- /dev/null +++ b/src/webcc/soap_request_handler.h @@ -0,0 +1,32 @@ +#ifndef WEBCC_SOAP_REQUEST_HANDLER_H_ +#define WEBCC_SOAP_REQUEST_HANDLER_H_ + +#include + +#include "webcc/http_request_handler.h" + +namespace webcc { + +class SoapRequestHandler : public HttpRequestHandler { +public: + SoapRequestHandler() = default; + ~SoapRequestHandler() override = default; + + // Register a SOAP service to the given URL path. + // The |url| path must start with "/", e.g., "/calculator". + // Registering to the same URL multiple times is allowed, but only the last + // one takes effect. + bool RegisterService(SoapServicePtr service, const std::string& url); + +private: + void HandleSession(HttpSessionPtr session) override; + + SoapServicePtr GetServiceByUrl(const std::string& url); + + typedef std::map UrlServiceMap; + UrlServiceMap url_service_map_; +}; + +} // namespace webcc + +#endif // WEBCC_SOAP_REQUEST_HANDLER_H_ diff --git a/src/webcc/soap_server.h b/src/webcc/soap_server.h index 6cfe1a0..3327576 100644 --- a/src/webcc/soap_server.h +++ b/src/webcc/soap_server.h @@ -3,53 +3,29 @@ // HTTP server handling SOAP requests. -#include -#include - -#include "webcc/http_request_handler.h" +#include "webcc/soap_request_handler.h" #include "webcc/http_server.h" namespace webcc { -//////////////////////////////////////////////////////////////////////////////// - -class SoapRequestHandler : public HttpRequestHandler { -public: - SoapRequestHandler() = default; - - // Register a SOAP service to the given URL path. - // \url URL path, must start with "/". E.g., "/calculator". - // NOTE: Registering to the same URL multiple times is allowed, but only the - // last one takes effect. - bool RegisterService(SoapServicePtr service, const std::string& url); - -private: - void HandleSession(HttpSessionPtr session) override; - - SoapServicePtr GetServiceByUrl(const std::string& url); - -private: - typedef std::map UrlServiceMap; - UrlServiceMap url_service_map_; -}; - -//////////////////////////////////////////////////////////////////////////////// - class SoapServer : public HttpServer { public: - SoapServer(unsigned short port, std::size_t workers); + SoapServer(unsigned short port, std::size_t workers) + : HttpServer(port, workers) { + } - ~SoapServer() override; + ~SoapServer() override = default; - bool RegisterService(SoapServicePtr service, const std::string& url); + bool RegisterService(SoapServicePtr service, const std::string& url) { + return request_handler_.RegisterService(service, url); + } private: HttpRequestHandler* GetRequestHandler() override { - return request_handler_; + return &request_handler_; } -private: - SoapRequestHandler* request_handler_; + SoapRequestHandler request_handler_; }; } // namespace webcc diff --git a/src/webcc/soap_service.h b/src/webcc/soap_service.h index 0cd3d00..d108442 100644 --- a/src/webcc/soap_service.h +++ b/src/webcc/soap_service.h @@ -2,7 +2,8 @@ #define WEBCC_SOAP_SERVICE_H_ #include -#include "webcc/common.h" + +#include "webcc/globals.h" namespace webcc { @@ -12,8 +13,7 @@ class SoapResponse; // Base class for your SOAP service. class SoapService { public: - virtual ~SoapService() { - } + virtual ~SoapService() = default; // Handle SOAP request, output the response. virtual bool Handle(const SoapRequest& soap_request, diff --git a/src/webcc/soap_xml.cc b/src/webcc/soap_xml.cc index b6c49ac..c7e2506 100644 --- a/src/webcc/soap_xml.cc +++ b/src/webcc/soap_xml.cc @@ -3,8 +3,7 @@ namespace webcc { namespace soap_xml { -void SplitName(const pugi::xml_node& xnode, - std::string* prefix, +void SplitName(const pugi::xml_node& xnode, std::string* prefix, std::string* name) { std::string full_name = xnode.name(); @@ -39,14 +38,12 @@ std::string GetNameNoPrefix(const pugi::xml_node& xnode) { return name; } -pugi::xml_node AddChild(pugi::xml_node& xnode, - const std::string& ns, +pugi::xml_node AddChild(pugi::xml_node& xnode, const std::string& ns, const std::string& name) { return xnode.append_child((ns + ":" + name).c_str()); } -pugi::xml_node GetChild(pugi::xml_node& xnode, - const std::string& ns, +pugi::xml_node GetChild(pugi::xml_node& xnode, const std::string& ns, const std::string& name) { return xnode.child((ns + ":" + name).c_str()); } @@ -72,28 +69,23 @@ pugi::xml_node GetChildNoNS(pugi::xml_node& xnode, const std::string& name) { return pugi::xml_node(); } -void AddAttr(pugi::xml_node& xnode, - const std::string& ns, - const std::string& name, - const std::string& value) { +void AddAttr(pugi::xml_node& xnode, const std::string& ns, + const std::string& name, const std::string& value) { std::string ns_name = ns + ":" + name; xnode.append_attribute(ns_name.c_str()) = value.c_str(); } -void AddNSAttr(pugi::xml_node& xnode, - const std::string& ns_name, +void AddNSAttr(pugi::xml_node& xnode, const std::string& ns_name, const std::string& ns_url) { AddAttr(xnode, "xmlns", ns_name, ns_url); } -std::string GetNSAttr(pugi::xml_node& xnode, - const std::string& ns_name) { +std::string GetNSAttr(pugi::xml_node& xnode, const std::string& ns_name) { std::string attr_name = "xmlns:" + ns_name; return xnode.attribute(attr_name.c_str()).as_string(); } -bool PrettyPrint(std::ostream& os, - const std::string& xml_string, +bool PrettyPrint(std::ostream& os, const std::string& xml_string, const char* indent) { pugi::xml_document xdoc; if (!xdoc.load_string(xml_string.c_str())) { diff --git a/src/webcc/soap_xml.h b/src/webcc/soap_xml.h index b7cd12b..0664ba4 100644 --- a/src/webcc/soap_xml.h +++ b/src/webcc/soap_xml.h @@ -1,19 +1,19 @@ #ifndef WEBCC_SOAP_XML_H_ #define WEBCC_SOAP_XML_H_ -// XML utilities. +// XML helpers for SOAP messages. #include + #include "pugixml/pugixml.hpp" namespace webcc { namespace soap_xml { // Split the node name into namespace prefix and real name. -// E.g., if the node name is "soapenv:Envelope", it will be splited to +// E.g., if the node name is "soapenv:Envelope", it will be splitted to // "soapenv" and "Envelope". -void SplitName(const pugi::xml_node& xnode, - std::string* prefix = NULL, +void SplitName(const pugi::xml_node& xnode, std::string* prefix = NULL, std::string* name = NULL); // Get the namespace prefix from node name. @@ -26,38 +26,31 @@ std::string GetNameNoPrefix(const pugi::xml_node& xnode); // Add a child with the given name which is prefixed by a namespace. // E.g., AppendChild(xnode, "soapenv", "Envelope") will append a child with // name "soapenv:Envelope". -pugi::xml_node AddChild(pugi::xml_node& xnode, - const std::string& ns, +pugi::xml_node AddChild(pugi::xml_node& xnode, const std::string& ns, const std::string& name); -pugi::xml_node GetChild(pugi::xml_node& xnode, - const std::string& ns, +pugi::xml_node GetChild(pugi::xml_node& xnode, const std::string& ns, const std::string& name); // TODO: Remove -pugi::xml_node GetChildNoNS(pugi::xml_node& xnode, - const std::string& name); +pugi::xml_node GetChildNoNS(pugi::xml_node& xnode, const std::string& name); // Add an attribute with the given name which is prefixed by a namespace. -void AddAttr(pugi::xml_node& xnode, - const std::string& ns, - const std::string& name, - const std::string& value); +void AddAttr(pugi::xml_node& xnode, const std::string& ns, + const std::string& name, const std::string& value); // Append "xmlns" attribute. // E.g., if the namespace is // { "soapenv", "http://schemas.xmlsoap.org/soap/envelope/" } // the attribute added will be // xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" -void AddNSAttr(pugi::xml_node& xnode, - const std::string& ns_name, +void AddNSAttr(pugi::xml_node& xnode, const std::string& ns_name, const std::string& ns_url); // Get namespace attribute value. // E.g., if the given namespace name is "soapenv", the value of // attribute "xmlns:soapenv" will be returned. -std::string GetNSAttr(pugi::xml_node& xnode, - const std::string& ns_name); +std::string GetNSAttr(pugi::xml_node& xnode, const std::string& ns_name); // An XML writer writing to a referenced string. // Example: @@ -68,12 +61,11 @@ std::string GetNSAttr(pugi::xml_node& xnode, // xdoc.save(writer, "\t", pugi::format_default, pugi::encoding_utf8); class XmlStrRefWriter : public pugi::xml_writer { public: - explicit XmlStrRefWriter(std::string* result) - : result_(result) { + explicit XmlStrRefWriter(std::string* result) : result_(result) { result_->clear(); } - virtual void write(const void* data, size_t size) override { + void write(const void* data, size_t size) override { result_->append(static_cast(data), size); } @@ -82,8 +74,7 @@ private: }; // Print the XML string to output stream in pretty format. -bool PrettyPrint(std::ostream& os, - const std::string& xml_string, +bool PrettyPrint(std::ostream& os, const std::string& xml_string, const char* indent = "\t"); } // namespace soap_xml diff --git a/src/webcc/url.cc b/src/webcc/url.cc index a8ecba5..54c322c 100644 --- a/src/webcc/url.cc +++ b/src/webcc/url.cc @@ -6,13 +6,78 @@ namespace webcc { //////////////////////////////////////////////////////////////////////////////// +// Helper functions to decode URL string. + +// Convert a hex character digit to a decimal character value. +static bool HexToDecimal(char hex, int* decimal) { + if (hex >= '0' && hex <= '9') { + *decimal = hex - '0'; + } else if (hex >= 'A' && hex <= 'F') { + *decimal = 10 + (hex - 'A'); + } else if (hex >= 'a' && hex <= 'f') { + *decimal = 10 + (hex - 'a'); + } else { + return false; + } + return true; +} + +static bool Decode(const std::string& encoded, std::string* raw) { + for (auto iter = encoded.begin(); iter != encoded.end(); ++iter) { + if (*iter == '%') { + if (++iter == encoded.end()) { + // Invalid URI string, two hexadecimal digits must follow '%'. + return false; + } + + int h_decimal = 0; + if (!HexToDecimal(*iter, &h_decimal)) { + return false; + } + + if (++iter == encoded.end()) { + // Invalid URI string, two hexadecimal digits must follow '%'. + return false; + } + + int l_decimal = 0; + if (!HexToDecimal(*iter, &l_decimal)) { + return false; + } + + raw->push_back(static_cast((h_decimal << 4) + l_decimal)); + + } else if (*iter > 127 || *iter < 0) { + // Invalid encoded URI string, must be entirely ASCII. + return false; + } else { + raw->push_back(*iter); + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +UrlQuery::UrlQuery(const std::map& map) { + for (auto& pair : map) { + Add(pair.first, pair.second); + } +} void UrlQuery::Add(std::string&& key, std::string&& value) { - if (!HasKey(key)) { + if (!Has(key)) { parameters_.push_back({ std::move(key), std::move(value) }); } } +void UrlQuery::Add(const std::string& key, const std::string& value) { + if (!Has(key)) { + parameters_.push_back({ key, value }); + } +} + void UrlQuery::Remove(const std::string& key) { auto it = Find(key); if (it != parameters_.end()) { @@ -20,16 +85,31 @@ void UrlQuery::Remove(const std::string& key) { } } -const std::string& UrlQuery::GetValue(const std::string& key) const { - static const std::string kEmptyValue; - +const std::string& UrlQuery::Get(const std::string& key) const { auto it = Find(key); if (it != parameters_.end()) { return it->value(); } + + static const std::string kEmptyValue; return kEmptyValue; } +std::string UrlQuery::ToString() const { + if (parameters_.empty()) { + return ""; + } + + std::string str = parameters_[0].ToString(); + + for (std::size_t i = 1; i < parameters_.size(); ++i) { + str += "&"; + str += parameters_[i].ToString(); + } + + return str; +} + UrlQuery::ConstIterator UrlQuery::Find(const std::string& key) const { return std::find_if(parameters_.begin(), parameters_.end(), @@ -38,13 +118,18 @@ UrlQuery::ConstIterator UrlQuery::Find(const std::string& key) const { //////////////////////////////////////////////////////////////////////////////// -Url::Url(const std::string& str) { - std::size_t pos = str.find('?'); - if (pos == std::string::npos) { - path_ = str; +Url::Url(const std::string& str, bool decode) { + if (!decode || str.find('%') == std::string::npos) { + Init(str); + return; + } + + std::string decoded; + if (Decode(str, &decoded)) { + Init(decoded); } else { - path_ = str.substr(0, pos); - query_ = str.substr(pos + 1); + // TODO: Exception? + Init(str); } } @@ -103,4 +188,14 @@ void Url::SplitQuery(const std::string& str, UrlQuery* query) { } } +void Url::Init(const std::string& str) { + std::size_t pos = str.find('?'); + if (pos == std::string::npos) { + path_ = str; + } else { + path_ = str.substr(0, pos); + query_ = str.substr(pos + 1); + } +} + } // namespace webcc diff --git a/src/webcc/url.h b/src/webcc/url.h index 2c61306..71aa9ec 100644 --- a/src/webcc/url.h +++ b/src/webcc/url.h @@ -11,27 +11,44 @@ #include #include -#include "webcc/common.h" +#include "webcc/globals.h" namespace webcc { -//////////////////////////////////////////////////////////////////////////////// +// ----------------------------------------------------------------------------- // URL query parameters. class UrlQuery { public: typedef std::vector Parameters; + UrlQuery() = default; + + // Construct from key-value pairs. + explicit UrlQuery(const std::map& map); + + void Add(const std::string& key, const std::string& value); + void Add(std::string&& key, std::string&& value); void Remove(const std::string& key); - const std::string& GetValue(const std::string& key) const; + // Get a value by key. + // Return empty string if the key doesn't exist. + const std::string& Get(const std::string& key) const; - bool HasKey(const std::string& key) const { + bool Has(const std::string& key) const { return Find(key) != parameters_.end(); } + bool IsEmpty() const { + return parameters_.empty(); + } + + // Return key-value pairs concatenated by '&'. + // E.g., "item=12731&color=blue&size=large". + std::string ToString() const; + private: typedef Parameters::const_iterator ConstIterator; ConstIterator Find(const std::string& key) const; @@ -40,13 +57,12 @@ private: Parameters parameters_; }; -//////////////////////////////////////////////////////////////////////////////// +// ----------------------------------------------------------------------------- class Url { public: - typedef std::map Query; - - Url(const std::string& str); + Url() = default; + Url(const std::string& str, bool decode); bool IsValid() const; @@ -73,6 +89,8 @@ public: static void SplitQuery(const std::string& str, UrlQuery* query); private: + void Init(const std::string& str); + std::string path_; std::string query_; }; diff --git a/src/webcc/utility.cc b/src/webcc/utility.cc index e2e2dd3..c0c24d0 100644 --- a/src/webcc/utility.cc +++ b/src/webcc/utility.cc @@ -1,4 +1,5 @@ #include "webcc/utility.h" + #include using tcp = boost::asio::ip::tcp; diff --git a/unittest/rest_service_manager_test.cc b/unittest/rest_service_manager_test.cc index 2ea792e..32a9734 100644 --- a/unittest/rest_service_manager_test.cc +++ b/unittest/rest_service_manager_test.cc @@ -1,10 +1,8 @@ -#include "webcc/rest_server.h" +#include "webcc/rest_service_manager.h" #include "gtest/gtest.h" using namespace webcc; -//////////////////////////////////////////////////////////////////////////////// - class TestRestService : public RestService { public: bool Handle(const std::string& http_method, @@ -16,8 +14,6 @@ public: } }; -//////////////////////////////////////////////////////////////////////////////// - TEST(RestServiceManager, URL_RegexBasic) { RestServiceManager service_manager;