Reorganize folder structure.
This commit is contained in:
@@ -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})
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
Reference in New Issue
Block a user