Rename project from csoap to webcc
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
option(WEBCC_DEBUG_OUTPUT "Enable debug output?" OFF)
|
||||
|
||||
if(WEBCC_DEBUG_OUTPUT)
|
||||
add_definitions(-DWEBCC_DEBUG_OUTPUT)
|
||||
endif()
|
||||
|
||||
# Don't use any deprecated definitions (e.g., io_service).
|
||||
add_definitions(-DBOOST_ASIO_NO_DEPRECATED)
|
||||
|
||||
file(GLOB SRCS *.cc *.h)
|
||||
|
||||
add_library(webcc ${SRCS})
|
||||
@@ -0,0 +1,106 @@
|
||||
#include "webcc/common.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// NOTE:
|
||||
// Field names are case-insensitive.
|
||||
// See: https://stackoverflow.com/a/5259004
|
||||
const std::string kContentType = "Content-Type";
|
||||
const std::string kContentLength = "Content-Length";
|
||||
const std::string kSoapAction = "SOAPAction";
|
||||
const std::string kHost = "Host";
|
||||
|
||||
// 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";
|
||||
|
||||
const std::string kTextJsonUtf8 = "text/json; charset=utf-8";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const char* GetErrorMessage(Error error) {
|
||||
switch (error) {
|
||||
case kHostResolveError:
|
||||
return "Cannot resolve the host.";
|
||||
|
||||
case kEndpointConnectError:
|
||||
return "Cannot connect to remote endpoint.";
|
||||
|
||||
case kSocketTimeoutError:
|
||||
return "Operation timeout.";
|
||||
|
||||
case kSocketReadError:
|
||||
return "Socket read error.";
|
||||
|
||||
case kSocketWriteError:
|
||||
return "Socket write error.";
|
||||
|
||||
case kHttpStartLineError:
|
||||
return "[HTTP Response] Start line is invalid.";
|
||||
|
||||
case kHttpStatusError:
|
||||
return "[HTTP Response] Status is not OK.";
|
||||
|
||||
case kHttpContentLengthError:
|
||||
return "[HTTP Response] Content-Length is invalid or missing.";
|
||||
|
||||
case kXmlError:
|
||||
return "XML error";
|
||||
|
||||
default:
|
||||
return "No error";
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const Namespace kSoapEnvNamespace{
|
||||
"soap",
|
||||
"http://schemas.xmlsoap.org/soap/envelope/"
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
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 (this != &rhs) {
|
||||
key_ = std::move(rhs.key_);
|
||||
value_ = std::move(rhs.value_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,160 @@
|
||||
#ifndef WEBCC_COMMON_H_
|
||||
#define WEBCC_COMMON_H_
|
||||
|
||||
// Common definitions.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace webcc {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Buffer size for sending HTTP request and receiving HTTP response.
|
||||
// TODO: Configurable for client and server separately.
|
||||
const std::size_t kBufferSize = 1024;
|
||||
|
||||
const std::size_t kInvalidLength = static_cast<std::size_t>(-1);
|
||||
|
||||
extern const std::string kContentType;
|
||||
extern const std::string kContentLength;
|
||||
extern const std::string kSoapAction;
|
||||
extern const std::string kHost;
|
||||
|
||||
extern const std::string kTextXmlUtf8;
|
||||
extern const std::string kTextJsonUtf8;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Error codes.
|
||||
enum Error {
|
||||
kNoError = 0, // OK
|
||||
|
||||
kHostResolveError,
|
||||
kEndpointConnectError,
|
||||
|
||||
kSocketTimeoutError,
|
||||
|
||||
kSocketReadError,
|
||||
kSocketWriteError,
|
||||
|
||||
// Invalid start line in the HTTP response.
|
||||
kHttpStartLineError,
|
||||
|
||||
// Status is not 200 in the HTTP response.
|
||||
kHttpStatusError,
|
||||
|
||||
// Invalid or missing Content-Length in the HTTP response.
|
||||
kHttpContentLengthError,
|
||||
|
||||
kXmlError,
|
||||
};
|
||||
|
||||
// Return a descriptive message for the given error code.
|
||||
const char* GetErrorMessage(Error error);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// HTTP methods (verbs).
|
||||
// NOTE: Don't use enum to avoid converting back and forth.
|
||||
const std::string kHttpHead = "HEAD";
|
||||
const std::string kHttpGet = "GET";
|
||||
const std::string kHttpPost = "POST";
|
||||
const std::string kHttpPatch = "PATCH";
|
||||
const std::string kHttpPut = "PUT";
|
||||
const std::string kHttpDelete = "DELETE";
|
||||
|
||||
//enum class HttpMethod : int {
|
||||
// kUnknown,
|
||||
// kHead,
|
||||
// kGet,
|
||||
// kPost,
|
||||
// kPatch,
|
||||
// kPut,
|
||||
// kDelete,
|
||||
//};
|
||||
|
||||
// HTTP response status.
|
||||
// This is not a full list.
|
||||
// Full list: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
|
||||
// NTOE: Don't use enum class because we want to convert to/from int easily.
|
||||
struct HttpStatus {
|
||||
enum Enum {
|
||||
kOK = 200,
|
||||
kCreated = 201,
|
||||
kAccepted = 202,
|
||||
kNoContent = 204,
|
||||
kNotModified = 304,
|
||||
kBadRequest = 400,
|
||||
kNotFound = 404,
|
||||
InternalServerError = 500,
|
||||
kNotImplemented = 501,
|
||||
kServiceUnavailable = 503,
|
||||
};
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// XML namespace name/url pair.
|
||||
// E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" }
|
||||
// TODO: Rename (add soap prefix)
|
||||
class Namespace {
|
||||
public:
|
||||
std::string name;
|
||||
std::string url;
|
||||
|
||||
bool IsValid() const {
|
||||
return !name.empty() && !url.empty();
|
||||
}
|
||||
};
|
||||
|
||||
// CSoap's default namespace for SOAP Envelope.
|
||||
extern const Namespace kSoapEnvNamespace;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Parameter in the SOAP request envelope.
|
||||
// TODO: Rename (add soap prefix)
|
||||
class Parameter {
|
||||
public:
|
||||
Parameter() = default;
|
||||
Parameter(const Parameter& rhs) = default;
|
||||
Parameter& operator=(const Parameter& rhs) = 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();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string key_;
|
||||
std::string value_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_COMMON_H_
|
||||
@@ -0,0 +1,141 @@
|
||||
#include "webcc/http_client.h"
|
||||
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
#include "boost/asio.hpp"
|
||||
#else
|
||||
#include "boost/asio/connect.hpp"
|
||||
#include "boost/asio/ip/tcp.hpp"
|
||||
#include "boost/asio/read.hpp"
|
||||
#include "boost/asio/write.hpp"
|
||||
#endif
|
||||
|
||||
#include "webcc/http_response_parser.h"
|
||||
#include "webcc/http_request.h"
|
||||
#include "webcc/http_response.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// See https://stackoverflow.com/a/9079092
|
||||
static void SetTimeout(boost::asio::ip::tcp::socket& socket,
|
||||
int timeout_seconds) {
|
||||
#if defined _WINDOWS
|
||||
|
||||
int ms = timeout_seconds * 1000;
|
||||
|
||||
const char* optval = reinterpret_cast<const char*>(&ms);
|
||||
size_t optlen = sizeof(ms);
|
||||
|
||||
setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, optval, optlen);
|
||||
setsockopt(socket.native_handle(), SOL_SOCKET, SO_SNDTIMEO, optval, optlen);
|
||||
|
||||
#else // POSIX
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout_seconds_;
|
||||
tv.tv_usec = 0;
|
||||
setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
setsockopt(socket.native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
HttpClient::HttpClient()
|
||||
: timeout_seconds_(15) {
|
||||
}
|
||||
|
||||
Error HttpClient::SendRequest(const HttpRequest& request,
|
||||
HttpResponse* response) {
|
||||
assert(response != NULL);
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
tcp::socket socket(io_context_);
|
||||
|
||||
tcp::resolver resolver(io_context_);
|
||||
|
||||
std::string port = request.port();
|
||||
if (port.empty()) {
|
||||
port = "80";
|
||||
}
|
||||
|
||||
boost::system::error_code ec;
|
||||
tcp::resolver::results_type endpoints =
|
||||
resolver.resolve(tcp::v4(), request.host(), port, ec);
|
||||
if (ec) {
|
||||
return kHostResolveError;
|
||||
}
|
||||
|
||||
boost::asio::connect(socket, endpoints, ec);
|
||||
if (ec) {
|
||||
return kEndpointConnectError;
|
||||
}
|
||||
|
||||
SetTimeout(socket, timeout_seconds_);
|
||||
|
||||
// Send HTTP request.
|
||||
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
std::cout << "# REQUEST" << std::endl << request << std::endl;
|
||||
#endif
|
||||
|
||||
try {
|
||||
boost::asio::write(socket, request.ToBuffers());
|
||||
} catch (boost::system::system_error&) {
|
||||
return kSocketWriteError;
|
||||
}
|
||||
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
std::cout << "# RESPONSE" << std::endl;
|
||||
#endif
|
||||
|
||||
// Read and parse HTTP response.
|
||||
|
||||
HttpResponseParser parser(response);
|
||||
|
||||
// NOTE:
|
||||
// We must stop trying to read once all content has been received,
|
||||
// because some servers will block extra call to read_some().
|
||||
while (!parser.finished()) {
|
||||
size_t length = socket.read_some(boost::asio::buffer(buffer_), ec);
|
||||
|
||||
if (length == 0 || ec) {
|
||||
if (ec.value() == WSAETIMEDOUT) {
|
||||
return kSocketTimeoutError;
|
||||
} else {
|
||||
return kSocketReadError;
|
||||
}
|
||||
}
|
||||
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
// NOTE: the content XML might not be well formated.
|
||||
std::cout.write(buffer_.data(), length);
|
||||
#endif
|
||||
|
||||
// Parse the response piece just read.
|
||||
// If the content has been fully received, next time flag "finished_"
|
||||
// will be set.
|
||||
Error error = parser.Parse(buffer_.data(), length);
|
||||
|
||||
if (error != kNoError) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
std::cout << std::endl;
|
||||
std::cout << "# RESPONSE (PARSED)" << std::endl;
|
||||
std::cout << *response << std::endl;
|
||||
#endif
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef WEBCC_HTTP_CLIENT_H_
|
||||
#define WEBCC_HTTP_CLIENT_H_
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "boost/asio/io_context.hpp"
|
||||
|
||||
#include "webcc/common.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class HttpRequest;
|
||||
class HttpResponse;
|
||||
|
||||
class HttpClient {
|
||||
public:
|
||||
HttpClient();
|
||||
|
||||
// Set socket send & recv timeout.
|
||||
void set_timeout_seconds(int seconds) {
|
||||
timeout_seconds_ = seconds;
|
||||
}
|
||||
|
||||
// Send an HTTP request, wait until the response is received.
|
||||
Error SendRequest(const HttpRequest& request,
|
||||
HttpResponse* response);
|
||||
|
||||
private:
|
||||
boost::asio::io_context io_context_;
|
||||
std::array<char, kBufferSize> buffer_;
|
||||
int timeout_seconds_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_HTTP_CLIENT_H_
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "webcc/http_message.h"
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,86 @@
|
||||
#ifndef WEBCC_HTTP_MESSAGE_H_
|
||||
#define WEBCC_HTTP_MESSAGE_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include "webcc/common.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class HttpHeader {
|
||||
public:
|
||||
std::string name;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
// Base class for HTTP request and response messages.
|
||||
class HttpMessage {
|
||||
public:
|
||||
HttpMessage() = default;
|
||||
HttpMessage(const HttpMessage&) = default;
|
||||
HttpMessage& operator=(const HttpMessage&) = default;
|
||||
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;
|
||||
}
|
||||
|
||||
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 SetContentLength(size_t content_length) {
|
||||
content_length_ = content_length;
|
||||
SetHeader(kContentLength, std::to_string(content_length));
|
||||
}
|
||||
|
||||
// Use move semantics to avoid copy.
|
||||
void set_content(std::string&& content) {
|
||||
content_ = std::move(content);
|
||||
}
|
||||
|
||||
void AppendContent(const char* data, size_t count) {
|
||||
content_.append(data, count);
|
||||
}
|
||||
|
||||
void AppendContent(const std::string& data) {
|
||||
content_.append(data);
|
||||
}
|
||||
|
||||
bool IsContentFull() const {
|
||||
assert(IsContentLengthValid());
|
||||
return content_.length() >= content_length_;
|
||||
}
|
||||
|
||||
bool IsContentLengthValid() const {
|
||||
return content_length_ != kInvalidLength;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Start line with trailing "\r\n".
|
||||
std::string start_line_;
|
||||
|
||||
std::size_t content_length_ = kInvalidLength;
|
||||
|
||||
std::vector<HttpHeader> headers_;
|
||||
|
||||
std::string content_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_HTTP_MESSAGE_H_
|
||||
@@ -0,0 +1,110 @@
|
||||
#include "webcc/http_parser.h"
|
||||
|
||||
#include "boost/algorithm/string.hpp"
|
||||
#include "boost/lexical_cast.hpp"
|
||||
|
||||
#include "webcc/http_message.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
HttpParser::HttpParser(HttpMessage* message)
|
||||
: message_(message)
|
||||
, start_line_parsed_(false)
|
||||
, header_parsed_(false)
|
||||
, finished_(false) {
|
||||
}
|
||||
|
||||
Error HttpParser::Parse(const char* data, size_t len) {
|
||||
if (header_parsed_) {
|
||||
// Add the data to the content.
|
||||
message_->AppendContent(data, len);
|
||||
|
||||
if (message_->IsContentFull()) {
|
||||
// All content has been read.
|
||||
finished_ = true;
|
||||
}
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
pending_data_.append(data, len);
|
||||
size_t off = 0;
|
||||
|
||||
while (true) {
|
||||
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;
|
||||
Error error = ParseStartLine(line);
|
||||
if (error != kNoError) {
|
||||
return error;
|
||||
}
|
||||
} else {
|
||||
// Currently, only Content-Length is important to us.
|
||||
// Other fields are ignored.
|
||||
if (!message_->IsContentLengthValid()) {
|
||||
ParseContentLength(line);
|
||||
}
|
||||
}
|
||||
|
||||
off = pos + 2; // Skip CRLF.
|
||||
}
|
||||
|
||||
if (header_parsed_) {
|
||||
// Headers just ended.
|
||||
|
||||
if (!message_->IsContentLengthValid()) {
|
||||
// No Content-Length?
|
||||
return kHttpContentLengthError;
|
||||
}
|
||||
|
||||
message_->AppendContent(pending_data_.substr(off));
|
||||
|
||||
if (message_->IsContentFull()) {
|
||||
// All content has been read.
|
||||
finished_ = true;
|
||||
}
|
||||
} else {
|
||||
// Save the unparsed piece for next parsing.
|
||||
pending_data_ = pending_data_.substr(off);
|
||||
}
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
void HttpParser::ParseContentLength(const std::string& line) {
|
||||
size_t pos = line.find(':');
|
||||
if (pos == std::string::npos) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string name = line.substr(0, pos);
|
||||
|
||||
if (boost::iequals(name, kContentLength)) {
|
||||
++pos; // Skip ':'.
|
||||
while (line[pos] == ' ') { // Skip spaces.
|
||||
++pos;
|
||||
}
|
||||
|
||||
std::string value = line.substr(pos);
|
||||
|
||||
try {
|
||||
message_->SetContentLength(boost::lexical_cast<size_t>(value));
|
||||
} catch (boost::bad_lexical_cast&) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef WEBCC_HTTP_PARSER_H_
|
||||
#define WEBCC_HTTP_PARSER_H_
|
||||
|
||||
#include <string>
|
||||
#include "webcc/common.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class HttpMessage;
|
||||
|
||||
// HttpParser parses HTTP request and response.
|
||||
class HttpParser {
|
||||
public:
|
||||
explicit HttpParser(HttpMessage* message);
|
||||
|
||||
bool finished() const {
|
||||
return finished_;
|
||||
}
|
||||
|
||||
Error Parse(const char* data, size_t len);
|
||||
|
||||
protected:
|
||||
// Parse HTTP start line.
|
||||
virtual Error ParseStartLine(const std::string& line) = 0;
|
||||
|
||||
void ParseContentLength(const std::string& line);
|
||||
|
||||
protected:
|
||||
// The result HTTP message.
|
||||
HttpMessage* message_;
|
||||
|
||||
Error error_;
|
||||
|
||||
// Data waiting to be parsed.
|
||||
std::string pending_data_;
|
||||
|
||||
// Parsing helper flags.
|
||||
bool start_line_parsed_;
|
||||
bool header_parsed_;
|
||||
bool finished_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_HTTP_PARSER_H_
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "webcc/http_request.h"
|
||||
|
||||
#include "boost/algorithm/string.hpp"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const HttpRequest& request) {
|
||||
os << request.start_line();
|
||||
|
||||
for (const HttpHeader& h : request.headers_) {
|
||||
os << h.name << ": " << h.value << std::endl;
|
||||
}
|
||||
|
||||
os << std::endl;
|
||||
|
||||
os << request.content() << std::endl;
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
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::MakeStartLine() {
|
||||
if (start_line_.empty()) {
|
||||
start_line_ = method_;
|
||||
start_line_ += " ";
|
||||
start_line_ += url_;
|
||||
start_line_ += " HTTP/1.1\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
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> HttpRequest::ToBuffers() const {
|
||||
assert(!start_line_.empty());
|
||||
assert(IsContentLengthValid());
|
||||
|
||||
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));
|
||||
|
||||
buffers.push_back(boost::asio::buffer(content_));
|
||||
|
||||
return buffers;
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,78 @@
|
||||
#ifndef WEBCC_HTTP_REQUEST_H_
|
||||
#define WEBCC_HTTP_REQUEST_H_
|
||||
|
||||
#include <string>
|
||||
#include "boost/asio/buffer.hpp" // for const_buffer
|
||||
#include "webcc/http_message.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class HttpRequest;
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const HttpRequest& request);
|
||||
|
||||
class HttpRequest : public HttpMessage {
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os,
|
||||
const HttpRequest& request);
|
||||
|
||||
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_;
|
||||
}
|
||||
|
||||
// \param host Descriptive host name or numeric IP address.
|
||||
// \param port Numeric port number, "80" will be used if it's empty.
|
||||
void SetHost(const std::string& host, const std::string& port);
|
||||
|
||||
// Compose start line from method, url, etc.
|
||||
void MakeStartLine();
|
||||
|
||||
// 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.
|
||||
// NOTE: Please call MakeStartLine() before.
|
||||
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_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_HTTP_REQUEST_H_
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "webcc/http_request_handler.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
#include "webcc/common.h"
|
||||
#include "webcc/http_request.h"
|
||||
#include "webcc/http_response.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
void HttpRequestHandler::Enqueue(HttpSessionPtr session) {
|
||||
queue_.Push(session);
|
||||
}
|
||||
|
||||
void HttpRequestHandler::Start(std::size_t count) {
|
||||
assert(count > 0 && workers_.size() == 0);
|
||||
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
boost::thread* worker =
|
||||
#endif
|
||||
workers_.create_thread(std::bind(&HttpRequestHandler::WorkerRoutine, this));
|
||||
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
std::cout << "Worker is running (thread: " << worker->get_id() << ")\n";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequestHandler::Stop() {
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
std::cout << "Stopping workers...\n";
|
||||
#endif
|
||||
|
||||
// Close pending sessions.
|
||||
for (HttpSessionPtr conn = queue_.Pop(); conn; conn = queue_.Pop()) {
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
std::cout << "Closing pending session...\n";
|
||||
#endif
|
||||
conn->Stop();
|
||||
}
|
||||
|
||||
// Enqueue a null session to trigger the first worker to stop.
|
||||
queue_.Push(HttpSessionPtr());
|
||||
|
||||
workers_.join_all();
|
||||
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
std::cout << "All workers have been stopped.\n";
|
||||
#endif
|
||||
}
|
||||
|
||||
void HttpRequestHandler::WorkerRoutine() {
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
boost::thread::id thread_id = boost::this_thread::get_id();
|
||||
#endif
|
||||
|
||||
for (;;) {
|
||||
HttpSessionPtr session = queue_.PopOrWait();
|
||||
|
||||
if (!session) {
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
std::cout << "Worker is going to stop (thread: " << thread_id << ")\n";
|
||||
#endif
|
||||
// For stopping next worker.
|
||||
queue_.Push(HttpSessionPtr());
|
||||
|
||||
// Stop the worker.
|
||||
break;
|
||||
}
|
||||
|
||||
HandleSession(session);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef WEBCC_HTTP_REQUEST_HANDLER_H_
|
||||
#define WEBCC_HTTP_REQUEST_HANDLER_H_
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include "boost/thread/thread.hpp"
|
||||
|
||||
#include "webcc/http_session.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(const HttpRequestHandler&) = delete;
|
||||
HttpRequestHandler& operator=(const HttpRequestHandler&) = delete;
|
||||
|
||||
HttpRequestHandler() = default;
|
||||
|
||||
virtual ~HttpRequestHandler() {
|
||||
}
|
||||
|
||||
// Put the session into the queue.
|
||||
void Enqueue(HttpSessionPtr session);
|
||||
|
||||
// Start worker threads.
|
||||
void Start(std::size_t count);
|
||||
|
||||
// Close pending sessions and stop worker threads.
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
void WorkerRoutine();
|
||||
|
||||
// Called by the worker routine.
|
||||
virtual HttpStatus::Enum HandleSession(HttpSessionPtr session) = 0;
|
||||
|
||||
private:
|
||||
Queue<HttpSessionPtr> 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) {
|
||||
}
|
||||
|
||||
Error 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 kHttpStartLineError;
|
||||
}
|
||||
|
||||
request_->set_method(strs[0]);
|
||||
request_->set_url(strs[1]);
|
||||
|
||||
// HTTP version is currently ignored.
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef WEBCC_HTTP_REQUEST_PARSER_H_
|
||||
#define WEBCC_HTTP_REQUEST_PARSER_H_
|
||||
|
||||
#include "webcc/http_parser.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class HttpRequest;
|
||||
|
||||
class HttpRequestParser : public HttpParser {
|
||||
public:
|
||||
explicit HttpRequestParser(HttpRequest* request);
|
||||
|
||||
private:
|
||||
Error ParseStartLine(const std::string& line) override;
|
||||
|
||||
private:
|
||||
HttpRequest* request_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_HTTP_REQUEST_PARSER_H_
|
||||
@@ -0,0 +1,121 @@
|
||||
#include "webcc/http_response.h"
|
||||
|
||||
#include "webcc/common.h"
|
||||
#include "webcc/xml.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const HttpResponse& response) {
|
||||
os << response.start_line();
|
||||
|
||||
for (const HttpHeader& h : response.headers_) {
|
||||
os << h.name << ": " << h.value << std::endl;
|
||||
}
|
||||
|
||||
os << std::endl;
|
||||
|
||||
// Pretty print the SOAP response XML.
|
||||
if (!xml::PrettyPrintXml(os, response.content())) {
|
||||
os << response.content();
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
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 (IsContentLengthValid()) {
|
||||
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,43 @@
|
||||
#ifndef WEBCC_HTTP_RESPONSE_H_
|
||||
#define WEBCC_HTTP_RESPONSE_H_
|
||||
|
||||
#include <string>
|
||||
#include "boost/asio/buffer.hpp" // for const_buffer
|
||||
#include "webcc/http_message.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class HttpResponse;
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const HttpResponse& response);
|
||||
|
||||
class HttpResponse : public HttpMessage {
|
||||
friend std::ostream& operator<<(std::ostream& os,
|
||||
const HttpResponse& response);
|
||||
|
||||
public:
|
||||
HttpResponse() {
|
||||
}
|
||||
|
||||
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_ = HttpStatus::kOK; // TODO: HttpStatus
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_HTTP_RESPONSE_H_
|
||||
@@ -0,0 +1,49 @@
|
||||
#include "webcc/http_response_parser.h"
|
||||
#include "boost/lexical_cast.hpp"
|
||||
#include "webcc/http_response.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
HttpResponseParser::HttpResponseParser(HttpResponse* response)
|
||||
: HttpParser(response)
|
||||
, response_(response) {
|
||||
}
|
||||
|
||||
Error HttpResponseParser::ParseStartLine(const std::string& line) {
|
||||
response_->set_start_line(line + "\r\n");
|
||||
|
||||
size_t off = 0;
|
||||
|
||||
size_t pos = line.find(' ');
|
||||
if (pos == std::string::npos) {
|
||||
return kHttpStartLineError;
|
||||
}
|
||||
|
||||
// HTTP version
|
||||
|
||||
off = pos + 1; // Skip space.
|
||||
|
||||
pos = line.find(' ', off);
|
||||
if (pos == std::string::npos) {
|
||||
return kHttpStartLineError;
|
||||
}
|
||||
|
||||
// Status code
|
||||
std::string status_str = line.substr(off, pos - off);
|
||||
|
||||
try {
|
||||
response_->set_status(boost::lexical_cast<int>(status_str));
|
||||
} catch (boost::bad_lexical_cast&) {
|
||||
return kHttpStartLineError;
|
||||
}
|
||||
|
||||
off = pos + 1; // Skip space.
|
||||
|
||||
if (response_->status() != HttpStatus::kOK) {
|
||||
return kHttpStatusError;
|
||||
}
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef WEBCC_HTTP_RESPONSE_PARSER_H_
|
||||
#define WEBCC_HTTP_RESPONSE_PARSER_H_
|
||||
|
||||
#include "webcc/http_parser.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class HttpResponse;
|
||||
|
||||
class HttpResponseParser : public HttpParser {
|
||||
public:
|
||||
explicit HttpResponseParser(HttpResponse* response);
|
||||
|
||||
private:
|
||||
// Parse HTTP start line; E.g., "HTTP/1.1 200 OK".
|
||||
Error ParseStartLine(const std::string& line) override;
|
||||
|
||||
private:
|
||||
// The result response message.
|
||||
HttpResponse* response_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_HTTP_RESPONSE_PARSER_H_
|
||||
@@ -0,0 +1,98 @@
|
||||
#include "webcc/http_server.h"
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
#include "webcc/http_request_handler.h"
|
||||
#include "webcc/soap_service.h"
|
||||
#include "webcc/utility.h"
|
||||
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
|
||||
namespace webcc {
|
||||
|
||||
HttpServer::HttpServer(unsigned short 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.
|
||||
// TODO: Verify if this works for Windows.
|
||||
signals_.add(SIGINT);
|
||||
signals_.add(SIGTERM);
|
||||
#if defined(SIGQUIT)
|
||||
signals_.add(SIGQUIT);
|
||||
#endif
|
||||
|
||||
DoAwaitStop();
|
||||
|
||||
// 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
|
||||
// http://www.andy-pearce.com/blog/posts/2013/Feb/so_reuseaddr-on-windows/
|
||||
// TODO: SO_EXCLUSIVEADDRUSE
|
||||
acceptor_.reset(new tcp::acceptor(io_context_,
|
||||
tcp::endpoint(tcp::v4(), port),
|
||||
true)); // reuse_addr
|
||||
|
||||
DoAccept();
|
||||
}
|
||||
|
||||
HttpServer::~HttpServer() {
|
||||
}
|
||||
|
||||
void HttpServer::Run() {
|
||||
assert(request_handler_ != NULL);
|
||||
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
boost::thread::id thread_id = boost::this_thread::get_id();
|
||||
std::cout << "Server main thread: " << thread_id << std::endl;
|
||||
#endif
|
||||
|
||||
// Start worker threads.
|
||||
request_handler_->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::DoAccept() {
|
||||
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) {
|
||||
HttpSessionPtr conn{
|
||||
new HttpSession(std::move(socket), request_handler_)
|
||||
};
|
||||
conn->Start();
|
||||
}
|
||||
|
||||
DoAccept();
|
||||
});
|
||||
}
|
||||
|
||||
void HttpServer::DoAwaitStop() {
|
||||
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.
|
||||
acceptor_->close();
|
||||
request_handler_->Stop();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef WEBCC_HTTP_SERVER_H_
|
||||
#define WEBCC_HTTP_SERVER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "boost/scoped_ptr.hpp"
|
||||
#include "boost/thread/thread.hpp"
|
||||
|
||||
#include "boost/asio/io_context.hpp"
|
||||
#include "boost/asio/signal_set.hpp"
|
||||
#include "boost/asio/ip/tcp.hpp"
|
||||
|
||||
#include "webcc/http_session.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class HttpRequestHandler;
|
||||
|
||||
// HTTP server accepts TCP connections from TCP clients.
|
||||
// NOTE: Only support IPv4.
|
||||
class HttpServer {
|
||||
public:
|
||||
HttpServer(const HttpServer&) = delete;
|
||||
HttpServer& operator=(const HttpServer&) = delete;
|
||||
|
||||
HttpServer(unsigned short port, std::size_t workers);
|
||||
|
||||
virtual ~HttpServer();
|
||||
|
||||
// Run the server's io_service loop.
|
||||
void Run();
|
||||
|
||||
private:
|
||||
// Initiate an asynchronous accept operation.
|
||||
void DoAccept();
|
||||
|
||||
// Wait for a request to stop the server.
|
||||
void DoAwaitStop();
|
||||
|
||||
protected:
|
||||
// The handler for all incoming requests.
|
||||
// TODO: Replace with virtual GetRequestHandler()?
|
||||
HttpRequestHandler* request_handler_;
|
||||
|
||||
private:
|
||||
// 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_
|
||||
@@ -0,0 +1,109 @@
|
||||
#include "webcc/http_session.h"
|
||||
|
||||
#include <vector>
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
#include "boost/asio/write.hpp"
|
||||
|
||||
#include "webcc/http_request_handler.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
HttpSession::HttpSession(boost::asio::ip::tcp::socket socket,
|
||||
HttpRequestHandler* handler)
|
||||
: socket_(std::move(socket))
|
||||
, request_handler_(handler)
|
||||
, request_parser_(&request_) {
|
||||
}
|
||||
|
||||
void HttpSession::Start() {
|
||||
DoRead();
|
||||
}
|
||||
|
||||
void HttpSession::Stop() {
|
||||
socket_.close();
|
||||
}
|
||||
|
||||
void HttpSession::SetResponseContent(const std::string& content_type,
|
||||
std::size_t content_length,
|
||||
std::string&& content) {
|
||||
response_.SetContentType(content_type);
|
||||
response_.SetContentLength(content.length());
|
||||
response_.set_content(std::move(content));
|
||||
}
|
||||
|
||||
void HttpSession::SendResponse() {
|
||||
DoWrite();
|
||||
}
|
||||
|
||||
void HttpSession::DoRead() {
|
||||
auto handler = std::bind(&HttpSession::HandleRead,
|
||||
shared_from_this(),
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2);
|
||||
socket_.async_read_some(boost::asio::buffer(buffer_), handler);
|
||||
}
|
||||
|
||||
void HttpSession::DoWrite() {
|
||||
auto handler = std::bind(&HttpSession::HandleWrite,
|
||||
shared_from_this(),
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2);
|
||||
boost::asio::async_write(socket_, response_.ToBuffers(), handler);
|
||||
}
|
||||
|
||||
void HttpSession::HandleRead(boost::system::error_code ec,
|
||||
std::size_t length) {
|
||||
if (ec) {
|
||||
if (ec != boost::asio::error::operation_aborted) {
|
||||
Stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Error error = request_parser_.Parse(buffer_.data(), length);
|
||||
|
||||
if (error != kNoError) {
|
||||
// Bad request.
|
||||
response_ = HttpResponse::Fault(HttpStatus::kBadRequest);
|
||||
DoWrite();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!request_parser_.finished()) {
|
||||
// Continue to read the request.
|
||||
DoRead();
|
||||
return;
|
||||
}
|
||||
|
||||
// Enqueue this session.
|
||||
// Some worker thread will handle it later.
|
||||
// And DoWrite() will be called in the worker thread.
|
||||
request_handler_->Enqueue(shared_from_this());
|
||||
}
|
||||
|
||||
// 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 HttpSession::HandleWrite(boost::system::error_code ec,
|
||||
size_t length) {
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
boost::thread::id thread_id = boost::this_thread::get_id();
|
||||
std::cout << "Response has been sent back (thread: " << thread_id << ")\n";
|
||||
#endif
|
||||
|
||||
if (!ec) {
|
||||
// Initiate graceful connection closure.
|
||||
boost::system::error_code ignored_ec;
|
||||
socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
|
||||
}
|
||||
|
||||
if (ec != boost::asio::error::operation_aborted) {
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,82 @@
|
||||
#ifndef WEBCC_HTTP_SESSION_H_
|
||||
#define WEBCC_HTTP_SESSION_H_
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include "boost/asio/ip/tcp.hpp" // for ip::tcp::socket
|
||||
|
||||
#include "webcc/common.h"
|
||||
#include "webcc/http_request.h"
|
||||
#include "webcc/http_request_parser.h"
|
||||
#include "webcc/http_response.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class HttpRequestHandler;
|
||||
|
||||
class HttpSession : public std::enable_shared_from_this<HttpSession> {
|
||||
public:
|
||||
friend class HttpRequestHandler;
|
||||
|
||||
HttpSession(const HttpSession&) = delete;
|
||||
HttpSession& operator=(const HttpSession&) = delete;
|
||||
|
||||
HttpSession(boost::asio::ip::tcp::socket socket,
|
||||
HttpRequestHandler* handler);
|
||||
|
||||
const HttpRequest& request() const {
|
||||
return request_;
|
||||
}
|
||||
|
||||
void Start();
|
||||
|
||||
void Stop();
|
||||
|
||||
void SetResponseStatus(int status) {
|
||||
response_.set_status(status);
|
||||
}
|
||||
|
||||
void SetResponseContent(const std::string& content_type,
|
||||
std::size_t content_length,
|
||||
std::string&& content);
|
||||
|
||||
// Write response back to the client.
|
||||
void SendResponse();
|
||||
|
||||
private:
|
||||
void DoRead();
|
||||
|
||||
void DoWrite();
|
||||
|
||||
void HandleRead(boost::system::error_code ec,
|
||||
std::size_t length);
|
||||
|
||||
void HandleWrite(boost::system::error_code ec,
|
||||
std::size_t length);
|
||||
|
||||
private:
|
||||
// Socket for the connection.
|
||||
boost::asio::ip::tcp::socket socket_;
|
||||
|
||||
// The handler used to process the incoming request.
|
||||
HttpRequestHandler* request_handler_;
|
||||
|
||||
// Buffer for incoming data.
|
||||
std::array<char, kBufferSize> buffer_;
|
||||
|
||||
// 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<HttpSession> HttpSessionPtr;
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_HTTP_SESSION_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(const Queue& rhs) = delete;
|
||||
Queue& operator=(const Queue& rhs) = delete;
|
||||
|
||||
Queue() = default;
|
||||
|
||||
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,120 @@
|
||||
#include "webcc/rest_server.h"
|
||||
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
#include "webcc/url.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool RestServiceManager::AddService(RestServicePtr service,
|
||||
const std::string& url) {
|
||||
assert(service);
|
||||
|
||||
ServiceItem item(service, url);
|
||||
|
||||
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(item);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (std::regex_error& e) {
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
std::cout << e.what() << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
RestServicePtr RestServiceManager::GetService(
|
||||
const std::string& url,
|
||||
std::vector<std::string>* sub_matches) {
|
||||
assert(sub_matches != NULL);
|
||||
|
||||
for (ServiceItem& item : service_items_) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return RestServicePtr();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool RestRequestHandler::RegisterService(RestServicePtr service,
|
||||
const std::string& url) {
|
||||
return service_manager_.AddService(service, url);
|
||||
}
|
||||
|
||||
HttpStatus::Enum RestRequestHandler::HandleSession(HttpSessionPtr session) {
|
||||
Url url(session->request().url());
|
||||
|
||||
if (!url.IsValid()) {
|
||||
session->SetResponseStatus(HttpStatus::kBadRequest);
|
||||
session->SendResponse();
|
||||
return HttpStatus::kBadRequest;
|
||||
}
|
||||
|
||||
std::vector<std::string> sub_matches;
|
||||
RestServicePtr service = service_manager_.GetService(url.path(), &sub_matches);
|
||||
if (!service) {
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
std::cout << "No service matches the URL: " << url.path() << std::endl;
|
||||
#endif
|
||||
session->SetResponseStatus(HttpStatus::kBadRequest);
|
||||
session->SendResponse();
|
||||
return HttpStatus::kBadRequest;
|
||||
}
|
||||
|
||||
// TODO: Error handling.
|
||||
std::string content;
|
||||
service->Handle(session->request().method(),
|
||||
session->request().content(),
|
||||
&content);
|
||||
|
||||
session->SetResponseStatus(HttpStatus::kOK);
|
||||
session->SetResponseContent(kTextJsonUtf8,
|
||||
content.length(),
|
||||
std::move(content));
|
||||
session->SendResponse();
|
||||
|
||||
return HttpStatus::kOK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
RestServer::RestServer(unsigned short port, std::size_t workers)
|
||||
: HttpServer(port, workers)
|
||||
, rest_request_handler_(new RestRequestHandler()) {
|
||||
request_handler_ = rest_request_handler_;
|
||||
}
|
||||
|
||||
RestServer::~RestServer() {
|
||||
request_handler_ = NULL;
|
||||
delete rest_request_handler_;
|
||||
}
|
||||
|
||||
bool RestServer::RegisterService(RestServicePtr service,
|
||||
const std::string& url) {
|
||||
return rest_request_handler_->RegisterService(service, url);
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,105 @@
|
||||
#ifndef WEBCC_REST_SERVER_H_
|
||||
#define WEBCC_REST_SERVER_H_
|
||||
|
||||
// HTTP server handling REST requests.
|
||||
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webcc/http_request_handler.h"
|
||||
#include "webcc/http_server.h"
|
||||
#include "webcc/rest_service.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class Url;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class RestServiceManager {
|
||||
public:
|
||||
RestServiceManager() = default;
|
||||
RestServiceManager(const RestServiceManager&) = delete;
|
||||
RestServiceManager& operator=(const RestServiceManager&) = delete;
|
||||
|
||||
// Add a service and bind it with the given URL.
|
||||
// The URL should start with "/" and could be a regular expression or not.
|
||||
// E.g., "/instances". "/instances/(\\d+)"
|
||||
bool AddService(RestServicePtr service, const std::string& url);
|
||||
|
||||
// Parameter '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)
|
||||
: service(_service), url(_url) {
|
||||
}
|
||||
|
||||
ServiceItem(const ServiceItem& rhs) = default;
|
||||
ServiceItem& operator=(const ServiceItem& rhs) = default;
|
||||
|
||||
ServiceItem(ServiceItem&& rhs)
|
||||
: url(std::move(rhs.url))
|
||||
, url_regex(std::move(rhs.url_regex))
|
||||
, service(rhs.service) { // No move
|
||||
}
|
||||
|
||||
RestServicePtr service;
|
||||
|
||||
// URL string, e.g., "/instances/(\\d+)".
|
||||
std::string url;
|
||||
|
||||
// Compiled regex for URL string.
|
||||
std::regex url_regex;
|
||||
};
|
||||
|
||||
std::vector<ServiceItem> service_items_;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class RestRequestHandler : public HttpRequestHandler {
|
||||
public:
|
||||
RestRequestHandler() = default;
|
||||
|
||||
// Register a REST service to the given URL path.
|
||||
// The URL should start with "/" and could be a regular expression or not.
|
||||
// E.g., "/instances". "/instances/(\\d+)"
|
||||
bool RegisterService(RestServicePtr service, const std::string& url);
|
||||
|
||||
private:
|
||||
HttpStatus::Enum HandleSession(HttpSessionPtr session) override;
|
||||
|
||||
private:
|
||||
RestServiceManager service_manager_;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class RestServer : public HttpServer {
|
||||
public:
|
||||
RestServer(unsigned short port, std::size_t workers);
|
||||
|
||||
~RestServer() override;
|
||||
|
||||
// Register a REST service to the given URL path.
|
||||
// The URL should start with "/" and could be a regular expression or not.
|
||||
// E.g., "/instances". "/instances/(\\d+)"
|
||||
// NOTE: Registering to the same URL multiple times is allowed, but only the
|
||||
// last one takes effect.
|
||||
bool RegisterService(RestServicePtr service, const std::string& url);
|
||||
|
||||
private:
|
||||
RestRequestHandler* rest_request_handler_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_REST_SERVER_H_
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef WEBCC_REST_SERVICE_H_
|
||||
#define WEBCC_REST_SERVICE_H_
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include "webcc/common.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// Base class for your REST service.
|
||||
class RestService {
|
||||
public:
|
||||
virtual ~RestService() {
|
||||
}
|
||||
|
||||
// Handle REST request, output the response.
|
||||
// Both the request and response parameters should be JSON.
|
||||
// TODO: Query parameters.
|
||||
virtual bool Handle(const std::string& http_method,
|
||||
const std::string& request,
|
||||
std::string* response) = 0;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<RestService> RestServicePtr;
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_REST_SERVICE_H_
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "webcc/soap_client.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#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.SetContentLength(http_content.size());
|
||||
http_request.SetHost(host_, port_);
|
||||
http_request.SetHeader(kSoapAction, operation);
|
||||
http_request.set_content(std::move(http_content));
|
||||
|
||||
http_request.MakeStartLine();
|
||||
|
||||
HttpResponse http_response;
|
||||
|
||||
HttpClient http_client;
|
||||
Error error = http_client.SendRequest(http_request, &http_response);
|
||||
|
||||
if (error != kNoError) {
|
||||
return error;
|
||||
}
|
||||
|
||||
SoapResponse soap_response;
|
||||
soap_response.set_result_name(result_name_);
|
||||
|
||||
if (!soap_response.FromXml(http_response.content())) {
|
||||
return kXmlError;
|
||||
}
|
||||
|
||||
*result = soap_response.result();
|
||||
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef WEBCC_SOAP_CLIENT_H_
|
||||
#define WEBCC_SOAP_CLIENT_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webcc/common.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() {
|
||||
}
|
||||
|
||||
protected:
|
||||
SoapClient() {
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
protected:
|
||||
Namespace soapenv_ns_; // SOAP envelope namespace.
|
||||
Namespace 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,54 @@
|
||||
#include "webcc/soap_message.h"
|
||||
|
||||
#include <cassert>
|
||||
#include "webcc/xml.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
void SoapMessage::ToXml(std::string* xml_string) {
|
||||
assert(soapenv_ns_.IsValid() &&
|
||||
service_ns_.IsValid() &&
|
||||
!operation_.empty());
|
||||
|
||||
pugi::xml_document xdoc;
|
||||
|
||||
// TODO:
|
||||
// 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 = xml::AddChild(xdoc, soapenv_ns_.name, "Envelope");
|
||||
|
||||
xml::AddNSAttr(xroot, soapenv_ns_.name, soapenv_ns_.url);
|
||||
|
||||
pugi::xml_node xbody = xml::AddChild(xroot, soapenv_ns_.name, "Body");
|
||||
|
||||
ToXmlBody(xbody);
|
||||
|
||||
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 = xml::GetPrefix(xroot);
|
||||
soapenv_ns_.url = xml::GetNSAttr(xroot, soapenv_ns_.name);
|
||||
|
||||
pugi::xml_node xbody = xml::GetChild(xroot, soapenv_ns_.name, "Body");
|
||||
if (xbody) {
|
||||
return FromXmlBody(xbody);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef WEBCC_SOAP_MESSAGE_H_
|
||||
#define WEBCC_SOAP_MESSAGE_H_
|
||||
|
||||
#include <string>
|
||||
#include "pugixml/pugixml.hpp"
|
||||
#include "webcc/common.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// Base class for SOAP request and response.
|
||||
class SoapMessage {
|
||||
public:
|
||||
// E.g., set as kSoapEnvNamespace.
|
||||
void set_soapenv_ns(const Namespace& soapenv_ns) {
|
||||
soapenv_ns_ = soapenv_ns;
|
||||
}
|
||||
|
||||
void set_service_ns(const Namespace& 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:
|
||||
SoapMessage() {
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
protected:
|
||||
Namespace soapenv_ns_; // SOAP envelope namespace.
|
||||
Namespace service_ns_; // Namespace for your web service.
|
||||
|
||||
std::string operation_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_SOAP_MESSAGE_H_
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "webcc/soap_request.h"
|
||||
#include "webcc/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 = xml::AddChild(xbody, service_ns_.name, operation_);
|
||||
xml::AddNSAttr(xop, service_ns_.name, service_ns_.url);
|
||||
|
||||
for (Parameter& p : parameters_) {
|
||||
pugi::xml_node xparam = 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;
|
||||
}
|
||||
|
||||
xml::SplitName(xoperation, &service_ns_.name, &operation_);
|
||||
service_ns_.url = xml::GetNSAttr(xoperation, service_ns_.name);
|
||||
|
||||
pugi::xml_node xparameter = xoperation.first_child();
|
||||
while (xparameter) {
|
||||
parameters_.push_back({
|
||||
xml::GetNameNoPrefix(xparameter),
|
||||
std::string(xparameter.text().as_string())
|
||||
});
|
||||
|
||||
xparameter = xparameter.next_sibling();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef WEBCC_SOAP_REQUEST_H_
|
||||
#define WEBCC_SOAP_REQUEST_H_
|
||||
|
||||
#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,35 @@
|
||||
#include "webcc/soap_response.h"
|
||||
|
||||
#include <cassert>
|
||||
#include "webcc/xml.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
void SoapResponse::ToXmlBody(pugi::xml_node xbody) {
|
||||
std::string rsp_operation = operation_ + "Response";
|
||||
pugi::xml_node xop = xml::AddChild(xbody, service_ns_.name, rsp_operation);
|
||||
xml::AddNSAttr(xop, service_ns_.name, service_ns_.url);
|
||||
|
||||
pugi::xml_node xresult = 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) {
|
||||
xml::SplitName(xresponse, &service_ns_.name, NULL);
|
||||
service_ns_.url = xml::GetNSAttr(xresponse, service_ns_.name);
|
||||
|
||||
pugi::xml_node xresult = xml::GetChildNoNS(xresponse, result_name_);
|
||||
if (xresult) {
|
||||
result_ = xresult.text().get();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef WEBCC_SOAP_RESPONSE_H_
|
||||
#define WEBCC_SOAP_RESPONSE_H_
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
const std::string& result() const {
|
||||
return result_;
|
||||
}
|
||||
|
||||
void set_result(const std::string& result) {
|
||||
result_ = result;
|
||||
}
|
||||
|
||||
void set_result(std::string&& result) {
|
||||
result_ = 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,89 @@
|
||||
#include "webcc/soap_server.h"
|
||||
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
#include "webcc/soap_request.h"
|
||||
#include "webcc/soap_response.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool SoapRequestHandler::RegisterService(SoapServicePtr service,
|
||||
const std::string& url) {
|
||||
assert(service);
|
||||
|
||||
url_service_map_[url] = service;
|
||||
return true;
|
||||
}
|
||||
|
||||
HttpStatus::Enum SoapRequestHandler::HandleSession(HttpSessionPtr session) {
|
||||
SoapServicePtr service = GetServiceByUrl(session->request().url());
|
||||
if (!service) {
|
||||
session->SetResponseStatus(HttpStatus::kBadRequest);
|
||||
session->SendResponse();
|
||||
return HttpStatus::kBadRequest;
|
||||
}
|
||||
|
||||
// Parse the SOAP request XML.
|
||||
SoapRequest soap_request;
|
||||
if (!soap_request.FromXml(session->request().content())) {
|
||||
session->SetResponseStatus(HttpStatus::kBadRequest);
|
||||
session->SendResponse();
|
||||
return HttpStatus::kBadRequest;
|
||||
}
|
||||
|
||||
// TODO: Error handling.
|
||||
SoapResponse soap_response;
|
||||
service->Handle(soap_request, &soap_response);
|
||||
|
||||
std::string content;
|
||||
soap_response.ToXml(&content);
|
||||
|
||||
session->SetResponseStatus(HttpStatus::kOK);
|
||||
session->SetResponseContent(kTextXmlUtf8,
|
||||
content.length(),
|
||||
std::move(content));
|
||||
session->SendResponse();
|
||||
|
||||
return HttpStatus::kOK;
|
||||
}
|
||||
|
||||
SoapServicePtr SoapRequestHandler::GetServiceByUrl(const std::string& url) {
|
||||
UrlServiceMap::const_iterator it = url_service_map_.find(url);
|
||||
|
||||
if (it != url_service_map_.end()) {
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
std::cout << "Service matches the URL: " << url << std::endl;
|
||||
#endif
|
||||
return it->second;
|
||||
}
|
||||
|
||||
#if WEBCC_DEBUG_OUTPUT
|
||||
std::cout << "No service matches the URL: " << url << std::endl;
|
||||
#endif
|
||||
|
||||
return SoapServicePtr();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SoapServer::SoapServer(unsigned short port, std::size_t workers)
|
||||
: HttpServer(port, workers)
|
||||
, soap_request_handler_(new SoapRequestHandler()) {
|
||||
request_handler_ = soap_request_handler_;
|
||||
}
|
||||
|
||||
SoapServer::~SoapServer() {
|
||||
request_handler_ = NULL;
|
||||
delete soap_request_handler_;
|
||||
}
|
||||
|
||||
bool SoapServer::RegisterService(SoapServicePtr service,
|
||||
const std::string& url) {
|
||||
return soap_request_handler_->RegisterService(service, url);
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,52 @@
|
||||
#ifndef WEBCC_SOAP_SERVER_H_
|
||||
#define WEBCC_SOAP_SERVER_H_
|
||||
|
||||
// HTTP server handling SOAP requests.
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "webcc/http_request_handler.h"
|
||||
#include "webcc/http_server.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class SoapRequestHandler : public HttpRequestHandler {
|
||||
public:
|
||||
SoapRequestHandler() = default;
|
||||
|
||||
// Register a SOAP service to the given URL path.
|
||||
// \url URL path, must start with "/". E.g., "/calculator".
|
||||
// NOTE: Registering to the same URL multiple times is allowed, but only the
|
||||
// last one takes effect.
|
||||
bool RegisterService(SoapServicePtr service, const std::string& url);
|
||||
|
||||
private:
|
||||
HttpStatus::Enum HandleSession(HttpSessionPtr session) override;
|
||||
|
||||
SoapServicePtr GetServiceByUrl(const std::string& url);
|
||||
|
||||
private:
|
||||
typedef std::map<std::string, SoapServicePtr> UrlServiceMap;
|
||||
UrlServiceMap url_service_map_;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class SoapServer : public HttpServer {
|
||||
public:
|
||||
SoapServer(unsigned short port, std::size_t workers);
|
||||
|
||||
~SoapServer() override;
|
||||
|
||||
bool RegisterService(SoapServicePtr service, const std::string& url);
|
||||
|
||||
private:
|
||||
SoapRequestHandler* soap_request_handler_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_SOAP_SERVER_H_
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef WEBCC_SOAP_SERVICE_H_
|
||||
#define WEBCC_SOAP_SERVICE_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class SoapRequest;
|
||||
class SoapResponse;
|
||||
|
||||
// Base class for your SOAP service.
|
||||
class SoapService {
|
||||
public:
|
||||
virtual ~SoapService() {
|
||||
}
|
||||
|
||||
// Handle SOAP request, output the response.
|
||||
virtual bool Handle(const SoapRequest& soap_request,
|
||||
SoapResponse* soap_response) = 0;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<SoapService> SoapServicePtr;
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_SOAP_SERVICE_H_
|
||||
@@ -0,0 +1,76 @@
|
||||
#include "webcc/url.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace webcc {
|
||||
|
||||
Url::Url(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);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Url::Query Url::SplitQuery(const std::string& query) {
|
||||
const std::size_t NPOS = std::string::npos;
|
||||
|
||||
Query result;
|
||||
|
||||
// Split into key value pairs separated by '&'.
|
||||
std::size_t i = 0;
|
||||
while (i != NPOS) {
|
||||
std::size_t j = query.find_first_of('&', i);
|
||||
|
||||
std::string kv;
|
||||
if (j == NPOS) {
|
||||
kv = query.substr(i);
|
||||
i = NPOS;
|
||||
} else {
|
||||
kv = query.substr(i, j - i);
|
||||
i = j + 1;
|
||||
}
|
||||
|
||||
std::string key;
|
||||
std::string value;
|
||||
if (SplitKeyValue(kv, &key, &value)) {
|
||||
result[key] = value; // TODO: Move
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,53 @@
|
||||
#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>
|
||||
|
||||
namespace webcc {
|
||||
|
||||
class Url {
|
||||
public:
|
||||
typedef std::map<std::string, std::string> Query;
|
||||
|
||||
Url(const std::string& str);
|
||||
|
||||
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 map.
|
||||
static Query SplitQuery(const std::string& query);
|
||||
|
||||
private:
|
||||
std::string path_;
|
||||
std::string query_;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_URL_H_
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "webcc/utility.h"
|
||||
#include <iostream>
|
||||
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// Print the resolved endpoints.
|
||||
// NOTE: Endpoint is one word, don't use "end point".
|
||||
// TODO
|
||||
void DumpEndpoints(tcp::resolver::results_type& endpoints) {
|
||||
std::cout << "Endpoints: " << endpoints.size() << std::endl;
|
||||
|
||||
tcp::resolver::results_type::iterator it = endpoints.begin();
|
||||
for (; it != endpoints.end(); ++it) {
|
||||
std::cout << " - " << it->endpoint();
|
||||
|
||||
if (it->endpoint().protocol() == tcp::v4()) {
|
||||
std::cout << ", v4";
|
||||
} else if (it->endpoint().protocol() == tcp::v6()) {
|
||||
std::cout << ", v6";
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef WEBCC_UTILITY_H_
|
||||
#define WEBCC_UTILITY_H_
|
||||
|
||||
#include "boost/asio/ip/tcp.hpp"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// Print the resolved endpoints.
|
||||
// NOTE: Endpoint is one word, don't use "end point".
|
||||
void DumpEndpoints(boost::asio::ip::tcp::resolver::results_type& endpoints);
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_UTILITY_H_
|
||||
@@ -0,0 +1,109 @@
|
||||
#include "webcc/xml.h"
|
||||
|
||||
namespace webcc {
|
||||
namespace 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 != NULL) {
|
||||
*prefix = full_name.substr(0, pos);
|
||||
}
|
||||
if (name != NULL) {
|
||||
*name = full_name.substr(pos + 1);
|
||||
}
|
||||
} else {
|
||||
if (prefix != NULL) {
|
||||
*prefix = "";
|
||||
}
|
||||
if (name != NULL) {
|
||||
*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(pugi::xml_node& xnode,
|
||||
const std::string& ns,
|
||||
const std::string& name) {
|
||||
return xnode.child((ns + ":" + name).c_str());
|
||||
}
|
||||
|
||||
pugi::xml_node GetChildNoNS(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(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 PrettyPrintXml(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 xml
|
||||
} // namespace webcc
|
||||
@@ -0,0 +1,91 @@
|
||||
#ifndef WEBCC_XML_H_
|
||||
#define WEBCC_XML_H_
|
||||
|
||||
// XML utilities.
|
||||
|
||||
#include <string>
|
||||
#include "pugixml/pugixml.hpp"
|
||||
|
||||
namespace webcc {
|
||||
namespace xml {
|
||||
|
||||
// Split the node name into namespace prefix and real name.
|
||||
// E.g., if the node name is "soapenv:Envelope", it will be splited to
|
||||
// "soapenv" and "Envelope".
|
||||
void SplitName(const pugi::xml_node& xnode,
|
||||
std::string* prefix = NULL,
|
||||
std::string* name = NULL);
|
||||
|
||||
// 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(pugi::xml_node& xnode,
|
||||
const std::string& ns,
|
||||
const std::string& name);
|
||||
|
||||
// TODO: Remove
|
||||
pugi::xml_node GetChildNoNS(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(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();
|
||||
}
|
||||
|
||||
virtual void write(const void* data, size_t size) override {
|
||||
result_->append(static_cast<const char*>(data), size);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string* result_;
|
||||
};
|
||||
|
||||
bool PrettyPrintXml(std::ostream& os,
|
||||
const std::string& xml_string,
|
||||
const char* indent = "\t");
|
||||
|
||||
} // namespace xml
|
||||
} // namespace webcc
|
||||
|
||||
#endif // WEBCC_XML_H_
|
||||
Reference in New Issue
Block a user