From 2793357da337af9aa2159b2938481079235ec714 Mon Sep 17 00:00:00 2001 From: Adam Gu Date: Tue, 16 Jan 2018 16:38:07 +0800 Subject: [PATCH] Support std::move for parameters; fix result name issue. --- README.md | 112 ++++++++++++++++-- src/csoap/common.cc | 17 +++ src/csoap/common.h | 14 +++ src/csoap/http_client.cc | 4 - src/csoap/http_response.cc | 4 - src/csoap/http_response.h | 4 - src/csoap/soap_client.cc | 22 ++-- src/csoap/soap_client.h | 11 +- src/csoap/soap_request.cc | 48 +------- src/csoap/soap_request.h | 3 +- src/csoap/soap_response.cc | 3 +- .../calculator_client/calculator_client.cc | 12 +- .../calculator_server/calculator_service.cc | 20 ++-- .../calculator_server/calculator_service.h | 6 +- src/demo/csdm_client/csdm_client.cc | 18 ++- src/demo/csdm_client/csdm_client.h | 3 +- 16 files changed, 188 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index 288c143..ad23220 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,10 @@ # csoap -[中文版介绍](https://segmentfault.com/a/1190000009874151) - A lightweight C++ SOAP client & server library based on Boost.Asio. -NOTE: The server part is currently under development, not stable enough to be used in a real production. - ## Client Usage -Firstly, please install SoapUI if you don't have it. We need SoapUI to generate sample requests for each web service operation. The open source version is good enough. +Firstly, please install **SoapUI** if you don't have it. We need SoapUI to generate sample requests for each web service operation. The open source version is good enough. Take the calculator web service provided by ParaSoft as an example. Download the WSDL from http://ws1.parasoft.com/glue/calculator.wsdl, create a SOAP project within SoapUI (remember to check "**Create sample requests for all operations?**"), you will see the sample request for "add" operation as the following: ```xml @@ -59,14 +55,19 @@ bool Calc(const std::string& operation, double y, double* result) { // Prepare parameters. - csoap::Parameter parameters[] = { + std::vector parameters{ { x_name, x }, { y_name, y } }; // Make the call. std::string result_str; - if (!Call(operation, parameters, 2, &result_str)) { + csoap::Error error = Call(operation, std::move(parameters), &result_str); + + // Error handling if any. + if (error != csoap::kNoError) { + std::cerr << "Error: " << error; + std::cerr << ", " << csoap::GetErrorMessage(error) << std::endl; return false; } @@ -81,6 +82,8 @@ bool Calc(const std::string& operation, } ``` +Note that the local parameters are moved (with `std::move()`). This is to avoid expensive copy of long string parameters, e.g., XML strings. + Finally, we implement the four operations simply as the following: ```cpp bool Add(double x, double y, double* result) { @@ -102,7 +105,98 @@ bool Divide(double x, double y, double* result) { See? It's not that complicated. Check folder ***demo/calculator_client*** for the full example. ## Server Usage -TODO + +*NOTE: The server part is currently under development, not stable enough to be used in a real production.* + +Suppose you want to provide a calculator web service just like the one from ParaSoft. + +Firstly, create a class `CalculatorService` which is derived from `csoap::SoapService`, override the `Handle` method: +```cpp +#include "csoap/soap_service.h" + +class CalculatorService : public csoap::SoapService { +public: + CalculatorService(); + + bool Handle(const csoap::SoapRequest& soap_request, + csoap::SoapResponse* soap_response) override; +}; +``` +The `Handle` method has two parameters, one for request (input), one for response (output). +The implementation is quite straightforward: +- Get operation from request. +- Get parameters from request. +- Calculate the result. +- Set namespaces, result name and so on to response. +- Set result to response. + +```cpp +#include "calculator_service.h" + +#include "boost/lexical_cast.hpp" + +#include "csoap/soap_request.h" +#include "csoap/soap_response.h" + +CalculatorService::CalculatorService() { +} + +bool CalculatorService::Handle(const csoap::SoapRequest& soap_request, + csoap::SoapResponse* soap_response) { + try { + if (soap_request.operation() == "add") { + double x = boost::lexical_cast(soap_request.GetParameter("x")); + double y = boost::lexical_cast(soap_request.GetParameter("y")); + + double result = x + y; + + soap_response->set_soapenv_ns(csoap::kSoapEnvNamespace); + soap_response->set_service_ns({ "cal", "http://mycalculator/" }); + soap_response->set_operation(soap_request.operation()); + soap_response->set_result_name("Result"); + soap_response->set_result(boost::lexical_cast(result)); + + return true; + + } else { + // NOT_IMPLEMENTED + } + } catch (boost::bad_lexical_cast&) { + // BAD_REQUEST + } + + return false; +} +``` + +The `main` function would be: +```cpp +int main(int argc, char* argv[]) { + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + std::cout << " E.g.," << std::endl; + std::cout << " " << argv[0] << " 8080" << std::endl; + return 1; + } + + const char* host = "0.0.0.0"; // TODO + + try { + csoap::HttpServer server(host, argv[1]); + + csoap::SoapServicePtr service(new CalculatorService); + server.RegisterService(service); + + server.Run(); + + } catch (std::exception& e) { + std::cerr << "exception: " << e.what() << "\n"; + return 1; + } + + return 0; +} +``` ## Limitations @@ -113,6 +207,6 @@ TODO ## Dependencies -- Boost.Asio is used for both client and server. +- Boost 1.66+. - XML processing is based on PugiXml, which is already included in the source tree. - Build system is CMake, but it should be quite easy to integrate into your own project. diff --git a/src/csoap/common.cc b/src/csoap/common.cc index 83168ca..0e61964 100644 --- a/src/csoap/common.cc +++ b/src/csoap/common.cc @@ -63,6 +63,10 @@ const Namespace kSoapEnvNamespace{ //////////////////////////////////////////////////////////////////////////////// +Parameter::Parameter(const std::string& key, const char* value) + : key_(key), value_(value) { +} + Parameter::Parameter(const std::string& key, const std::string& value) : key_(key), value_(value) { } @@ -84,4 +88,17 @@ Parameter::Parameter(const std::string& key, bool value) value_ = value ? "true" : "false"; } +Parameter::Parameter(Parameter&& rhs) + : key_(std::move(rhs.key_)) + , value_(std::move(rhs.value_)) { +} + +Parameter& Parameter::operator=(Parameter&& rhs) { + if (this != &rhs) { + key_ = std::move(rhs.key_); + value_ = std::move(rhs.value_); + } + return *this; +} + } // namespace csoap diff --git a/src/csoap/common.h b/src/csoap/common.h index 27e7979..f2c9614 100644 --- a/src/csoap/common.h +++ b/src/csoap/common.h @@ -84,11 +84,17 @@ extern const Namespace kSoapEnvNamespace; // Parameter in the SOAP request envelope. class Parameter { public: + Parameter(const std::string& key, const char* value); Parameter(const std::string& key, const std::string& value); Parameter(const std::string& key, int value); Parameter(const std::string& key, double value); Parameter(const std::string& key, bool value); + // Move constructor. + Parameter(Parameter&& rhs); + + Parameter& operator=(Parameter&& rhs); + const std::string& key() const { return key_; } @@ -97,6 +103,14 @@ public: return value_; } + const char* c_key() const { + return key_.c_str(); + } + + const char* c_value() const { + return value_.c_str(); + } + private: std::string key_; std::string value_; diff --git a/src/csoap/http_client.cc b/src/csoap/http_client.cc index aa22b49..704c52b 100644 --- a/src/csoap/http_client.cc +++ b/src/csoap/http_client.cc @@ -17,10 +17,6 @@ #include "csoap/http_request.h" #include "csoap/http_response.h" -#if CSOAP_ENABLE_OUTPUT -#include "csoap/xml.h" // For pretty print response XML. -#endif - namespace csoap { //////////////////////////////////////////////////////////////////////////////// diff --git a/src/csoap/http_response.cc b/src/csoap/http_response.cc index d05f984..ec38556 100644 --- a/src/csoap/http_response.cc +++ b/src/csoap/http_response.cc @@ -5,8 +5,6 @@ namespace csoap { -//////////////////////////////////////////////////////////////////////////////// - std::ostream& operator<<(std::ostream& os, const HttpResponse& response) { os << response.start_line(); @@ -24,8 +22,6 @@ std::ostream& operator<<(std::ostream& os, const HttpResponse& response) { return os; } -//////////////////////////////////////////////////////////////////////////////// - namespace status_strings { const std::string OK = "HTTP/1.1 200 OK\r\n"; diff --git a/src/csoap/http_response.h b/src/csoap/http_response.h index 05c05c7..b182986 100644 --- a/src/csoap/http_response.h +++ b/src/csoap/http_response.h @@ -7,14 +7,10 @@ namespace csoap { -//////////////////////////////////////////////////////////////////////////////// - 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); diff --git a/src/csoap/soap_client.cc b/src/csoap/soap_client.cc index 5bc2484..dc22010 100644 --- a/src/csoap/soap_client.cc +++ b/src/csoap/soap_client.cc @@ -11,23 +11,25 @@ namespace csoap { Error SoapClient::Call(const std::string& operation, - const Parameter* parameters, - std::size_t count, + std::vector&& parameters, std::string* result) { - assert(!url_.empty() && - !host_.empty() && - !result_name_.empty() && - service_ns_.IsValid()); + assert(service_ns_.IsValid()); + assert(!url_.empty() && !host_.empty()); + assert(!result_name_.empty()); + + if (!soapenv_ns_.IsValid()) { + soapenv_ns_ = kSoapEnvNamespace; + } SoapRequest soap_request; - soap_request.set_soapenv_ns(kSoapEnvNamespace); // TODO: Configurable + soap_request.set_soapenv_ns(soapenv_ns_); soap_request.set_service_ns(service_ns_); soap_request.set_operation(operation); - for (std::size_t i = 0; i < count; ++i) { - soap_request.AddParameter(parameters[i]); + for (Parameter& p : parameters) { + soap_request.AddParameter(std::move(p)); } std::string http_content; @@ -55,7 +57,7 @@ Error SoapClient::Call(const std::string& operation, soap_response.set_result_name(result_name_); if (!soap_response.FromXml(http_response.content())) { - return kXmlError; // TODO: Some SOAP error? + return kXmlError; } *result = soap_response.result(); diff --git a/src/csoap/soap_client.h b/src/csoap/soap_client.h index 17713e2..68e4aaf 100644 --- a/src/csoap/soap_client.h +++ b/src/csoap/soap_client.h @@ -2,6 +2,7 @@ #define CSOAP_SOAP_CLIENT_H_ #include +#include #include "csoap/common.h" namespace csoap { @@ -19,12 +20,15 @@ protected: } // A generic wrapper to make a call. + // NOTE: The parameters should be movable. Error Call(const std::string& operation, - const Parameter* parameters, - std::size_t count, + std::vector&& parameters, std::string* result); protected: + Namespace soapenv_ns_; // SOAP envelope namespace. + Namespace service_ns_; // Namespace for your web service. + // Request URL. // Could be a complete URL (http://ws1.parasoft.com/glue/calculator) // or just the path component of it (/glue/calculator). @@ -33,9 +37,6 @@ protected: std::string host_; std::string port_; // Leave this empty to use default 80. - // The namespace of your service. - csoap::Namespace service_ns_; - // Response result XML node name. // E.g., "Result". std::string result_name_; diff --git a/src/csoap/soap_request.cc b/src/csoap/soap_request.cc index 6e9b251..eae0d53 100644 --- a/src/csoap/soap_request.cc +++ b/src/csoap/soap_request.cc @@ -3,15 +3,14 @@ namespace csoap { -void SoapRequest::AddParameter(const std::string& key, - const std::string& value) { - parameters_.push_back(Parameter(key, value)); -} - void SoapRequest::AddParameter(const Parameter& parameter) { parameters_.push_back(parameter); } +void SoapRequest::AddParameter(Parameter&& parameter) { + parameters_.push_back(std::move(parameter)); +} + std::string SoapRequest::GetParameter(const std::string& key) const { for (const Parameter& p : parameters_) { if (p.key() == key) { @@ -21,45 +20,6 @@ std::string SoapRequest::GetParameter(const std::string& key) const { return ""; } -//bool SoapRequest::FromXml(const std::string& xml_string) { -// pugi::xml_document xdoc; -// pugi::xml_parse_result result = xdoc.load_string(xml_string.c_str()); -// -// if (!result) { -// return false; -// } -// -// pugi::xml_node xroot = xdoc.document_element(); -// -// soapenv_ns_.name = xml::GetPrefix(xroot); -// soapenv_ns_.url = xml::GetNSAttr(xroot, soapenv_ns_.name); -// -// pugi::xml_node xbody = xml::GetChild(xroot, soapenv_ns_.name, "Body"); -// if (!xbody) { -// return false; -// } -// -// // Operation -// -// pugi::xml_node xoperation = xbody.first_child(); -// xml::SplitName(xoperation, &service_ns_.name, &operation_); -// service_ns_.url = xml::GetNSAttr(xoperation, service_ns_.name); -// -// // Parameters -// -// pugi::xml_node xparameter = xoperation.first_child(); -// while (xparameter) { -// parameters_.push_back({ -// xml::GetNameNoPrefix(xparameter), -// std::string(xparameter.text().as_string()) -// }); -// -// xparameter = xparameter.next_sibling(); -// } -// -// return true; -//} - void SoapRequest::ToXmlBody(pugi::xml_node xbody) { pugi::xml_node xop = xml::AddChild(xbody, service_ns_.name, operation_); xml::AddNSAttr(xop, service_ns_.name, service_ns_.url); diff --git a/src/csoap/soap_request.h b/src/csoap/soap_request.h index db3a5e1..899199e 100644 --- a/src/csoap/soap_request.h +++ b/src/csoap/soap_request.h @@ -11,9 +11,10 @@ namespace csoap { // request body. class SoapRequest : public SoapMessage { public: - void AddParameter(const std::string& key, const std::string& value); void AddParameter(const Parameter& parameter); + void AddParameter(Parameter&& parameter); + // Get parameter value by key. std::string GetParameter(const std::string& key) const; diff --git a/src/csoap/soap_response.cc b/src/csoap/soap_response.cc index dc5a067..061209c 100644 --- a/src/csoap/soap_response.cc +++ b/src/csoap/soap_response.cc @@ -10,8 +10,7 @@ void SoapResponse::ToXmlBody(pugi::xml_node xbody) { pugi::xml_node xop = xml::AddChild(xbody, service_ns_.name, rsp_operation); xml::AddNSAttr(xop, service_ns_.name, service_ns_.url); - // TODO: Leave the user to decide the result name. - pugi::xml_node xresult = xml::AddChild(xop, service_ns_.name, "Result"); + pugi::xml_node xresult = xml::AddChild(xop, service_ns_.name, result_name_); xresult.text().set(result_.c_str()); } diff --git a/src/demo/calculator_client/calculator_client.cc b/src/demo/calculator_client/calculator_client.cc index 1e57361..b545784 100644 --- a/src/demo/calculator_client/calculator_client.cc +++ b/src/demo/calculator_client/calculator_client.cc @@ -30,8 +30,8 @@ void CalculatorClient::Init() { #if ACCESS_PARASOFT url_ = "/glue/calculator"; host_ = "ws1.parasoft.com"; - port_ = "80"; // Or leave it empty because 80 is the default HTTP port. - service_ns_ = { "ser", "http://www.parasoft.com/wsdl/calculator/" }; + port_ = ""; // Default to "80". + service_ns_ = { "cal", "http://www.parasoft.com/wsdl/calculator/" }; result_name_ = "Result"; #else url_ = "/"; @@ -48,20 +48,24 @@ bool CalculatorClient::Calc(const std::string& operation, double x, double y, double* result) { - csoap::Parameter parameters[] = { + // Prepare parameters. + std::vector parameters{ { x_name, x }, { y_name, y } }; + // Make the call. std::string result_str; - csoap::Error error = Call(operation, parameters, 2, &result_str); + csoap::Error error = Call(operation, std::move(parameters), &result_str); + // Error handling if any. if (error != csoap::kNoError) { std::cerr << "Error: " << error; std::cerr << ", " << csoap::GetErrorMessage(error) << std::endl; return false; } + // Convert the result from string to double. try { *result = boost::lexical_cast(result_str); } catch (boost::bad_lexical_cast&) { diff --git a/src/demo/calculator_server/calculator_service.cc b/src/demo/calculator_server/calculator_service.cc index a4a6f14..766fbc6 100644 --- a/src/demo/calculator_server/calculator_service.cc +++ b/src/demo/calculator_server/calculator_service.cc @@ -8,20 +8,20 @@ CalculatorService::CalculatorService() { } -bool CalculatorService::Handle(const csoap::SoapRequest& request, - csoap::SoapResponse* response) { +bool CalculatorService::Handle(const csoap::SoapRequest& soap_request, + csoap::SoapResponse* soap_response) { try { - if (request.operation() == "add") { - double x = boost::lexical_cast(request.GetParameter("x")); - double y = boost::lexical_cast(request.GetParameter("y")); + if (soap_request.operation() == "add") { + double x = boost::lexical_cast(soap_request.GetParameter("x")); + double y = boost::lexical_cast(soap_request.GetParameter("y")); double result = x + y; - response->set_soapenv_ns(csoap::kSoapEnvNamespace); - response->set_service_ns({ "ser", "http://mycalculator/" }); - response->set_operation(request.operation()); - response->set_result_name("Result"); - response->set_result(boost::lexical_cast(result)); + soap_response->set_soapenv_ns(csoap::kSoapEnvNamespace); + soap_response->set_service_ns({ "cal", "http://mycalculator/" }); + soap_response->set_operation(soap_request.operation()); + soap_response->set_result_name("Result"); + soap_response->set_result(boost::lexical_cast(result)); return true; diff --git a/src/demo/calculator_server/calculator_service.h b/src/demo/calculator_server/calculator_service.h index ccf3bc9..f821d9b 100644 --- a/src/demo/calculator_server/calculator_service.h +++ b/src/demo/calculator_server/calculator_service.h @@ -7,10 +7,8 @@ class CalculatorService : public csoap::SoapService { public: CalculatorService(); - bool Handle(const csoap::SoapRequest& request, - csoap::SoapResponse* response) override; - -protected: + bool Handle(const csoap::SoapRequest& soap_request, + csoap::SoapResponse* soap_response) override; }; #endif // CALCULATOR_SERVICE_H_ diff --git a/src/demo/csdm_client/csdm_client.cc b/src/demo/csdm_client/csdm_client.cc index 10023b9..4d7ca48 100644 --- a/src/demo/csdm_client/csdm_client.cc +++ b/src/demo/csdm_client/csdm_client.cc @@ -22,18 +22,18 @@ void CsdmClient::Init() { bool CsdmClient::Call0(const std::string& operation, std::string* result_xml) { - return CallEx(operation, NULL, 0, result_xml); + std::vector parameters; + return CallEx(operation, std::move(parameters), result_xml); } bool CsdmClient::Call1(const std::string& operation, const std::string& name, const std::string& value, std::string* result_xml) { - csoap::Parameter parameters[] = { + std::vector parameters{ { name, value }, }; - - return CallEx(operation, parameters, 1, result_xml); + return CallEx(operation, std::move(parameters), result_xml); } bool CsdmClient::Call2(const std::string& operation, @@ -42,18 +42,16 @@ bool CsdmClient::Call2(const std::string& operation, const std::string& name2, const std::string& value2, std::string* result_xml) { - csoap::Parameter parameters[] = { + std::vector parameters{ { name1, value1 }, { name2, value2 }, }; - - return CallEx(operation, parameters, 2, result_xml); + return CallEx(operation, std::move(parameters), result_xml); } bool CsdmClient::CallEx(const std::string& operation, - const csoap::Parameter* parameters, - std::size_t count, + std::vector&& parameters, std::string* result) { - csoap::Error error = Call(operation, parameters, count, result); + csoap::Error error = Call(operation, std::move(parameters), result); return error == csoap::kNoError; } diff --git a/src/demo/csdm_client/csdm_client.h b/src/demo/csdm_client/csdm_client.h index 28785ae..be9e329 100644 --- a/src/demo/csdm_client/csdm_client.h +++ b/src/demo/csdm_client/csdm_client.h @@ -35,8 +35,7 @@ protected: // A wrapper of CSoapClient::Call(). bool CallEx(const std::string& operation, - const csoap::Parameter* parameters, - std::size_t count, + std::vector&& parameters, std::string* result); };