Update readme
This commit is contained in:
@@ -1,22 +1,27 @@
|
|||||||
# webcc
|
# webcc
|
||||||
|
|
||||||
A lightweight C++ REST and SOAP client and server library based on Boost.Asio.
|
A lightweight C++ REST and SOAP client and server library based on *Boost.Asio*.
|
||||||
|
|
||||||
## Tutorials
|
Please see the `doc` folder for tutorials and `example` folder for examples.
|
||||||
|
|
||||||
**SOAP:**
|
## Build Instructions
|
||||||
- [SOAP Client Tutorial](doc/SoapClientTutorial.md)
|
|
||||||
- [SOAP Server Tutorial](doc/SoapServerTutorial.md)
|
|
||||||
|
|
||||||
- [SOAP 客户端教程](doc/SoapClientTutorial_zh-CN.md)
|
A lot of C++11 features are used, e.g., `std::move`. But C++14 is not required. It means that you can still build `webcc` using VS2013.
|
||||||
|
|
||||||
## Dependencies
|
[CMake 3.1.0+](https://cmake.org/) is required as the build system. But if you don't use CMake, you can just copy the `src/webcc` folder to your own project then manage it by yourself.
|
||||||
|
|
||||||
- C++11
|
[C++ Boost](https://www.boost.org/) should be 1.66+ because Asio made some broken changes to the API in 1.66.
|
||||||
- Boost 1.66+
|
|
||||||
|
|
||||||
If enable SOAP support, **pugixml** is needed to parse and compose XML strings.
|
### Build Options
|
||||||
|
|
||||||
## Build
|
The following CMake options determine how you build the projects. They are quite self-explanatory.
|
||||||
|
|
||||||
The build system is CMake.
|
```cmake
|
||||||
|
option(WEBCC_ENABLE_LOG "Enable console logger?" ON)
|
||||||
|
option(WEBCC_ENABLE_SOAP "Enable SOAP support (need pugixml)?" ON)
|
||||||
|
option(WEBCC_BUILD_UNITTEST "Build unit test?" ON)
|
||||||
|
option(WEBCC_BUILD_REST_EXAMPLE "Build REST example?" ON)
|
||||||
|
option(WEBCC_BUILD_SOAP_EXAMPLE "Build SOAP example?" ON)
|
||||||
|
```
|
||||||
|
|
||||||
|
If `WEBCC_ENABLE_SOAP` is `ON`, **pugixml** (already included) is used to parse and compose XML strings.
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public:
|
|||||||
http_request.Build();
|
http_request.Build();
|
||||||
|
|
||||||
webcc::HttpClient http_client;
|
webcc::HttpClient http_client;
|
||||||
webcc::Error error = http_client.SendRequest(http_request, http_response);
|
webcc::Error error = http_client.MakeRequest(http_request, http_response);
|
||||||
|
|
||||||
return error == webcc::kNoError;
|
return error == webcc::kNoError;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "boost/lexical_cast.hpp"
|
#include "boost/lexical_cast.hpp"
|
||||||
|
|
||||||
|
// Set to 0 to test our own calculator server created with webcc.
|
||||||
|
#define ACCESS_PARASOFT 0
|
||||||
|
|
||||||
CalcClient::CalcClient() {
|
CalcClient::CalcClient() {
|
||||||
Init();
|
Init();
|
||||||
}
|
}
|
||||||
@@ -19,13 +22,22 @@ bool CalcClient::Multiply(double x, double y, double* result) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool CalcClient::Divide(double x, double y, double* result) {
|
bool CalcClient::Divide(double x, double y, double* result) {
|
||||||
|
// ParaSoft's Calculator Service uses different parameter names for Divide.
|
||||||
|
#if ACCESS_PARASOFT
|
||||||
return Calc("divide", "numerator", "denominator", x, y, result);
|
return Calc("divide", "numerator", "denominator", x, y, result);
|
||||||
|
#else
|
||||||
|
return Calc("divide", "x", "y", x, y, result);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set to 0 to test our own calculator server created with webcc.
|
bool CalcClient::NotExist(double x, double y, double* result) {
|
||||||
#define ACCESS_PARASOFT 0
|
return Calc("notexist", "x", "y", x, y, result);
|
||||||
|
}
|
||||||
|
|
||||||
void CalcClient::Init() {
|
void CalcClient::Init() {
|
||||||
|
// Override the default timeout.
|
||||||
|
timeout_seconds_ = 5;
|
||||||
|
|
||||||
#if ACCESS_PARASOFT
|
#if ACCESS_PARASOFT
|
||||||
url_ = "/glue/calculator";
|
url_ = "/glue/calculator";
|
||||||
host_ = "ws1.parasoft.com";
|
host_ = "ws1.parasoft.com";
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ public:
|
|||||||
|
|
||||||
bool Divide(double x, double y, double* result);
|
bool Divide(double x, double y, double* result);
|
||||||
|
|
||||||
|
// For testing purpose.
|
||||||
|
bool NotExist(double x, double y, double* result);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void Init();
|
void Init();
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ int main() {
|
|||||||
printf("add: %.1f\n", result);
|
printf("add: %.1f\n", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
|
||||||
if (calc.Subtract(x, y, &result)) {
|
if (calc.Subtract(x, y, &result)) {
|
||||||
printf("subtract: %.1f\n", result);
|
printf("subtract: %.1f\n", result);
|
||||||
}
|
}
|
||||||
@@ -24,7 +23,11 @@ int main() {
|
|||||||
if (calc.Divide(x, y, &result)) {
|
if (calc.Divide(x, y, &result)) {
|
||||||
printf("divide: %.1f\n", result);
|
printf("divide: %.1f\n", result);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
// Try to call a non-existing operation.
|
||||||
|
if (calc.NotExist(x, y, &result)) {
|
||||||
|
printf("notexist: %.1f\n", result);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,94 @@
|
|||||||
#include "calc_service.h"
|
#include "calc_service.h"
|
||||||
|
|
||||||
|
// Sleep several seconds for the client to test timeout control.
|
||||||
|
#define SLEEP_FOR_TIMEOUT_TEST 0
|
||||||
|
|
||||||
#include "boost/lexical_cast.hpp"
|
#include "boost/lexical_cast.hpp"
|
||||||
|
|
||||||
|
#if SLEEP_FOR_TIMEOUT_TEST
|
||||||
|
#include "boost/thread/thread.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "webcc/logger.h"
|
||||||
#include "webcc/soap_request.h"
|
#include "webcc/soap_request.h"
|
||||||
#include "webcc/soap_response.h"
|
#include "webcc/soap_response.h"
|
||||||
|
|
||||||
|
#if SLEEP_FOR_TIMEOUT_TEST
|
||||||
|
static const int kSleepSeconds = 3;
|
||||||
|
#endif
|
||||||
|
|
||||||
bool CalcService::Handle(const webcc::SoapRequest& soap_request,
|
bool CalcService::Handle(const webcc::SoapRequest& soap_request,
|
||||||
webcc::SoapResponse* soap_response) {
|
webcc::SoapResponse* soap_response) {
|
||||||
try {
|
double x = 0.0;
|
||||||
if (soap_request.operation() == "add") {
|
double y = 0.0;
|
||||||
double x = boost::lexical_cast<double>(soap_request.GetParameter("x"));
|
if (!GetParameters(soap_request, &x, &y)) {
|
||||||
double y = boost::lexical_cast<double>(soap_request.GetParameter("y"));
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
double result = x + y;
|
const std::string& op = soap_request.operation();
|
||||||
|
|
||||||
|
LOG_INFO("Soap operation '%s': %.2f, %.2f", op.c_str(), x, y);
|
||||||
|
|
||||||
|
std::function<double(double, double)> calc;
|
||||||
|
|
||||||
|
if (op == "add") {
|
||||||
|
calc = [](double x, double y) { return x + y; };
|
||||||
|
|
||||||
|
} else if (op == "subtract") {
|
||||||
|
calc = [](double x, double y) { return x - y; };
|
||||||
|
|
||||||
|
} else if (op == "multiply") {
|
||||||
|
calc = [](double x, double y) { return x * y; };
|
||||||
|
|
||||||
|
} else if (op == "divide") {
|
||||||
|
calc = [](double x, double y) { return x / y; };
|
||||||
|
|
||||||
|
if (y == 0.0) {
|
||||||
|
LOG_ERRO("Cannot divide by 0.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG_ERRO("Operation '%s' is not supported.", op.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double result = calc(x, y);
|
||||||
|
|
||||||
soap_response->set_soapenv_ns(webcc::kSoapEnvNamespace);
|
soap_response->set_soapenv_ns(webcc::kSoapEnvNamespace);
|
||||||
soap_response->set_service_ns({
|
soap_response->set_service_ns({
|
||||||
"cal",
|
"cal",
|
||||||
"http://www.example.com/calculator/"
|
"http://www.example.com/calculator/"
|
||||||
});
|
});
|
||||||
|
|
||||||
soap_response->set_operation(soap_request.operation());
|
soap_response->set_operation(soap_request.operation());
|
||||||
|
|
||||||
soap_response->set_result_name("Result");
|
soap_response->set_result_name("Result");
|
||||||
soap_response->set_result(std::to_string(result));
|
soap_response->set_result(std::to_string(result));
|
||||||
|
|
||||||
|
#if SLEEP_FOR_TIMEOUT_TEST
|
||||||
|
LOG_INFO("Sleep %d seconds for the client to test timeout control.",
|
||||||
|
kSleepSeconds);
|
||||||
|
boost::this_thread::sleep_for(boost::chrono::seconds(kSleepSeconds));
|
||||||
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} else {
|
|
||||||
// NOT_IMPLEMENTED
|
|
||||||
}
|
|
||||||
} catch (boost::bad_lexical_cast&) {
|
|
||||||
// BAD_REQUEST
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CalcService::GetParameters(const webcc::SoapRequest& soap_request,
|
||||||
|
double* x,
|
||||||
|
double* y) {
|
||||||
|
try {
|
||||||
|
*x = boost::lexical_cast<double>(soap_request.GetParameter("x"));
|
||||||
|
*y = boost::lexical_cast<double>(soap_request.GetParameter("y"));
|
||||||
|
|
||||||
|
} catch (boost::bad_lexical_cast& e) {
|
||||||
|
LOG_ERRO("Parameter cast error: %s", e.what());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ public:
|
|||||||
|
|
||||||
bool Handle(const webcc::SoapRequest& soap_request,
|
bool Handle(const webcc::SoapRequest& soap_request,
|
||||||
webcc::SoapResponse* soap_response) override;
|
webcc::SoapResponse* soap_response) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool GetParameters(const webcc::SoapRequest& soap_request,
|
||||||
|
double* x,
|
||||||
|
double* y);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CALC_SERVICE_H_
|
#endif // CALC_SERVICE_H_
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include "webcc/logger.h"
|
||||||
#include "webcc/soap_server.h"
|
#include "webcc/soap_server.h"
|
||||||
#include "calc_service.h"
|
#include "calc_service.h"
|
||||||
|
|
||||||
@@ -14,6 +15,8 @@ int main(int argc, char* argv[]) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_INIT(webcc::VERB, 0);
|
||||||
|
|
||||||
unsigned short port = std::atoi(argv[1]);
|
unsigned short port = std::atoi(argv[1]);
|
||||||
|
|
||||||
std::size_t workers = 2;
|
std::size_t workers = 2;
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ const std::string kTextXmlUtf8 = "text/xml; charset=utf-8";
|
|||||||
|
|
||||||
const std::string kTextJsonUtf8 = "text/json; charset=utf-8";
|
const std::string kTextJsonUtf8 = "text/json; charset=utf-8";
|
||||||
|
|
||||||
|
const std::string kHttpHead = "HEAD";
|
||||||
|
const std::string kHttpGet = "GET";
|
||||||
|
const std::string kHttpPost = "POST";
|
||||||
|
const std::string kHttpPatch = "PATCH";
|
||||||
|
const std::string kHttpPut = "PUT";
|
||||||
|
const std::string kHttpDelete = "DELETE";
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
const char* GetErrorMessage(Error error) {
|
const char* GetErrorMessage(Error error) {
|
||||||
|
|||||||
+30
-30
@@ -26,6 +26,36 @@ extern const std::string kTextJsonUtf8;
|
|||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// HTTP methods (verbs) in string ("HEAD", "GET", etc.).
|
||||||
|
// NOTE: Don't use enum to avoid converting back and forth.
|
||||||
|
extern const std::string kHttpHead;
|
||||||
|
extern const std::string kHttpGet;
|
||||||
|
extern const std::string kHttpPost;
|
||||||
|
extern const std::string kHttpPatch;
|
||||||
|
extern const std::string kHttpPut;
|
||||||
|
extern const std::string kHttpDelete;
|
||||||
|
|
||||||
|
// HTTP response status.
|
||||||
|
// This is not a full list.
|
||||||
|
// Full list: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
|
||||||
|
// NTOE: Don't use enum class because we want to convert to/from int easily.
|
||||||
|
struct HttpStatus {
|
||||||
|
enum Enum {
|
||||||
|
kOK = 200,
|
||||||
|
kCreated = 201,
|
||||||
|
kAccepted = 202,
|
||||||
|
kNoContent = 204,
|
||||||
|
kNotModified = 304,
|
||||||
|
kBadRequest = 400,
|
||||||
|
kNotFound = 404,
|
||||||
|
InternalServerError = 500,
|
||||||
|
kNotImplemented = 501,
|
||||||
|
kServiceUnavailable = 503,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Error codes.
|
// Error codes.
|
||||||
enum Error {
|
enum Error {
|
||||||
kNoError = 0, // OK
|
kNoError = 0, // OK
|
||||||
@@ -55,36 +85,6 @@ const char* GetErrorMessage(Error error);
|
|||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// HTTP methods (verbs).
|
|
||||||
// NOTE: Don't use enum to avoid converting back and forth.
|
|
||||||
const std::string kHttpHead = "HEAD";
|
|
||||||
const std::string kHttpGet = "GET";
|
|
||||||
const std::string kHttpPost = "POST";
|
|
||||||
const std::string kHttpPatch = "PATCH";
|
|
||||||
const std::string kHttpPut = "PUT";
|
|
||||||
const std::string kHttpDelete = "DELETE";
|
|
||||||
|
|
||||||
// HTTP response status.
|
|
||||||
// This is not a full list.
|
|
||||||
// Full list: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
|
|
||||||
// NTOE: Don't use enum class because we want to convert to/from int easily.
|
|
||||||
struct HttpStatus {
|
|
||||||
enum Enum {
|
|
||||||
kOK = 200,
|
|
||||||
kCreated = 201,
|
|
||||||
kAccepted = 202,
|
|
||||||
kNoContent = 204,
|
|
||||||
kNotModified = 304,
|
|
||||||
kBadRequest = 400,
|
|
||||||
kNotFound = 404,
|
|
||||||
InternalServerError = 500,
|
|
||||||
kNotImplemented = 501,
|
|
||||||
kServiceUnavailable = 503,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// XML namespace name/url pair.
|
// XML namespace name/url pair.
|
||||||
// E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" }
|
// E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" }
|
||||||
// TODO: Rename (add soap prefix)
|
// TODO: Rename (add soap prefix)
|
||||||
|
|||||||
+150
-77
@@ -1,62 +1,73 @@
|
|||||||
#include "webcc/http_client.h"
|
#include "webcc/http_client.h"
|
||||||
|
|
||||||
|
#include "boost/date_time/posix_time/posix_time.hpp"
|
||||||
|
#include "boost/lambda/bind.hpp"
|
||||||
|
#include "boost/lambda/lambda.hpp"
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
#include "boost/asio.hpp"
|
#include "boost/asio.hpp"
|
||||||
#else
|
#else
|
||||||
#include "boost/asio/connect.hpp"
|
#include "boost/asio/connect.hpp"
|
||||||
#include "boost/asio/ip/tcp.hpp"
|
|
||||||
#include "boost/asio/read.hpp"
|
#include "boost/asio/read.hpp"
|
||||||
#include "boost/asio/write.hpp"
|
#include "boost/asio/write.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "webcc/logger.h"
|
#include "webcc/logger.h"
|
||||||
#include "webcc/http_response_parser.h"
|
|
||||||
#include "webcc/http_request.h"
|
#include "webcc/http_request.h"
|
||||||
#include "webcc/http_response.h"
|
#include "webcc/http_response.h"
|
||||||
|
|
||||||
|
// NOTE:
|
||||||
|
// The timeout control is inspired by the following Asio example:
|
||||||
|
// example\cpp03\timeouts\blocking_tcp_client.cpp
|
||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// See https://stackoverflow.com/a/9079092
|
static const int kConnectMaxSeconds = 10;
|
||||||
static void SetTimeout(boost::asio::ip::tcp::socket& socket,
|
static const int kSendMaxSeconds = 10;
|
||||||
int timeout_seconds) {
|
static const int kReceiveMaxSeconds = 30;
|
||||||
#if defined _WINDOWS
|
|
||||||
|
|
||||||
int ms = timeout_seconds * 1000;
|
|
||||||
|
|
||||||
const char* optval = reinterpret_cast<const char*>(&ms);
|
|
||||||
std::size_t optlen = sizeof(ms);
|
|
||||||
|
|
||||||
setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, optval, optlen);
|
|
||||||
setsockopt(socket.native_handle(), SOL_SOCKET, SO_SNDTIMEO, optval, optlen);
|
|
||||||
|
|
||||||
#else // POSIX
|
|
||||||
|
|
||||||
// TODO: This doesn't work! Consider to control timeout in server side.
|
|
||||||
|
|
||||||
struct timeval tv;
|
|
||||||
tv.tv_sec = timeout_seconds;
|
|
||||||
tv.tv_usec = 0;
|
|
||||||
setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
||||||
setsockopt(socket.native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
|
||||||
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
HttpClient::HttpClient()
|
HttpClient::HttpClient()
|
||||||
: timeout_seconds_(15) {
|
: socket_(io_context_)
|
||||||
|
, timeout_seconds_(kReceiveMaxSeconds)
|
||||||
|
, deadline_timer_(io_context_) {
|
||||||
|
|
||||||
|
deadline_timer_.expires_at(boost::posix_time::pos_infin);
|
||||||
|
|
||||||
|
// Start the persistent actor that checks for deadline expiry.
|
||||||
|
CheckDeadline();
|
||||||
}
|
}
|
||||||
|
|
||||||
Error HttpClient::SendRequest(const HttpRequest& request,
|
Error HttpClient::MakeRequest(const HttpRequest& request,
|
||||||
HttpResponse* response) {
|
HttpResponse* response) {
|
||||||
assert(response != NULL);
|
assert(response != NULL);
|
||||||
|
|
||||||
using boost::asio::ip::tcp;
|
Error error = kNoError;
|
||||||
|
|
||||||
tcp::socket socket(io_context_);
|
if ((error = Connect(request)) != kNoError) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send HTTP request.
|
||||||
|
|
||||||
|
if ((error = SendReqeust(request)) != kNoError) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and parse HTTP response.
|
||||||
|
|
||||||
|
parser_ = std::make_unique<HttpResponseParser>(response);
|
||||||
|
|
||||||
|
error = ReadResponse(response);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error HttpClient::Connect(const HttpRequest& request) {
|
||||||
|
using boost::asio::ip::tcp;
|
||||||
|
|
||||||
tcp::resolver resolver(io_context_);
|
tcp::resolver resolver(io_context_);
|
||||||
|
|
||||||
@@ -66,7 +77,6 @@ Error HttpClient::SendRequest(const HttpRequest& request,
|
|||||||
}
|
}
|
||||||
|
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
// tcp::resolver::results_type
|
|
||||||
auto endpoints = resolver.resolve(tcp::v4(), request.host(), port, ec);
|
auto endpoints = resolver.resolve(tcp::v4(), request.host(), port, ec);
|
||||||
|
|
||||||
if (ec) {
|
if (ec) {
|
||||||
@@ -77,58 +87,121 @@ Error HttpClient::SendRequest(const HttpRequest& request,
|
|||||||
return kHostResolveError;
|
return kHostResolveError;
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::asio::connect(socket, endpoints, ec);
|
deadline_timer_.expires_from_now(
|
||||||
|
boost::posix_time::seconds(kConnectMaxSeconds));
|
||||||
|
|
||||||
|
ec = boost::asio::error::would_block;
|
||||||
|
|
||||||
|
boost::asio::async_connect(socket_,
|
||||||
|
endpoints,
|
||||||
|
boost::lambda::var(ec) = boost::lambda::_1);
|
||||||
|
|
||||||
|
// Block until the asynchronous operation has completed.
|
||||||
|
do {
|
||||||
|
io_context_.run_one();
|
||||||
|
} while (ec == boost::asio::error::would_block);
|
||||||
|
|
||||||
|
// Determine whether a connection was successfully established. The
|
||||||
|
// deadline actor may have had a chance to run and close our socket, even
|
||||||
|
// though the connect operation notionally succeeded. Therefore we must
|
||||||
|
// check whether the socket is still open before deciding if we succeeded
|
||||||
|
// or failed.
|
||||||
|
if (ec || !socket_.is_open()) {
|
||||||
if (ec) {
|
if (ec) {
|
||||||
return kEndpointConnectError;
|
return kEndpointConnectError;
|
||||||
}
|
} else {
|
||||||
|
|
||||||
SetTimeout(socket, timeout_seconds_);
|
|
||||||
|
|
||||||
// Send HTTP request.
|
|
||||||
|
|
||||||
LOG_VERB("http request:\n{\n%s}", request.Dump().c_str());
|
|
||||||
|
|
||||||
try {
|
|
||||||
boost::asio::write(socket, request.ToBuffers());
|
|
||||||
} catch (boost::system::system_error&) {
|
|
||||||
return kSocketWriteError;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read and parse HTTP response.
|
|
||||||
|
|
||||||
HttpResponseParser parser(response);
|
|
||||||
|
|
||||||
// NOTE:
|
|
||||||
// We must stop trying to read once all content has been received,
|
|
||||||
// because some servers will block extra call to read_some().
|
|
||||||
while (!parser.finished()) {
|
|
||||||
// read_some() will block until one or more bytes of data has been
|
|
||||||
// read successfully, or until an error occurs.
|
|
||||||
std::size_t length = socket.read_some(boost::asio::buffer(buffer_), ec);
|
|
||||||
|
|
||||||
if (length == 0 || ec) {
|
|
||||||
#if defined _WINDOWS
|
|
||||||
if (ec.value() == WSAETIMEDOUT) {
|
|
||||||
return kSocketTimeoutError;
|
return kSocketTimeoutError;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
return kSocketReadError;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the response piece just read.
|
|
||||||
// If the content has been fully received, next time flag "finished_"
|
|
||||||
// will be set.
|
|
||||||
Error error = parser.Parse(buffer_.data(), length);
|
|
||||||
|
|
||||||
if (error != kNoError) {
|
|
||||||
LOG_ERRO("failed to parse http response.");
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_VERB("http response:\n{\n%s}", response->Dump().c_str());
|
|
||||||
|
|
||||||
return kNoError;
|
return kNoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Error HttpClient::SendReqeust(const HttpRequest& request) {
|
||||||
|
LOG_VERB("http request:\n{\n%s}", request.Dump().c_str());
|
||||||
|
|
||||||
|
deadline_timer_.expires_from_now(boost::posix_time::seconds(kSendMaxSeconds));
|
||||||
|
|
||||||
|
boost::system::error_code ec = boost::asio::error::would_block;
|
||||||
|
|
||||||
|
boost::asio::async_write(socket_,
|
||||||
|
request.ToBuffers(),
|
||||||
|
boost::lambda::var(ec) = boost::lambda::_1);
|
||||||
|
|
||||||
|
// Block until the asynchronous operation has completed.
|
||||||
|
do {
|
||||||
|
io_context_.run_one();
|
||||||
|
} while (ec == boost::asio::error::would_block);
|
||||||
|
|
||||||
|
if (ec) {
|
||||||
|
return kSocketWriteError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return kNoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error HttpClient::ReadResponse(HttpResponse* response) {
|
||||||
|
deadline_timer_.expires_from_now(
|
||||||
|
boost::posix_time::seconds(timeout_seconds_));
|
||||||
|
|
||||||
|
boost::system::error_code ec = boost::asio::error::would_block;
|
||||||
|
Error error = kNoError;
|
||||||
|
|
||||||
|
socket_.async_read_some(
|
||||||
|
boost::asio::buffer(buffer_),
|
||||||
|
[this, &ec, &error, response](boost::system::error_code inner_ec,
|
||||||
|
std::size_t length) {
|
||||||
|
ec = inner_ec;
|
||||||
|
|
||||||
|
if (inner_ec || length == 0) {
|
||||||
|
error = kSocketReadError;
|
||||||
|
} else {
|
||||||
|
// Parse the response piece just read.
|
||||||
|
// If the content has been fully received, next time flag "finished_"
|
||||||
|
// will be set.
|
||||||
|
error = parser_->Parse(buffer_.data(), length);
|
||||||
|
|
||||||
|
if (error != kNoError) {
|
||||||
|
LOG_ERRO("failed to parse http response.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parser_->finished()) {
|
||||||
|
// Stop trying to read once all content has been received,
|
||||||
|
// because some servers will block extra call to read_some().
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadResponse(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Block until the asynchronous operation has completed.
|
||||||
|
do {
|
||||||
|
io_context_.run_one();
|
||||||
|
} while (ec == boost::asio::error::would_block);
|
||||||
|
|
||||||
|
if (error == kNoError) {
|
||||||
|
LOG_VERB("http response:\n{\n%s}", response->Dump().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpClient::CheckDeadline() {
|
||||||
|
if (deadline_timer_.expires_at() <=
|
||||||
|
boost::asio::deadline_timer::traits_type::now()) {
|
||||||
|
// The deadline has passed.
|
||||||
|
// The socket is closed so that any outstanding asynchronous operations
|
||||||
|
// are canceled.
|
||||||
|
boost::system::error_code ignored_ec;
|
||||||
|
socket_.close(ignored_ec);
|
||||||
|
|
||||||
|
deadline_timer_.expires_at(boost::posix_time::pos_infin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the actor back to sleep.
|
||||||
|
deadline_timer_.async_wait(std::bind(&HttpClient::CheckDeadline, this));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webcc
|
} // namespace webcc
|
||||||
|
|||||||
+35
-6
@@ -2,10 +2,14 @@
|
|||||||
#define WEBCC_HTTP_CLIENT_H_
|
#define WEBCC_HTTP_CLIENT_H_
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "boost/asio/deadline_timer.hpp"
|
||||||
|
#include "boost/asio/ip/tcp.hpp"
|
||||||
#include "boost/asio/io_context.hpp"
|
#include "boost/asio/io_context.hpp"
|
||||||
|
|
||||||
#include "webcc/common.h"
|
#include "webcc/common.h"
|
||||||
|
#include "webcc/http_response_parser.h"
|
||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
@@ -16,19 +20,44 @@ class HttpClient {
|
|||||||
public:
|
public:
|
||||||
HttpClient();
|
HttpClient();
|
||||||
|
|
||||||
// Set socket send & recv timeout.
|
~HttpClient() = default;
|
||||||
void set_timeout_seconds(int seconds) {
|
|
||||||
timeout_seconds_ = seconds;
|
HttpClient(const HttpClient&) = delete;
|
||||||
|
HttpClient& operator=(const HttpClient&) = delete;
|
||||||
|
|
||||||
|
void set_timeout_seconds(int timeout_seconds) {
|
||||||
|
timeout_seconds_ = timeout_seconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send an HTTP request, wait until the response is received.
|
// Make a HTTP request.
|
||||||
Error SendRequest(const HttpRequest& request,
|
// Connect to the server, send the request, wait until the response is
|
||||||
HttpResponse* response);
|
// received.
|
||||||
|
Error MakeRequest(const HttpRequest& request, HttpResponse* response);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Error Connect(const HttpRequest& request);
|
||||||
|
|
||||||
|
Error SendReqeust(const HttpRequest& request);
|
||||||
|
|
||||||
|
Error ReadResponse(HttpResponse* response);
|
||||||
|
|
||||||
|
void CheckDeadline();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
boost::asio::io_context io_context_;
|
boost::asio::io_context io_context_;
|
||||||
|
|
||||||
|
boost::asio::ip::tcp::socket socket_;
|
||||||
|
|
||||||
std::array<char, kBufferSize> buffer_;
|
std::array<char, kBufferSize> buffer_;
|
||||||
|
|
||||||
|
std::unique_ptr<HttpResponseParser> parser_;
|
||||||
|
|
||||||
|
// Maximum seconds to wait before the client cancels the operation.
|
||||||
|
// Only for receiving response from server.
|
||||||
int timeout_seconds_;
|
int timeout_seconds_;
|
||||||
|
|
||||||
|
// Timer for the timeout control.
|
||||||
|
boost::asio::deadline_timer deadline_timer_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace webcc
|
} // namespace webcc
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ class HttpParser {
|
|||||||
public:
|
public:
|
||||||
explicit HttpParser(HttpMessage* message);
|
explicit HttpParser(HttpMessage* message);
|
||||||
|
|
||||||
~HttpParser() = default;
|
virtual ~HttpParser() = default;
|
||||||
|
|
||||||
HttpParser(const HttpParser&) = delete;
|
HttpParser(const HttpParser&) = delete;
|
||||||
HttpParser& operator=(const HttpParser&) = delete;
|
HttpParser& operator=(const HttpParser&) = delete;
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ private:
|
|||||||
void WorkerRoutine();
|
void WorkerRoutine();
|
||||||
|
|
||||||
// Called by the worker routine.
|
// Called by the worker routine.
|
||||||
virtual HttpStatus::Enum HandleSession(HttpSessionPtr session) = 0;
|
virtual void HandleSession(HttpSessionPtr session) = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Queue<HttpSessionPtr> queue_;
|
Queue<HttpSessionPtr> queue_;
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ class HttpRequestParser : public HttpParser {
|
|||||||
public:
|
public:
|
||||||
explicit HttpRequestParser(HttpRequest* request);
|
explicit HttpRequestParser(HttpRequest* request);
|
||||||
|
|
||||||
|
~HttpRequestParser() override = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Error ParseStartLine(const std::string& line) override;
|
Error ParseStartLine(const std::string& line) override;
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ class HttpResponse : public HttpMessage {
|
|||||||
const HttpResponse& response);
|
const HttpResponse& response);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
HttpResponse() {
|
HttpResponse() = default;
|
||||||
}
|
~HttpResponse() override = default;
|
||||||
|
|
||||||
int status() const {
|
int status() const {
|
||||||
return status_;
|
return status_;
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ class HttpResponseParser : public HttpParser {
|
|||||||
public:
|
public:
|
||||||
explicit HttpResponseParser(HttpResponse* response);
|
explicit HttpResponseParser(HttpResponse* response);
|
||||||
|
|
||||||
|
~HttpResponseParser() override = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Parse HTTP start line; E.g., "HTTP/1.1 200 OK".
|
// Parse HTTP start line; E.g., "HTTP/1.1 200 OK".
|
||||||
Error ParseStartLine(const std::string& line) override;
|
Error ParseStartLine(const std::string& line) override;
|
||||||
|
|||||||
+12
-11
@@ -29,31 +29,32 @@ void HttpSession::Stop() {
|
|||||||
socket_.close(ec);
|
socket_.close(ec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::SetResponseContent(const std::string& content_type,
|
void HttpSession::SetResponseContent(std::string&& content,
|
||||||
std::size_t content_length,
|
const std::string& content_type) {
|
||||||
std::string&& content) {
|
|
||||||
response_.SetContentType(content_type);
|
|
||||||
response_.SetContent(std::move(content));
|
response_.SetContent(std::move(content));
|
||||||
|
response_.SetContentType(content_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::SendResponse() {
|
void HttpSession::SendResponse(int status) {
|
||||||
|
response_.set_status(status);
|
||||||
DoWrite();
|
DoWrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::DoRead() {
|
void HttpSession::DoRead() {
|
||||||
auto handler = std::bind(&HttpSession::HandleRead,
|
socket_.async_read_some(boost::asio::buffer(buffer_),
|
||||||
|
std::bind(&HttpSession::HandleRead,
|
||||||
shared_from_this(),
|
shared_from_this(),
|
||||||
std::placeholders::_1,
|
std::placeholders::_1,
|
||||||
std::placeholders::_2);
|
std::placeholders::_2));
|
||||||
socket_.async_read_some(boost::asio::buffer(buffer_), handler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::DoWrite() {
|
void HttpSession::DoWrite() {
|
||||||
auto handler = std::bind(&HttpSession::HandleWrite,
|
boost::asio::async_write(socket_,
|
||||||
|
response_.ToBuffers(),
|
||||||
|
std::bind(&HttpSession::HandleWrite,
|
||||||
shared_from_this(),
|
shared_from_this(),
|
||||||
std::placeholders::_1,
|
std::placeholders::_1,
|
||||||
std::placeholders::_2);
|
std::placeholders::_2));
|
||||||
boost::asio::async_write(socket_, response_.ToBuffers(), handler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::HandleRead(boost::system::error_code ec,
|
void HttpSession::HandleRead(boost::system::error_code ec,
|
||||||
|
|||||||
@@ -33,16 +33,11 @@ public:
|
|||||||
|
|
||||||
void Stop();
|
void Stop();
|
||||||
|
|
||||||
void SetResponseStatus(int status) {
|
void SetResponseContent(std::string&& content,
|
||||||
response_.set_status(status);
|
const std::string& content_type);
|
||||||
}
|
|
||||||
|
|
||||||
void SetResponseContent(const std::string& content_type,
|
// Write response back to the client with the given HTTP status.
|
||||||
std::size_t content_length,
|
void SendResponse(int status);
|
||||||
std::string&& content);
|
|
||||||
|
|
||||||
// Write response back to the client.
|
|
||||||
void SendResponse();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void DoRead();
|
void DoRead();
|
||||||
|
|||||||
+13
-13
@@ -23,9 +23,11 @@ void LogInit(int level, int modes);
|
|||||||
|
|
||||||
void LogWrite(int level, const char* file, int line, const char* format, ...);
|
void LogWrite(int level, const char* file, int line, const char* format, ...);
|
||||||
|
|
||||||
|
} // namespace webcc
|
||||||
|
|
||||||
// Initialize the logger with a level.
|
// Initialize the logger with a level.
|
||||||
// E.g., LOG_INIT(ERRO, FLUSH)
|
// E.g., LOG_INIT(ERRO, FLUSH)
|
||||||
#define LOG_INIT(level, modes) LogInit(level, modes);
|
#define LOG_INIT(level, modes) webcc::LogInit(level, modes);
|
||||||
|
|
||||||
#if (defined(WIN32) || defined(_WIN64))
|
#if (defined(WIN32) || defined(_WIN64))
|
||||||
|
|
||||||
@@ -33,19 +35,19 @@ void LogWrite(int level, const char* file, int line, const char* format, ...);
|
|||||||
#define __FILENAME__ strrchr("\\" __FILE__, '\\') + 1
|
#define __FILENAME__ strrchr("\\" __FILE__, '\\') + 1
|
||||||
|
|
||||||
#define LOG_VERB(format, ...) \
|
#define LOG_VERB(format, ...) \
|
||||||
LogWrite(VERB, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
webcc::LogWrite(webcc::VERB, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
||||||
|
|
||||||
#define LOG_INFO(format, ...) \
|
#define LOG_INFO(format, ...) \
|
||||||
LogWrite(INFO, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
webcc::LogWrite(webcc::INFO, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
||||||
|
|
||||||
#define LOG_WARN(format, ...) \
|
#define LOG_WARN(format, ...) \
|
||||||
LogWrite(WARN, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
webcc::LogWrite(webcc::WARN, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
||||||
|
|
||||||
#define LOG_ERRO(format, ...) \
|
#define LOG_ERRO(format, ...) \
|
||||||
LogWrite(ERRO, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
webcc::LogWrite(webcc::ERRO, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
||||||
|
|
||||||
#define LOG_FATA(format, ...) \
|
#define LOG_FATA(format, ...) \
|
||||||
LogWrite(FATA, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
webcc::LogWrite(webcc::FATA, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
@@ -53,19 +55,19 @@ void LogWrite(int level, const char* file, int line, const char* format, ...);
|
|||||||
#define __FILENAME__ strrchr("/" __FILE__, '/') + 1
|
#define __FILENAME__ strrchr("/" __FILE__, '/') + 1
|
||||||
|
|
||||||
#define LOG_VERB(format, args...) \
|
#define LOG_VERB(format, args...) \
|
||||||
LogWrite(VERB, __FILENAME__, __LINE__, format, ##args);
|
webcc::LogWrite(webcc::VERB, __FILENAME__, __LINE__, format, ##args);
|
||||||
|
|
||||||
#define LOG_INFO(format, args...) \
|
#define LOG_INFO(format, args...) \
|
||||||
LogWrite(INFO, __FILENAME__, __LINE__, format, ##args);
|
webcc::LogWrite(webcc::INFO, __FILENAME__, __LINE__, format, ##args);
|
||||||
|
|
||||||
#define LOG_WARN(format, args...) \
|
#define LOG_WARN(format, args...) \
|
||||||
LogWrite(WARN, __FILENAME__, __LINE__, format, ##args);
|
webcc::LogWrite(webcc::WARN, __FILENAME__, __LINE__, format, ##args);
|
||||||
|
|
||||||
#define LOG_ERRO(format, args...) \
|
#define LOG_ERRO(format, args...) \
|
||||||
LogWrite(ERRO, __FILENAME__, __LINE__, format, ##args);
|
webcc::LogWrite(webcc::ERRO, __FILENAME__, __LINE__, format, ##args);
|
||||||
|
|
||||||
#define LOG_FATA(format, args...) \
|
#define LOG_FATA(format, args...) \
|
||||||
LogWrite(FATA, __FILENAME__, __LINE__, format, ##args);
|
webcc::LogWrite(webcc::FATA, __FILENAME__, __LINE__, format, ##args);
|
||||||
|
|
||||||
#endif // defined(WIN32) || defined(_WIN64)
|
#endif // defined(WIN32) || defined(_WIN64)
|
||||||
|
|
||||||
@@ -89,6 +91,4 @@ void LogWrite(int level, const char* file, int line, const char* format, ...);
|
|||||||
|
|
||||||
#endif // WEBCC_ENABLE_LOG
|
#endif // WEBCC_ENABLE_LOG
|
||||||
|
|
||||||
} // namespace webcc
|
|
||||||
|
|
||||||
#endif // WEBCC_LOGGER_H_
|
#endif // WEBCC_LOGGER_H_
|
||||||
|
|||||||
+13
-16
@@ -59,42 +59,39 @@ bool RestRequestHandler::RegisterService(RestServicePtr service,
|
|||||||
return service_manager_.AddService(service, url);
|
return service_manager_.AddService(service, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpStatus::Enum RestRequestHandler::HandleSession(HttpSessionPtr session) {
|
void RestRequestHandler::HandleSession(HttpSessionPtr session) {
|
||||||
Url url(session->request().url());
|
Url url(session->request().url());
|
||||||
|
|
||||||
if (!url.IsValid()) {
|
if (!url.IsValid()) {
|
||||||
session->SetResponseStatus(HttpStatus::kBadRequest);
|
session->SendResponse(HttpStatus::kBadRequest);
|
||||||
session->SendResponse();
|
return;
|
||||||
return HttpStatus::kBadRequest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> sub_matches;
|
std::vector<std::string> sub_matches;
|
||||||
RestServicePtr service = service_manager_.GetService(url.path(), &sub_matches);
|
RestServicePtr service = service_manager_.GetService(url.path(), &sub_matches);
|
||||||
if (!service) {
|
if (!service) {
|
||||||
LOG_WARN("No service matches the URL: %s", url.path().c_str());
|
LOG_WARN("No service matches the URL: %s", url.path().c_str());
|
||||||
session->SetResponseStatus(HttpStatus::kBadRequest);
|
session->SendResponse(HttpStatus::kBadRequest);
|
||||||
session->SendResponse();
|
return;
|
||||||
return HttpStatus::kBadRequest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Only for GET?
|
// TODO: Only for GET?
|
||||||
Url::Query query = Url::SplitQuery(url.query());
|
Url::Query query = Url::SplitQuery(url.query());
|
||||||
|
|
||||||
// TODO: Error handling.
|
|
||||||
std::string content;
|
std::string content;
|
||||||
service->Handle(session->request().method(),
|
bool ok = service->Handle(session->request().method(),
|
||||||
sub_matches,
|
sub_matches,
|
||||||
query,
|
query,
|
||||||
session->request().content(),
|
session->request().content(),
|
||||||
&content);
|
&content);
|
||||||
|
if (!ok) {
|
||||||
|
// TODO: Could be other than kBadRequest.
|
||||||
|
session->SendResponse(HttpStatus::kBadRequest);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
session->SetResponseStatus(HttpStatus::kOK);
|
session->SetResponseContent(std::move(content), kTextJsonUtf8);
|
||||||
session->SetResponseContent(kTextJsonUtf8,
|
session->SendResponse(HttpStatus::kOK);
|
||||||
content.length(),
|
|
||||||
std::move(content));
|
|
||||||
session->SendResponse();
|
|
||||||
|
|
||||||
return HttpStatus::kOK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ public:
|
|||||||
bool RegisterService(RestServicePtr service, const std::string& url);
|
bool RegisterService(RestServicePtr service, const std::string& url);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HttpStatus::Enum HandleSession(HttpSessionPtr session) override;
|
void HandleSession(HttpSessionPtr session) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RestServiceManager service_manager_;
|
RestServiceManager service_manager_;
|
||||||
|
|||||||
@@ -48,7 +48,12 @@ Error SoapClient::Call(const std::string& operation,
|
|||||||
HttpResponse http_response;
|
HttpResponse http_response;
|
||||||
|
|
||||||
HttpClient http_client;
|
HttpClient http_client;
|
||||||
Error error = http_client.SendRequest(http_request, &http_response);
|
|
||||||
|
if (timeout_seconds_ != -1) {
|
||||||
|
http_client.set_timeout_seconds(timeout_seconds_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Error error = http_client.MakeRequest(http_request, &http_response);
|
||||||
|
|
||||||
if (error != kNoError) {
|
if (error != kNoError) {
|
||||||
return error;
|
return error;
|
||||||
|
|||||||
@@ -13,12 +13,10 @@ namespace webcc {
|
|||||||
//
|
//
|
||||||
class SoapClient {
|
class SoapClient {
|
||||||
public:
|
public:
|
||||||
virtual ~SoapClient() {
|
virtual ~SoapClient() = default;
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SoapClient() {
|
SoapClient() = default;
|
||||||
}
|
|
||||||
|
|
||||||
// A generic wrapper to make a call.
|
// A generic wrapper to make a call.
|
||||||
// NOTE: The parameters should be movable.
|
// NOTE: The parameters should be movable.
|
||||||
@@ -27,6 +25,9 @@ protected:
|
|||||||
std::string* result);
|
std::string* result);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
// -1 means default timeout (normally 30s) will be used.
|
||||||
|
int timeout_seconds_ = -1;
|
||||||
|
|
||||||
Namespace soapenv_ns_; // SOAP envelope namespace.
|
Namespace soapenv_ns_; // SOAP envelope namespace.
|
||||||
Namespace service_ns_; // Namespace for your web service.
|
Namespace service_ns_; // Namespace for your web service.
|
||||||
|
|
||||||
|
|||||||
+11
-17
@@ -16,36 +16,30 @@ bool SoapRequestHandler::RegisterService(SoapServicePtr service,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpStatus::Enum SoapRequestHandler::HandleSession(HttpSessionPtr session) {
|
void SoapRequestHandler::HandleSession(HttpSessionPtr session) {
|
||||||
SoapServicePtr service = GetServiceByUrl(session->request().url());
|
SoapServicePtr service = GetServiceByUrl(session->request().url());
|
||||||
if (!service) {
|
if (!service) {
|
||||||
session->SetResponseStatus(HttpStatus::kBadRequest);
|
session->SendResponse(HttpStatus::kBadRequest);
|
||||||
session->SendResponse();
|
return;
|
||||||
return HttpStatus::kBadRequest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the SOAP request XML.
|
// Parse the SOAP request XML.
|
||||||
SoapRequest soap_request;
|
SoapRequest soap_request;
|
||||||
if (!soap_request.FromXml(session->request().content())) {
|
if (!soap_request.FromXml(session->request().content())) {
|
||||||
session->SetResponseStatus(HttpStatus::kBadRequest);
|
session->SendResponse(HttpStatus::kBadRequest);
|
||||||
session->SendResponse();
|
return;
|
||||||
return HttpStatus::kBadRequest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Error handling.
|
|
||||||
SoapResponse soap_response;
|
SoapResponse soap_response;
|
||||||
service->Handle(soap_request, &soap_response);
|
if (!service->Handle(soap_request, &soap_response)) {
|
||||||
|
session->SendResponse(HttpStatus::kBadRequest);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::string content;
|
std::string content;
|
||||||
soap_response.ToXml(&content);
|
soap_response.ToXml(&content);
|
||||||
|
session->SetResponseContent(std::move(content), kTextXmlUtf8);
|
||||||
session->SetResponseStatus(HttpStatus::kOK);
|
session->SendResponse(HttpStatus::kOK);
|
||||||
session->SetResponseContent(kTextXmlUtf8,
|
|
||||||
content.length(),
|
|
||||||
std::move(content));
|
|
||||||
session->SendResponse();
|
|
||||||
|
|
||||||
return HttpStatus::kOK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SoapServicePtr SoapRequestHandler::GetServiceByUrl(const std::string& url) {
|
SoapServicePtr SoapRequestHandler::GetServiceByUrl(const std::string& url) {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public:
|
|||||||
bool RegisterService(SoapServicePtr service, const std::string& url);
|
bool RegisterService(SoapServicePtr service, const std::string& url);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HttpStatus::Enum HandleSession(HttpSessionPtr session) override;
|
void HandleSession(HttpSessionPtr session) override;
|
||||||
|
|
||||||
SoapServicePtr GetServiceByUrl(const std::string& url);
|
SoapServicePtr GetServiceByUrl(const std::string& url);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#define WEBCC_SOAP_SERVICE_H_
|
#define WEBCC_SOAP_SERVICE_H_
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include "webcc/common.h"
|
||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
@@ -17,6 +18,9 @@ public:
|
|||||||
// Handle SOAP request, output the response.
|
// Handle SOAP request, output the response.
|
||||||
virtual bool Handle(const SoapRequest& soap_request,
|
virtual bool Handle(const SoapRequest& soap_request,
|
||||||
SoapResponse* soap_response) = 0;
|
SoapResponse* soap_response) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
HttpStatus::Enum http_status_ = HttpStatus::kOK;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::shared_ptr<SoapService> SoapServicePtr;
|
typedef std::shared_ptr<SoapService> SoapServicePtr;
|
||||||
|
|||||||
Reference in New Issue
Block a user