Replace boost thread with std thread; update rest book example; move soap tutorials to wiki.
This commit is contained in:
@@ -1,101 +0,0 @@
|
||||
# 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,153 +0,0 @@
|
||||
# SOAP Client Tutorial (zh-CN)
|
||||
|
||||
## 背景
|
||||
|
||||
首先,[gSoap](http://www.cs.fsu.edu/~engelen/soap.html) 肯定是个不错的选择,但是如果你的程序要调用多个 Web Services(即有多个 WSDL),gSoap 会比较麻烦。还有一个问题就是,gSoap 从 WSDL 自动生成的代码实在是太难用了。当然,这些都不是什么问题,真在的问题是许可证(License),gSoap 用在商业产品中是要收费的。
|
||||
|
||||
公司比较穷,舍不得花钱买 gSoap,但是 C++ 调 Web Service 还真没什么好办法。尝试了五六个半死不活的库后,最终锁定了 [WWSAPI](https://msdn.microsoft.com/en-us/library/windows/desktop/dd430435%28v=vs.85%29.aspx)(Windows Web Services API)。
|
||||
|
||||
WWSAPI 的官方文档经常让人摸不着头脑,没有完整的示例,给出一段代码,常常需要几经调整才能使用。WWSAPI 自动生成的代码,是纯 C 的接口,在难用程度上,较 gSoap 有过之而无不及。在消息参数上,它强制使用双字节 Unicode,我们的输入输出都是 UTF8 的 `std::string`,于是莫名地多出很多编码转换。WWSAPI 需要你手动分配堆(heap),需要你指定消息的缓冲大小,而最严重的问题是,它不够稳定,特别是在子线程里调用时,莫名其妙连接就会断掉。
|
||||
|
||||
于是,我就动手自己写了个 [webcc](https://github.com/sprinfall/webcc)。
|
||||
一开始 webcc 只支持 SOAP,名字就叫 csoap,后来支持了 REST,于是改名为 webcc,取 Web C++ 的意思。
|
||||
|
||||
## 原理
|
||||
|
||||
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
|
||||
<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>
|
||||
```
|
||||
这个操作是 "add",有两个参数:x 和 y。此外值得注意的还有 XML namespace,比如 `xmlns:cal="http://www.parasoft.com/wsdl/calculator/"`。
|
||||
|
||||
要调用这个 "add" 操作,只要发一个 HTTP 请求,并把上面这个 SOAP Envelope 作为请求的 Content。在 SoapUI 里把 Request 切换到 “Raw" 模式,就可以看到下面这样完整的 HTTP 请求:
|
||||
```
|
||||
POST http://ws1.parasoft.com/glue/calculator HTTP/1.1
|
||||
Accept-Encoding: gzip,deflate
|
||||
Content-Type: text/xml;charset=UTF-8
|
||||
SOAPAction: "add"
|
||||
Content-Length: 300
|
||||
Host: ws1.parasoft.com
|
||||
Connection: Keep-Alive
|
||||
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
|
||||
|
||||
<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>1</cal:y>
|
||||
</cal:add>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
```
|
||||
所以 webcc 所做的,只不过是跟 `ws1.parasoft.com` 建立 TCP Socket 连接,然后发送上面这段内容而已。
|
||||
|
||||
## 用法
|
||||
|
||||
首先,创建一个类 `CalcClient`,继承自 `webcc::SoapClient`:
|
||||
|
||||
```cpp
|
||||
#include <string>
|
||||
#include "webcc/soap_client.h"
|
||||
|
||||
class CalcClient : public webcc::SoapClient {
|
||||
public:
|
||||
CalcClient() {
|
||||
Init();
|
||||
}
|
||||
```
|
||||
|
||||
在 `Init()` 函数里,初始化 URL、host、port 等等:
|
||||
```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";
|
||||
}
|
||||
```
|
||||
|
||||
由于四个计算器操作(*add*, *subtract*, *multiply* 和 *divide*)都一致的具有两个参数,我们可以稍微封装一下,弄一个辅助函数叫 `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;
|
||||
}
|
||||
```
|
||||
|
||||
值得注意的是,作为局部变量的参数(parameters),利用了 C++11 的 Move 语义,避免了额外的拷贝开销。
|
||||
当参数为很长的字符串时(比如 XML string),这一点特别有用。
|
||||
|
||||
最后,四个操作就是简单的转调 `Calc` 而已:
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
## 局限
|
||||
|
||||
当然,webcc 有很多局限,比如:
|
||||
- 只支持 `int`, `double`, `bool` 和 `string` 这几种参数类型;
|
||||
- 只支持 UTF-8 编码的消息内容;
|
||||
- 一次调用一个连接;
|
||||
- 连接是同步(阻塞)模式,可以指定 timeout(缺省为 15s)。
|
||||
|
||||
## 依赖
|
||||
|
||||
在实现上,webcc 有下面这些依赖:
|
||||
- Boost 1.66+;
|
||||
- XML 解析和构造基于 pugixml;
|
||||
- 构建系统是 CMake,应该可以很方便地集成到其他 C++ 项目中。
|
||||
@@ -1,137 +0,0 @@
|
||||
# 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
|
||||
// calc_service.h
|
||||
|
||||
#include "webcc/soap_service.h"
|
||||
|
||||
class CalcService : public webcc::SoapService {
|
||||
public:
|
||||
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 (e.g., add) from request;
|
||||
- Get parameters (e.g., x and y) from request;
|
||||
- Calculate the result.
|
||||
- Set namespaces, result name and so on to response.
|
||||
- Set result to response.
|
||||
|
||||
```cpp
|
||||
// calc_service.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;
|
||||
}
|
||||
|
||||
// Other operations ...
|
||||
|
||||
} catch (boost::bad_lexical_cast&) {
|
||||
// ...
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
Next step, create a `SoapServer` and register `CalcService` to it with a URL.
|
||||
|
||||
```cpp
|
||||
int main(int argc, char* argv[]) {
|
||||
// Check argc and argv ...
|
||||
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
The server is created with a **port number** which will be listened on to **asynchnously** accept client connections. The connections will be firstly put into a **queue** and then processed by the **worker threads**. The number of worker threads is determined by the `workers` parameter.
|
||||
|
||||
When register service, the URL is what the clients will put in the HTTP request to access your service:
|
||||
```
|
||||
POST /calculator HTTP/1.1
|
||||
```
|
||||
|
||||
Registering multiple services to a server is allowed, but the URL must be unique for each service.
|
||||
|
||||
To invoke the `add` operation of the calculator service, an example of the client HTTP request would be:
|
||||
```
|
||||
POST /calculator HTTP/1.1
|
||||
Content-Type: text/xml; charset=utf-8
|
||||
Content-Length: 263
|
||||
Host: localhost:8080
|
||||
SOAPAction: add
|
||||
|
||||
<?xml version="1.0"?>
|
||||
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<soap:Body>
|
||||
<ser:add xmlns:ser="http://www.example.com/calculator/">
|
||||
<ser:x>1.000000</ser:x>
|
||||
<ser:y>2.000000</ser:y>
|
||||
</ser:add>
|
||||
</soap:Body>
|
||||
</soap:Envelope>
|
||||
```
|
||||
|
||||
And the HTTP response is:
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: text/xml; charset=utf-8
|
||||
Content-Length: 262
|
||||
|
||||
<?xml version="1.0"?>
|
||||
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<soap:Body>
|
||||
<cal:addResponse xmlns:cal="http://www.example.com/calculator/">
|
||||
<cal:Result>3.000000</cal:Result>
|
||||
</cal:addResponse>
|
||||
</soap:Body>
|
||||
</soap:Envelope>
|
||||
```
|
||||
|
||||
See [example/soap_calc_server](https://github.com/sprinfall/webcc/tree/master/example/soap_calc_server) for the full example.
|
||||
Reference in New Issue
Block a user