Support std::move for parameters; fix result name issue.

master
Adam Gu 8 years ago
parent f43b1d0c36
commit 2793357da3

@ -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);

@ -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);
};

Loading…
Cancel
Save