diff --git a/README.md b/README.md
index 4abe4fc..6cc8cd7 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,8 @@
A lightweight C++ REST and SOAP client and server library based on *Boost.Asio*.
+Please turn to our [Wiki](https://github.com/sprinfall/webcc/wiki) for more tutorials and guides.
+
## Quick Start
### REST Server
diff --git a/doc/SoapClientTutorial.md b/doc/SoapClientTutorial.md
deleted file mode 100644
index 71c47e8..0000000
--- a/doc/SoapClientTutorial.md
+++ /dev/null
@@ -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
-
-
-
-
- 1
- 2
-
-
-
-```
-
-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
-#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 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(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.
diff --git a/doc/SoapClientTutorial_zh-CN.md b/doc/SoapClientTutorial_zh-CN.md
deleted file mode 100644
index 979fdf1..0000000
--- a/doc/SoapClientTutorial_zh-CN.md
+++ /dev/null
@@ -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
-
-
-
-
- 1
- 2
-
-
-
-```
-这个操作是 "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)
-
-
-
-
-
- 1
- 1
-
-
-
-```
-所以 webcc 所做的,只不过是跟 `ws1.parasoft.com` 建立 TCP Socket 连接,然后发送上面这段内容而已。
-
-## 用法
-
-首先,创建一个类 `CalcClient`,继承自 `webcc::SoapClient`:
-
-```cpp
-#include
-#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 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(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++ 项目中。
diff --git a/doc/SoapServerTutorial.md b/doc/SoapServerTutorial.md
deleted file mode 100644
index 5322b89..0000000
--- a/doc/SoapServerTutorial.md
+++ /dev/null
@@ -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(soap_request.GetParameter("x"));
- double y = boost::lexical_cast(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(), "/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
-
-
-
-
-
- 1.000000
- 2.000000
-
-
-
-```
-
-And the HTTP response is:
-```
-HTTP/1.1 200 OK
-Content-Type: text/xml; charset=utf-8
-Content-Length: 262
-
-
-
-
-
- 3.000000
-
-
-
-```
-
-See [example/soap_calc_server](https://github.com/sprinfall/webcc/tree/master/example/soap_calc_server) for the full example.
diff --git a/example/rest_book_client/main.cc b/example/rest_book_client/main.cc
index 406ffcb..edc641c 100644
--- a/example/rest_book_client/main.cc
+++ b/example/rest_book_client/main.cc
@@ -20,12 +20,24 @@
// -----------------------------------------------------------------------------
-// Write a JSON object to string.
-std::string JsonToString(const Json::Value& json) {
+static std::string JsonToString(const Json::Value& json) {
Json::StreamWriterBuilder builder;
return Json::writeString(builder, json);
}
+static Json::Value StringToJson(const std::string& str) {
+ Json::Value json;
+
+ Json::CharReaderBuilder builder;
+ std::stringstream stream(str);
+ std::string errs;
+ if (!Json::parseFromStream(builder, stream, &json, &errs)) {
+ std::cerr << errs << std::endl;
+ }
+
+ return json;
+}
+
// -----------------------------------------------------------------------------
class BookClientBase {
@@ -78,26 +90,25 @@ public:
return true;
}
- bool CreateBook(const std::string& id,
- const std::string& title,
- double price) {
+ bool CreateBook(const std::string& title, double price, std::string* id) {
PrintSeparateLine();
- std::cout << "CreateBook: " << id << ", " << title << ", " << price
- << std::endl;
+ std::cout << "CreateBook: " << title << ", " << price << std::endl;
- Json::Value json(Json::objectValue);
- json["id"] = id;
- json["title"] = title;
- json["price"] = price;
+ Json::Value req_json(Json::objectValue);
+ req_json["title"] = title;
+ req_json["price"] = price;
- if (!rest_client_.Post("/books", JsonToString(json))) {
+ if (!rest_client_.Post("/books", JsonToString(req_json))) {
PrintError();
return false;
}
std::cout << rest_client_.response_status() << std::endl;
- return true;
+ Json::Value rsp_json = StringToJson(rest_client_.response_content());
+ *id = rsp_json["id"].asString();
+
+ return !id->empty();
}
};
@@ -123,8 +134,7 @@ public:
return true;
}
- bool UpdateBook(const std::string& id,
- const std::string& title,
+ bool UpdateBook(const std::string& id, const std::string& title,
double price) {
PrintSeparateLine();
std::cout << "UpdateBook: " << id << ", " << title << ", " << price
@@ -187,12 +197,14 @@ int main(int argc, char* argv[]) {
BookDetailClient detail_client(host, port, timeout_seconds);
list_client.ListBooks();
- list_client.CreateBook("1", "1984", 12.3);
- detail_client.GetBook("1");
- detail_client.UpdateBook("1", "1Q84", 32.1);
- detail_client.GetBook("1");
- detail_client.DeleteBook("1");
+ std::string id;
+ list_client.CreateBook("1984", 12.3, &id);
+
+ detail_client.GetBook(id);
+ detail_client.UpdateBook(id, "1Q84", 32.1);
+ detail_client.GetBook(id);
+ detail_client.DeleteBook(id);
list_client.ListBooks();
diff --git a/example/rest_book_server/services.cc b/example/rest_book_server/services.cc
index 308acee..043fb71 100644
--- a/example/rest_book_server/services.cc
+++ b/example/rest_book_server/services.cc
@@ -2,8 +2,8 @@
#include
#include
+#include
-#include "boost/thread/thread.hpp"
#include "json/json.h"
#include "webcc/logger.h"
@@ -42,11 +42,11 @@ static bool JsonToBook(const std::string& json, Book* book) {
// Return all books as a JSON array.
// TODO: Support query parameters.
-bool BookListService::Get(const webcc::UrlQuery& /* query */,
+bool BookListService::Get(const webcc::UrlQuery& /*query*/,
std::string* response_content) {
if (sleep_seconds_ > 0) {
LOG_INFO("Sleep %d seconds...", sleep_seconds_);
- boost::this_thread::sleep_for(boost::chrono::seconds(sleep_seconds_));
+ std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_));
}
Json::Value root(Json::arrayValue);
@@ -61,17 +61,23 @@ bool BookListService::Get(const webcc::UrlQuery& /* query */,
}
// Add a new book.
-// No response content.
bool BookListService::Post(const std::string& request_content,
- std::string* /* response_content */) {
+ std::string* response_content) {
if (sleep_seconds_ > 0) {
LOG_INFO("Sleep %d seconds...", sleep_seconds_);
- boost::this_thread::sleep_for(boost::chrono::seconds(sleep_seconds_));
+ std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_));
}
Book book;
if (JsonToBook(request_content, &book)) {
- g_book_store.AddBook(book); // TODO: return ID
+ std::string id = g_book_store.AddBook(book);
+
+ Json::Value root;
+ root["id"] = id;
+
+ Json::StreamWriterBuilder builder;
+ *response_content = Json::writeString(builder, root);
+
return true;
}
@@ -85,7 +91,7 @@ bool BookDetailService::Get(const std::vector& url_sub_matches,
std::string* response_content) {
if (sleep_seconds_ > 0) {
LOG_INFO("Sleep %d seconds...", sleep_seconds_);
- boost::this_thread::sleep_for(boost::chrono::seconds(sleep_seconds_));
+ std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_));
}
if (url_sub_matches.size() != 1) {
@@ -110,7 +116,7 @@ bool BookDetailService::Put(const std::vector& url_sub_matches,
std::string* response_content) {
if (sleep_seconds_ > 0) {
LOG_INFO("Sleep %d seconds...", sleep_seconds_);
- boost::this_thread::sleep_for(boost::chrono::seconds(sleep_seconds_));
+ std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_));
}
if (url_sub_matches.size() != 1) {
@@ -132,7 +138,7 @@ bool BookDetailService::Delete(
const std::vector& url_sub_matches) {
if (sleep_seconds_ > 0) {
LOG_INFO("Sleep %d seconds...", sleep_seconds_);
- boost::this_thread::sleep_for(boost::chrono::seconds(sleep_seconds_));
+ std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_));
}
if (url_sub_matches.size() != 1) {
diff --git a/example/soap_calc_client_parasoft/main.cc b/example/soap_calc_client_parasoft/main.cc
index c0c086b..e912395 100644
--- a/example/soap_calc_client_parasoft/main.cc
+++ b/example/soap_calc_client_parasoft/main.cc
@@ -79,8 +79,7 @@ class CalcClient {
int main() {
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
- // Default port 80.
- CalcClient calc("ws1.parasoft.com", "");
+ CalcClient calc("ws1.parasoft.com", ""); // Use default port 80
double x = 1.0;
double y = 2.0;
diff --git a/example/soap_calc_server/calc_service.cc b/example/soap_calc_server/calc_service.cc
index ae20f70..70a0103 100644
--- a/example/soap_calc_server/calc_service.cc
+++ b/example/soap_calc_server/calc_service.cc
@@ -11,7 +11,11 @@ bool CalcService::Handle(const webcc::SoapRequest& soap_request,
webcc::SoapResponse* soap_response) {
double x = 0.0;
double y = 0.0;
- if (!GetParameters(soap_request, &x, &y)) {
+ try {
+ x = std::stod(soap_request.GetParameter("x"));
+ y = std::stod(soap_request.GetParameter("y"));
+ } catch (const std::exception& e) {
+ LOG_ERRO("SoapParameter cast error: %s", e.what());
return false;
}
@@ -61,16 +65,3 @@ bool CalcService::Handle(const webcc::SoapRequest& soap_request,
return true;
}
-
-bool CalcService::GetParameters(const webcc::SoapRequest& soap_request,
- double* x, double* y) {
- try {
- *x = std::stod(soap_request.GetParameter("x"));
- *y = std::stod(soap_request.GetParameter("y"));
- } catch (const std::exception& e) {
- LOG_ERRO("SoapParameter cast error: %s", e.what());
- return false;
- }
-
- return true;
-}
diff --git a/example/soap_calc_server/calc_service.h b/example/soap_calc_server/calc_service.h
index deb3321..2d767e3 100644
--- a/example/soap_calc_server/calc_service.h
+++ b/example/soap_calc_server/calc_service.h
@@ -10,10 +10,6 @@ class CalcService : public webcc::SoapService {
bool Handle(const webcc::SoapRequest& soap_request,
webcc::SoapResponse* soap_response) override;
-
- private:
- bool GetParameters(const webcc::SoapRequest& soap_request,
- double* x, double* y);
};
#endif // CALC_SERVICE_H_
diff --git a/webcc/http_request_handler.cc b/webcc/http_request_handler.cc
index 82a8443..4067094 100644
--- a/webcc/http_request_handler.cc
+++ b/webcc/http_request_handler.cc
@@ -17,7 +17,7 @@ void HttpRequestHandler::Start(std::size_t count) {
assert(count > 0 && workers_.size() == 0);
for (std::size_t i = 0; i < count; ++i) {
- workers_.create_thread(std::bind(&HttpRequestHandler::WorkerRoutine, this));
+ workers_.emplace_back(std::bind(&HttpRequestHandler::WorkerRoutine, this));
}
}
@@ -33,7 +33,11 @@ void HttpRequestHandler::Stop() {
// Enqueue a null connection to trigger the first worker to stop.
queue_.Push(HttpConnectionPtr());
- workers_.join_all();
+ for (auto& worker : workers_) {
+ if (worker.joinable()) {
+ worker.join();
+ }
+ }
LOG_INFO("All workers have been stopped.");
}
diff --git a/webcc/http_request_handler.h b/webcc/http_request_handler.h
index 1151867..61ab114 100644
--- a/webcc/http_request_handler.h
+++ b/webcc/http_request_handler.h
@@ -2,10 +2,9 @@
#define WEBCC_HTTP_REQUEST_HANDLER_H_
#include
+#include
#include
-#include "boost/thread/thread.hpp"
-
#include "webcc/http_connection.h"
#include "webcc/queue.h"
#include "webcc/soap_service.h"
@@ -39,7 +38,7 @@ class HttpRequestHandler {
virtual void HandleConnection(HttpConnectionPtr connection) = 0;
Queue queue_;
- boost::thread_group workers_;
+ std::vector workers_;
};
} // namespace webcc