Update readme

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

@ -1,22 +1,27 @@
# 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:**
- [SOAP Client Tutorial](doc/SoapClientTutorial.md)
- [SOAP Server Tutorial](doc/SoapServerTutorial.md)
## Build Instructions
- [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
- Boost 1.66+
[C++ Boost](https://www.boost.org/) should be 1.66+ because Asio made some broken changes to the API in 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();
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;
}

@ -2,6 +2,9 @@
#include <iostream>
#include "boost/lexical_cast.hpp"
// Set to 0 to test our own calculator server created with webcc.
#define ACCESS_PARASOFT 0
CalcClient::CalcClient() {
Init();
}
@ -19,13 +22,22 @@ bool CalcClient::Multiply(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);
#else
return Calc("divide", "x", "y", x, y, result);
#endif
}
// Set to 0 to test our own calculator server created with webcc.
#define ACCESS_PARASOFT 0
bool CalcClient::NotExist(double x, double y, double* result) {
return Calc("notexist", "x", "y", x, y, result);
}
void CalcClient::Init() {
// Override the default timeout.
timeout_seconds_ = 5;
#if ACCESS_PARASOFT
url_ = "/glue/calculator";
host_ = "ws1.parasoft.com";

@ -16,6 +16,9 @@ public:
bool Divide(double x, double y, double* result);
// For testing purpose.
bool NotExist(double x, double y, double* result);
protected:
void Init();

@ -12,7 +12,6 @@ int main() {
printf("add: %.1f\n", result);
}
#if 0
if (calc.Subtract(x, y, &result)) {
printf("subtract: %.1f\n", result);
}
@ -24,7 +23,11 @@ int main() {
if (calc.Divide(x, y, &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;
}

@ -1,36 +1,94 @@
#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"
#if SLEEP_FOR_TIMEOUT_TEST
#include "boost/thread/thread.hpp"
#endif
#include "webcc/logger.h"
#include "webcc/soap_request.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,
webcc::SoapResponse* soap_response) {
try {
if (soap_request.operation() == "add") {
double x = boost::lexical_cast<double>(soap_request.GetParameter("x"));
double y = boost::lexical_cast<double>(soap_request.GetParameter("y"));
double x = 0.0;
double y = 0.0;
if (!GetParameters(soap_request, &x, &y)) {
return false;
}
const std::string& op = soap_request.operation();
double result = x + y;
LOG_INFO("Soap operation '%s': %.2f, %.2f", op.c_str(), 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));
std::function<double(double, double)> calc;
return true;
if (op == "add") {
calc = [](double x, double y) { return x + y; };
} else {
// NOT_IMPLEMENTED
} 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;
}
} catch (boost::bad_lexical_cast&) {
// BAD_REQUEST
} 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_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));
#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;
}
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,
webcc::SoapResponse* soap_response) override;
private:
bool GetParameters(const webcc::SoapRequest& soap_request,
double* x,
double* y);
};
#endif // CALC_SERVICE_H_

@ -1,4 +1,5 @@
#include <iostream>
#include "webcc/logger.h"
#include "webcc/soap_server.h"
#include "calc_service.h"
@ -14,6 +15,8 @@ int main(int argc, char* argv[]) {
return 1;
}
LOG_INIT(webcc::VERB, 0);
unsigned short port = std::atoi(argv[1]);
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 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) {

@ -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.
enum Error {
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.
// E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" }
// TODO: Rename (add soap prefix)

@ -1,62 +1,73 @@
#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
#include "boost/asio.hpp"
#else
#include "boost/asio/connect.hpp"
#include "boost/asio/ip/tcp.hpp"
#include "boost/asio/read.hpp"
#include "boost/asio/write.hpp"
#endif
#include "webcc/logger.h"
#include "webcc/http_response_parser.h"
#include "webcc/http_request.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 {
////////////////////////////////////////////////////////////////////////////////
// See https://stackoverflow.com/a/9079092
static void SetTimeout(boost::asio::ip::tcp::socket& socket,
int timeout_seconds) {
#if defined _WINDOWS
static const int kConnectMaxSeconds = 10;
static const int kSendMaxSeconds = 10;
static const int kReceiveMaxSeconds = 30;
int ms = timeout_seconds * 1000;
////////////////////////////////////////////////////////////////////////////////
const char* optval = reinterpret_cast<const char*>(&ms);
std::size_t optlen = sizeof(ms);
HttpClient::HttpClient()
: socket_(io_context_)
, timeout_seconds_(kReceiveMaxSeconds)
, deadline_timer_(io_context_) {
setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, optval, optlen);
setsockopt(socket.native_handle(), SOL_SOCKET, SO_SNDTIMEO, optval, optlen);
deadline_timer_.expires_at(boost::posix_time::pos_infin);
#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;
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));
Error error = kNoError;
#endif
}
if ((error = Connect(request)) != kNoError) {
return error;
}
// Send HTTP request.
////////////////////////////////////////////////////////////////////////////////
if ((error = SendReqeust(request)) != kNoError) {
return error;
}
HttpClient::HttpClient()
: timeout_seconds_(15) {
}
// Read and parse HTTP response.
Error HttpClient::SendRequest(const HttpRequest& request,
HttpResponse* response) {
assert(response != NULL);
parser_ = std::make_unique<HttpResponseParser>(response);
using boost::asio::ip::tcp;
error = ReadResponse(response);
tcp::socket socket(io_context_);
return error;
}
Error HttpClient::Connect(const HttpRequest& request) {
using boost::asio::ip::tcp;
tcp::resolver resolver(io_context_);
@ -66,7 +77,6 @@ Error HttpClient::SendRequest(const HttpRequest& request,
}
boost::system::error_code ec;
// tcp::resolver::results_type
auto endpoints = resolver.resolve(tcp::v4(), request.host(), port, ec);
if (ec) {
@ -77,58 +87,121 @@ Error HttpClient::SendRequest(const HttpRequest& request,
return kHostResolveError;
}
boost::asio::connect(socket, endpoints, ec);
if (ec) {
return kEndpointConnectError;
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) {
return kEndpointConnectError;
} else {
return kSocketTimeoutError;
}
}
SetTimeout(socket, timeout_seconds_);
// Send HTTP request.
return kNoError;
}
Error HttpClient::SendReqeust(const HttpRequest& request) {
LOG_VERB("http request:\n{\n%s}", request.Dump().c_str());
try {
boost::asio::write(socket, request.ToBuffers());
} catch (boost::system::system_error&) {
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;
}
// Read and parse HTTP response.
return kNoError;
}
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;
}
#endif
return kSocketReadError;
}
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());
}
// 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);
return error;
}
if (error != kNoError) {
LOG_ERRO("failed to parse http response.");
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);
LOG_VERB("http response:\n{\n%s}", response->Dump().c_str());
deadline_timer_.expires_at(boost::posix_time::pos_infin);
}
return kNoError;
// Put the actor back to sleep.
deadline_timer_.async_wait(std::bind(&HttpClient::CheckDeadline, this));
}
} // namespace webcc

@ -2,10 +2,14 @@
#define WEBCC_HTTP_CLIENT_H_
#include <array>
#include <memory>
#include "boost/asio/deadline_timer.hpp"
#include "boost/asio/ip/tcp.hpp"
#include "boost/asio/io_context.hpp"
#include "webcc/common.h"
#include "webcc/http_response_parser.h"
namespace webcc {
@ -16,19 +20,44 @@ class HttpClient {
public:
HttpClient();
// Set socket send & recv timeout.
void set_timeout_seconds(int seconds) {
timeout_seconds_ = seconds;
~HttpClient() = default;
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.
Error SendRequest(const HttpRequest& request,
HttpResponse* response);
// Make a HTTP request.
// Connect to the server, send the request, wait until the response is
// 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:
boost::asio::io_context io_context_;
boost::asio::ip::tcp::socket socket_;
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_;
// Timer for the timeout control.
boost::asio::deadline_timer deadline_timer_;
};
} // namespace webcc

@ -13,7 +13,8 @@ class HttpParser {
public:
explicit HttpParser(HttpMessage* message);
~HttpParser() = default;
virtual ~HttpParser() = default;
HttpParser(const HttpParser&) = delete;
HttpParser& operator=(const HttpParser&) = delete;

@ -39,7 +39,7 @@ private:
void WorkerRoutine();
// Called by the worker routine.
virtual HttpStatus::Enum HandleSession(HttpSessionPtr session) = 0;
virtual void HandleSession(HttpSessionPtr session) = 0;
private:
Queue<HttpSessionPtr> queue_;

@ -11,6 +11,8 @@ class HttpRequestParser : public HttpParser {
public:
explicit HttpRequestParser(HttpRequest* request);
~HttpRequestParser() override = default;
private:
Error ParseStartLine(const std::string& line) override;

@ -16,8 +16,8 @@ class HttpResponse : public HttpMessage {
const HttpResponse& response);
public:
HttpResponse() {
}
HttpResponse() = default;
~HttpResponse() override = default;
int status() const {
return status_;

@ -11,6 +11,8 @@ class HttpResponseParser : public HttpParser {
public:
explicit HttpResponseParser(HttpResponse* response);
~HttpResponseParser() override = default;
private:
// Parse HTTP start line; E.g., "HTTP/1.1 200 OK".
Error ParseStartLine(const std::string& line) override;

@ -29,31 +29,32 @@ void HttpSession::Stop() {
socket_.close(ec);
}
void HttpSession::SetResponseContent(const std::string& content_type,
std::size_t content_length,
std::string&& content) {
response_.SetContentType(content_type);
void HttpSession::SetResponseContent(std::string&& content,
const std::string& content_type) {
response_.SetContent(std::move(content));
response_.SetContentType(content_type);
}
void HttpSession::SendResponse() {
void HttpSession::SendResponse(int status) {
response_.set_status(status);
DoWrite();
}
void HttpSession::DoRead() {
auto handler = std::bind(&HttpSession::HandleRead,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2);
socket_.async_read_some(boost::asio::buffer(buffer_), handler);
socket_.async_read_some(boost::asio::buffer(buffer_),
std::bind(&HttpSession::HandleRead,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void HttpSession::DoWrite() {
auto handler = std::bind(&HttpSession::HandleWrite,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2);
boost::asio::async_write(socket_, response_.ToBuffers(), handler);
boost::asio::async_write(socket_,
response_.ToBuffers(),
std::bind(&HttpSession::HandleWrite,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void HttpSession::HandleRead(boost::system::error_code ec,

@ -33,16 +33,11 @@ public:
void Stop();
void SetResponseStatus(int status) {
response_.set_status(status);
}
void SetResponseContent(const std::string& content_type,
std::size_t content_length,
std::string&& content);
void SetResponseContent(std::string&& content,
const std::string& content_type);
// Write response back to the client.
void SendResponse();
// Write response back to the client with the given HTTP status.
void SendResponse(int status);
private:
void DoRead();

@ -23,9 +23,11 @@ void LogInit(int level, int modes);
void LogWrite(int level, const char* file, int line, const char* format, ...);
} // namespace webcc
// Initialize the logger with a level.
// 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))
@ -33,19 +35,19 @@ void LogWrite(int level, const char* file, int line, const char* format, ...);
#define __FILENAME__ strrchr("\\" __FILE__, '\\') + 1
#define LOG_VERB(format, ...) \
LogWrite(VERB, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
webcc::LogWrite(webcc::VERB, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
#define LOG_INFO(format, ...) \
LogWrite(INFO, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
webcc::LogWrite(webcc::INFO, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
#define LOG_WARN(format, ...) \
LogWrite(WARN, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
webcc::LogWrite(webcc::WARN, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
#define LOG_ERRO(format, ...) \
LogWrite(ERRO, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
webcc::LogWrite(webcc::ERRO, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
#define LOG_FATA(format, ...) \
LogWrite(FATA, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
webcc::LogWrite(webcc::FATA, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
#else
@ -53,19 +55,19 @@ void LogWrite(int level, const char* file, int line, const char* format, ...);
#define __FILENAME__ strrchr("/" __FILE__, '/') + 1
#define LOG_VERB(format, args...) \
LogWrite(VERB, __FILENAME__, __LINE__, format, ##args);
webcc::LogWrite(webcc::VERB, __FILENAME__, __LINE__, 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...) \
LogWrite(WARN, __FILENAME__, __LINE__, format, ##args);
webcc::LogWrite(webcc::WARN, __FILENAME__, __LINE__, 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...) \
LogWrite(FATA, __FILENAME__, __LINE__, format, ##args);
webcc::LogWrite(webcc::FATA, __FILENAME__, __LINE__, format, ##args);
#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
} // namespace webcc
#endif // WEBCC_LOGGER_H_

@ -59,42 +59,39 @@ bool RestRequestHandler::RegisterService(RestServicePtr service,
return service_manager_.AddService(service, url);
}
HttpStatus::Enum RestRequestHandler::HandleSession(HttpSessionPtr session) {
void RestRequestHandler::HandleSession(HttpSessionPtr session) {
Url url(session->request().url());
if (!url.IsValid()) {
session->SetResponseStatus(HttpStatus::kBadRequest);
session->SendResponse();
return HttpStatus::kBadRequest;
session->SendResponse(HttpStatus::kBadRequest);
return;
}
std::vector<std::string> sub_matches;
RestServicePtr service = service_manager_.GetService(url.path(), &sub_matches);
if (!service) {
LOG_WARN("No service matches the URL: %s", url.path().c_str());
session->SetResponseStatus(HttpStatus::kBadRequest);
session->SendResponse();
return HttpStatus::kBadRequest;
session->SendResponse(HttpStatus::kBadRequest);
return;
}
// TODO: Only for GET?
Url::Query query = Url::SplitQuery(url.query());
// TODO: Error handling.
std::string content;
service->Handle(session->request().method(),
sub_matches,
query,
session->request().content(),
&content);
session->SetResponseStatus(HttpStatus::kOK);
session->SetResponseContent(kTextJsonUtf8,
content.length(),
std::move(content));
session->SendResponse();
return HttpStatus::kOK;
bool ok = service->Handle(session->request().method(),
sub_matches,
query,
session->request().content(),
&content);
if (!ok) {
// TODO: Could be other than kBadRequest.
session->SendResponse(HttpStatus::kBadRequest);
return;
}
session->SetResponseContent(std::move(content), kTextJsonUtf8);
session->SendResponse(HttpStatus::kOK);
}
////////////////////////////////////////////////////////////////////////////////

@ -75,7 +75,7 @@ public:
bool RegisterService(RestServicePtr service, const std::string& url);
private:
HttpStatus::Enum HandleSession(HttpSessionPtr session) override;
void HandleSession(HttpSessionPtr session) override;
private:
RestServiceManager service_manager_;

@ -48,7 +48,12 @@ Error SoapClient::Call(const std::string& operation,
HttpResponse http_response;
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) {
return error;

@ -13,12 +13,10 @@ namespace webcc {
//
class SoapClient {
public:
virtual ~SoapClient() {
}
virtual ~SoapClient() = default;
protected:
SoapClient() {
}
SoapClient() = default;
// A generic wrapper to make a call.
// NOTE: The parameters should be movable.
@ -27,6 +25,9 @@ protected:
std::string* result);
protected:
// -1 means default timeout (normally 30s) will be used.
int timeout_seconds_ = -1;
Namespace soapenv_ns_; // SOAP envelope namespace.
Namespace service_ns_; // Namespace for your web service.

@ -16,36 +16,30 @@ bool SoapRequestHandler::RegisterService(SoapServicePtr service,
return true;
}
HttpStatus::Enum SoapRequestHandler::HandleSession(HttpSessionPtr session) {
void SoapRequestHandler::HandleSession(HttpSessionPtr session) {
SoapServicePtr service = GetServiceByUrl(session->request().url());
if (!service) {
session->SetResponseStatus(HttpStatus::kBadRequest);
session->SendResponse();
return HttpStatus::kBadRequest;
session->SendResponse(HttpStatus::kBadRequest);
return;
}
// Parse the SOAP request XML.
SoapRequest soap_request;
if (!soap_request.FromXml(session->request().content())) {
session->SetResponseStatus(HttpStatus::kBadRequest);
session->SendResponse();
return HttpStatus::kBadRequest;
session->SendResponse(HttpStatus::kBadRequest);
return;
}
// TODO: Error handling.
SoapResponse soap_response;
service->Handle(soap_request, &soap_response);
if (!service->Handle(soap_request, &soap_response)) {
session->SendResponse(HttpStatus::kBadRequest);
return;
}
std::string content;
soap_response.ToXml(&content);
session->SetResponseStatus(HttpStatus::kOK);
session->SetResponseContent(kTextXmlUtf8,
content.length(),
std::move(content));
session->SendResponse();
return HttpStatus::kOK;
session->SetResponseContent(std::move(content), kTextXmlUtf8);
session->SendResponse(HttpStatus::kOK);
}
SoapServicePtr SoapRequestHandler::GetServiceByUrl(const std::string& url) {

@ -24,7 +24,7 @@ public:
bool RegisterService(SoapServicePtr service, const std::string& url);
private:
HttpStatus::Enum HandleSession(HttpSessionPtr session) override;
void HandleSession(HttpSessionPtr session) override;
SoapServicePtr GetServiceByUrl(const std::string& url);

@ -2,6 +2,7 @@
#define WEBCC_SOAP_SERVICE_H_
#include <memory>
#include "webcc/common.h"
namespace webcc {
@ -17,6 +18,9 @@ public:
// Handle SOAP request, output the response.
virtual bool Handle(const SoapRequest& soap_request,
SoapResponse* soap_response) = 0;
protected:
HttpStatus::Enum http_status_ = HttpStatus::kOK;
};
typedef std::shared_ptr<SoapService> SoapServicePtr;

Loading…
Cancel
Save