Update README and tutorials.

master
Adam Gu 7 years ago
parent 5bc988b093
commit 63e9e87f4f

@ -1,214 +1,21 @@
# csoap
# webcc
A lightweight C++ SOAP client & server library based on Boost.Asio.
A lightweight C++ REST and SOAP client and server library based on Boost.Asio.
[中文教程](doc/ClientTutorial_zh-CN.md)
## Tutorials
## Client Usage
**SOAP:**
- [SOAP Client Tutorial](doc/SoapClientTutorial.md)
- [SOAP Server Tutorial](doc/SoapServerTutorial.md)
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.
- [SOAP 客户端教程](doc/SoapClientTutorial_zh-CN.md)
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
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:cal="http://www.parasoft.com/wsdl/calculator/">
<soapenv:Header/>
<soapenv:Body>
<cal:add>
<cal:x>1</cal:x>
<cal:y>2</cal:y>
</cal:add>
</soapenv:Body>
</soapenv:Envelope>
```
In order to call the "add" operation, we have to send a HTTP request with the above SOAP envelope as the content. Let's see how to do this with *csoap*.
Firstly, create a class `CalculatorClient` which is derived from `csoap::SoapClient`:
```cpp
#include <string>
#include "csoap/soap_client.h"
class CalculatorClient : public csoap::SoapClient {
public:
CalculatorClient() {
Init();
}
```
Initialize the URL, host, port, etc. in `Init()`:
```cpp
private:
void Init() {
url_ = "/glue/calculator";
host_ = "ws1.parasoft.com";
port_ = ""; // Default to "80".
service_ns_ = { "cal", "http://www.parasoft.com/wsdl/calculator/" };
result_name_ = "Result";
}
```
Because four calculator operations (*add*, *subtract*, *multiply* and *divide*) all have two parameters, we create a wrapper for `SoapClient::Call()`, name is as `Calc`:
```cpp
bool Calc(const std::string& operation,
const std::string& x_name,
const std::string& y_name,
double x,
double y,
double* result) {
// 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, 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&) {
return false;
}
return true;
}
```
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) {
return Calc("add", "x", "y", x, y, result);
}
bool Subtract(double x, double y, double* result) {
return Calc("subtract", "x", "y", x, y, result);
}
bool Multiply(double x, double y, double* result) {
return Calc("multiply", "x", "y", x, y, result);
}
bool Divide(double x, double y, double* result) {
return Calc("divide", "numerator", "denominator", x, y, result);
}
```
See? It's not that complicated. Check folder ***demo/calculator_client*** for the full example.
## Server Usage
*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
## Dependencies
- Only support `int`, `double`, `bool` and `string` parameters.
- Only support UTF-8 encoded content.
- One connection one call.
- Connection is in synchronous (or blocking) mode; timeout (default to 30s) is configurable.
- C++11
- Boost 1.66+
- pugixml (already included in the source tree) (SOAP only)
## Dependencies
## Build
- 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.
TODO

@ -0,0 +1,101 @@
# SOAP Client Tutorial
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
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:cal="http://www.parasoft.com/wsdl/calculator/">
<soapenv:Header/>
<soapenv:Body>
<cal:add>
<cal:x>1</cal:x>
<cal:y>2</cal:y>
</cal:add>
</soapenv:Body>
</soapenv:Envelope>
```
In order to call the "add" operation, we have to send a HTTP request with the above SOAP envelope as the content. Let's see how to do this with *webcc*.
Firstly, create a class `CalcClient` which is derived from `webcc::SoapClient`:
```cpp
#include <string>
#include "webcc/soap_client.h"
class CalcClient : public webcc::SoapClient {
public:
CalcClient() {
Init();
}
```
Initialize the URL, host, port, etc. in `Init()`:
```cpp
private:
void Init() {
url_ = "/glue/calculator";
host_ = "ws1.parasoft.com";
port_ = ""; // Default to "80".
service_ns_ = { "cal", "http://www.parasoft.com/wsdl/calculator/" };
result_name_ = "Result";
}
```
Because four calculator operations (*add*, *subtract*, *multiply* and *divide*) all have two parameters, we create a wrapper for `SoapClient::Call()`, name is as `Calc`:
```cpp
bool Calc(const std::string& operation,
const std::string& x_name,
const std::string& y_name,
double x,
double y,
double* result) {
// Prepare parameters.
std::vector<webcc::Parameter> parameters{
{ x_name, x },
{ y_name, y }
};
// Make the call.
std::string result_str;
webcc::Error error = Call(operation, std::move(parameters), &result_str);
// Error handling if any.
if (error != webcc::kNoError) {
std::cerr << "Error: " << error;
std::cerr << ", " << webcc::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&) {
return false;
}
return true;
}
```
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) {
return Calc("add", "x", "y", x, y, result);
}
bool Subtract(double x, double y, double* result) {
return Calc("subtract", "x", "y", x, y, result);
}
bool Multiply(double x, double y, double* result) {
return Calc("multiply", "x", "y", x, y, result);
}
bool Divide(double x, double y, double* result) {
return Calc("divide", "numerator", "denominator", x, y, result);
}
```
See? It's not that complicated. Check folder ***demo/calculator_client*** for the full example.

@ -1,4 +1,4 @@
# cSoap 客户端使用指南
# SOAP Client Tutorial (zh-CN)
## 背景
@ -8,11 +8,12 @@
WWSAPI 的官方文档经常让人摸不着头脑没有完整的示例给出一段代码常常需要几经调整才能使用。WWSAPI 自动生成的代码,是纯 C 的接口,在难用程度上,较 gSoap 有过之而无不及。在消息参数上,它强制使用双字节 Unicode我们的输入输出都是 UTF8 的 `std::string`于是莫名地多出很多编码转换。WWSAPI 需要你手动分配堆heap需要你指定消息的缓冲大小而最严重的问题是它不够稳定特别是在子线程里调用时莫名其妙连接就会断掉。
于是,我就动手自己写了个 [cSoap](https://github.com/sprinfall/csoap)。
于是,我就动手自己写了个 [webcc](https://github.com/sprinfall/webcc)。
一开始 webcc 只支持 SOAP名字就叫 csoap后来支持了 REST于是改名为 webcc取 Web C++ 的意思。
## 原理
cSoap 没有提供从 WSDL 自动生成代码的功能,一来是因为这一过程太复杂了,二来是自动生成的代码一般都不好用。所以 cSoap 最好搭配 [SoapUI](https://www.soapui.org) 一起使用。SoapUI 可以帮助我们为每一个 Web Service 操作operation生成请求的样例基于请求样例就很容易发起调用了也避免了直接阅读 WSDL。
Webcc 没有提供从 WSDL 自动生成代码的功能,一来是因为这一过程太复杂了,二来是自动生成的代码一般都不好用。所以 webcc 最好搭配 [SoapUI](https://www.soapui.org) 一起使用。SoapUI 可以帮助我们为每一个 Web Service 操作operation生成请求的样例基于请求样例就很容易发起调用了也避免了直接阅读 WSDL。
下面以 ParaSoft 提供的 [Calculator](http://ws1.parasoft.com/glue/calculator.wsdl) 为例,首先下载 WSDL然后在 SoapUI 里创建一个 SOAP 项目,记得勾上 "Create sample requests for all operations?" 这个选项,然后就能看到下面这样的请求样例了:
```xml
@ -49,19 +50,19 @@ User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
</soapenv:Body>
</soapenv:Envelope>
```
所以 cSoap 所做的,只不过是跟 `ws1.parasoft.com` 建立 TCP Socket 连接,然后发送上面这段内容而已。
所以 webcc 所做的,只不过是跟 `ws1.parasoft.com` 建立 TCP Socket 连接,然后发送上面这段内容而已。
## 用法
首先,创建一个类 `CalculatorClient`,继承自 `csoap::SoapClient`
首先,创建一个类 `CalcClient`,继承自 `webcc::SoapClient`
```cpp
#include <string>
#include "csoap/soap_client.h"
#include "webcc/soap_client.h"
class CalculatorClient : public csoap::SoapClient {
class CalcClient : public webcc::SoapClient {
public:
CalculatorClient() {
CalcClient() {
Init();
}
```
@ -87,19 +88,19 @@ bool Calc(const std::string& operation,
double y,
double* result) {
// Prepare parameters.
std::vector<csoap::Parameter> parameters{
std::vector<webcc::Parameter> parameters{
{ x_name, x },
{ y_name, y }
};
// Make the call.
std::string result_str;
csoap::Error error = Call(operation, std::move(parameters), &result_str);
webcc::Error error = Call(operation, std::move(parameters), &result_str);
// Error handling if any.
if (error != csoap::kNoError) {
if (error != webcc::kNoError) {
std::cerr << "Error: " << error;
std::cerr << ", " << csoap::GetErrorMessage(error) << std::endl;
std::cerr << ", " << webcc::GetErrorMessage(error) << std::endl;
return false;
}
@ -138,7 +139,7 @@ bool Divide(double x, double y, double* result) {
## 局限
当然,cSoap 有很多局限,比如:
当然,webcc 有很多局限,比如:
- 只支持 `int`, `double`, `bool``string` 这几种参数类型;
- 只支持 UTF-8 编码的消息内容;
- 一次调用一个连接;
@ -146,7 +147,7 @@ bool Divide(double x, double y, double* result) {
## 依赖
在实现上,cSoap 有下面这些依赖:
在实现上,webcc 有下面这些依赖:
- Boost 1.66+
- XML 解析和构造基于 PugiXml
- XML 解析和构造基于 pugixml
- 构建系统是 CMake应该可以很方便地集成到其他 C++ 项目中。

@ -0,0 +1,94 @@
# SOAP Server Tutorial
Suppose you want to provide a calculator web service just like the one from [ParaSoft](http://ws1.parasoft.com/glue/calculator.wsdl).
Firstly, create a class `CalcService` which is derived from `webcc::SoapService`, override the `Handle` method:
```cpp
#include "webcc/soap_service.h"
class CalcService : public webcc::SoapService {
public:
CalcService() = default;
bool Handle(const webcc::SoapRequest& soap_request,
webcc::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 "calc_service.h"
#include "boost/lexical_cast.hpp"
#include "webcc/soap_request.h"
#include "webcc/soap_response.h"
bool CalcService::Handle(const webcc::SoapRequest& soap_request,
webcc::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(webcc::kSoapEnvNamespace);
soap_response->set_service_ns({
"cal",
"http://www.example.com/calculator/"
});
soap_response->set_operation(soap_request.operation());
soap_response->set_result_name("Result");
soap_response->set_result(std::to_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;
}
unsigned short port = std::atoi(argv[1]);
// Number of worker threads.
std::size_t workers = 2;
try {
webcc::SoapServer server(port, workers);
server.RegisterService(std::make_shared<CalcService>(),
"/calculator");
server.Run();
} catch (std::exception& e) {
std::cerr << "exception: " << e.what() << std::endl;
return 1;
}
return 0;
}
```
Loading…
Cancel
Save