From 2327edbd5d991a8f2d689b20dd2e098e84649cde Mon Sep 17 00:00:00 2001 From: Adam Gu Date: Tue, 23 Jan 2018 15:52:03 +0800 Subject: [PATCH] Define _WIN32_WINNT using a cmake macro; support move semantics for parameter; remove address from HttpServer; add utility for dumping endpoints. --- CMakeLists.txt | 44 ++++++++++++++++++------------ src/csoap/CMakeLists.txt | 6 ++++ src/csoap/common.cc | 4 +++ src/csoap/common.h | 5 ++++ src/csoap/http_client.h | 2 ++ src/csoap/http_server.cc | 37 +++++++++++++------------ src/csoap/http_server.h | 11 ++++---- src/csoap/soap_request.cc | 6 ++-- src/csoap/soap_request.h | 2 +- src/csoap/soap_response.h | 4 +++ src/csoap/utility.cc | 28 +++++++++++++++++++ src/csoap/utility.h | 14 ++++++++++ src/demo/calculator_server/main.cc | 8 +++--- 13 files changed, 122 insertions(+), 49 deletions(-) create mode 100644 src/csoap/utility.cc create mode 100644 src/csoap/utility.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f90bd72..7a8a9b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,19 +1,32 @@ cmake_minimum_required(VERSION 3.0) project(csoap) -# TODO: Replace with log level. -option(CSOAP_ENABLE_OUTPUT "Enable output for request & response?" OFF) - -if(CSOAP_ENABLE_OUTPUT) - add_definitions(-DCSOAP_ENABLE_OUTPUT) -endif() - -add_definitions(-DUNICODE -D_UNICODE) - -if(MSVC) - # Win7 (Boost.Asio needs this) - add_definitions(-D_WIN32_WINNT=0x0601) -endif() +# See: https://stackoverflow.com/a/40217291 +if(WIN32) + macro(get_WIN32_WINNT version) + if(CMAKE_SYSTEM_VERSION) + set(ver ${CMAKE_SYSTEM_VERSION}) + string(REGEX MATCH "^([0-9]+).([0-9])" ver ${ver}) + string(REGEX MATCH "^([0-9]+)" verMajor ${ver}) + # Check for Windows 10, b/c we'll need to convert to hex 'A'. + if("${verMajor}" MATCHES "10") + set(verMajor "A") + string(REGEX REPLACE "^([0-9]+)" ${verMajor} ver ${ver}) + endif("${verMajor}" MATCHES "10") + # Remove all remaining '.' characters. + string(REPLACE "." "" ver ${ver}) + # Prepend each digit with a zero. + string(REGEX REPLACE "([0-9A-Z])" "0\\1" ver ${ver}) + set(${version} "0x${ver}") + endif(CMAKE_SYSTEM_VERSION) + endmacro(get_WIN32_WINNT) + + get_WIN32_WINNT(ver) + # E.g., 0x0601 for Win7. + message(STATUS "_WIN32_WINNT=${ver}") + # Asio needs this! + add_definitions(-D_WIN32_WINNT=${ver}) +endif(WIN32) # Boost version: 1.66+ set(Boost_USE_STATIC_LIBS ON) @@ -25,9 +38,4 @@ endif() include_directories(${PROJECT_SOURCE_DIR}/src) -if(MSVC) - add_definitions(-D_CRT_SECURE_NO_WARNINGS) -endif() - add_subdirectory(src) - diff --git a/src/csoap/CMakeLists.txt b/src/csoap/CMakeLists.txt index 402ab2f..0b1d78e 100644 --- a/src/csoap/CMakeLists.txt +++ b/src/csoap/CMakeLists.txt @@ -2,6 +2,12 @@ if(UNIX) add_definitions(-D_GLIBCXX_USE_WCHAR_T -std=c++11) endif() +option(CSOAP_ENABLE_OUTPUT "Enable output for request & response?" OFF) + +if(CSOAP_ENABLE_OUTPUT) + add_definitions(-DCSOAP_ENABLE_OUTPUT) +endif() + # Don't use any deprecated definitions (e.g., io_service). add_definitions(-DBOOST_ASIO_NO_DEPRECATED) diff --git a/src/csoap/common.cc b/src/csoap/common.cc index 0e61964..110938c 100644 --- a/src/csoap/common.cc +++ b/src/csoap/common.cc @@ -71,6 +71,10 @@ Parameter::Parameter(const std::string& key, const std::string& value) : key_(key), value_(value) { } +Parameter::Parameter(const std::string& key, std::string&& value) + : key_(key), value_(std::move(value)) { +} + Parameter::Parameter(const std::string& key, int value) : key_(key) { value_ = boost::lexical_cast(value); diff --git a/src/csoap/common.h b/src/csoap/common.h index f2c9614..4e13c74 100644 --- a/src/csoap/common.h +++ b/src/csoap/common.h @@ -84,8 +84,13 @@ extern const Namespace kSoapEnvNamespace; // Parameter in the SOAP request envelope. class Parameter { public: + Parameter() = default; + Parameter(const Parameter& rhs) = default; + Parameter& operator=(const Parameter& rhs) = default; + Parameter(const std::string& key, const char* value); Parameter(const std::string& key, const std::string& value); + Parameter(const std::string& key, std::string&& value); Parameter(const std::string& key, int value); Parameter(const std::string& key, double value); Parameter(const std::string& key, bool value); diff --git a/src/csoap/http_client.h b/src/csoap/http_client.h index 8e3c709..c37fc79 100644 --- a/src/csoap/http_client.h +++ b/src/csoap/http_client.h @@ -2,7 +2,9 @@ #define CSOAP_HTTP_CLIENT_H_ #include + #include "boost/asio/io_context.hpp" + #include "csoap/common.h" namespace csoap { diff --git a/src/csoap/http_server.cc b/src/csoap/http_server.cc index 8c20c1a..79e6550 100644 --- a/src/csoap/http_server.cc +++ b/src/csoap/http_server.cc @@ -3,14 +3,15 @@ #include #include "csoap/soap_service.h" +#include "csoap/utility.h" + +using tcp = boost::asio::ip::tcp; namespace csoap { -HttpServer::HttpServer(const std::string& address, - const std::string& port) - : io_context_(1) // TODO: concurrency_hint (threads) - , signals_(io_context_) - , acceptor_(io_context_) { +HttpServer::HttpServer(unsigned short port) + : io_context_(1) // TODO: concurrency_hint (threads) + , signals_(io_context_) { // Register to handle the signals that indicate when the server should exit. // It is safe to register for the same signal multiple times in a program, @@ -24,15 +25,15 @@ HttpServer::HttpServer(const std::string& address, DoAwaitStop(); - // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR). - // TODO: What does SO_REUSEADDR mean? - // TODO: Why need an address? - boost::asio::ip::tcp::resolver resolver(io_context_); - boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(address, port).begin(); - acceptor_.open(endpoint.protocol()); - acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); - acceptor_.bind(endpoint); - acceptor_.listen(); + // NOTE: + // "reuse_addr=true" means option SO_REUSEADDR will be set. + // For more details about SO_REUSEADDR, see: + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms740621(v=vs.85).aspx + // http://www.andy-pearce.com/blog/posts/2013/Feb/so_reuseaddr-on-windows/ + // TODO: SO_EXCLUSIVEADDRUSE + acceptor_.reset(new tcp::acceptor(io_context_, + tcp::endpoint(tcp::v4(), port), + true)); // reuse_addr DoAccept(); } @@ -50,11 +51,11 @@ void HttpServer::Run() { } void HttpServer::DoAccept() { - acceptor_.async_accept( - [this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) { + acceptor_->async_accept( + [this](boost::system::error_code ec, tcp::socket socket) { // Check whether the server was stopped by a signal before this // completion handler had a chance to run. - if (!acceptor_.is_open()) { + if (!acceptor_->is_open()) { return; } @@ -75,7 +76,7 @@ void HttpServer::DoAwaitStop() { // The server is stopped by cancelling all outstanding asynchronous // operations. Once all operations have finished the io_context::run() // call will exit. - acceptor_.close(); + acceptor_->close(); connection_manager_.StopAll(); }); } diff --git a/src/csoap/http_server.h b/src/csoap/http_server.h index 655879d..2ef183d 100644 --- a/src/csoap/http_server.h +++ b/src/csoap/http_server.h @@ -4,6 +4,7 @@ #include #include +#include "boost/scoped_ptr.hpp" #include "boost/asio/io_context.hpp" #include "boost/asio/signal_set.hpp" #include "boost/asio/ip/tcp.hpp" @@ -14,16 +15,14 @@ namespace csoap { -// The top-level class of the HTTP server. +// HTTP server accepts TCP connections from TCP clients. +// NOTE: Only support IPv4. class HttpServer { public: HttpServer(const HttpServer&) = delete; HttpServer& operator=(const HttpServer&) = delete; - // Construct the server to listen on the specified TCP address and port, and - // serve up files from the given directory. - HttpServer(const std::string& address, - const std::string& port); + HttpServer(unsigned short port); bool RegisterService(SoapServicePtr soap_service); @@ -45,7 +44,7 @@ private: boost::asio::signal_set signals_; // Acceptor used to listen for incoming connections. - boost::asio::ip::tcp::acceptor acceptor_; + boost::scoped_ptr acceptor_; // The connection manager which owns all live connections. ConnectionManager connection_manager_; diff --git a/src/csoap/soap_request.cc b/src/csoap/soap_request.cc index eae0d53..53c953a 100644 --- a/src/csoap/soap_request.cc +++ b/src/csoap/soap_request.cc @@ -11,13 +11,15 @@ void SoapRequest::AddParameter(Parameter&& parameter) { parameters_.push_back(std::move(parameter)); } -std::string SoapRequest::GetParameter(const std::string& key) const { +const std::string& SoapRequest::GetParameter(const std::string& key) const { for (const Parameter& p : parameters_) { if (p.key() == key) { return p.value(); } } - return ""; + + static const std::string kEmptyValue; + return kEmptyValue; } void SoapRequest::ToXmlBody(pugi::xml_node xbody) { diff --git a/src/csoap/soap_request.h b/src/csoap/soap_request.h index 899199e..d081aeb 100644 --- a/src/csoap/soap_request.h +++ b/src/csoap/soap_request.h @@ -16,7 +16,7 @@ public: void AddParameter(Parameter&& parameter); // Get parameter value by key. - std::string GetParameter(const std::string& key) const; + const std::string& GetParameter(const std::string& key) const; protected: void ToXmlBody(pugi::xml_node xbody) override; diff --git a/src/csoap/soap_response.h b/src/csoap/soap_response.h index 94558e9..59dc14b 100644 --- a/src/csoap/soap_response.h +++ b/src/csoap/soap_response.h @@ -23,6 +23,10 @@ public: result_ = result; } + void set_result(std::string&& result) { + result_ = std::move(result); + } + protected: void ToXmlBody(pugi::xml_node xbody) override; diff --git a/src/csoap/utility.cc b/src/csoap/utility.cc new file mode 100644 index 0000000..e77c2aa --- /dev/null +++ b/src/csoap/utility.cc @@ -0,0 +1,28 @@ +#include "csoap/utility.h" +#include + +using tcp = boost::asio::ip::tcp; + +namespace csoap { + +// Print the resolved endpoints. +// NOTE: Endpoint is one word, don't use "end point". +// TODO +void DumpEndpoints(tcp::resolver::results_type& endpoints) { + std::cout << "Endpoints: " << endpoints.size() << std::endl; + + tcp::resolver::results_type::iterator it = endpoints.begin(); + for (; it != endpoints.end(); ++it) { + std::cout << " - " << it->endpoint(); + + if (it->endpoint().protocol() == tcp::v4()) { + std::cout << ", v4"; + } else if (it->endpoint().protocol() == tcp::v6()) { + std::cout << ", v6"; + } + + std::cout << std::endl; + } +} + +} // namespace csoap diff --git a/src/csoap/utility.h b/src/csoap/utility.h new file mode 100644 index 0000000..1c37e21 --- /dev/null +++ b/src/csoap/utility.h @@ -0,0 +1,14 @@ +#ifndef CSOAP_UTILITY_H_ +#define CSOAP_UTILITY_H_ + +#include "boost/asio/ip/tcp.hpp" + +namespace csoap { + +// Print the resolved endpoints. +// NOTE: Endpoint is one word, don't use "end point". +void DumpEndpoints(boost::asio::ip::tcp::resolver::results_type& endpoints); + +} // namespace csoap + +#endif // CSOAP_UTILITY_H_ diff --git a/src/demo/calculator_server/main.cc b/src/demo/calculator_server/main.cc index 788c14d..bd2ad25 100644 --- a/src/demo/calculator_server/main.cc +++ b/src/demo/calculator_server/main.cc @@ -2,7 +2,7 @@ #include "csoap/http_server.h" #include "calculator_service.h" -static void PrintHelp(const char* argv0) { +static void Help(const char* argv0) { std::cout << "Usage: " << argv0 << " " << std::endl; std::cout << " E.g.," << std::endl; std::cout << " " << argv0 << " 8080" << std::endl; @@ -10,14 +10,14 @@ static void PrintHelp(const char* argv0) { int main(int argc, char* argv[]) { if (argc != 2) { - PrintHelp(argv[0]); + Help(argv[0]); return 1; } - const char* host = "0.0.0.0"; // TODO + unsigned short port = std::atoi(argv[1]); try { - csoap::HttpServer server(host, argv[1]); + csoap::HttpServer server(port); csoap::SoapServicePtr service(new CalculatorService); server.RegisterService(service);