Update readme

master
Adam Gu 7 years ago
parent 662b7c4112
commit 81ed82a302

@ -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;
}
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;
double result = x + y; 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 { bool CalcService::GetParameters(const webcc::SoapRequest& soap_request,
// NOT_IMPLEMENTED double* x,
} double* y) {
} catch (boost::bad_lexical_cast&) { try {
// BAD_REQUEST *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) {

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

@ -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); HttpClient::HttpClient()
std::size_t optlen = sizeof(ms); : socket_(io_context_)
, timeout_seconds_(kReceiveMaxSeconds)
, deadline_timer_(io_context_) {
setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, optval, optlen); deadline_timer_.expires_at(boost::posix_time::pos_infin);
setsockopt(socket.native_handle(), SOL_SOCKET, SO_SNDTIMEO, optval, optlen);
#else // POSIX // Start the persistent actor that checks for deadline expiry.
CheckDeadline();
}
// TODO: This doesn't work! Consider to control timeout in server side. Error HttpClient::MakeRequest(const HttpRequest& request,
HttpResponse* response) {
assert(response != NULL);
struct timeval tv; Error error = kNoError;
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 if ((error = Connect(request)) != kNoError) {
} return error;
}
//////////////////////////////////////////////////////////////////////////////// // Send HTTP request.
HttpClient::HttpClient() if ((error = SendReqeust(request)) != kNoError) {
: timeout_seconds_(15) { return error;
} }
Error HttpClient::SendRequest(const HttpRequest& request, // Read and parse HTTP response.
HttpResponse* response) {
assert(response != NULL);
using boost::asio::ip::tcp; parser_ = std::make_unique<HttpResponseParser>(response);
error = ReadResponse(response);
return error;
}
tcp::socket socket(io_context_); 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 {
return kSocketTimeoutError;
}
} }
SetTimeout(socket, timeout_seconds_); return kNoError;
}
// Send HTTP request.
Error HttpClient::SendReqeust(const HttpRequest& request) {
LOG_VERB("http request:\n{\n%s}", request.Dump().c_str()); LOG_VERB("http request:\n{\n%s}", request.Dump().c_str());
try { deadline_timer_.expires_from_now(boost::posix_time::seconds(kSendMaxSeconds));
boost::asio::write(socket, request.ToBuffers());
} catch (boost::system::system_error&) { 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 kSocketWriteError;
} }
// Read and parse HTTP response. return kNoError;
}
HttpResponseParser parser(response); Error HttpClient::ReadResponse(HttpResponse* response) {
deadline_timer_.expires_from_now(
boost::posix_time::seconds(timeout_seconds_));
// NOTE: boost::system::error_code ec = boost::asio::error::would_block;
// We must stop trying to read once all content has been received, Error error = kNoError;
// because some servers will block extra call to read_some().
while (!parser.finished()) { socket_.async_read_some(
// read_some() will block until one or more bytes of data has been boost::asio::buffer(buffer_),
// read successfully, or until an error occurs. [this, &ec, &error, response](boost::system::error_code inner_ec,
std::size_t length = socket.read_some(boost::asio::buffer(buffer_), ec); std::size_t length) {
ec = inner_ec;
if (length == 0 || ec) {
#if defined _WINDOWS
if (ec.value() == WSAETIMEDOUT) {
return kSocketTimeoutError;
}
#endif
return kSocketReadError;
}
if (inner_ec || length == 0) {
error = kSocketReadError;
} else {
// Parse the response piece just read. // Parse the response piece just read.
// If the content has been fully received, next time flag "finished_" // If the content has been fully received, next time flag "finished_"
// will be set. // will be set.
Error error = parser.Parse(buffer_.data(), length); error = parser_->Parse(buffer_.data(), length);
if (error != kNoError) { if (error != kNoError) {
LOG_ERRO("failed to parse http response."); LOG_ERRO("failed to parse http response.");
return error; 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()); LOG_VERB("http response:\n{\n%s}", response->Dump().c_str());
}
return kNoError; 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

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

@ -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,
std::size_t content_length,
std::string&& content);
// Write response back to the client. // Write response back to the client with the given HTTP status.
void SendResponse(); void SendResponse(int status);
private: private:
void DoRead(); void DoRead();

@ -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_

@ -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.

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

Loading…
Cancel
Save