Support std::move for parameters; fix result name issue.
This commit is contained in:
@@ -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<csoap::Parameter> 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<double>(soap_request.GetParameter("x"));
|
||||
double y = boost::lexical_cast<double>(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<std::string>(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] << " <port>" << 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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
+12
-10
@@ -11,23 +11,25 @@
|
||||
namespace csoap {
|
||||
|
||||
Error SoapClient::Call(const std::string& operation,
|
||||
const Parameter* parameters,
|
||||
std::size_t count,
|
||||
std::vector<Parameter>&& 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();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define CSOAP_SOAP_CLIENT_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#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<Parameter>&& 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_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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<csoap::Parameter> 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<double>(result_str);
|
||||
} catch (boost::bad_lexical_cast&) {
|
||||
|
||||
@@ -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<double>(request.GetParameter("x"));
|
||||
double y = boost::lexical_cast<double>(request.GetParameter("y"));
|
||||
if (soap_request.operation() == "add") {
|
||||
double x = boost::lexical_cast<double>(soap_request.GetParameter("x"));
|
||||
double y = boost::lexical_cast<double>(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<std::string>(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<std::string>(result));
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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<csoap::Parameter> 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<csoap::Parameter> 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<csoap::Parameter> 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<csoap::Parameter>&& 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;
|
||||
}
|
||||
|
||||
@@ -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<csoap::Parameter>&& parameters,
|
||||
std::string* result);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user