Reorganize folder structure.

This commit is contained in:
Adam Gu
2018-07-06 13:59:16 +08:00
parent 3b13cc74f7
commit 88511caff6
82 changed files with 38 additions and 48 deletions
+75
View File
@@ -0,0 +1,75 @@
# Don't use any deprecated definitions (e.g., io_service).
add_definitions(-DBOOST_ASIO_NO_DEPRECATED)
if(MSVC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif()
set(SRCS
async_http_client.cc
async_http_client.h
async_rest_client.cc
async_rest_client.h
globals.cc
globals.h
http_client.cc
http_client.h
http_connection.cc
http_connection.h
http_message.cc
http_message.h
http_parser.cc
http_parser.h
http_request.cc
http_request.h
http_request_handler.cc
http_request_handler.h
http_request_parser.cc
http_request_parser.h
http_response.cc
http_response.h
http_response_parser.cc
http_response_parser.h
http_server.cc
http_server.h
logger.cc
logger.h
queue.h
rest_client.cc
rest_client.h
rest_request_handler.cc
rest_request_handler.h
rest_service_manager.cc
rest_service_manager.h
rest_server.h
rest_service.cc
rest_service.h
url.cc
url.h
utility.cc
utility.h
)
if(WEBCC_ENABLE_SOAP)
# SOAP specific sources.
set(SOAP_SRCS
soap_client.cc
soap_message.h
soap_request_handler.cc
soap_response.h
soap_xml.cc
soap_client.h
soap_request.cc
soap_request_handler.h
soap_server.h
soap_xml.h
soap_message.cc
soap_request.h
soap_response.cc
soap_service.h
)
set(SRCS ${SRCS} ${SOAP_SRCS})
endif()
add_library(webcc ${SRCS})
+235
View File
@@ -0,0 +1,235 @@
#include "webcc/async_http_client.h"
#include "boost/asio/connect.hpp"
#include "boost/asio/read.hpp"
#include "boost/asio/write.hpp"
#include "webcc/logger.h"
#include "webcc/utility.h"
// NOTE:
// The timeout control is inspired by the following Asio example:
// example\cpp03\timeouts\async_tcp_client.cpp
namespace webcc {
extern void AdjustBufferSize(std::size_t content_length,
std::vector<char>* buffer);
AsyncHttpClient::AsyncHttpClient(boost::asio::io_context& io_context)
: socket_(io_context),
resolver_(new tcp::resolver(io_context)),
buffer_(kBufferSize),
deadline_(io_context),
timeout_seconds_(kMaxReceiveSeconds),
stopped_(false),
timed_out_(false) {
}
Error AsyncHttpClient::Request(std::shared_ptr<HttpRequest> request,
HttpResponseHandler response_handler) {
assert(request);
assert(response_handler);
response_.reset(new HttpResponse());
response_parser_.reset(new HttpResponseParser(response_.get()));
LOG_VERB("HTTP request:\n%s", request->Dump(4, "> ").c_str());
request_ = request;
response_handler_ = response_handler;
std::string port = request->port();
if (port.empty()) {
port = "80";
}
auto handler = std::bind(&AsyncHttpClient::ResolveHandler,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2);
resolver_->async_resolve(tcp::v4(), request->host(), port, handler);
return kNoError;
}
void AsyncHttpClient::Stop() {
stopped_ = true;
boost::system::error_code ignored_ec;
socket_.close(ignored_ec);
deadline_.cancel();
}
void AsyncHttpClient::ResolveHandler(boost::system::error_code ec,
tcp::resolver::results_type results) {
if (ec) {
LOG_ERRO("Can't resolve host (%s): %s, %s", ec.message().c_str(),
request_->host().c_str(), request_->port().c_str());
response_handler_(response_, kHostResolveError, timed_out_);
} else {
// Start the connect actor.
endpoints_ = results;
AsyncConnect(endpoints_.begin());
// Start the deadline actor. You will note that we're not setting any
// particular deadline here. Instead, the connect and input actors will
// update the deadline prior to each asynchronous operation.
deadline_.async_wait(std::bind(&AsyncHttpClient::CheckDeadline,
shared_from_this()));
}
}
void AsyncHttpClient::AsyncConnect(EndpointIterator endpoint_iter) {
if (endpoint_iter != endpoints_.end()) {
LOG_VERB("Connecting to [%s]...",
EndpointToString(endpoint_iter->endpoint()).c_str());
// Set a deadline for the connect operation.
deadline_.expires_from_now(boost::posix_time::seconds(kMaxConnectSeconds));
timed_out_ = false;
// Start the asynchronous connect operation.
socket_.async_connect(endpoint_iter->endpoint(),
std::bind(&AsyncHttpClient::ConnectHandler,
shared_from_this(),
std::placeholders::_1,
endpoint_iter));
} else {
// There are no more endpoints to try. Shut down the client.
Stop();
response_handler_(response_, kEndpointConnectError, timed_out_);
}
}
void AsyncHttpClient::ConnectHandler(boost::system::error_code ec,
EndpointIterator endpoint_iter) {
if (stopped_) {
return;
}
if (!socket_.is_open()) {
// The async_connect() function automatically opens the socket at the start
// of the asynchronous operation. If the socket is closed at this time then
// the timeout handler must have run first.
LOG_WARN("Connect timed out.");
// Try the next available endpoint.
AsyncConnect(++endpoint_iter);
} else if (ec) {
// The connect operation failed before the deadline expired.
// We need to close the socket used in the previous connection attempt
// before starting a new one.
socket_.close();
// Try the next available endpoint.
AsyncConnect(++endpoint_iter);
} else {
// Connection established.
AsyncWrite();
}
}
void AsyncHttpClient::AsyncWrite() {
if (stopped_) {
return;
}
deadline_.expires_from_now(boost::posix_time::seconds(kMaxSendSeconds));
boost::asio::async_write(socket_,
request_->ToBuffers(),
std::bind(&AsyncHttpClient::WriteHandler,
shared_from_this(),
std::placeholders::_1));
}
void AsyncHttpClient::WriteHandler(boost::system::error_code ec) {
if (stopped_) {
return;
}
if (ec) {
Stop();
response_handler_(response_, kSocketWriteError, timed_out_);
} else {
deadline_.expires_from_now(boost::posix_time::seconds(timeout_seconds_));
AsyncRead();
}
}
void AsyncHttpClient::AsyncRead() {
socket_.async_read_some(boost::asio::buffer(buffer_),
std::bind(&AsyncHttpClient::ReadHandler,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void AsyncHttpClient::ReadHandler(boost::system::error_code ec,
std::size_t length) {
if (stopped_) {
return;
}
LOG_VERB("Socket async read handler.");
if (ec || length == 0) {
Stop();
LOG_ERRO("Socket read error.");
response_handler_(response_, kSocketReadError, timed_out_);
return;
}
LOG_INFO("Read data, length: %d.", length);
bool content_length_parsed = response_parser_->content_length_parsed();
// Parse the response piece just read.
// If the content has been fully received, |finished()| will be true.
if (!response_parser_->Parse(buffer_.data(), length)) {
Stop();
LOG_ERRO("Failed to parse HTTP response.");
response_handler_(response_, kHttpError, timed_out_);
return;
}
if (!content_length_parsed &&
response_parser_->content_length_parsed()) {
// Content length just has been parsed.
AdjustBufferSize(response_parser_->content_length(), &buffer_);
}
if (response_parser_->finished()) {
LOG_INFO("Finished to read and parse HTTP response.");
LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str());
Stop();
response_handler_(response_, kNoError, timed_out_);
return;
}
AsyncRead();
}
void AsyncHttpClient::CheckDeadline() {
if (stopped_) {
return;
}
if (deadline_.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.
LOG_WARN("HTTP client timed out.");
Stop();
timed_out_ = true;
}
// Put the actor back to sleep.
deadline_.async_wait(std::bind(&AsyncHttpClient::CheckDeadline,
shared_from_this()));
}
} // namespace webcc
+87
View File
@@ -0,0 +1,87 @@
#ifndef WEBCC_ASYNC_HTTP_CLIENT_H_
#define WEBCC_ASYNC_HTTP_CLIENT_H_
#include <functional>
#include <memory>
#include <vector>
#include "boost/asio/deadline_timer.hpp"
#include "boost/asio/io_context.hpp"
#include "boost/asio/ip/tcp.hpp"
#include "webcc/globals.h"
#include "webcc/http_request.h"
#include "webcc/http_response.h"
#include "webcc/http_response_parser.h"
namespace webcc {
// Request handler/callback.
typedef std::function<void(HttpResponsePtr, Error, bool)> HttpResponseHandler;
class AsyncHttpClient : public std::enable_shared_from_this<AsyncHttpClient> {
public:
explicit AsyncHttpClient(boost::asio::io_context& io_context);
DELETE_COPY_AND_ASSIGN(AsyncHttpClient);
void set_timeout_seconds(int timeout_seconds) {
timeout_seconds_ = timeout_seconds;
}
// Asynchronously connect to the server, send the request, read the response,
// and call the |response_handler| when all these finish.
Error Request(HttpRequestPtr request, HttpResponseHandler response_handler);
// Terminate all the actors to shut down the connection. It may be called by
// the user of the client class, or by the class itself in response to
// graceful termination or an unrecoverable error.
void Stop();
private:
using tcp = boost::asio::ip::tcp;
typedef tcp::resolver::results_type::iterator EndpointIterator;
void ResolveHandler(boost::system::error_code ec,
tcp::resolver::results_type results);
void AsyncConnect(EndpointIterator endpoint_iter);
void ConnectHandler(boost::system::error_code ec,
EndpointIterator endpoint_iter);
void AsyncWrite();
void WriteHandler(boost::system::error_code ec);
void AsyncRead();
void ReadHandler(boost::system::error_code ec, std::size_t length);
void CheckDeadline();
tcp::socket socket_;
std::unique_ptr<tcp::resolver> resolver_;
tcp::resolver::results_type endpoints_;
std::shared_ptr<HttpRequest> request_;
std::vector<char> buffer_;
HttpResponsePtr response_;
std::unique_ptr<HttpResponseParser> response_parser_;
HttpResponseHandler response_handler_;
// Timer for the timeout control.
boost::asio::deadline_timer deadline_;
// Maximum seconds to wait before the client cancels the operation.
// Only for receiving response from server.
int timeout_seconds_;
bool stopped_;
bool timed_out_;
};
typedef std::shared_ptr<AsyncHttpClient> HttpAsyncClientPtr;
} // namespace webcc
#endif // WEBCC_ASYNC_HTTP_CLIENT_H_
+38
View File
@@ -0,0 +1,38 @@
#include "webcc/async_rest_client.h"
namespace webcc {
AsyncRestClient::AsyncRestClient(boost::asio::io_context& io_context,
const std::string& host,
const std::string& port)
: io_context_(io_context), host_(host), port_(port), timeout_seconds_(0) {
}
void AsyncRestClient::Request(const std::string& method,
const std::string& url,
const std::string& content,
HttpResponseHandler response_handler) {
response_handler_ = response_handler;
HttpRequestPtr request(new webcc::HttpRequest());
request->set_method(method);
request->set_url(url);
request->SetHost(host_, port_);
if (!content.empty()) {
request->SetContent(content);
}
request->Build();
HttpAsyncClientPtr http_client(new AsyncHttpClient(io_context_));
if (timeout_seconds_ > 0) {
http_client->set_timeout_seconds(timeout_seconds_);
}
http_client->Request(request, response_handler_);
}
} // namespace webcc
+67
View File
@@ -0,0 +1,67 @@
#ifndef WEBCC_ASYNC_REST_CLIENT_H_
#define WEBCC_ASYNC_REST_CLIENT_H_
#include <string>
#include "webcc/async_http_client.h"
namespace webcc {
class AsyncRestClient {
public:
AsyncRestClient(boost::asio::io_context& io_context, // NOLINT
const std::string& host,
const std::string& port);
void set_timeout_seconds(int timeout_seconds) {
timeout_seconds_ = timeout_seconds;
}
void Get(const std::string& url,
HttpResponseHandler response_handler) {
Request(kHttpGet, url, "", response_handler);
}
void Post(const std::string& url,
const std::string& content,
HttpResponseHandler response_handler) {
Request(kHttpPost, url, content, response_handler);
}
void Put(const std::string& url,
const std::string& content,
HttpResponseHandler response_handler) {
Request(kHttpPut, url, content, response_handler);
}
void Patch(const std::string& url,
const std::string& content,
HttpResponseHandler response_handler) {
Request(kHttpPatch, url, content, response_handler);
}
void Delete(const std::string& url,
HttpResponseHandler response_handler) {
Request(kHttpDelete, url, "", response_handler);
}
private:
void Request(const std::string& method,
const std::string& url,
const std::string& content,
HttpResponseHandler response_handler);
boost::asio::io_context& io_context_;
std::string host_;
std::string port_;
HttpResponseHandler response_handler_;
// Timeout in seconds; only effective when > 0.
int timeout_seconds_;
};
} // namespace webcc
#endif // WEBCC_ASYNC_REST_CLIENT_H_
+95
View File
@@ -0,0 +1,95 @@
#include "webcc/globals.h"
#include <utility> // for move()
namespace webcc {
// -----------------------------------------------------------------------------
// NOTE: Field names are case-insensitive.
// See https://stackoverflow.com/a/5259004 for more details.
const std::string kHost = "Host";
const std::string kContentType = "Content-Type";
const std::string kContentLength = "Content-Length";
#ifdef WEBCC_ENABLE_SOAP
const std::string kSoapAction = "SOAPAction";
#endif // WEBCC_ENABLE_SOAP
const std::string kTextJsonUtf8 = "text/json; charset=utf-8";
#ifdef WEBCC_ENABLE_SOAP
// According to www.w3.org when placing SOAP messages in HTTP bodies, the HTTP
// Content-type header must be chosen as "application/soap+xml" [RFC 3902].
// But in practice, many web servers cannot understand it.
// See: https://www.w3.org/TR/2007/REC-soap12-part0-20070427/#L26854
const std::string kTextXmlUtf8 = "text/xml; charset=utf-8";
#endif // WEBCC_ENABLE_SOAP
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* DescribeError(Error error) {
switch (error) {
case kHostResolveError:
return "Host resolve error";
case kEndpointConnectError:
return "Endpoint connect error";
case kSocketReadError:
return "Socket read error";
case kSocketWriteError:
return "Socket write error";
case kHttpError:
return "HTTP error";
case kXmlError:
return "XML error";
default:
return "";
}
}
// -----------------------------------------------------------------------------
Parameter::Parameter(const std::string& key, const char* value)
: key_(key), value_(value) {
}
Parameter::Parameter(const std::string& key, const std::string& value)
: key_(key), value_(value) {
}
Parameter::Parameter(const std::string& key, std::string&& value)
: key_(key), value_(std::move(value)) {
}
Parameter::Parameter(const std::string& key, int value)
: key_(key), value_(std::to_string(value)) {
}
Parameter::Parameter(const std::string& key, double value)
: key_(key), value_(std::to_string(value)) {
}
Parameter::Parameter(const std::string& key, bool value)
: key_(key), value_(value ? "true" : "false") {
}
Parameter::Parameter(Parameter&& rhs)
: key_(std::move(rhs.key_)), value_(std::move(rhs.value_)) {
}
Parameter& Parameter::operator=(Parameter&& rhs) {
if (&rhs != this) {
key_ = std::move(rhs.key_);
value_ = std::move(rhs.value_);
}
return *this;
}
} // namespace webcc
+126
View File
@@ -0,0 +1,126 @@
#ifndef WEBCC_GLOBALS_H_
#define WEBCC_GLOBALS_H_
#include <string>
#include <vector>
// -----------------------------------------------------------------------------
// Macros
// Explicitly declare the copy constructor and assignment operator as deleted.
#define DELETE_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&) = delete; \
TypeName& operator=(const TypeName&) = delete;
namespace webcc {
// -----------------------------------------------------------------------------
// Constants
// Default buffer size for socket reading.
const std::size_t kBufferSize = 1024;
const std::size_t kInvalidLength = static_cast<std::size_t>(-1);
// Timeout seconds.
// TODO
const int kMaxConnectSeconds = 10;
const int kMaxSendSeconds = 30;
const int kMaxReceiveSeconds = 30;
extern const std::string kHost;
extern const std::string kContentType;
extern const std::string kContentLength;
#ifdef WEBCC_ENABLE_SOAP
extern const std::string kSoapAction;
#endif // WEBCC_ENABLE_SOAP
extern const std::string kTextJsonUtf8;
#ifdef WEBCC_ENABLE_SOAP
extern const std::string kTextXmlUtf8;
#endif // WEBCC_ENABLE_SOAP
// 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,
kHostResolveError,
kEndpointConnectError,
kSocketReadError,
kSocketWriteError,
kHttpError,
kXmlError,
};
// Return a descriptive message for the given error code.
const char* DescribeError(Error error);
// -----------------------------------------------------------------------------
// Key-value parameter.
class Parameter {
public:
Parameter() = default;
Parameter(const Parameter&) = default;
Parameter& operator=(const Parameter&) = default;
Parameter(const std::string& key, const char* value);
Parameter(const std::string& key, const std::string& value);
Parameter(const std::string& key, std::string&& value);
Parameter(const std::string& key, int value);
Parameter(const std::string& key, double value);
Parameter(const std::string& key, bool value);
// Use "= default" if drop the support of VS 2013.
Parameter(Parameter&& rhs);
// Use "= default" if drop the support of VS 2013.
Parameter& operator=(Parameter&& rhs);
const std::string& key() const { return key_; }
const std::string& value() const { return value_; }
const char* c_key() const { return key_.c_str(); }
const char* c_value() const { return value_.c_str(); }
std::string ToString() const {
return key_ + "=" + value_;
}
private:
std::string key_;
std::string value_;
};
} // namespace webcc
#endif // WEBCC_GLOBALS_H_
+247
View File
@@ -0,0 +1,247 @@
#include "webcc/http_client.h"
#include <string>
#include "boost/asio/connect.hpp"
#include "boost/asio/read.hpp"
#include "boost/asio/write.hpp"
#include "boost/date_time/posix_time/posix_time.hpp"
#include "boost/lambda/bind.hpp"
#include "boost/lambda/lambda.hpp"
#include "webcc/logger.h"
// NOTE:
// The timeout control is inspired by the following Asio example:
// example\cpp03\timeouts\blocking_tcp_client.cpp
namespace webcc {
// Adjust buffer size according to content length.
// This is to avoid reading too many times.
// Also used by AsyncHttpClient.
void AdjustBufferSize(std::size_t content_length, std::vector<char>* buffer) {
const std::size_t kMaxTimes = 10;
// According to test, a client never read more than 200000 bytes a time.
// So it doesn't make sense to set any larger size, e.g., 1MB.
const std::size_t kMaxBufferSize = 200000;
LOG_INFO("Adjust buffer size according to content length.");
std::size_t min_buffer_size = content_length / kMaxTimes;
if (min_buffer_size > buffer->size()) {
buffer->resize(std::min(min_buffer_size, kMaxBufferSize));
LOG_INFO("Resize read buffer to %u.", buffer->size());
} else {
LOG_INFO("Keep the current buffer size: %u.", buffer->size());
}
}
HttpClient::HttpClient()
: socket_(io_context_),
buffer_(kBufferSize),
deadline_(io_context_),
timeout_seconds_(kMaxReceiveSeconds),
stopped_(false),
timed_out_(false),
error_(kNoError) {
}
bool HttpClient::Request(const HttpRequest& request) {
response_.reset(new HttpResponse());
response_parser_.reset(new HttpResponseParser(response_.get()));
stopped_ = false;
timed_out_ = false;
// Start the persistent actor that checks for deadline expiry.
deadline_.expires_at(boost::posix_time::pos_infin);
CheckDeadline();
if ((error_ = Connect(request)) != kNoError) {
return false;
}
if ((error_ = SendReqeust(request)) != kNoError) {
return false;
}
if ((error_ = ReadResponse()) != kNoError) {
return false;
}
return true;
}
void HttpClient::Stop() {
stopped_ = true;
boost::system::error_code ignored_ec;
socket_.close(ignored_ec);
deadline_.cancel();
}
Error HttpClient::Connect(const HttpRequest& request) {
using boost::asio::ip::tcp;
tcp::resolver resolver(io_context_);
std::string port = request.port();
if (port.empty()) {
port = "80";
}
boost::system::error_code ec;
auto endpoints = resolver.resolve(tcp::v4(), request.host(), port, ec);
if (ec) {
LOG_ERRO("Can't resolve host (%s): %s, %s", ec.message().c_str(),
request.host().c_str(), port.c_str());
return kHostResolveError;
}
deadline_.expires_from_now(boost::posix_time::seconds(kMaxConnectSeconds));
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()) {
Stop();
if (!ec) {
timed_out_ = true;
}
return kEndpointConnectError;
}
return kNoError;
}
Error HttpClient::SendReqeust(const HttpRequest& request) {
LOG_VERB("HTTP request:\n%s", request.Dump(4, "> ").c_str());
deadline_.expires_from_now(boost::posix_time::seconds(kMaxSendSeconds));
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) {
Stop();
return kSocketWriteError;
}
return kNoError;
}
Error HttpClient::ReadResponse() {
deadline_.expires_from_now(boost::posix_time::seconds(timeout_seconds_));
Error error = kNoError;
DoReadResponse(&error);
if (error == kNoError) {
LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str());
}
return error;
}
void HttpClient::DoReadResponse(Error* error) {
boost::system::error_code ec = boost::asio::error::would_block;
socket_.async_read_some(
boost::asio::buffer(buffer_),
[this, &ec, error](boost::system::error_code inner_ec,
std::size_t length) {
ec = inner_ec;
LOG_VERB("Socket async read handler.");
if (stopped_) {
return;
}
if (inner_ec || length == 0) {
Stop();
*error = kSocketReadError;
LOG_ERRO("Socket read error.");
return;
}
LOG_INFO("Read data, length: %u.", length);
bool content_length_parsed = response_parser_->content_length_parsed();
// Parse the response piece just read.
if (!response_parser_->Parse(buffer_.data(), length)) {
Stop();
*error = kHttpError;
LOG_ERRO("Failed to parse HTTP response.");
return;
}
if (!content_length_parsed &&
response_parser_->content_length_parsed()) {
// Content length just has been parsed.
AdjustBufferSize(response_parser_->content_length(), &buffer_);
}
if (response_parser_->finished()) {
// Stop trying to read once all content has been received,
// because some servers will block extra call to read_some().
Stop();
LOG_INFO("Finished to read and parse HTTP response.");
return;
}
DoReadResponse(error);
});
// Block until the asynchronous operation has completed.
do {
io_context_.run_one();
} while (ec == boost::asio::error::would_block);
}
void HttpClient::CheckDeadline() {
if (stopped_) {
return;
}
if (deadline_.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.
LOG_WARN("HTTP client timed out.");
Stop();
timed_out_ = true;
}
// Put the actor back to sleep.
deadline_.async_wait(std::bind(&HttpClient::CheckDeadline, this));
}
} // namespace webcc
+78
View File
@@ -0,0 +1,78 @@
#ifndef WEBCC_HTTP_CLIENT_H_
#define WEBCC_HTTP_CLIENT_H_
#include <cassert>
#include <memory>
#include <vector>
#include "boost/asio/deadline_timer.hpp"
#include "boost/asio/io_context.hpp"
#include "boost/asio/ip/tcp.hpp"
#include "webcc/globals.h"
#include "webcc/http_request.h"
#include "webcc/http_response.h"
#include "webcc/http_response_parser.h"
namespace webcc {
class HttpClient {
public:
HttpClient();
~HttpClient() = default;
DELETE_COPY_AND_ASSIGN(HttpClient);
void set_timeout_seconds(int timeout_seconds) {
assert(timeout_seconds > 0);
timeout_seconds_ = timeout_seconds;
}
HttpResponsePtr response() const { return response_; }
bool timed_out() const { return timed_out_; }
Error error() const { return error_; }
// Connect to server, send request, wait until response is received.
bool Request(const HttpRequest& request);
private:
// Terminate all the actors to shut down the connection.
void Stop();
Error Connect(const HttpRequest& request);
Error SendReqeust(const HttpRequest& request);
Error ReadResponse();
void DoReadResponse(Error* error);
void CheckDeadline();
boost::asio::io_context io_context_;
boost::asio::ip::tcp::socket socket_;
std::vector<char> buffer_;
HttpResponsePtr response_;
std::unique_ptr<HttpResponseParser> response_parser_;
boost::asio::deadline_timer deadline_;
// Maximum seconds to wait before the client cancels the operation.
// Only for receiving response from server.
int timeout_seconds_;
bool stopped_;
// If the error was caused by timeout or not.
bool timed_out_;
Error error_;
};
} // namespace webcc
#endif // WEBCC_HTTP_CLIENT_H_
+109
View File
@@ -0,0 +1,109 @@
#include "webcc/http_connection.h"
#include <utility> // for move()
#include <vector>
#include "boost/asio/write.hpp"
#include "boost/date_time/posix_time/posix_time.hpp"
#include "webcc/http_request_handler.h"
#include "webcc/logger.h"
namespace webcc {
HttpConnection::HttpConnection(boost::asio::ip::tcp::socket socket,
HttpRequestHandler* handler)
: socket_(std::move(socket)),
buffer_(kBufferSize),
request_handler_(handler),
request_parser_(&request_) {
}
void HttpConnection::Start() {
AsyncRead();
}
void HttpConnection::Close() {
boost::system::error_code ec;
socket_.close(ec);
}
void HttpConnection::SetResponseContent(std::string&& content,
const std::string& content_type) {
response_.SetContent(std::move(content));
response_.SetContentType(content_type);
}
void HttpConnection::SendResponse(HttpStatus::Enum status) {
response_.set_status(status);
AsyncWrite();
}
void HttpConnection::AsyncRead() {
socket_.async_read_some(boost::asio::buffer(buffer_),
std::bind(&HttpConnection::ReadHandler,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void HttpConnection::ReadHandler(boost::system::error_code ec,
std::size_t length) {
if (ec) {
if (ec != boost::asio::error::operation_aborted) {
Close();
}
return;
}
if (!request_parser_.Parse(buffer_.data(), length)) {
// Bad request.
response_ = HttpResponse::Fault(HttpStatus::kBadRequest);
AsyncWrite();
return;
}
if (!request_parser_.finished()) {
// Continue to read the request.
AsyncRead();
return;
}
// Enqueue this connection.
// Some worker thread will handle it later.
// And DoWrite() will be called in the worker thread.
request_handler_->Enqueue(shared_from_this());
}
void HttpConnection::AsyncWrite() {
boost::asio::async_write(socket_,
response_.ToBuffers(),
std::bind(&HttpConnection::WriteHandler,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
// NOTE:
// This write handler will be called from main thread (the thread calling
// io_context.run), even though DoWrite() is invoked by worker threads. This is
// ensured by Asio.
void HttpConnection::WriteHandler(boost::system::error_code ec,
std::size_t length) {
if (!ec) {
LOG_INFO("Response has been sent back, length: %u.", length);
// Initiate graceful connection closure.
boost::system::error_code ec;
socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
} else {
LOG_ERRO("Sending response error: %s", ec.message().c_str());
if (ec != boost::asio::error::operation_aborted) {
Close();
}
}
}
} // namespace webcc
+74
View File
@@ -0,0 +1,74 @@
#ifndef WEBCC_HTTP_CONNECTION_H_
#define WEBCC_HTTP_CONNECTION_H_
#include <memory>
#include <string>
#include <vector>
#include "boost/asio/ip/tcp.hpp" // for ip::tcp::socket
#include "webcc/globals.h"
#include "webcc/http_request.h"
#include "webcc/http_request_parser.h"
#include "webcc/http_response.h"
namespace webcc {
class HttpRequestHandler;
class HttpConnection : public std::enable_shared_from_this<HttpConnection> {
public:
HttpConnection(boost::asio::ip::tcp::socket socket, // Will be moved
HttpRequestHandler* handler);
~HttpConnection() = default;
DELETE_COPY_AND_ASSIGN(HttpConnection);
const HttpRequest& request() const {
return request_;
}
// Start to read and process the client request.
void Start();
// Close the socket.
void Close();
void SetResponseContent(std::string&& content,
const std::string& content_type);
// Send response to client with the given status.
void SendResponse(HttpStatus::Enum status);
private:
void AsyncRead();
void ReadHandler(boost::system::error_code ec, std::size_t length);
void AsyncWrite();
void WriteHandler(boost::system::error_code ec, std::size_t length);
// Socket for the connection.
boost::asio::ip::tcp::socket socket_;
// Buffer for incoming data.
std::vector<char> buffer_;
// The handler used to process the incoming request.
HttpRequestHandler* request_handler_;
// The incoming request.
HttpRequest request_;
// The parser for the incoming request.
HttpRequestParser request_parser_;
// The response to be sent back to the client.
HttpResponse response_;
};
typedef std::shared_ptr<HttpConnection> HttpConnectionPtr;
} // namespace webcc
#endif // WEBCC_HTTP_CONNECTION_H_
+61
View File
@@ -0,0 +1,61 @@
#include "webcc/http_message.h"
#include <sstream>
#include "boost/algorithm/string.hpp"
namespace webcc {
void HttpMessage::SetHeader(const std::string& name, const std::string& value) {
for (HttpHeader& h : headers_) {
if (h.name == name) {
h.value = value;
return;
}
}
headers_.push_back({ name, value });
}
void HttpMessage::Dump(std::ostream& os, std::size_t indent,
const std::string& prefix) const {
std::string indent_str;
if (indent > 0) {
indent_str.append(indent, ' ');
}
indent_str.append(prefix);
os << indent_str << start_line_;
for (const HttpHeader& h : headers_) {
os << indent_str << h.name << ": " << h.value << std::endl;
}
os << indent_str << std::endl;
if (!content_.empty()) {
if (indent == 0) {
os << content_ << std::endl;
} else {
std::vector<std::string> splitted;
boost::split(splitted, content_, boost::is_any_of("\r\n"));
for (const std::string& line : splitted) {
os << indent_str << line << std::endl;
}
}
}
}
std::string HttpMessage::Dump(std::size_t indent,
const std::string& prefix) const {
std::stringstream ss;
Dump(ss, indent, prefix);
return ss.str();
}
std::ostream& operator<<(std::ostream& os, const HttpMessage& message) {
message.Dump(os);
return os;
}
} // namespace webcc
+78
View File
@@ -0,0 +1,78 @@
#ifndef WEBCC_HTTP_MESSAGE_H_
#define WEBCC_HTTP_MESSAGE_H_
#include <cassert>
#include <string>
#include <utility> // for move()
#include <vector>
#include "webcc/globals.h"
namespace webcc {
struct HttpHeader {
std::string name;
std::string value;
};
// Base class for HTTP request and response messages.
class HttpMessage {
public:
virtual ~HttpMessage() = default;
const std::string& start_line() const { return start_line_; }
void set_start_line(const std::string& start_line) {
start_line_ = start_line;
}
std::size_t content_length() const { return content_length_; }
const std::string& content() const { return content_; }
void SetHeader(const std::string& name, const std::string& value);
// E.g., "text/xml; charset=utf-8"
void SetContentType(const std::string& content_type) {
SetHeader(kContentType, content_type);
}
void SetContent(std::string&& content) {
content_ = std::move(content);
SetContentLength(content_.size());
}
void SetContent(const std::string& content) {
content_ = content;
SetContentLength(content_.size());
}
// Dump to output stream.
void Dump(std::ostream& os, std::size_t indent = 0,
const std::string& prefix = "") const;
// Dump to string, only used by logger.
std::string Dump(std::size_t indent = 0,
const std::string& prefix = "") const;
protected:
void SetContentLength(std::size_t content_length) {
content_length_ = content_length;
SetHeader(kContentLength, std::to_string(content_length));
}
// Start line with trailing "\r\n".
std::string start_line_;
std::size_t content_length_ = kInvalidLength;
std::vector<HttpHeader> headers_;
std::string content_;
};
std::ostream& operator<<(std::ostream& os, const HttpMessage& message);
} // namespace webcc
#endif // WEBCC_HTTP_MESSAGE_H_
+148
View File
@@ -0,0 +1,148 @@
#include "webcc/http_parser.h"
#include "boost/algorithm/string.hpp"
#include "webcc/http_message.h"
#include "webcc/logger.h"
namespace webcc {
HttpParser::HttpParser(HttpMessage* message)
: message_(message),
content_length_(kInvalidLength),
start_line_parsed_(false),
content_length_parsed_(false),
header_parsed_(false),
finished_(false) {
}
bool HttpParser::Parse(const char* data, std::size_t length) {
if (header_parsed_) {
// Add the data to the content.
AppendContent(data, length);
if (IsContentFull()) {
// All content has been read.
Finish();
}
return true;
}
pending_data_.append(data, length);
std::size_t off = 0;
while (true) {
std::size_t pos = pending_data_.find("\r\n", off);
if (pos == std::string::npos) {
break;
}
if (pos == off) { // End of headers.
off = pos + 2; // Skip CRLF.
header_parsed_ = true;
break;
}
std::string line = pending_data_.substr(off, pos - off);
if (!start_line_parsed_) {
start_line_parsed_ = true;
if (!ParseStartLine(line)) {
return false;
}
} else {
// Currently, only Content-Length is important to us.
// Other header fields are ignored.
if (!content_length_parsed_) {
ParseContentLength(line);
}
}
off = pos + 2; // Skip CRLF.
}
if (header_parsed_) {
// Headers just ended.
LOG_INFO("HTTP headers parsed.");
if (!content_length_parsed_) {
// No Content-Length, no content.
Finish();
return true;
} else {
// Invalid Content-Length in the request.
if (content_length_ == kInvalidLength) {
return false;
}
}
AppendContent(pending_data_.substr(off));
if (IsContentFull()) {
// All content has been read.
Finish();
}
} else {
// Save the unparsed piece for next parsing.
pending_data_ = pending_data_.substr(off);
}
return true;
}
void HttpParser::ParseContentLength(const std::string& line) {
std::size_t pos = line.find(':');
if (pos == std::string::npos) {
return;
}
std::string name = line.substr(0, pos);
if (boost::iequals(name, kContentLength)) {
content_length_parsed_ = true;
++pos; // Skip ':'.
while (line[pos] == ' ') { // Skip spaces.
++pos;
}
std::string value = line.substr(pos);
try {
content_length_ = static_cast<std::size_t>(std::stoul(value));
} catch (const std::exception& e) {
LOG_ERRO("Invalid content length: %s.", value.c_str());
}
LOG_INFO("Content length: %u.", content_length_);
try {
// Reserve memory to avoid frequent reallocation when append.
content_.reserve(content_length_);
} catch (const std::exception& e) {
LOG_ERRO("Failed to reserve content memory: %s.", e.what());
}
}
}
void HttpParser::Finish() {
// Move content to message.
message_->SetContent(std::move(content_));
finished_ = true;
}
void HttpParser::AppendContent(const char* data, std::size_t count) {
content_.append(data, count);
}
void HttpParser::AppendContent(const std::string& data) {
content_.append(data);
}
bool HttpParser::IsContentFull() const {
return content_length_ != kInvalidLength &&
content_length_ <= content_.length();
}
} // namespace webcc
+57
View File
@@ -0,0 +1,57 @@
#ifndef WEBCC_HTTP_PARSER_H_
#define WEBCC_HTTP_PARSER_H_
#include <string>
#include "webcc/globals.h"
namespace webcc {
class HttpMessage;
// HttpParser parses HTTP request and response.
class HttpParser {
public:
explicit HttpParser(HttpMessage* message);
virtual ~HttpParser() = default;
DELETE_COPY_AND_ASSIGN(HttpParser);
bool finished() const { return finished_; }
bool content_length_parsed() const { return content_length_parsed_; }
std::size_t content_length() const { return content_length_; }
bool Parse(const char* data, std::size_t length);
protected:
virtual bool ParseStartLine(const std::string& line) = 0;
void ParseContentLength(const std::string& line);
void Finish();
void AppendContent(const char* data, std::size_t count);
void AppendContent(const std::string& data);
bool IsContentFull() const;
// The result HTTP message.
HttpMessage* message_;
// Data waiting to be parsed.
std::string pending_data_;
// Temporary data and helper flags for parsing.
std::size_t content_length_;
std::string content_;
bool start_line_parsed_;
bool content_length_parsed_;
bool header_parsed_;
bool finished_;
};
} // namespace webcc
#endif // WEBCC_HTTP_PARSER_H_
+60
View File
@@ -0,0 +1,60 @@
#include "webcc/http_request.h"
namespace webcc {
// -----------------------------------------------------------------------------
namespace misc_strings {
const char NAME_VALUE_SEPARATOR[] = { ':', ' ' };
const char CRLF[] = { '\r', '\n' };
} // misc_strings
// -----------------------------------------------------------------------------
void HttpRequest::SetHost(const std::string& host, const std::string& port) {
host_ = host;
port_ = port;
if (port.empty()) {
SetHeader(kHost, host);
} else {
SetHeader(kHost, host + ":" + port);
}
}
void HttpRequest::Build() {
if (start_line_.empty()) {
start_line_ = method_;
start_line_ += " ";
start_line_ += url_;
start_line_ += " HTTP/1.1\r\n";
}
}
// ATTENTION: The buffers don't hold the memory!
std::vector<boost::asio::const_buffer> HttpRequest::ToBuffers() const {
assert(!start_line_.empty());
std::vector<boost::asio::const_buffer> buffers;
buffers.push_back(boost::asio::buffer(start_line_));
for (const HttpHeader& h : headers_) {
buffers.push_back(boost::asio::buffer(h.name));
buffers.push_back(boost::asio::buffer(misc_strings::NAME_VALUE_SEPARATOR));
buffers.push_back(boost::asio::buffer(h.value));
buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
}
buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
if (content_length_ > 0) {
buffers.push_back(boost::asio::buffer(content_));
}
return buffers;
}
} // namespace webcc
+62
View File
@@ -0,0 +1,62 @@
#ifndef WEBCC_HTTP_REQUEST_H_
#define WEBCC_HTTP_REQUEST_H_
#include <memory>
#include <string>
#include <vector>
#include "boost/asio/buffer.hpp" // for const_buffer
#include "webcc/http_message.h"
namespace webcc {
class HttpRequest : public HttpMessage {
public:
HttpRequest() = default;
HttpRequest(const HttpRequest&) = default;
HttpRequest& operator=(const HttpRequest&) = default;
virtual ~HttpRequest() = default;
const std::string& method() const { return method_; }
void set_method(const std::string& method) { method_ = method; }
const std::string& url() const { return url_; }
void set_url(const std::string& url) { url_ = url; }
const std::string& host() const { return host_; }
const std::string& port() const { return port_; }
// Set host name and port number.
// The |host| is a descriptive name or a numeric IP address. The |port| is
// a numeric number (e.g., "9000") and "80" will be used if it's empty.
void SetHost(const std::string& host, const std::string& port);
// Compose start line, etc.
// Must be called before ToBuffers()!
void Build();
// Convert the response into a vector of buffers. The buffers do not own the
// underlying memory blocks, therefore the request object must remain valid
// and not be changed until the write operation has completed.
std::vector<boost::asio::const_buffer> ToBuffers() const;
private:
// HTTP method.
std::string method_;
// Request URL.
// A complete URL naming the requested resource, or the path component of
// the URL.
std::string url_;
std::string host_;
std::string port_;
};
typedef std::shared_ptr<HttpRequest> HttpRequestPtr;
} // namespace webcc
#endif // WEBCC_HTTP_REQUEST_H_
+61
View File
@@ -0,0 +1,61 @@
#include "webcc/http_request_handler.h"
#include <sstream>
#include "webcc/globals.h"
#include "webcc/http_request.h"
#include "webcc/http_response.h"
#include "webcc/logger.h"
namespace webcc {
void HttpRequestHandler::Enqueue(HttpConnectionPtr connection) {
queue_.Push(connection);
}
void HttpRequestHandler::Start(std::size_t count) {
assert(count > 0 && workers_.size() == 0);
for (std::size_t i = 0; i < count; ++i) {
workers_.create_thread(std::bind(&HttpRequestHandler::WorkerRoutine, this));
}
}
void HttpRequestHandler::Stop() {
LOG_INFO("Stopping workers...");
// Close pending connections.
for (HttpConnectionPtr conn = queue_.Pop(); conn; conn = queue_.Pop()) {
LOG_INFO("Closing pending connection...");
conn->Close();
}
// Enqueue a null connection to trigger the first worker to stop.
queue_.Push(HttpConnectionPtr());
workers_.join_all();
LOG_INFO("All workers have been stopped.");
}
void HttpRequestHandler::WorkerRoutine() {
LOG_INFO("Worker is running.");
for (;;) {
HttpConnectionPtr connection = queue_.PopOrWait();
if (!connection) {
LOG_INFO("Worker is going to stop.");
// For stopping next worker.
queue_.Push(HttpConnectionPtr());
// Stop the worker.
break;
}
HandleConnection(connection);
}
}
} // namespace webcc
+47
View File
@@ -0,0 +1,47 @@
#ifndef WEBCC_HTTP_REQUEST_HANDLER_H_
#define WEBCC_HTTP_REQUEST_HANDLER_H_
#include <list>
#include <vector>
#include "boost/thread/thread.hpp"
#include "webcc/http_connection.h"
#include "webcc/queue.h"
#include "webcc/soap_service.h"
namespace webcc {
class HttpRequest;
class HttpResponse;
// The common handler for all incoming requests.
class HttpRequestHandler {
public:
HttpRequestHandler() = default;
virtual ~HttpRequestHandler() = default;
DELETE_COPY_AND_ASSIGN(HttpRequestHandler);
// Put the connection into the queue.
void Enqueue(HttpConnectionPtr connection);
// Start worker threads.
void Start(std::size_t count);
// Close pending connections and stop worker threads.
void Stop();
private:
void WorkerRoutine();
// Called by the worker routine.
virtual void HandleConnection(HttpConnectionPtr connection) = 0;
Queue<HttpConnectionPtr> queue_;
boost::thread_group workers_;
};
} // namespace webcc
#endif // WEBCC_HTTP_REQUEST_HANDLER_H_
+30
View File
@@ -0,0 +1,30 @@
#include "webcc/http_request_parser.h"
#include <vector>
#include "boost/algorithm/string.hpp"
#include "webcc/http_request.h"
namespace webcc {
HttpRequestParser::HttpRequestParser(HttpRequest* request)
: HttpParser(request), request_(request) {
}
bool HttpRequestParser::ParseStartLine(const std::string& line) {
std::vector<std::string> strs;
boost::split(strs, line, boost::is_any_of(" "), boost::token_compress_on);
if (strs.size() != 3) {
return false;
}
request_->set_method(strs[0]);
request_->set_url(strs[1]);
// HTTP version is ignored.
return true;
}
} // namespace webcc
+26
View File
@@ -0,0 +1,26 @@
#ifndef WEBCC_HTTP_REQUEST_PARSER_H_
#define WEBCC_HTTP_REQUEST_PARSER_H_
#include <string>
#include "webcc/http_parser.h"
namespace webcc {
class HttpRequest;
class HttpRequestParser : public HttpParser {
public:
explicit HttpRequestParser(HttpRequest* request);
~HttpRequestParser() override = default;
private:
bool ParseStartLine(const std::string& line) override;
HttpRequest* request_;
};
} // namespace webcc
#endif // WEBCC_HTTP_REQUEST_PARSER_H_
+99
View File
@@ -0,0 +1,99 @@
#include "webcc/http_response.h"
namespace webcc {
namespace status_strings {
const std::string OK = "HTTP/1.1 200 OK\r\n";
const std::string CREATED = "HTTP/1.0 201 Created\r\n";
const std::string ACCEPTED = "HTTP/1.0 202 Accepted\r\n";
const std::string NO_CONTENT = "HTTP/1.0 204 No Content\r\n";
const std::string NOT_MODIFIED = "HTTP/1.0 304 Not Modified\r\n";
const std::string BAD_REQUEST = "HTTP/1.1 400 Bad Request\r\n";
const std::string NOT_FOUND = "HTTP/1.0 404 Not Found\r\n";
const std::string INTERNAL_SERVER_ERROR =
"HTTP/1.1 500 Internal Server Error\r\n";
const std::string NOT_IMPLEMENTED = "HTTP/1.1 501 Not Implemented\r\n";
const std::string SERVICE_UNAVAILABLE = "HTTP/1.1 503 Service Unavailable\r\n";
boost::asio::const_buffer ToBuffer(int status) {
switch (status) {
case HttpStatus::kOK:
return boost::asio::buffer(OK);
case HttpStatus::kCreated:
return boost::asio::buffer(CREATED);
case HttpStatus::kAccepted:
return boost::asio::buffer(ACCEPTED);
case HttpStatus::kNoContent:
return boost::asio::buffer(NO_CONTENT);
case HttpStatus::kNotModified:
return boost::asio::buffer(NOT_MODIFIED);
case HttpStatus::kBadRequest:
return boost::asio::buffer(BAD_REQUEST);
case HttpStatus::kNotFound:
return boost::asio::buffer(NOT_FOUND);
case HttpStatus::InternalServerError:
return boost::asio::buffer(INTERNAL_SERVER_ERROR);
case HttpStatus::kNotImplemented:
return boost::asio::buffer(NOT_IMPLEMENTED);
case HttpStatus::kServiceUnavailable:
return boost::asio::buffer(SERVICE_UNAVAILABLE);
default:
return boost::asio::buffer(NOT_IMPLEMENTED);
}
}
} // namespace status_strings
namespace misc_strings {
const char NAME_VALUE_SEPARATOR[] = { ':', ' ' };
const char CRLF[] = { '\r', '\n' };
} // misc_strings
// ATTENTION: The buffers don't hold the memory!
std::vector<boost::asio::const_buffer> HttpResponse::ToBuffers() const {
std::vector<boost::asio::const_buffer> buffers;
// Status line
buffers.push_back(status_strings::ToBuffer(status_));
// Header fields (optional)
for (const HttpHeader& h : headers_) {
buffers.push_back(boost::asio::buffer(h.name));
buffers.push_back(boost::asio::buffer(misc_strings::NAME_VALUE_SEPARATOR));
buffers.push_back(boost::asio::buffer(h.value));
buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
}
buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
// Content (optional)
if (!content_.empty()) {
buffers.push_back(boost::asio::buffer(content_));
}
return buffers;
}
HttpResponse HttpResponse::Fault(HttpStatus::Enum status) {
assert(status != HttpStatus::kOK);
HttpResponse response;
response.set_status(status);
return response;
}
} // namespace webcc
+40
View File
@@ -0,0 +1,40 @@
#ifndef WEBCC_HTTP_RESPONSE_H_
#define WEBCC_HTTP_RESPONSE_H_
#include <memory>
#include <string>
#include <vector>
#include "boost/asio/buffer.hpp" // for const_buffer
#include "webcc/http_message.h"
namespace webcc {
class HttpResponse : public HttpMessage {
public:
HttpResponse() : status_(HttpStatus::kOK) {
}
~HttpResponse() override = default;
int status() const { return status_; }
void set_status(int status) { status_ = status; }
// Convert the response into a vector of buffers. The buffers do not own the
// underlying memory blocks, therefore the response object must remain valid
// and not be changed until the write operation has completed.
std::vector<boost::asio::const_buffer> ToBuffers() const;
// Get a fault response when HTTP status is not OK.
static HttpResponse Fault(HttpStatus::Enum status);
private:
int status_;
};
typedef std::shared_ptr<HttpResponse> HttpResponsePtr;
} // namespace webcc
#endif // WEBCC_HTTP_RESPONSE_H_
+48
View File
@@ -0,0 +1,48 @@
#include "webcc/http_response_parser.h"
#include "webcc/logger.h"
#include "webcc/http_response.h"
namespace webcc {
HttpResponseParser::HttpResponseParser(HttpResponse* response)
: HttpParser(response), response_(response) {
}
bool HttpResponseParser::ParseStartLine(const std::string& line) {
response_->set_start_line(line + "\r\n");
std::size_t off = 0;
std::size_t pos = line.find(' ');
if (pos == std::string::npos) {
return false;
}
// HTTP version
off = pos + 1; // Skip space.
pos = line.find(' ', off);
if (pos == std::string::npos) {
return false;
}
// Status code
std::string status_str = line.substr(off, pos - off);
try {
response_->set_status(std::stoi(status_str));
} catch (const std::exception&) {
LOG_ERRO("Invalid HTTP status: %s", status_str.c_str());
return false;
}
if (response_->status() != HttpStatus::kOK) {
return false;
}
return true;
}
} // namespace webcc
+28
View File
@@ -0,0 +1,28 @@
#ifndef WEBCC_HTTP_RESPONSE_PARSER_H_
#define WEBCC_HTTP_RESPONSE_PARSER_H_
#include <string>
#include "webcc/http_parser.h"
namespace webcc {
class HttpResponse;
class HttpResponseParser : public HttpParser {
public:
explicit HttpResponseParser(HttpResponse* response);
~HttpResponseParser() override = default;
private:
// Parse HTTP start line; E.g., "HTTP/1.1 200 OK".
bool ParseStartLine(const std::string& line) override;
// The result response message.
HttpResponse* response_;
};
} // namespace webcc
#endif // WEBCC_HTTP_RESPONSE_PARSER_H_
+91
View File
@@ -0,0 +1,91 @@
#include "webcc/http_server.h"
#include <csignal>
#include <utility>
#include "webcc/http_request_handler.h"
#include "webcc/logger.h"
#include "webcc/soap_service.h"
#include "webcc/utility.h"
using tcp = boost::asio::ip::tcp;
namespace webcc {
HttpServer::HttpServer(std::uint16_t port, std::size_t workers)
: signals_(io_context_) , workers_(workers) {
// Register to handle the signals that indicate when the server should exit.
// It is safe to register for the same signal multiple times in a program,
// provided all registration for the specified signal is made through asio.
signals_.add(SIGINT); // Ctrl+C
signals_.add(SIGTERM);
#if defined(SIGQUIT)
signals_.add(SIGQUIT);
#endif
AsyncAwaitStop();
// NOTE:
// "reuse_addr=true" means option SO_REUSEADDR will be set.
// For more details about SO_REUSEADDR, see:
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms740621(v=vs.85).aspx
// https://stackoverflow.com/a/3233022
// http://www.andy-pearce.com/blog/posts/2013/Feb/so_reuseaddr-on-windows/
// When |reuse_addr| is true, multiple servers can listen on the same port.
acceptor_.reset(new tcp::acceptor(io_context_,
tcp::endpoint(tcp::v4(), port),
true)); // reuse_addr
AsyncAccept();
}
void HttpServer::Run() {
assert(GetRequestHandler() != nullptr);
LOG_INFO("Server is going to run...");
// Start worker threads.
GetRequestHandler()->Start(workers_);
// The io_context::run() call will block until all asynchronous operations
// have finished. While the server is running, there is always at least one
// asynchronous operation outstanding: the asynchronous accept call waiting
// for new incoming connections.
io_context_.run();
}
void HttpServer::AsyncAccept() {
acceptor_->async_accept(
[this](boost::system::error_code ec, tcp::socket socket) {
// Check whether the server was stopped by a signal before this
// completion handler had a chance to run.
if (!acceptor_->is_open()) {
return;
}
if (!ec) {
LOG_INFO("Accepted a connection.");
HttpConnectionPtr connection{
new HttpConnection(std::move(socket), GetRequestHandler())
};
connection->Start();
}
AsyncAccept();
});
}
void HttpServer::AsyncAwaitStop() {
signals_.async_wait(
[this](boost::system::error_code, int signo) {
// The server is stopped by canceling all outstanding asynchronous
// operations. Once all operations have finished the io_context::run()
// call will exit.
LOG_INFO("On signal %d, stopping the server...", signo);
acceptor_->close();
GetRequestHandler()->Stop();
});
}
} // namespace webcc
+58
View File
@@ -0,0 +1,58 @@
#ifndef WEBCC_HTTP_SERVER_H_
#define WEBCC_HTTP_SERVER_H_
#include <string>
#include <vector>
#include "boost/asio/io_context.hpp"
#include "boost/asio/ip/tcp.hpp"
#include "boost/asio/signal_set.hpp"
#include "boost/scoped_ptr.hpp"
#include "boost/thread/thread.hpp"
#include "webcc/globals.h"
#include "webcc/http_connection.h"
namespace webcc {
class HttpRequestHandler;
// HTTP server accepts TCP connections from TCP clients.
// NOTE: Only support IPv4.
class HttpServer {
public:
HttpServer(std::uint16_t port, std::size_t workers);
virtual ~HttpServer() = default;
DELETE_COPY_AND_ASSIGN(HttpServer);
// Run the server's io_service loop.
void Run();
private:
// Initiate an asynchronous accept operation.
void AsyncAccept();
// Wait for a request to stop the server.
void AsyncAwaitStop();
// Get the handler for incoming requests.
virtual HttpRequestHandler* GetRequestHandler() = 0;
// The number of worker threads.
std::size_t workers_;
// The io_context used to perform asynchronous operations.
boost::asio::io_context io_context_;
// The signal_set is used to register for process termination notifications.
boost::asio::signal_set signals_;
// Acceptor used to listen for incoming connections.
boost::scoped_ptr<boost::asio::ip::tcp::acceptor> acceptor_;
};
} // namespace webcc
#endif // WEBCC_HTTP_SERVER_H_
+161
View File
@@ -0,0 +1,161 @@
#include "webcc/logger.h"
#if WEBCC_ENABLE_LOG
#include <cassert>
#include <chrono>
#include <cstdarg>
#include <ctime>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>
#include "boost/filesystem.hpp"
namespace webcc {
struct Logger {
Logger() : file(nullptr), modes(0) {
}
void Init(const std::string& path, int _modes) {
modes = _modes;
if (!path.empty()) {
if ((modes & LOG_OVERWRITE) != 0) {
file = fopen(path.c_str(), "w+");
} else {
// Append to existing file.
file = fopen(path.c_str(), "a+");
}
}
}
~Logger() {
if (file != nullptr) {
fclose(file);
}
}
FILE* file;
int modes;
std::mutex mutex;
};
// Global logger.
static Logger g_logger;
static std::thread::id g_main_thread_id;
static const char* kLevelNames[] = {
"VERB", "INFO", "WARN", "ERRO", "FATA"
};
namespace bfs = boost::filesystem;
static bfs::path InitLogPath(const std::string& dir) {
if (dir.empty()) {
return bfs::current_path() / WEBCC_LOG_FILE_NAME;
}
bfs::path path = bfs::path(dir);
if (!bfs::exists(path) || !bfs::is_directory(path)) {
boost::system::error_code ec;
if (!bfs::create_directories(path, ec) || ec) {
return bfs::path();
}
}
path /= WEBCC_LOG_FILE_NAME;
return path;
}
void LogInit(const std::string& dir, int modes) {
bfs::path path = InitLogPath(dir);
g_logger.Init(path.string(), modes);
// Suppose LogInit() is called from the main thread.
g_main_thread_id = std::this_thread::get_id();
}
static std::string GetTimestamp() {
using namespace std::chrono;
auto now = system_clock::now();
std::time_t now_c = system_clock::to_time_t(now);
std::tm* now_tm = std::localtime(&now_c);
char buf[20];
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", now_tm);
std::string timestamp(buf);
milliseconds milli_seconds = duration_cast<milliseconds>(
now.time_since_epoch());
std::string micro_seconds_str = std::to_string(milli_seconds.count() % 1000);
while (micro_seconds_str.size() < 3) {
micro_seconds_str = "0" + micro_seconds_str;
}
timestamp.append(".");
timestamp.append(micro_seconds_str);
return timestamp;
}
static std::string GetThreadID() {
std::thread::id thread_id = std::this_thread::get_id();
if (thread_id == g_main_thread_id) {
return "main";
}
std::stringstream ss;
ss << thread_id;
return ss.str();
}
void LogWrite(int level, const char* file, int line, const char* format, ...) {
assert(format != nullptr);
va_list args;
va_start(args, format);
if ((g_logger.modes & LOG_FILE) != 0 && g_logger.file != nullptr) {
std::lock_guard<std::mutex> lock(g_logger.mutex);
fprintf(g_logger.file, "%s, %s, %5s, %24s, %4d, ",
GetTimestamp().c_str(), kLevelNames[level], GetThreadID().c_str(),
file, line);
vfprintf(g_logger.file, format, args);
fprintf(g_logger.file, "\n");
if ((g_logger.modes & LOG_FLUSH) != 0) {
fflush(g_logger.file);
}
}
if ((g_logger.modes & LOG_CONSOLE) != 0) {
std::lock_guard<std::mutex> lock(g_logger.mutex);
fprintf(stderr, "%s, %s, %5s, %24s, %4d, ",
GetTimestamp().c_str(), kLevelNames[level], GetThreadID().c_str(),
file, line);
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
if ((g_logger.modes & LOG_FLUSH) != 0) {
fflush(stderr);
}
}
va_end(args);
}
} // namespace webcc
#endif // WEBCC_ENABLE_LOG
+153
View File
@@ -0,0 +1,153 @@
#ifndef WEBCC_LOGGER_H_
#define WEBCC_LOGGER_H_
// Simple console logger.
#if WEBCC_ENABLE_LOG
#include <cstring> // for strrchr()
#include <string>
// Log levels.
#define WEBCC_VERB 0 // Similar to DEBUG in other projects.
#define WEBCC_INFO 1
#define WEBCC_WARN 2
#define WEBCC_ERRO 3
#define WEBCC_FATA 4
// Default log level.
// You have to define a proper log level in CMakeLists.txt, e.g.,
// add_definitions(-DWEBCC_LOG_LEVEL=2)
#ifndef WEBCC_LOG_LEVEL
#define WEBCC_LOG_LEVEL WEBCC_WARN
#endif
#define WEBCC_LOG_FILE_NAME "webcc.log"
namespace webcc {
enum LogMode {
LOG_FILE = 1, // Log to file.
LOG_CONSOLE = 2, // Log to console.
LOG_FLUSH = 4, // Flush on each log.
LOG_OVERWRITE = 8, // Overwrite any existing log file.
};
// Commonly used modes.
const int LOG_CONSOLE_FILE_APPEND = LOG_CONSOLE | LOG_FILE;
const int LOG_CONSOLE_FILE_OVERWRITE = LOG_CONSOLE | LOG_FILE | LOG_OVERWRITE;
// Initialize logger.
// If |dir| is empty, log file will be generated in current directory.
void LogInit(const std::string& dir, int modes);
void LogWrite(int level, const char* file, int line, const char* format, ...);
} // namespace webcc
// Initialize the logger with a level.
#define WEBCC_LOG_INIT(dir, modes) webcc::LogInit(dir, modes);
#if (defined(WIN32) || defined(_WIN64))
// See: https://stackoverflow.com/a/8488201
#define __FILENAME__ std::strrchr("\\" __FILE__, '\\') + 1
#if WEBCC_LOG_LEVEL <= WEBCC_VERB
#define LOG_VERB(format, ...) \
webcc::LogWrite(WEBCC_VERB, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
#else
#define LOG_VERB(format, ...)
#endif
#if WEBCC_LOG_LEVEL <= WEBCC_INFO
#define LOG_INFO(format, ...) \
webcc::LogWrite(WEBCC_INFO, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
#else
#define LOG_INFO(format, ...)
#endif
#if WEBCC_LOG_LEVEL <= WEBCC_WARN
#define LOG_WARN(format, ...) \
webcc::LogWrite(WEBCC_WARN, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
#else
#define LOG_WARN(format, ...)
#endif
#if WEBCC_LOG_LEVEL <= WEBCC_ERRO
#define LOG_ERRO(format, ...) \
webcc::LogWrite(WEBCC_ERRO, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
#else
#define LOG_ERRO(format, ...)
#endif
#if WEBCC_LOG_LEVEL <= WEBCC_FATA
#define LOG_FATA(format, ...) \
webcc::LogWrite(WEBCC_FATA, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
#else
#define LOG_FATA(format, ...)
#endif
#else
// See: https://stackoverflow.com/a/8488201
#define __FILENAME__ std::strrchr("/" __FILE__, '/') + 1
#if WEBCC_LOG_LEVEL <= WEBCC_VERB
#define LOG_VERB(format, args...) \
webcc::LogWrite(WEBCC_VERB, __FILENAME__, __LINE__, format, ##args);
#else
#define LOG_VERB(format, args...)
#endif
#if WEBCC_LOG_LEVEL <= WEBCC_INFO
#define LOG_INFO(format, args...) \
webcc::LogWrite(WEBCC_INFO, __FILENAME__, __LINE__, format, ##args);
#else
#define LOG_INFO(format, args...)
#endif
#if WEBCC_LOG_LEVEL <= WEBCC_WARN
#define LOG_WARN(format, args...) \
webcc::LogWrite(WEBCC_WARN, __FILENAME__, __LINE__, format, ##args);
#else
#define LOG_WARN(format, args...)
#endif
#if WEBCC_LOG_LEVEL <= WEBCC_ERRO
#define LOG_ERRO(format, args...) \
webcc::LogWrite(WEBCC_ERRO, __FILENAME__, __LINE__, format, ##args);
#else
#define LOG_ERRO(format, args...)
#endif
#if WEBCC_LOG_LEVEL <= WEBCC_FATA
#define LOG_FATA(format, args...) \
webcc::LogWrite(WEBCC_FATA, __FILENAME__, __LINE__, format, ##args);
#else
#define LOG_FATA(format, args...)
#endif
#endif // defined(WIN32) || defined(_WIN64)
#else // WEBCC_ENABLE_LOG == 0
#define WEBCC_LOG_INIT(dir, modes)
#if (defined(WIN32) || defined(_WIN64))
#define LOG_VERB(format, ...)
#define LOG_INFO(format, ...)
#define LOG_WARN(format, ...)
#define LOG_ERRO(format, ...)
#define LOG_FATA(format, ...)
#else
#define LOG_VERB(format, args...)
#define LOG_INFO(format, args...)
#define LOG_WARN(format, args...)
#define LOG_ERRO(format, args...)
#define LOG_FATA(format, args...)
#endif // defined(WIN32) || defined(_WIN64)
#endif // WEBCC_ENABLE_LOG
#endif // WEBCC_LOGGER_H_
+62
View File
@@ -0,0 +1,62 @@
#ifndef WEBCC_QUEUE_H_
#define WEBCC_QUEUE_H_
// A general message queue.
#include <list>
#include <queue>
#include "boost/thread/condition_variable.hpp"
#include "boost/thread/locks.hpp"
#include "boost/thread/mutex.hpp"
namespace webcc {
template <typename T>
class Queue {
public:
Queue() = default;
Queue(const Queue&) = delete;
Queue& operator=(const Queue&) = delete;
T PopOrWait() {
boost::unique_lock<boost::mutex> lock(mutex_);
// Wait for a message.
not_empty_cv_.wait(lock, [this] { return !message_list_.empty(); });
T message = message_list_.front();
message_list_.pop_front();
return message;
}
T Pop() {
boost::lock_guard<boost::mutex> lock(mutex_);
if (message_list_.empty()) {
return T();
}
T message = message_list_.front();
message_list_.pop_front();
return message;
}
void Push(const T& message) {
{
boost::lock_guard<boost::mutex> lock(mutex_);
message_list_.push_back(message);
}
not_empty_cv_.notify_one();
}
private:
std::list<T> message_list_;
boost::mutex mutex_;
boost::condition_variable not_empty_cv_;
};
} // namespace webcc
#endif // WEBCC_QUEUE_H_
+52
View File
@@ -0,0 +1,52 @@
#include "webcc/rest_client.h"
#include "webcc/http_client.h"
#include "webcc/http_request.h"
namespace webcc {
RestClient::RestClient(const std::string& host, const std::string& port)
: host_(host),
port_(port),
timeout_seconds_(0),
timed_out_(false),
error_(kNoError) {
}
bool RestClient::Request(const std::string& method,
const std::string& url,
const std::string& content) {
response_.reset();
error_ = kNoError;
timed_out_ = false;
HttpRequest request;
request.set_method(method);
request.set_url(url);
request.SetHost(host_, port_);
if (!content.empty()) {
request.SetContent(content);
}
request.Build();
HttpClient http_client;
if (timeout_seconds_ > 0) {
http_client.set_timeout_seconds(timeout_seconds_);
}
if (!http_client.Request(request)) {
error_ = http_client.error();
timed_out_ = http_client.timed_out();
return false;
}
response_ = http_client.response();
return true;
}
} // namespace webcc
+81
View File
@@ -0,0 +1,81 @@
#ifndef WEBCC_REST_CLIENT_H_
#define WEBCC_REST_CLIENT_H_
#include <cassert>
#include <string>
#include "webcc/globals.h"
#include "webcc/http_response.h"
namespace webcc {
class RestClient {
public:
RestClient(const std::string& host, const std::string& port);
~RestClient() = default;
DELETE_COPY_AND_ASSIGN(RestClient);
void set_timeout_seconds(int timeout_seconds) {
timeout_seconds_ = timeout_seconds;
}
HttpResponsePtr response() const { return response_; }
int response_status() const {
assert(response_);
return response_->status();
}
const std::string& response_content() const {
assert(response_);
return response_->content();
}
bool timed_out() const { return timed_out_; }
Error error() const { return error_; }
inline bool Get(const std::string& url) {
return Request(kHttpGet, url, "");
}
inline bool Post(const std::string& url, const std::string& content) {
return Request(kHttpPost, url, content);
}
inline bool Put(const std::string& url, const std::string& content) {
return Request(kHttpPut, url, content);
}
inline bool Patch(const std::string& url, const std::string& content) {
return Request(kHttpPatch, url, content);
}
inline bool Delete(const std::string& url) {
return Request(kHttpDelete, url, "");
}
private:
bool Request(const std::string& method,
const std::string& url,
const std::string& content);
std::string host_;
std::string port_;
// Timeout in seconds; only effective when > 0.
int timeout_seconds_;
HttpResponsePtr response_;
// If the error was caused by timeout or not.
bool timed_out_;
Error error_;
};
} // namespace webcc
#endif // WEBCC_REST_CLIENT_H_
+52
View File
@@ -0,0 +1,52 @@
#include "webcc/rest_request_handler.h"
#include <utility> // for move()
#include <vector>
#include "webcc/logger.h"
#include "webcc/url.h"
namespace webcc {
bool RestRequestHandler::Bind(RestServicePtr service,
const std::string& url,
bool is_regex) {
return service_manager_.AddService(service, url, is_regex);
}
void RestRequestHandler::HandleConnection(HttpConnectionPtr connection) {
Url url(connection->request().url(), true);
if (!url.IsValid()) {
connection->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());
connection->SendResponse(HttpStatus::kBadRequest);
return;
}
UrlQuery query;
Url::SplitQuery(url.query(), &query);
std::string content;
bool ok = service->Handle(connection->request().method(),
sub_matches,
query,
connection->request().content(),
&content);
if (!ok) {
connection->SendResponse(HttpStatus::kBadRequest);
return;
}
connection->SetResponseContent(std::move(content), kTextJsonUtf8);
connection->SendResponse(HttpStatus::kOK);
}
} // namespace webcc
+28
View File
@@ -0,0 +1,28 @@
#ifndef WEBCC_REST_REQUEST_HANDLER_H_
#define WEBCC_REST_REQUEST_HANDLER_H_
// HTTP server handling REST requests.
#include <string>
#include "webcc/http_request_handler.h"
#include "webcc/rest_service_manager.h"
namespace webcc {
class RestRequestHandler : public HttpRequestHandler {
public:
RestRequestHandler() = default;
~RestRequestHandler() override = default;
bool Bind(RestServicePtr service, const std::string& url, bool is_regex);
private:
void HandleConnection(HttpConnectionPtr connection) override;
RestServiceManager service_manager_;
};
} // namespace webcc
#endif // WEBCC_REST_REQUEST_HANDLER_H_
+44
View File
@@ -0,0 +1,44 @@
#ifndef WEBCC_REST_SERVER_H_
#define WEBCC_REST_SERVER_H_
// HTTP server handling REST requests.
#include <string>
#include "webcc/http_server.h"
#include "webcc/rest_request_handler.h"
#include "webcc/rest_service.h"
namespace webcc {
class RestServer : public HttpServer {
public:
RestServer(std::uint16_t port, std::size_t workers)
: HttpServer(port, workers) {
}
~RestServer() override = default;
// Bind a REST service to the given URL path.
// The URL should start with "/" and it will be treated as a regular
// expression if |is_regex| is true.
// Examples:
// - "/instances"
// - "/instances/(\\d+)"
// Binding to the same URL multiple times is allowed, but only the last one
// takes effect.
bool Bind(RestServicePtr service, const std::string& url, bool is_regex) {
return request_handler_.Bind(service, url, is_regex);
}
private:
HttpRequestHandler* GetRequestHandler() override {
return &request_handler_;
}
RestRequestHandler request_handler_;
};
} // namespace webcc
#endif // WEBCC_REST_SERVER_H_
+56
View File
@@ -0,0 +1,56 @@
#include "webcc/rest_service.h"
#include "webcc/logger.h"
namespace webcc {
// -----------------------------------------------------------------------------
bool RestListService::Handle(const std::string& http_method,
const std::vector<std::string>& url_sub_matches,
const UrlQuery& query,
const std::string& request_content,
std::string* response_content) {
if (http_method == kHttpGet) {
return Get(query, response_content);
}
if (http_method == kHttpPost) {
return Post(request_content, response_content);
}
LOG_ERRO("RestListService doesn't support '%s' method.", http_method.c_str());
return false;
}
// -----------------------------------------------------------------------------
bool RestDetailService::Handle(const std::string& http_method,
const std::vector<std::string>& url_sub_matches,
const UrlQuery& query,
const std::string& request_content,
std::string* response_content) {
if (http_method == kHttpGet) {
return Get(url_sub_matches, query, response_content);
}
if (http_method == kHttpPut) {
return Put(url_sub_matches, request_content, response_content);
}
if (http_method == kHttpPatch) {
return Patch(url_sub_matches, request_content, response_content);
}
if (http_method == kHttpDelete) {
return Delete(url_sub_matches);
}
LOG_ERRO("RestDetailService doesn't support '%s' method.",
http_method.c_str());
return false;
}
} // namespace webcc
+102
View File
@@ -0,0 +1,102 @@
#ifndef WEBCC_REST_SERVICE_H_
#define WEBCC_REST_SERVICE_H_
// NOTE:
// The design of RestListService and RestDetailService is very similar to
// XxxListView and XxxDetailView in Python Django Rest Framework.
// Deriving from them instead of RestService can simplify your own REST services
// a lot. But if you find the filtered parameters cannot meet your needs, feel
// free to derive from RestService directly.
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "webcc/globals.h"
namespace webcc {
class UrlQuery;
// -----------------------------------------------------------------------------
// Base class for your REST service.
class RestService {
public:
virtual ~RestService() {
}
// Handle REST request, output the response.
// The regex sub-matches of the URL (usually resource IDs) were stored in
// |url_sub_matches|. The |query| part of the URL is normally only for GET
// request. Both the request and response contents are JSON strings.
virtual bool Handle(const std::string& http_method,
const std::vector<std::string>& url_sub_matches,
const UrlQuery& query,
const std::string& request_content,
std::string* response_content) = 0;
};
typedef std::shared_ptr<RestService> RestServicePtr;
// -----------------------------------------------------------------------------
class RestListService : public RestService {
public:
bool Handle(const std::string& http_method,
const std::vector<std::string>& url_sub_matches,
const UrlQuery& query,
const std::string& request_content,
std::string* response_content) final;
protected:
RestListService() = default;
virtual bool Get(const UrlQuery& query, std::string* response_content) {
return false;
}
virtual bool Post(const std::string& request_content,
std::string* response_content) {
return false;
}
};
// -----------------------------------------------------------------------------
class RestDetailService : public RestService {
public:
bool Handle(const std::string& http_method,
const std::vector<std::string>& url_sub_matches,
const UrlQuery& query,
const std::string& request_content,
std::string* response_content) final;
protected:
virtual bool Get(const std::vector<std::string>& url_sub_matches,
const UrlQuery& query,
std::string* response_content) {
return false;
}
virtual bool Put(const std::vector<std::string>& url_sub_matches,
const std::string& request_content,
std::string* response_content) {
return false;
}
virtual bool Patch(const std::vector<std::string>& url_sub_matches,
const std::string& request_content,
std::string* response_content) {
return false;
}
virtual bool Delete(const std::vector<std::string>& url_sub_matches) {
return false;
}
};
} // namespace webcc
#endif // WEBCC_REST_SERVICE_H_
+61
View File
@@ -0,0 +1,61 @@
#include "webcc/rest_service_manager.h"
#include <cassert>
#include "webcc/logger.h"
namespace webcc {
bool RestServiceManager::AddService(RestServicePtr service,
const std::string& url,
bool is_regex) {
assert(service);
ServiceItem item(service, url, is_regex);
if (!is_regex) {
service_items_.push_back(std::move(item));
return true;
}
std::regex::flag_type flags = std::regex::ECMAScript | std::regex::icase;
try {
// Compile the regex.
item.url_regex.assign(url, flags);
service_items_.push_back(std::move(item));
return true;
} catch (std::regex_error& e) {
LOG_ERRO("URL is not a valid regular expression: %s", e.what());
return false;
}
}
RestServicePtr RestServiceManager::GetService(
const std::string& url, std::vector<std::string>* sub_matches) {
assert(sub_matches != nullptr);
for (ServiceItem& item : service_items_) {
if (item.is_regex) {
std::smatch match;
if (std::regex_match(url, match, item.url_regex)) {
// Any sub-matches?
// NOTE: Start from 1 because match[0] is the whole string itself.
for (size_t i = 1; i < match.size(); ++i) {
sub_matches->push_back(match[i].str());
}
return item.service;
}
} else {
if (item.url == url) {
return item.service;
}
}
}
return RestServicePtr();
}
} // namespace webcc
+68
View File
@@ -0,0 +1,68 @@
#ifndef WEBCC_REST_SERVICE_MANAGER_H_
#define WEBCC_REST_SERVICE_MANAGER_H_
#include <regex> // NOLINT
#include <string>
#include <utility> // for move()
#include <vector>
#include "webcc/rest_service.h"
namespace webcc {
class RestServiceManager {
public:
RestServiceManager() = default;
DELETE_COPY_AND_ASSIGN(RestServiceManager);
// Add a service and bind it with the given URL.
// The |url| should start with "/" and will be treated as a regular expression
// if |regex| is true.
// Examples: "/instances", "/instances/(\\d+)".
bool AddService(RestServicePtr service, const std::string& url,
bool is_regex);
// The |sub_matches| is only available when the |url| bound to the
// service is a regular expression and has sub-expressions.
// E.g., the URL bound to the service is "/instances/(\\d+)", now match
// "/instances/12345" against it, you will get one sub-match of "12345".
RestServicePtr GetService(const std::string& url,
std::vector<std::string>* sub_matches);
private:
class ServiceItem {
public:
ServiceItem(RestServicePtr _service, const std::string& _url,
bool _is_regex)
: service(_service), url(_url), is_regex(_is_regex) {
}
ServiceItem(const ServiceItem&) = default;
ServiceItem& operator=(const ServiceItem&) = default;
ServiceItem(ServiceItem&& rhs)
: service(rhs.service),
url(std::move(rhs.url)),
is_regex(rhs.is_regex),
url_regex(std::move(rhs.url_regex)) {
}
RestServicePtr service;
// URL string, e.g., "/instances/(\\d+)".
std::string url;
// If the URL is a regular expression or not.
bool is_regex;
// Compiled regex for URL string.
std::regex url_regex;
};
std::vector<ServiceItem> service_items_;
};
} // namespace webcc
#endif // WEBCC_REST_SERVICE_MANAGER_H_
+74
View File
@@ -0,0 +1,74 @@
#include "webcc/soap_client.h"
#include <cassert>
#include <utility> // for move()
#include "webcc/http_client.h"
#include "webcc/http_request.h"
#include "webcc/http_response.h"
#include "webcc/soap_request.h"
#include "webcc/soap_response.h"
namespace webcc {
Error SoapClient::Call(const std::string& operation,
std::vector<Parameter>&& parameters,
std::string* result) {
assert(service_ns_.IsValid());
assert(!url_.empty() && !host_.empty());
assert(!result_name_.empty());
if (!soapenv_ns_.IsValid()) {
soapenv_ns_ = kSoapEnvNamespace;
}
SoapRequest soap_request;
soap_request.set_soapenv_ns(soapenv_ns_);
soap_request.set_service_ns(service_ns_);
soap_request.set_operation(operation);
for (Parameter& p : parameters) {
soap_request.AddParameter(std::move(p));
}
std::string http_content;
soap_request.ToXml(&http_content);
HttpRequest http_request;
http_request.set_method(kHttpPost);
http_request.set_url(url_);
http_request.SetContentType(kTextXmlUtf8);
http_request.SetContent(std::move(http_content));
http_request.SetHost(host_, port_);
http_request.SetHeader(kSoapAction, operation);
http_request.Build();
HttpResponse http_response;
HttpClient http_client;
if (timeout_seconds_ > 0) {
http_client.set_timeout_seconds(timeout_seconds_);
}
if (!http_client.Request(http_request)) {
timed_out_ = http_client.timed_out();
return http_client.error();
}
SoapResponse soap_response;
soap_response.set_result_name(result_name_);
if (!soap_response.FromXml(http_client.response()->content())) {
return kXmlError;
}
*result = soap_response.result_moved();
return kNoError;
}
} // namespace webcc
+52
View File
@@ -0,0 +1,52 @@
#ifndef WEBCC_SOAP_CLIENT_H_
#define WEBCC_SOAP_CLIENT_H_
#include <string>
#include <vector>
#include "webcc/globals.h"
#include "webcc/soap_message.h"
namespace webcc {
// Base class for your SOAP client.
// Set URL, host, port, etc. in your sub-class before make the call.
class SoapClient {
public:
virtual ~SoapClient() = default;
bool timed_out() const { return timed_out_; }
protected:
SoapClient() : timeout_seconds_(0), timed_out_(false) {
}
// A generic wrapper to make a call.
// NOTE: The parameters should be movable.
Error Call(const std::string& operation,
std::vector<Parameter>&& parameters,
std::string* result);
// Timeout in seconds; only effective when > 0.
int timeout_seconds_;
// If the error was caused by timeout or not.
bool timed_out_;
SoapNamespace soapenv_ns_; // SOAP envelope namespace.
SoapNamespace service_ns_; // Namespace for your web service.
// Request URL.
std::string url_;
std::string host_;
std::string port_; // Leave this empty to use default 80.
// Response result XML node name.
// E.g., "Result".
std::string result_name_;
};
} // namespace webcc
#endif // WEBCC_SOAP_CLIENT_H_
+60
View File
@@ -0,0 +1,60 @@
#include "webcc/soap_message.h"
#include <cassert>
#include "webcc/soap_xml.h"
namespace webcc {
const SoapNamespace kSoapEnvNamespace{
"soap",
"http://schemas.xmlsoap.org/soap/envelope/"
};
void SoapMessage::ToXml(std::string* xml_string) {
assert(soapenv_ns_.IsValid() &&
service_ns_.IsValid() &&
!operation_.empty());
pugi::xml_document xdoc;
// TODO(Adam):
// When save with format_default, declaration will be generated
// automatically but without encoding.
// pugi::xml_node xdecl = xdoc.prepend_child(pugi::node_declaration);
// xdecl.append_attribute("version").set_value("1.0");
pugi::xml_node xroot = soap_xml::AddChild(xdoc, soapenv_ns_.name, "Envelope");
soap_xml::AddNSAttr(xroot, soapenv_ns_.name, soapenv_ns_.url);
pugi::xml_node xbody = soap_xml::AddChild(xroot, soapenv_ns_.name, "Body");
ToXmlBody(xbody);
soap_xml::XmlStrRefWriter writer(xml_string);
xdoc.save(writer, "\t", pugi::format_default, pugi::encoding_utf8);
}
bool SoapMessage::FromXml(const std::string& xml_string) {
pugi::xml_document xdoc;
pugi::xml_parse_result result = xdoc.load_string(xml_string.c_str());
if (!result) {
return false;
}
pugi::xml_node xroot = xdoc.document_element();
soapenv_ns_.name = soap_xml::GetPrefix(xroot);
soapenv_ns_.url = soap_xml::GetNSAttr(xroot, soapenv_ns_.name);
pugi::xml_node xbody = soap_xml::GetChild(xroot, soapenv_ns_.name, "Body");
if (xbody) {
return FromXmlBody(xbody);
}
return false;
}
} // namespace webcc
+70
View File
@@ -0,0 +1,70 @@
#ifndef WEBCC_SOAP_MESSAGE_H_
#define WEBCC_SOAP_MESSAGE_H_
#include <string>
#include "pugixml/pugixml.hpp"
#include "webcc/globals.h"
namespace webcc {
// XML namespace name/url pair.
// E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" }
class SoapNamespace {
public:
std::string name;
std::string url;
bool IsValid() const {
return !name.empty() && !url.empty();
}
};
// CSoap's default namespace for SOAP Envelope.
extern const SoapNamespace kSoapEnvNamespace;
// Base class for SOAP request and response.
class SoapMessage {
public:
virtual ~SoapMessage() {}
// E.g., set as kSoapEnvNamespace.
void set_soapenv_ns(const SoapNamespace& soapenv_ns) {
soapenv_ns_ = soapenv_ns;
}
void set_service_ns(const SoapNamespace& service_ns) {
service_ns_ = service_ns;
}
const std::string& operation() const {
return operation_;
}
void set_operation(const std::string& operation) {
operation_ = operation;
}
// Convert to SOAP request XML.
void ToXml(std::string* xml_string);
// Parse from SOAP request XML.
bool FromXml(const std::string& xml_string);
protected:
// Convert to SOAP body XML.
virtual void ToXmlBody(pugi::xml_node xbody) = 0;
// Parse from SOAP body XML.
virtual bool FromXmlBody(pugi::xml_node xbody) = 0;
SoapNamespace soapenv_ns_; // SOAP envelope namespace.
SoapNamespace service_ns_; // Namespace for your web service.
std::string operation_;
};
} // namespace webcc
#endif // WEBCC_SOAP_MESSAGE_H_
+58
View File
@@ -0,0 +1,58 @@
#include "webcc/soap_request.h"
#include "webcc/soap_xml.h"
namespace webcc {
void SoapRequest::AddParameter(const Parameter& parameter) {
parameters_.push_back(parameter);
}
void SoapRequest::AddParameter(Parameter&& parameter) {
parameters_.push_back(std::move(parameter));
}
const std::string& SoapRequest::GetParameter(const std::string& key) const {
for (const Parameter& p : parameters_) {
if (p.key() == key) {
return p.value();
}
}
static const std::string kEmptyValue;
return kEmptyValue;
}
void SoapRequest::ToXmlBody(pugi::xml_node xbody) {
pugi::xml_node xop = soap_xml::AddChild(xbody, service_ns_.name, operation_);
soap_xml::AddNSAttr(xop, service_ns_.name, service_ns_.url);
for (Parameter& p : parameters_) {
pugi::xml_node xparam = soap_xml::AddChild(xop, service_ns_.name, p.key());
xparam.text().set(p.value().c_str());
}
}
bool SoapRequest::FromXmlBody(pugi::xml_node xbody) {
pugi::xml_node xoperation = xbody.first_child();
if (!xoperation) {
return false;
}
soap_xml::SplitName(xoperation, &service_ns_.name, &operation_);
service_ns_.url = soap_xml::GetNSAttr(xoperation, service_ns_.name);
pugi::xml_node xparameter = xoperation.first_child();
while (xparameter) {
parameters_.push_back({
soap_xml::GetNameNoPrefix(xparameter),
std::string(xparameter.text().as_string())
});
xparameter = xparameter.next_sibling();
}
return true;
}
} // namespace webcc
+33
View File
@@ -0,0 +1,33 @@
#ifndef WEBCC_SOAP_REQUEST_H_
#define WEBCC_SOAP_REQUEST_H_
#include <string>
#include <vector>
#include "webcc/soap_message.h"
namespace webcc {
// SOAP request.
// Used to compose the SOAP request envelope XML which will be sent as the HTTP
// request body.
class SoapRequest : public SoapMessage {
public:
void AddParameter(const Parameter& parameter);
void AddParameter(Parameter&& parameter);
// Get parameter value by key.
const std::string& GetParameter(const std::string& key) const;
protected:
void ToXmlBody(pugi::xml_node xbody) override;
bool FromXmlBody(pugi::xml_node xbody) override;
private:
std::vector<Parameter> parameters_;
};
} // namespace webcc
#endif // WEBCC_SOAP_REQUEST_H_
+57
View File
@@ -0,0 +1,57 @@
#include "webcc/soap_request_handler.h"
#include <utility> // for move()
#include "webcc/logger.h"
#include "webcc/soap_request.h"
#include "webcc/soap_response.h"
namespace webcc {
bool SoapRequestHandler::Bind(SoapServicePtr service, const std::string& url) {
assert(service);
url_service_map_[url] = service;
return true;
}
void SoapRequestHandler::HandleConnection(HttpConnectionPtr connection) {
SoapServicePtr service = GetServiceByUrl(connection->request().url());
if (!service) {
connection->SendResponse(HttpStatus::kBadRequest);
return;
}
// Parse the SOAP request XML.
SoapRequest soap_request;
if (!soap_request.FromXml(connection->request().content())) {
connection->SendResponse(HttpStatus::kBadRequest);
return;
}
SoapResponse soap_response;
if (!service->Handle(soap_request, &soap_response)) {
connection->SendResponse(HttpStatus::kBadRequest);
return;
}
std::string content;
soap_response.ToXml(&content);
connection->SetResponseContent(std::move(content), kTextXmlUtf8);
connection->SendResponse(HttpStatus::kOK);
}
SoapServicePtr SoapRequestHandler::GetServiceByUrl(const std::string& url) {
UrlServiceMap::const_iterator it = url_service_map_.find(url);
if (it != url_service_map_.end()) {
LOG_VERB("Service matches the URL: %s", url.c_str());
return it->second;
}
LOG_WARN("No service matches the URL: %s", url.c_str());
return SoapServicePtr();
}
} // namespace webcc
+29
View File
@@ -0,0 +1,29 @@
#ifndef WEBCC_SOAP_REQUEST_HANDLER_H_
#define WEBCC_SOAP_REQUEST_HANDLER_H_
#include <map>
#include <string>
#include "webcc/http_request_handler.h"
namespace webcc {
class SoapRequestHandler : public HttpRequestHandler {
public:
SoapRequestHandler() = default;
~SoapRequestHandler() override = default;
bool Bind(SoapServicePtr service, const std::string& url);
private:
void HandleConnection(HttpConnectionPtr connection) override;
SoapServicePtr GetServiceByUrl(const std::string& url);
typedef std::map<std::string, SoapServicePtr> UrlServiceMap;
UrlServiceMap url_service_map_;
};
} // namespace webcc
#endif // WEBCC_SOAP_REQUEST_HANDLER_H_
+36
View File
@@ -0,0 +1,36 @@
#include "webcc/soap_response.h"
#include <cassert>
#include "webcc/soap_xml.h"
namespace webcc {
void SoapResponse::ToXmlBody(pugi::xml_node xbody) {
pugi::xml_node xop = soap_xml::AddChild(xbody, service_ns_.name,
operation_ + "Response");
soap_xml::AddNSAttr(xop, service_ns_.name, service_ns_.url);
pugi::xml_node xresult = soap_xml::AddChild(xop, service_ns_.name,
result_name_);
xresult.text().set(result_.c_str());
}
bool SoapResponse::FromXmlBody(pugi::xml_node xbody) {
assert(!result_name_.empty());
pugi::xml_node xresponse = xbody.first_child();
if (xresponse) {
soap_xml::SplitName(xresponse, &service_ns_.name, nullptr);
service_ns_.url = soap_xml::GetNSAttr(xresponse, service_ns_.name);
pugi::xml_node xresult = soap_xml::GetChildNoNS(xresponse, result_name_);
if (xresult) {
result_ = xresult.text().get();
return true;
}
}
return false;
}
} // namespace webcc
+53
View File
@@ -0,0 +1,53 @@
#ifndef WEBCC_SOAP_RESPONSE_H_
#define WEBCC_SOAP_RESPONSE_H_
#include <string>
#include <utility> // for move()
#include "webcc/soap_message.h"
namespace webcc {
// SOAP response.
class SoapResponse : public SoapMessage {
public:
// Could be "Price" for an operation/method like "GetXyzPrice".
// Really depend on the service.
// Most services use a general name "Result".
void set_result_name(const std::string& result_name) {
result_name_ = result_name;
}
void set_result(const std::string& result) {
result_ = result;
}
void set_result(std::string&& result) {
result_ = std::move(result);
}
std::string result_moved() {
return std::move(result_);
}
protected:
void ToXmlBody(pugi::xml_node xbody) override;
bool FromXmlBody(pugi::xml_node xbody) override;
private:
// NOTE:
// Multiple results might be necessary. But for most cases, single result
// should be enough, because an API normally returns only one value.
// Result XML node name.
// Used to parse the response XML from client side.
std::string result_name_;
// Result value.
std::string result_;
};
} // namespace webcc
#endif // WEBCC_SOAP_RESPONSE_H_
+39
View File
@@ -0,0 +1,39 @@
#ifndef WEBCC_SOAP_SERVER_H_
#define WEBCC_SOAP_SERVER_H_
// HTTP server handling SOAP requests.
#include <string>
#include "webcc/soap_request_handler.h"
#include "webcc/http_server.h"
namespace webcc {
class SoapServer : public HttpServer {
public:
SoapServer(std::uint16_t port, std::size_t workers)
: HttpServer(port, workers) {
}
~SoapServer() override = default;
// Bind a SOAP service to the given URL path.
// The |url| path must start with "/", e.g., "/calculator".
// Binding to the same URL multiple times is allowed, but only the last
// one takes effect.
bool Bind(SoapServicePtr service, const std::string& url) {
return request_handler_.Bind(service, url);
}
private:
HttpRequestHandler* GetRequestHandler() override {
return &request_handler_;
}
SoapRequestHandler request_handler_;
};
} // namespace webcc
#endif // WEBCC_SOAP_SERVER_H_
+30
View File
@@ -0,0 +1,30 @@
#ifndef WEBCC_SOAP_SERVICE_H_
#define WEBCC_SOAP_SERVICE_H_
#include <memory>
#include "webcc/globals.h"
namespace webcc {
class SoapRequest;
class SoapResponse;
// Base class for your SOAP service.
class SoapService {
public:
virtual ~SoapService() = default;
// 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;
} // namespace webcc
#endif // WEBCC_SOAP_SERVICE_H_
+102
View File
@@ -0,0 +1,102 @@
#include "webcc/soap_xml.h"
namespace webcc {
namespace soap_xml {
void SplitName(const pugi::xml_node& xnode, std::string* prefix,
std::string* name) {
std::string full_name = xnode.name();
size_t pos = full_name.find(':');
if (pos != std::string::npos) {
if (prefix != nullptr) {
*prefix = full_name.substr(0, pos);
}
if (name != nullptr) {
*name = full_name.substr(pos + 1);
}
} else {
if (prefix != nullptr) {
*prefix = "";
}
if (name != nullptr) {
*name = full_name;
}
}
}
std::string GetPrefix(const pugi::xml_node& xnode) {
std::string ns_prefix;
SplitName(xnode, &ns_prefix, nullptr);
return ns_prefix;
}
std::string GetNameNoPrefix(const pugi::xml_node& xnode) {
std::string name;
SplitName(xnode, nullptr, &name);
return name;
}
pugi::xml_node AddChild(pugi::xml_node xnode,
const std::string& ns, const std::string& name) {
return xnode.append_child((ns + ":" + name).c_str());
}
pugi::xml_node GetChild(const pugi::xml_node& xnode, const std::string& ns,
const std::string& name) {
return xnode.child((ns + ":" + name).c_str());
}
pugi::xml_node GetChildNoNS(const pugi::xml_node& xnode,
const std::string& name) {
pugi::xml_node xchild = xnode.first_child();
while (xchild) {
std::string child_name = xchild.name();
// Remove NS prefix.
size_t pos = child_name.find(':');
if (pos != std::string::npos) {
child_name = child_name.substr(pos + 1);
}
if (child_name == name) {
return xchild;
}
xchild = xchild.next_sibling();
}
return pugi::xml_node();
}
void AddAttr(pugi::xml_node xnode, const std::string& ns,
const std::string& name, const std::string& value) {
std::string ns_name = ns + ":" + name;
xnode.append_attribute(ns_name.c_str()) = value.c_str();
}
void AddNSAttr(pugi::xml_node xnode, const std::string& ns_name,
const std::string& ns_url) {
AddAttr(xnode, "xmlns", ns_name, ns_url);
}
std::string GetNSAttr(const pugi::xml_node& xnode, const std::string& ns_name) {
std::string attr_name = "xmlns:" + ns_name;
return xnode.attribute(attr_name.c_str()).as_string();
}
bool PrettyPrint(std::ostream& os, const std::string& xml_string,
const char* indent) {
pugi::xml_document xdoc;
if (!xdoc.load_string(xml_string.c_str())) {
os << "Invalid XML" << std::endl;
return false;
}
xdoc.save(os, indent);
return true;
}
} // namespace soap_xml
} // namespace webcc
+84
View File
@@ -0,0 +1,84 @@
#ifndef WEBCC_SOAP_XML_H_
#define WEBCC_SOAP_XML_H_
// XML helpers for SOAP messages.
#include <string>
#include "pugixml/pugixml.hpp"
namespace webcc {
namespace soap_xml {
// Split the node name into namespace prefix and real name.
// E.g., if the node name is "soapenv:Envelope", it will be splitted to
// "soapenv" and "Envelope".
void SplitName(const pugi::xml_node& xnode, std::string* prefix = nullptr,
std::string* name = nullptr);
// Get the namespace prefix from node name.
// E.g., if the node name is "soapenv:Envelope", NS prefix will be "soapenv".
std::string GetPrefix(const pugi::xml_node& xnode);
// Get the node name without namespace prefix.
std::string GetNameNoPrefix(const pugi::xml_node& xnode);
// Add a child with the given name which is prefixed by a namespace.
// E.g., AppendChild(xnode, "soapenv", "Envelope") will append a child with
// name "soapenv:Envelope".
pugi::xml_node AddChild(pugi::xml_node xnode,
const std::string& ns, const std::string& name);
pugi::xml_node GetChild(const pugi::xml_node& xnode, const std::string& ns,
const std::string& name);
pugi::xml_node GetChildNoNS(const pugi::xml_node& xnode,
const std::string& name);
// Add an attribute with the given name which is prefixed by a namespace.
void AddAttr(pugi::xml_node xnode, const std::string& ns,
const std::string& name, const std::string& value);
// Append "xmlns" attribute.
// E.g., if the namespace is
// { "soapenv", "http://schemas.xmlsoap.org/soap/envelope/" }
// the attribute added will be
// xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
void AddNSAttr(pugi::xml_node xnode, const std::string& ns_name,
const std::string& ns_url);
// Get namespace attribute value.
// E.g., if the given namespace name is "soapenv", the value of
// attribute "xmlns:soapenv" will be returned.
std::string GetNSAttr(const pugi::xml_node& xnode,
const std::string& ns_name);
// An XML writer writing to a referenced string.
// Example:
// pugi::xml_document xdoc;
// ...
// std::string xml_string;
// XmlStrRefWriter writer(&xml_string);
// xdoc.save(writer, "\t", pugi::format_default, pugi::encoding_utf8);
class XmlStrRefWriter : public pugi::xml_writer {
public:
explicit XmlStrRefWriter(std::string* result) : result_(result) {
result_->clear();
}
void write(const void* data, std::size_t size) override {
result_->append(static_cast<const char*>(data), size);
}
private:
std::string* result_;
};
// Print the XML string to output stream in pretty format.
bool PrettyPrint(std::ostream& os, const std::string& xml_string,
const char* indent = "\t");
} // namespace soap_xml
} // namespace webcc
#endif // WEBCC_SOAP_XML_H_
+202
View File
@@ -0,0 +1,202 @@
#include "webcc/url.h"
#include <algorithm>
#include <sstream>
#include <utility> // for move()
namespace webcc {
// -----------------------------------------------------------------------------
// Helper functions to decode URL string.
// Convert a hex character digit to a decimal character value.
static bool HexToDecimal(char hex, int* decimal) {
if (hex >= '0' && hex <= '9') {
*decimal = hex - '0';
} else if (hex >= 'A' && hex <= 'F') {
*decimal = 10 + (hex - 'A');
} else if (hex >= 'a' && hex <= 'f') {
*decimal = 10 + (hex - 'a');
} else {
return false;
}
return true;
}
static bool Decode(const std::string& encoded, std::string* raw) {
for (auto iter = encoded.begin(); iter != encoded.end(); ++iter) {
if (*iter == '%') {
if (++iter == encoded.end()) {
// Invalid URI string, two hexadecimal digits must follow '%'.
return false;
}
int h_decimal = 0;
if (!HexToDecimal(*iter, &h_decimal)) {
return false;
}
if (++iter == encoded.end()) {
// Invalid URI string, two hexadecimal digits must follow '%'.
return false;
}
int l_decimal = 0;
if (!HexToDecimal(*iter, &l_decimal)) {
return false;
}
raw->push_back(static_cast<char>((h_decimal << 4) + l_decimal));
} else if (*iter > 127 || *iter < 0) {
// Invalid encoded URI string, must be entirely ASCII.
return false;
} else {
raw->push_back(*iter);
}
}
return true;
}
// -----------------------------------------------------------------------------
UrlQuery::UrlQuery(const std::map<std::string, std::string>& map) {
for (auto& pair : map) {
Add(pair.first, pair.second);
}
}
void UrlQuery::Add(std::string&& key, std::string&& value) {
if (!Has(key)) {
parameters_.push_back({ std::move(key), std::move(value) });
}
}
void UrlQuery::Add(const std::string& key, const std::string& value) {
if (!Has(key)) {
parameters_.push_back({ key, value });
}
}
void UrlQuery::Remove(const std::string& key) {
auto it = Find(key);
if (it != parameters_.end()) {
parameters_.erase(it);
}
}
const std::string& UrlQuery::Get(const std::string& key) const {
auto it = Find(key);
if (it != parameters_.end()) {
return it->value();
}
static const std::string kEmptyValue;
return kEmptyValue;
}
std::string UrlQuery::ToString() const {
if (parameters_.empty()) {
return "";
}
std::string str = parameters_[0].ToString();
for (std::size_t i = 1; i < parameters_.size(); ++i) {
str += "&";
str += parameters_[i].ToString();
}
return str;
}
UrlQuery::ConstIterator UrlQuery::Find(const std::string& key) const {
return std::find_if(parameters_.begin(),
parameters_.end(),
[&key](const Parameter& p) { return p.key() == key; });
}
// -----------------------------------------------------------------------------
Url::Url(const std::string& str, bool decode) {
if (!decode || str.find('%') == std::string::npos) {
Init(str);
return;
}
std::string decoded;
if (Decode(str, &decoded)) {
Init(decoded);
} else {
// TODO(Adam): Exception?
Init(str);
}
}
bool Url::IsValid() const {
return !path_.empty();
}
std::vector<std::string> Url::SplitPath(const std::string& path) {
std::vector<std::string> results;
std::stringstream iss(path);
std::string s;
while (std::getline(iss, s, '/')) {
if (!s.empty()) {
results.push_back(s);
}
}
return results;
}
static bool SplitKeyValue(const std::string& kv,
std::string* key,
std::string* value) {
std::size_t i = kv.find_first_of('=');
if (i == std::string::npos || i == 0) {
return false;
}
*key = kv.substr(0, i);
*value = kv.substr(i + 1);
return true;
}
// static
void Url::SplitQuery(const std::string& str, UrlQuery* query) {
const std::size_t NPOS = std::string::npos;
// Split into key value pairs separated by '&'.
std::size_t i = 0;
while (i != NPOS) {
std::size_t j = str.find_first_of('&', i);
std::string kv;
if (j == NPOS) {
kv = str.substr(i);
i = NPOS;
} else {
kv = str.substr(i, j - i);
i = j + 1;
}
std::string key;
std::string value;
if (SplitKeyValue(kv, &key, &value)) {
query->Add(std::move(key), std::move(value));
}
}
}
void Url::Init(const std::string& str) {
std::size_t pos = str.find('?');
if (pos == std::string::npos) {
path_ = str;
} else {
path_ = str.substr(0, pos);
query_ = str.substr(pos + 1);
}
}
} // namespace webcc
+99
View File
@@ -0,0 +1,99 @@
#ifndef WEBCC_URL_H_
#define WEBCC_URL_H_
// A simplified implementation of URL (or URI).
// The URL should start with "/".
// The parameters (separated by ";") are not supported.
// Example:
// /inventory-check.cgi?item=12731&color=blue&size=large
#include <map>
#include <string>
#include <vector>
#include "webcc/globals.h"
namespace webcc {
// -----------------------------------------------------------------------------
// URL query parameters.
class UrlQuery {
public:
typedef std::vector<Parameter> Parameters;
UrlQuery() = default;
// Construct from key-value pairs.
explicit UrlQuery(const std::map<std::string, std::string>& map);
void Add(const std::string& key, const std::string& value);
void Add(std::string&& key, std::string&& value);
void Remove(const std::string& key);
// Get a value by key.
// Return empty string if the key doesn't exist.
const std::string& Get(const std::string& key) const;
bool Has(const std::string& key) const {
return Find(key) != parameters_.end();
}
bool IsEmpty() const {
return parameters_.empty();
}
// Return key-value pairs concatenated by '&'.
// E.g., "item=12731&color=blue&size=large".
std::string ToString() const;
private:
typedef Parameters::const_iterator ConstIterator;
ConstIterator Find(const std::string& key) const;
Parameters parameters_;
};
// -----------------------------------------------------------------------------
class Url {
public:
Url() = default;
Url(const std::string& str, bool decode);
bool IsValid() const;
const std::string& path() const {
return path_;
}
void set_path(const std::string& path) {
path_ = path;
}
const std::string& query() const {
return query_;
}
void set_query(const std::string& query) {
query_ = query;
}
// Split a path into its hierarchical components.
static std::vector<std::string> SplitPath(const std::string& path);
// Split query string into key-value parameters.
static void SplitQuery(const std::string& str, UrlQuery* query);
private:
void Init(const std::string& str);
std::string path_;
std::string query_;
};
} // namespace webcc
#endif // WEBCC_URL_H_
+37
View File
@@ -0,0 +1,37 @@
#include "webcc/utility.h"
#include <ostream>
#include <sstream>
using tcp = boost::asio::ip::tcp;
namespace webcc {
void PrintEndpoint(std::ostream& ostream,
const boost::asio::ip::tcp::endpoint& endpoint) {
ostream << endpoint;
if (endpoint.protocol() == tcp::v4()) {
ostream << ", v4";
} else if (endpoint.protocol() == tcp::v6()) {
ostream << ", v6";
}
}
void PrintEndpoints(std::ostream& ostream,
const tcp::resolver::results_type& endpoints) {
ostream << "Endpoints: " << endpoints.size() << std::endl;
tcp::resolver::results_type::iterator it = endpoints.begin();
for (; it != endpoints.end(); ++it) {
ostream << " - ";
PrintEndpoint(ostream, it->endpoint());
ostream << std::endl;
}
}
std::string EndpointToString(const boost::asio::ip::tcp::endpoint& endpoint) {
std::stringstream ss;
PrintEndpoint(ss, endpoint);
return ss.str();
}
} // namespace webcc
+22
View File
@@ -0,0 +1,22 @@
#ifndef WEBCC_UTILITY_H_
#define WEBCC_UTILITY_H_
#include <iosfwd>
#include <string>
#include "boost/asio/ip/tcp.hpp"
namespace webcc {
void PrintEndpoint(std::ostream& ostream,
const boost::asio::ip::tcp::endpoint& endpoint);
void PrintEndpoints(
std::ostream& ostream,
const boost::asio::ip::tcp::resolver::results_type& endpoints);
std::string EndpointToString(const boost::asio::ip::tcp::endpoint& endpoint);
} // namespace webcc
#endif // WEBCC_UTILITY_H_