Support to create soap server. (draft)

This commit is contained in:
Adam Gu
2018-01-11 17:23:07 +08:00
parent 7b5f4f69b7
commit 692df1b937
61 changed files with 2149 additions and 7250 deletions
+19
View File
@@ -4,6 +4,18 @@
namespace csoap {
// NOTE:
// Field names are case-insensitive.
// See: https://stackoverflow.com/a/5259004
const std::string kContentTypeName = "Content-Type";
const std::string kContentLengthName = "Content-Length";
// 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 char* GetErrorMessage(ErrorCode error_code) {
@@ -42,6 +54,13 @@ const char* GetErrorMessage(ErrorCode error_code) {
////////////////////////////////////////////////////////////////////////////////
const Namespace kSoapEnvNamespace{
"soap",
"http://schemas.xmlsoap.org/soap/envelope/"
};
////////////////////////////////////////////////////////////////////////////////
Parameter::Parameter(const std::string& key, const std::string& value)
: key_(key), value_(value) {
}
+58 -1
View File
@@ -4,11 +4,37 @@
// Common definitions.
#include <string>
#include <vector>
namespace csoap {
////////////////////////////////////////////////////////////////////////////////
// API decorators.
// For a given class, e.g., SoapRequest, some APIs are for client while others
// are for server. In order to make it clear to the user, use the following
// macros to decorate.
#define SERVER_API
#define CLIENT_API
////////////////////////////////////////////////////////////////////////////////
// TODO
// Buffer size for sending HTTP request and receiving HTTP response.
const std::size_t BUF_SIZE = 1024;
static const std::string kCRLF = "\r\n";
extern const std::string kContentTypeName;
extern const std::string kContentLengthName;
extern const std::string kTextXmlUtf8;
const std::size_t kInvalidLength = std::string::npos;
////////////////////////////////////////////////////////////////////////////////
enum ErrorCode {
kNoError = 0, // OK
@@ -37,14 +63,45 @@ const char* GetErrorMessage(ErrorCode error_code);
////////////////////////////////////////////////////////////////////////////////
// TODO: No 1.1 feature has been used or supported yet.
enum HttpVersion {
kHttpV10,
kHttpV11,
};
// HTTP response status.
// NOTE: Only support the listed status codes.
enum HttpStatus {
OK = 200,
BAD_REQUEST = 400,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
SERVICE_UNAVAILABLE = 503,
};
enum HeaderField {
kHeaderContentType,
kHeaderContentLength,
kHeaderHost,
};
////////////////////////////////////////////////////////////////////////////////
// XML namespace name/url pair.
// E.g., { "soapenv", "http://schemas.xmlsoap.org/soap/envelope/" }
// E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" }
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.
+90
View File
@@ -0,0 +1,90 @@
#include "csoap/connection.h"
#include <vector>
#include "boost/asio/write.hpp"
#include "csoap/connection_manager.h"
#include "csoap/http_request_handler.h"
namespace csoap {
Connection::Connection(boost::asio::ip::tcp::socket socket,
ConnectionManager& manager,
HttpRequestHandler& handler)
: socket_(std::move(socket))
, connection_manager_(manager)
, request_handler_(handler)
, request_parser_(&request_) {
}
void Connection::Start() {
DoRead();
}
void Connection::Stop() {
socket_.close();
}
void Connection::DoRead() {
auto handler = std::bind(&Connection::HandleRead,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2);
socket_.async_read_some(boost::asio::buffer(buffer_), handler);
}
void Connection::DoWrite() {
auto handler = std::bind(&Connection::HandleWrite,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2);
boost::asio::async_write(socket_, response_.ToBuffers(), handler);
}
void Connection::HandleRead(boost::system::error_code ec,
std::size_t bytes_transferred) {
if (ec) {
if (ec != boost::asio::error::operation_aborted) {
connection_manager_.Stop(shared_from_this());
}
return;
}
ErrorCode error = request_parser_.Parse(buffer_.data(), bytes_transferred);
if (error != kNoError) {
// Bad request.
response_ = HttpResponse::Fault(HttpStatus::BAD_REQUEST);
DoWrite();
return;
}
if (!request_parser_.finished()) {
// Continue to read the request.
DoRead();
return;
}
// Handle request.
request_handler_.HandleRequest(request_, response_);
// Send back the response.
DoWrite();
}
void Connection::HandleWrite(boost::system::error_code ec,
size_t bytes_transferred) {
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) {
connection_manager_.Stop(shared_from_this());
}
}
} // namespace csoap
+76
View File
@@ -0,0 +1,76 @@
#ifndef CSOAP_CONNECTION_H_
#define CSOAP_CONNECTION_H_
#include <array>
#include <memory>
#include "boost/asio/ip/tcp.hpp" // for ip::tcp::socket
#include "csoap/common.h"
#include "csoap/http_request.h"
#include "csoap/http_request_parser.h"
#include "csoap/http_response.h"
namespace csoap {
class ConnectionManager;
class HttpRequestHandler;
// Represents a single connection from a client.
class Connection : public std::enable_shared_from_this<Connection> {
public:
Connection(const Connection&) = delete;
Connection& operator=(const Connection&) = delete;
// Construct a connection with the given io_service.
Connection(boost::asio::ip::tcp::socket socket,
ConnectionManager& manager,
HttpRequestHandler& handler);
// Start the first asynchronous operation for the connection.
void Start();
// Stop all asynchronous operations associated with the connection.
void Stop();
private:
void DoRead();
void DoWrite();
// Handle completion of a read operation.
void HandleRead(boost::system::error_code ec,
std::size_t bytes_transferred);
// Handle completion of a write operation.
void HandleWrite(boost::system::error_code ec,
size_t bytes_transferred);
private:
// Socket for the connection.
boost::asio::ip::tcp::socket socket_;
// The manager for this connection.
ConnectionManager& connection_manager_;
// The handler used to process the incoming request.
HttpRequestHandler& request_handler_;
// Buffer for incoming data.
std::array<char, 8192> buffer_;
// The incoming request.
HttpRequest request_;
// The parser for the incoming request.
HttpRequestParser request_parser_;
// The reply to be sent back to the client.
HttpResponse response_;
};
typedef std::shared_ptr<Connection> ConnectionPtr;
} // namespace csoap
#endif // CSOAP_CONNECTION_H_
+25
View File
@@ -0,0 +1,25 @@
#include "csoap/connection_manager.h"
namespace csoap {
ConnectionManager::ConnectionManager() {
}
void ConnectionManager::Start(ConnectionPtr conn) {
connections_.insert(conn);
conn->Start();
}
void ConnectionManager::Stop(ConnectionPtr conn) {
connections_.erase(conn);
conn->Stop();
}
void ConnectionManager::StopAll() {
for (const ConnectionPtr& conn : connections_) {
conn->Stop();
}
connections_.clear();
}
} // namespace csoap
+35
View File
@@ -0,0 +1,35 @@
#ifndef CSOAP_CONNECTION_MANAGER_H_
#define CSOAP_CONNECTION_MANAGER_H_
#include <set>
#include "csoap/connection.h"
namespace csoap {
// ConnectionManager manages open connections so that they may be cleanly
// stopped when the server needs to shut down.
class ConnectionManager {
public:
ConnectionManager(const ConnectionManager&) = delete;
ConnectionManager& operator=(const ConnectionManager&) = delete;
// Construct a connection manager.
ConnectionManager();
// Add the specified connection to the manager and start it.
void Start(ConnectionPtr conn);
// Stop the specified connection.
void Stop(ConnectionPtr conn);
// Stop all connections.
void StopAll();
private:
// The managed connections.
std::set<ConnectionPtr> connections_;
};
} // namespace csoap
#endif // CSOAP_CONNECTION_MANAGER_H_
+76 -248
View File
@@ -1,222 +1,53 @@
#include "csoap/http_client.h"
#include "boost/bind.hpp"
#if CSOAP_ENABLE_OUTPUT
#include <iostream>
#endif
#include "boost/algorithm/string.hpp"
#include "boost/bind.hpp"
#include "boost/lexical_cast.hpp"
#include "csoap/common.h"
#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 "csoap/http_response_parser.h"
#include "csoap/http_request.h"
#include "csoap/http_response.h"
#include "csoap/xml.h"
namespace csoap {
////////////////////////////////////////////////////////////////////////////////
static const std::string kCRLF = "\r\n";
// See https://stackoverflow.com/a/9079092
static void SetTimeout(boost::asio::ip::tcp::socket& socket,
int timeout_seconds) {
#if defined _WINDOWS
// NOTE:
// Each header field consists of a name followed by a colon (":") and the
// field value. Field names are case-insensitive.
// See https://stackoverflow.com/a/5259004
static const std::string kFieldContentTypeName = "Content-Type";
static const std::string kFieldContentLengthName = "Content-Length";
int ms = timeout_seconds * 1000;
static const size_t kInvalidContentLength = std::string::npos;
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);
// NOTE (About Connection: keep-alive):
// Keep-alive is deprecated and no longer documented in the current HTTP/1.1
// specification.
// See https://stackoverflow.com/a/43451440
#else // POSIX
HttpRequest::HttpRequest(HttpVersion version)
: version_(version)
, content_length_(0) {
}
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));
void HttpRequest::ToString(std::string& req_string) const {
// Start line
req_string += "POST ";
req_string += url_;
req_string += " ";
if (version_ == kHttpV10) {
req_string += "HTTP/1.0";
} else {
req_string += "HTTP/1.1";
}
req_string += kCRLF;
// Header fields
req_string += kFieldContentTypeName;
req_string += ": ";
if (!content_type_.empty()) {
req_string += content_type_;
} else {
req_string += "text/xml; charset=utf-8";
}
req_string += kCRLF;
req_string += kFieldContentLengthName;
req_string += ": ";
req_string += boost::lexical_cast<std::string>(content_length_);
req_string += kCRLF;
req_string += "SOAPAction: ";
req_string += soap_action_;
req_string += kCRLF;
req_string += "Host: ";
req_string += host_;
if (!port_.empty()) {
req_string += ":";
req_string += port_;
}
req_string += kCRLF;
req_string += kCRLF; // End of Headers.
}
////////////////////////////////////////////////////////////////////////////////
HttpResponse::HttpResponse()
: status_(0)
, content_length_(kInvalidContentLength)
, start_line_parsed_(false)
, header_parsed_(false)
, finished_(false) {
}
ErrorCode HttpResponse::Parse(const char* data, size_t len) {
if (header_parsed_) {
// Add the data to the content.
content_.append(data, len);
if (content_.length() >= content_length_) {
// 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(kCRLF, 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;
ErrorCode error = ParseStartLine(line);
if (error != kNoError) {
return error;
}
} else {
// Currently, only Content-Length is important to us.
// Other fields are ignored.
if (content_length_ == kInvalidContentLength) { // Not parsed yet.
ParseContentLength(line);
}
}
off = pos + 2; // Skip CRLF.
}
if (header_parsed_) {
// Headers just ended.
if (content_length_ == kInvalidContentLength) {
// No Content-Length?
return kHttpContentLengthError;
}
content_ += pending_data_.substr(off);
if (content_.length() >= content_length_) {
// All content has been read.
finished_ = true;
}
} else {
// Save the unparsed piece for next parsing.
pending_data_ = pending_data_.substr(off);
}
return kNoError;
}
ErrorCode HttpResponse::ParseStartLine(const std::string& line) {
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 {
status_ = boost::lexical_cast<int>(status_str);
} catch (boost::bad_lexical_cast&) {
return kHttpStartLineError;
}
off = pos + 1; // Skip space.
reason_ = line.substr(off);
if (status_ != kHttpOK) {
return kHttpStatusError;
}
return kNoError;
}
void HttpResponse::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, kFieldContentLengthName)) {
++pos; // Skip ':'.
while (line[pos] == ' ') { // Skip spaces.
++pos;
}
std::string value = line.substr(pos);
try {
content_length_ = boost::lexical_cast<size_t>(value);
} catch (boost::bad_lexical_cast&) {
// TODO
}
}
#endif
}
////////////////////////////////////////////////////////////////////////////////
@@ -226,56 +57,64 @@ HttpClient::HttpClient()
}
ErrorCode HttpClient::SendRequest(const HttpRequest& request,
const std::string& body,
HttpResponse* response) {
assert(response != NULL);
using boost::asio::ip::tcp;
tcp::socket socket(io_service_);
tcp::socket socket(io_context_);
tcp::resolver resolver(io_service_);
tcp::resolver resolver(io_context_);
std::string port = request.port();
if (port.empty()) {
port = "80";
}
tcp::resolver::query query(request.host(), port);
boost::system::error_code ec;
tcp::resolver::iterator it = resolver.resolve(query, ec);
tcp::resolver::results_type endpoints =
resolver.resolve(/*tcp::v4(), */request.host(), port, ec);
if (ec) {
return kHostResolveError;
}
socket.connect(*it, ec);
boost::asio::connect(socket, endpoints, ec);
if (ec) {
return kEndpointConnectError;
}
SetTimeout(socket);
SetTimeout(socket, timeout_seconds_);
std::string request_str;
request.ToString(request_str);
// Send HTTP request.
std::string headers = request.GetHeaders();
std::vector<boost::asio::const_buffer> buffers{
boost::asio::buffer(headers),
boost::asio::buffer(request.content()),
};
#if CSOAP_ENABLE_OUTPUT
std::cout << request << std::endl;
#endif
try {
boost::asio::write(socket, boost::asio::buffer(request_str));
boost::asio::write(socket, boost::asio::buffer(body));
boost::asio::write(socket, buffers);
} catch (boost::system::system_error&) {
return kSocketWriteError;
}
// Read and parse HTTP response.
// We must stop trying to read some once all content has been received,
// because some servers will block extra call to read_some().
while (!response->finished()) {
size_t len = socket.read_some(boost::asio::buffer(bytes_), ec);
HttpResponseParser parser(response);
if (len == 0 || ec) {
// 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 {
@@ -283,40 +122,29 @@ ErrorCode HttpClient::SendRequest(const HttpRequest& request,
}
}
#if CSOAP_ENABLE_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.
ErrorCode error = response->Parse(bytes_.data(), len);
// If the content has been fully received, next time flag "finished_"
// will be set.
ErrorCode error = parser.Parse(buffer_.data(), length);
if (error != kNoError) {
return error;
}
}
#if CSOAP_ENABLE_OUTPUT
std::cout << std::endl << std::endl;
std::cout << "[ PRETTY PRINT ]" << std::endl;
xml::PrettyPrintXml(std::cout, response->content());
std::cout << std::endl;
#endif
return kNoError;
}
// See https://stackoverflow.com/a/9079092
void HttpClient::SetTimeout(boost::asio::ip::tcp::socket& socket) {
#if defined _WINDOWS
int ms = timeout_seconds_ * 1000;
const char* optval = reinterpret_cast<const char*>(&ms);
size_t optlen = sizeof(ms);
setsockopt(socket.native(), SOL_SOCKET, SO_RCVTIMEO, optval, optlen);
setsockopt(socket.native(), SOL_SOCKET, SO_SNDTIMEO, optval, optlen);
#else // POSIX
struct timeval tv;
tv.tv_sec = timeout_seconds_;
tv.tv_usec = 0;
setsockopt(socket.native(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(socket.native(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
#endif
}
} // namespace csoap
+11 -140
View File
@@ -1,145 +1,19 @@
#ifndef CSOAP_HTTP_CLIENT_H_
#define CSOAP_HTTP_CLIENT_H_
#include <array>
#include <string>
#include "boost/asio.hpp"
// Don't use any deprecated definitions (e.g., io_service).
#define BOOST_ASIO_NO_DEPRECATED
#include "boost/asio/io_context.hpp"
#include "csoap/common.h"
namespace csoap {
////////////////////////////////////////////////////////////////////////////////
enum HttpVersion {
kHttpV10,
kHttpV11,
};
enum HttpStatus {
kHttpOK = 200,
kHttpNotFound = 404,
};
enum HeaderField {
kHeaderContentType,
kHeaderContentLength,
kHeaderHost,
};
////////////////////////////////////////////////////////////////////////////////
// HTTP request.
// NOTE:
// - Only POST method is supported.
// See https://stackoverflow.com/a/26339467
class HttpRequest {
public:
HttpRequest(HttpVersion version);
// Set the URL for the HTTP request start line.
// Either a complete URL or the path component it is acceptable.
// E.g., both of the following URLs are OK:
// - http://ws1.parasoft.com/glue/calculator
// - /glue/calculator
void set_url(const std::string& url) {
url_ = url;
}
// Default: "text/xml; charset=utf-8"
void set_content_type(const std::string& content_type) {
content_type_ = content_type;
}
void set_content_length(size_t content_length) {
content_length_ = content_length;
}
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 set_host(const std::string& host, const std::string& port) {
host_ = host;
port_ = port;
}
// SOAP specific.
void set_soap_action(const std::string& soap_action) {
soap_action_ = soap_action;
}
void ToString(std::string& req_string) const;
private:
HttpVersion version_;
// Request URL.
// A complete URL naming the requested resource, or the path component of
// the URL.
std::string url_;
std::string content_type_;
size_t content_length_;
std::string host_;
std::string port_;
std::string soap_action_;
};
////////////////////////////////////////////////////////////////////////////////
class HttpResponse {
public:
HttpResponse();
ErrorCode Parse(const char* data, size_t len);
bool finished() const {
return finished_;
}
int status() const {
return status_;
}
const std::string& reason() const {
return reason_;
}
const std::string& content() const {
return content_;
};
private:
// Parse start line, e.g., "HTTP/1.1 200 OK".
ErrorCode ParseStartLine(const std::string& line);
void ParseContentLength(const std::string& line);
private:
int status_; // HTTP status, e.g., 200.
std::string reason_;
size_t content_length_;
std::string content_;
ErrorCode error_;
// Data waiting to be parsed.
std::string pending_data_;
// Parsing helper flags.
bool start_line_parsed_;
bool header_parsed_;
bool finished_;
};
////////////////////////////////////////////////////////////////////////////////
class HttpRequest;
class HttpResponse;
class HttpClient {
public:
@@ -150,16 +24,13 @@ public:
timeout_seconds_ = seconds;
}
// Send an HTTP request, wait until the response is received.
ErrorCode SendRequest(const HttpRequest& request,
const std::string& body,
HttpResponse* response);
private:
void SetTimeout(boost::asio::ip::tcp::socket& socket);
private:
boost::asio::io_service io_service_;
std::array<char, 1024> bytes_;
boost::asio::io_context io_context_;
std::array<char, BUF_SIZE> buffer_;
int timeout_seconds_;
};
+76
View File
@@ -0,0 +1,76 @@
#ifndef CSOAP_HTTP_MESSAGE_H_
#define CSOAP_HTTP_MESSAGE_H_
#include <cassert>
#include <string>
#include "csoap/common.h"
namespace csoap {
class HttpHeader {
public:
std::string name;
std::string value;
};
// Base class for HTTP request and response messages.
class HttpMessage {
public:
void set_version(HttpVersion version) {
version_ = version;
}
size_t content_length() const {
return content_length_;
}
void set_content_length(size_t length) {
content_length_ = length;
}
// E.g., "text/xml; charset=utf-8"
void set_content_type(const std::string& content_type) {
content_type_ = content_type;
}
void AddHeader(const std::string& name, const std::string& value) {
headers_.push_back({ name, value });
}
const std::string& content() const {
return content_;
}
void set_content(const std::string& content) {
content_ = 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(content_length_ != kInvalidLength);
return content_.length() >= content_length_;
}
protected:
HttpMessage() {
}
protected:
HttpVersion version_ = kHttpV11;
size_t content_length_ = kInvalidLength;
std::string content_type_;
std::vector<HttpHeader> headers_;
std::string content_;
};
} // namespace csoap
#endif // CSOAP_HTTP_MESSAGE_H_
+115
View File
@@ -0,0 +1,115 @@
#include "csoap/http_parser.h"
#include "boost/algorithm/string.hpp"
#include "boost/lexical_cast.hpp"
#include "csoap/http_message.h"
namespace csoap {
HttpParser::HttpParser(HttpMessage* message)
: message_(message)
, start_line_parsed_(false)
, header_parsed_(false)
, finished_(false) {
}
void HttpParser::Reset() {
// TODO: Reset parsing state.
}
ErrorCode 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(kCRLF, 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;
ErrorCode error = ParseStartLine(line);
if (error != kNoError) {
return error;
}
} else {
// Currently, only Content-Length is important to us.
// Other fields are ignored.
if (message_->content_length() == kInvalidLength) {
// Not parsed yet.
ParseContentLength(line);
}
}
off = pos + 2; // Skip CRLF.
}
if (header_parsed_) {
// Headers just ended.
if (message_->content_length() == kInvalidLength) {
// 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, kContentLengthName)) {
++pos; // Skip ':'.
while (line[pos] == ' ') { // Skip spaces.
++pos;
}
std::string value = line.substr(pos);
try {
message_->set_content_length(boost::lexical_cast<size_t>(value));
} catch (boost::bad_lexical_cast&) {
// TODO
}
}
}
} // namespace csoap
+49
View File
@@ -0,0 +1,49 @@
#ifndef CSOAP_HTTP_PARSER_H_
#define CSOAP_HTTP_PARSER_H_
#include <string>
#include "csoap/common.h"
namespace csoap {
class HttpMessage;
// HttpParser parses HTTP request and response.
class HttpParser {
public:
explicit HttpParser(HttpMessage* message);
bool finished() const {
return finished_;
}
// Reset parsing state.
void Reset();
ErrorCode Parse(const char* data, size_t len);
protected:
// Parse HTTP start line.
virtual ErrorCode ParseStartLine(const std::string& line) = 0;
void ParseContentLength(const std::string& line);
protected:
// The result HTTP message.
HttpMessage* message_;
ErrorCode error_;
// Data waiting to be parsed.
std::string pending_data_;
// Parsing helper flags.
bool start_line_parsed_;
bool header_parsed_;
bool finished_;
};
} // namespace csoap
#endif // CSOAP_HTTP_PARSER_H_
+66
View File
@@ -0,0 +1,66 @@
#include "csoap/http_request.h"
#include "boost/algorithm/string.hpp"
namespace csoap {
////////////////////////////////////////////////////////////////////////////////
std::ostream& operator<<(std::ostream& os, const HttpRequest& request) {
return os << request.GetHeaders() << request.content();
}
////////////////////////////////////////////////////////////////////////////////
std::string HttpRequest::GetHeaders() const {
std::string headers;
// Start line
headers += "POST ";
headers += url_;
headers += " ";
if (version_ == kHttpV10) {
headers += "HTTP/1.0";
} else {
headers += "HTTP/1.1";
}
headers += kCRLF;
// Header fields
headers += kContentTypeName;
headers += ": ";
if (!content_type_.empty()) {
headers += content_type_;
} else {
headers += kTextXmlUtf8;
}
headers += kCRLF;
headers += kContentLengthName;
headers += ": ";
headers += std::to_string(content_length_);
headers += kCRLF;
headers += "SOAPAction: ";
headers += soap_action_;
headers += kCRLF;
headers += "Host: ";
headers += host_;
if (!port_.empty()) {
headers += ":";
headers += port_;
}
headers += kCRLF;
headers += kCRLF; // End of Headers.
return headers;
}
} // namespace csoap
+78
View File
@@ -0,0 +1,78 @@
#ifndef CSOAP_HTTP_REQUEST_H_
#define CSOAP_HTTP_REQUEST_H_
#include <string>
#include <vector>
#include "csoap/common.h"
#include "csoap/http_message.h"
namespace csoap {
////////////////////////////////////////////////////////////////////////////////
class HttpRequest;
std::ostream& operator<<(std::ostream& os, const HttpRequest& request);
////////////////////////////////////////////////////////////////////////////////
// HTTP request.
// NOTE:
// - Only POST method is supported.
// See https://stackoverflow.com/a/26339467
//
class HttpRequest : public HttpMessage {
friend std::ostream& operator<<(std::ostream& os,
const HttpRequest& request);
public:
HttpRequest() {
}
// Set the URL for the HTTP request start line.
// Either a complete URL or the path component it is acceptable.
// E.g., both of the following URLs are OK:
// - http://ws1.parasoft.com/glue/calculator
// - /glue/calculator
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 set_host(const std::string& host, const std::string& port) {
host_ = host;
port_ = port;
}
// SOAP specific.
void set_soap_action(const std::string& soap_action) {
soap_action_ = soap_action;
}
std::string GetHeaders() const;
private:
// 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_;
std::string soap_action_;
};
} // namespace csoap
#endif // CSOAP_HTTP_REQUEST_H_
+110
View File
@@ -0,0 +1,110 @@
#include "csoap/http_request_handler.h"
#include <sstream>
#include "csoap/common.h"
#include "csoap/http_request.h"
#include "csoap/http_response.h"
#include "csoap/soap_request.h"
#include "csoap/soap_response.h"
#include "csoap/soap_service.h"
namespace csoap {
#if 0
// Perform URL-decoding on a string. Returns false if the encoding was invalid.
static bool UrlDecode(const std::string& in, std::string& out) {
out.clear();
out.reserve(in.size());
for (std::size_t i = 0; i < in.size(); ++i) {
if (in[i] == '%') {
if (i + 3 <= in.size()) {
int value = 0;
std::istringstream is(in.substr(i + 1, 2));
if (is >> std::hex >> value) {
out += static_cast<char>(value);
i += 2;
} else {
return false;
}
} else {
return false;
}
} else if (in[i] == '+') {
out += ' ';
} else {
out += in[i];
}
}
return true;
}
#endif
HttpRequestHandler::HttpRequestHandler() {
}
bool HttpRequestHandler::RegisterService(SoapServicePtr soap_service) {
assert(soap_service);
if (std::find(soap_services_.begin(), soap_services_.end(), soap_service) !=
soap_services_.end()) {
return false;
}
soap_services_.push_back(soap_service);
return true;
}
void HttpRequestHandler::HandleRequest(const HttpRequest& request,
HttpResponse& response) {
// Parse the SOAP request XML.
SoapRequest soap_request;
if (!soap_request.FromXml(request.content())) {
// TODO: Bad request
return;
}
// TEST
SoapResponse soap_response;
// Get service by URL.
for (SoapServicePtr& service : soap_services_) {
service->Handle(soap_request, &soap_response);
}
std::string content;
soap_response.ToXml(&content);
response.set_status(HttpStatus::OK);
response.AddHeader(kContentTypeName, kTextXmlUtf8);
response.AddHeader(kContentLengthName, std::to_string(content.length()));
response.set_content(content);
#if 0
// Decode URL to path.
std::string request_path;
if (!UrlDecode(request.uri, request_path)) {
reply = HttpReply::StockReply(HttpReply::BAD_REQUEST);
return;
}
// Request path must be absolute and not contain "..".
if (request_path.empty() ||
request_path[0] != '/' ||
request_path.find("..") != std::string::npos) {
reply = HttpReply::StockReply(HttpReply::BAD_REQUEST);
return;
}
// If path ends in slash (i.e. is a directory) then add "index.html".
if (request_path[request_path.size() - 1] == '/') {
request_path += "index.html";
}
#endif
}
} // namespace csoap
+33
View File
@@ -0,0 +1,33 @@
#ifndef CSOAP_HTTP_REQUEST_HANDLER_H_
#define CSOAP_HTTP_REQUEST_HANDLER_H_
#include <string>
#include <vector>
#include "csoap/soap_service.h"
namespace csoap {
class HttpRequest;
class HttpResponse;
// The common handler for all incoming requests.
class HttpRequestHandler {
public:
HttpRequestHandler(const HttpRequestHandler&) = delete;
HttpRequestHandler& operator=(const HttpRequestHandler&) = delete;
HttpRequestHandler();
bool RegisterService(SoapServicePtr soap_service);
// Handle a request and produce a reply.
void HandleRequest(const HttpRequest& request, HttpResponse& response);
private:
std::vector<SoapServicePtr> soap_services_;
};
} // namespace csoap
#endif // CSOAP_HTTP_REQUEST_HANDLER_H_
+37
View File
@@ -0,0 +1,37 @@
#include "csoap/http_request_parser.h"
#include <vector>
#include "boost/algorithm/string.hpp"
#include "csoap/http_request.h"
namespace csoap {
HttpRequestParser::HttpRequestParser(HttpRequest* request)
: HttpParser(request)
, request_(request) {
}
ErrorCode HttpRequestParser::ParseStartLine(const std::string& line) {
// Example: POST / HTTP/1.1
std::vector<std::string> strs;
boost::split(strs, line, boost::is_any_of(" "));
if (strs.size() != 3) {
return kHttpStartLineError;
}
if (strs[0] != "POST") {
// Only POST method is supported.
return kHttpStartLineError;
}
request_->set_url(strs[1]);
// TODO: strs[2];
return kNoError;
}
} // namespace csoap
+23
View File
@@ -0,0 +1,23 @@
#ifndef CSOAP_HTTP_REQUEST_PARSER_H_
#define CSOAP_HTTP_REQUEST_PARSER_H_
#include "csoap/http_parser.h"
namespace csoap {
class HttpRequest;
class HttpRequestParser : public HttpParser {
public:
explicit HttpRequestParser(HttpRequest* request);
private:
ErrorCode ParseStartLine(const std::string& line) override;
private:
HttpRequest* request_;
};
} // namespace csoap
#endif // CSOAP_HTTP_REQUEST_PARSER_H_
+132
View File
@@ -0,0 +1,132 @@
#include "csoap/http_response.h"
#include "csoap/common.h"
#include "csoap/xml.h"
namespace csoap {
////////////////////////////////////////////////////////////////////////////////
std::ostream& operator<<(std::ostream& os, const HttpResponse& response) {
// TODO
os << response.status() << 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 BAD_REQUEST = "HTTP/1.1 400 Bad Request\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::OK:
return boost::asio::buffer(OK);
case HttpStatus::BAD_REQUEST:
return boost::asio::buffer(BAD_REQUEST);
case HttpStatus::INTERNAL_SERVER_ERROR:
return boost::asio::buffer(INTERNAL_SERVER_ERROR);
case HttpStatus::NOT_IMPLEMENTED:
return boost::asio::buffer(NOT_IMPLEMENTED);
case HttpStatus::SERVICE_UNAVAILABLE:
return boost::asio::buffer(SERVICE_UNAVAILABLE);
default:
return boost::asio::buffer(SERVICE_UNAVAILABLE);
}
}
} // 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;
buffers.push_back(status_strings::ToBuffer(status_));
// Header fields
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;
}
// TODO: Move to SoapResponse
static void CreateSoapFaultResponse(HttpStatus status,
std::string* xml_string) {
Namespace soapenv_ns{
"soap",
"http://schemas.xmlsoap.org/soap/envelope/"
};
pugi::xml_document xdoc;
pugi::xml_node xroot = xml::AddChild(xdoc, soapenv_ns.name, "Envelope");
xml::AddNSAttr(xroot, soapenv_ns.name, soapenv_ns.url);
// FIXME: Body
// See https://www.w3schools.com/XML/xml_soap.asp
pugi::xml_node xfault = xml::AddChild(xroot, soapenv_ns.name, "Fault");
pugi::xml_node xfaultcode = xfault.append_child("faultcode");
xfaultcode.text().set(std::to_string(HttpStatus::BAD_REQUEST).c_str()); // TODO
pugi::xml_node xfaultstring = xfault.append_child("faultstring");
xfaultstring.text().set("Bad Request"); // TODO
// TODO: faultactor
xml::XmlStrRefWriter writer(xml_string);
xdoc.save(writer, "\t", pugi::format_default, pugi::encoding_utf8);
}
HttpResponse HttpResponse::Fault(HttpStatus status) {
assert(status != HttpStatus::OK);
HttpResponse response;
std::string content;
CreateSoapFaultResponse(status, &content);
response.set_content(content);
response.set_content_length(content.length());
response.set_content_type("text/xml");
response.set_status(status);
return response; // TODO: Output parameter?
}
} // namespace csoap
+47
View File
@@ -0,0 +1,47 @@
#ifndef CSOAP_HTTP_RESPONSE_H_
#define CSOAP_HTTP_RESPONSE_H_
#include <string>
#include "boost/asio/buffer.hpp" // for const_buffer
#include "csoap/http_message.h"
namespace csoap {
////////////////////////////////////////////////////////////////////////////////
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 status);
private:
int status_ = HttpStatus::OK; // TODO: HttpStatus
};
} // namespace csoap
#endif // CSOAP_HTTP_RESPONSE_H_
+49
View File
@@ -0,0 +1,49 @@
#include "csoap/http_response_parser.h"
#include "boost/lexical_cast.hpp"
#include "csoap/http_response.h"
namespace csoap {
HttpResponseParser::HttpResponseParser(HttpResponse* response)
: HttpParser(response)
, response_(response) {
}
// TODO: Use split.
ErrorCode HttpResponseParser::ParseStartLine(const std::string& line) {
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.
//response_->set_reason(line.substr(off));
if (response_->status() != HttpStatus::OK) {
return kHttpStatusError;
}
return kNoError;
}
} // namespace csoap
+25
View File
@@ -0,0 +1,25 @@
#ifndef CSOAP_HTTP_RESPONSE_PARSER_H_
#define CSOAP_HTTP_RESPONSE_PARSER_H_
#include "csoap/http_parser.h"
namespace csoap {
class HttpResponse;
class HttpResponseParser : public HttpParser {
public:
explicit HttpResponseParser(HttpResponse* response);
private:
// Parse HTTP start line; E.g., "HTTP/1.1 200 OK".
ErrorCode ParseStartLine(const std::string& line) override;
private:
// The result response message.
HttpResponse* response_;
};
} // namespace csoap
#endif // CSOAP_HTTP_RESPONSE_PARSER_H_
+83
View File
@@ -0,0 +1,83 @@
#include "csoap/http_server.h"
#include <signal.h>
#include "csoap/soap_service.h"
namespace csoap {
HttpServer::HttpServer(const std::string& address,
const std::string& port)
: io_context_(1) // TODO: concurrency_hint (threads)
, signals_(io_context_)
, acceptor_(io_context_) {
// 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();
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
// TODO: What does SO_REUSEADDR mean?
// TODO: Why need an address?
boost::asio::ip::tcp::resolver resolver(io_context_);
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(address, port).begin();
acceptor_.open(endpoint.protocol());
acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor_.bind(endpoint);
acceptor_.listen();
DoAccept();
}
bool HttpServer::RegisterService(SoapServicePtr soap_service) {
return request_handler_.RegisterService(soap_service);
}
void HttpServer::Run() {
// 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, boost::asio::ip::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) {
connection_manager_.Start(
std::make_shared<Connection>(std::move(socket),
connection_manager_,
request_handler_));
}
DoAccept();
});
}
void HttpServer::DoAwaitStop() {
signals_.async_wait(
[this](boost::system::error_code /*ec*/, int /*signo*/) {
// The server is stopped by cancelling all outstanding asynchronous
// operations. Once all operations have finished the io_context::run()
// call will exit.
acceptor_.close();
connection_manager_.StopAll();
});
}
} // namespace csoap
+59
View File
@@ -0,0 +1,59 @@
#ifndef CSOAP_HTTP_SERVER_H_
#define CSOAP_HTTP_SERVER_H_
#include <string>
#include <vector>
#include "boost/asio/io_context.hpp"
#include "boost/asio/signal_set.hpp"
#include "boost/asio/ip/tcp.hpp"
#include "csoap/connection.h"
#include "csoap/connection_manager.h"
#include "csoap/http_request_handler.h"
namespace csoap {
// The top-level class of the HTTP server.
class HttpServer {
public:
HttpServer(const HttpServer&) = delete;
HttpServer& operator=(const HttpServer&) = delete;
// Construct the server to listen on the specified TCP address and port, and
// serve up files from the given directory.
HttpServer(const std::string& address,
const std::string& port);
bool RegisterService(SoapServicePtr soap_service);
// 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();
private:
// 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::asio::ip::tcp::acceptor acceptor_;
// The connection manager which owns all live connections.
ConnectionManager connection_manager_;
// The handler for all incoming requests.
HttpRequestHandler request_handler_;
};
} // namespace csoap
#endif // CSOAP_HTTP_SERVER_H_
+75
View File
@@ -0,0 +1,75 @@
#include "csoap/soap_client.h"
#include <cassert>
#include <iostream>
#include "boost/lexical_cast.hpp"
#include "csoap/http_client.h"
#include "csoap/http_request.h"
#include "csoap/http_response.h"
#include "csoap/soap_request.h"
#include "csoap/soap_response.h"
namespace csoap {
bool SoapClient::Call(const std::string& operation,
const csoap::Parameter* parameters,
size_t count,
std::string* result) {
assert(!url_.empty() &&
!host_.empty() &&
!result_name_.empty() &&
service_ns_.IsValid());
csoap::SoapRequest soap_request;
soap_request.set_soapenv_ns(kSoapEnvNamespace);
soap_request.set_service_ns(service_ns_);
soap_request.set_operation(operation);
for (size_t i = 0; i < count; ++i) {
soap_request.AddParameter(parameters[i]);
}
std::string http_request_body;
soap_request.ToXml(&http_request_body);
csoap::HttpRequest http_request;
http_request.set_version(csoap::kHttpV11);
http_request.set_url(url_);
http_request.set_content_length(http_request_body.size());
http_request.set_content(http_request_body); // TODO: move
http_request.set_host(host_, port_);
http_request.set_soap_action(operation);
csoap::HttpResponse http_response;
csoap::HttpClient http_client;
csoap::ErrorCode ec = http_client.SendRequest(http_request, &http_response);
if (ec != csoap::kNoError) {
std::cerr << csoap::GetErrorMessage(ec) << std::endl;
if (ec == csoap::kHttpStatusError) {
//std::cerr << "\t"
// << http_response.status() << ", "
// << http_response.reason() << std::endl;
}
return false;
}
csoap::SoapResponse soap_response;
soap_response.set_result_name(result_name_);
if (soap_response.FromXml(http_response.content())) {
*result = soap_response.result();
return true;
}
return false;
}
} // namespace csoap
+46
View File
@@ -0,0 +1,46 @@
#ifndef CSOAP_SOAP_CLIENT_H_
#define CSOAP_SOAP_CLIENT_H_
#include <string>
#include "csoap/common.h"
namespace csoap {
// 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.
bool Call(const std::string& operation,
const csoap::Parameter* parameters,
size_t count,
std::string* result);
protected:
// Request URL.
// Could be a complete URL (http://ws1.parasoft.com/glue/calculator)
// or just the path component of it (/glue/calculator).
std::string url_;
std::string host_;
std::string port_; // Leave this empty to use default 80.
// The namespace of your service.
csoap::Namespace service_ns_;
// Response result XML node name.
// E.g., "Result".
std::string result_name_;
};
} // namespace csoap
#endif // CSOAP_SOAP_CLIENT_H_
+54
View File
@@ -0,0 +1,54 @@
#include "csoap/soap_message.h"
#include <cassert>
#include "csoap/xml.h"
namespace csoap {
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 csoap
+56
View File
@@ -0,0 +1,56 @@
#ifndef CSOAP_SOAP_MESSAGE_H_
#define CSOAP_SOAP_MESSAGE_H_
#include <string>
#include "pugixml/pugixml.hpp"
#include "csoap/common.h"
namespace csoap {
// Base class for SOAP request and response.
class SoapMessage {
public:
// E.g., set as kSoapEnvNamespace.
CLIENT_API void set_soapenv_ns(const Namespace& soapenv_ns) {
soapenv_ns_ = soapenv_ns;
}
CLIENT_API void set_service_ns(const Namespace& service_ns) {
service_ns_ = service_ns;
}
SERVER_API const std::string& operation() const {
return operation_;
}
CLIENT_API void set_operation(const std::string& operation) {
operation_ = operation;
}
// Convert to SOAP request XML.
CLIENT_API void ToXml(std::string* xml_string);
// Parse from SOAP request XML.
SERVER_API 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 csoap
#endif // CSOAP_SOAP_MESSAGE_H_
+69 -62
View File
@@ -3,32 +3,6 @@
namespace csoap {
////////////////////////////////////////////////////////////////////////////////
#ifdef CSOAP_USE_TINYXML
// Append "xmlns" attribute.
static void AddNSAttr(TiXmlElement* xnode, const Namespace& ns) {
xml::AddAttr(xnode, "xmlns", ns.name, ns.url);
}
#else
// Append "xmlns" attribute.
static void AddNSAttr(pugi::xml_node& xnode, const Namespace& ns) {
xml::AddAttr(xnode, "xmlns", ns.name, ns.url);
}
#endif // CSOAP_USE_TINYXML
////////////////////////////////////////////////////////////////////////////////
SoapRequest::SoapRequest(const std::string& operation)
: operation_(operation) {
soapenv_ns_.name = "soapenv";
soapenv_ns_.url = "http://schemas.xmlsoap.org/soap/envelope/";
}
void SoapRequest::AddParameter(const std::string& key,
const std::string& value) {
parameters_.push_back(Parameter(key, value));
@@ -38,51 +12,84 @@ void SoapRequest::AddParameter(const Parameter& parameter) {
parameters_.push_back(parameter);
}
void SoapRequest::ToXmlString(std::string* xml_string) {
#ifdef CSOAP_USE_TINYXML
TiXmlDocument xdoc;
TiXmlElement* xroot = xml::AppendChild(&xdoc, soapenv_ns_.name, "Envelope");
AddNSAttr(xroot, soapenv_ns_);
AddNSAttr(xroot, service_ns_);
xml::AppendChild(xroot, soapenv_ns_.name, "Header");
TiXmlElement* xbody = xml::AppendChild(xroot, soapenv_ns_.name, "Body");
TiXmlElement* xop = xml::AppendChild(xbody, service_ns_.name, operation_);
for (Parameter& p : parameters_) {
TiXmlElement* xparam = xml::AppendChild(xop, service_ns_.name, p.key());
xml::SetText(xparam, p.value());
std::string SoapRequest::GetParameter(const std::string& key) const {
for (const Parameter& p : parameters_) {
if (p.key() == key) {
return p.value();
}
}
return "";
}
TiXmlPrinter printer;
xdoc.Accept(&printer);
*xml_string = printer.CStr();
//bool SoapRequest::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 false;
// }
//
// // Operation
//
// pugi::xml_node xoperation = xbody.first_child();
// xml::SplitName(xoperation, &service_ns_.name, &operation_);
// service_ns_.url = xml::GetNSAttr(xoperation, service_ns_.name);
//
// // Parameters
//
// 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;
//}
#else
pugi::xml_document xdoc;
pugi::xml_node xroot = xml::AppendChild(xdoc, soapenv_ns_.name, "Envelope");
AddNSAttr(xroot, soapenv_ns_);
AddNSAttr(xroot, service_ns_);
xml::AppendChild(xroot, soapenv_ns_.name, "Header");
pugi::xml_node xbody = xml::AppendChild(xroot, soapenv_ns_.name, "Body");
pugi::xml_node xop = xml::AppendChild(xbody, service_ns_.name, operation_);
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::AppendChild(xop, service_ns_.name, p.key());
pugi::xml_node xparam = xml::AddChild(xop, service_ns_.name, p.key());
xparam.text().set(p.value().c_str());
}
}
xml::XmlStrRefWriter writer(xml_string);
xdoc.print(writer, "\t", pugi::format_default, pugi::encoding_utf8);
bool SoapRequest::FromXmlBody(pugi::xml_node xbody) {
pugi::xml_node xoperation = xbody.first_child();
if (!xoperation) {
return false;
}
#endif // #ifdef CSOAP_USE_TINYXML
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 csoap
+9 -26
View File
@@ -1,45 +1,28 @@
#ifndef CSOAP_SOAP_REQUEST_H_
#define CSOAP_SOAP_REQUEST_H_
#include <string>
#include <vector>
#include "csoap/common.h"
#include "csoap/soap_message.h"
namespace csoap {
// SOAP request.
// Used to compose the SOAP request envelope XML which will be sent as the HTTP
// request body.
class SoapRequest {
class SoapRequest : public SoapMessage {
public:
explicit SoapRequest(const std::string& operation);
CLIENT_API void AddParameter(const std::string& key, const std::string& value);
CLIENT_API void AddParameter(const Parameter& parameter);
// Set the name of SOAP envelope namespace if you don't like the default
// name "soapenv".
void set_soapenv_ns_name(const std::string& name) {
soapenv_ns_.name = name;
}
// Get parameter value by key.
SERVER_API std::string GetParameter(const std::string& key) const;
void set_service_ns(const Namespace& ns) {
service_ns_ = ns;
}
void AddParameter(const std::string& key, const std::string& value);
void AddParameter(const Parameter& parameter);
void ToXmlString(std::string* xml_string);
protected:
void ToXmlBody(pugi::xml_node xbody) override;
bool FromXmlBody(pugi::xml_node xbody) override;
private:
// SOAP envelope namespace.
// The URL is always "http://schemas.xmlsoap.org/soap/envelope/".
// The name is "soapenv" by default.
Namespace soapenv_ns_;
// Namespace for your web service.
Namespace service_ns_;
std::string operation_;
std::vector<Parameter> parameters_;
};
+21 -69
View File
@@ -1,84 +1,36 @@
#include "csoap/soap_response.h"
#include <cassert>
#include "csoap/xml.h"
namespace csoap {
SoapResponse::SoapResponse() {
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);
// TODO: Leave the user to decide the result name.
pugi::xml_node xresult = xml::AddChild(xop, service_ns_.name, "Result");
xresult.text().set(result_.c_str());
}
bool SoapResponse::Parse(const std::string& content,
const std::string& message_name,
const std::string& element_name,
std::string* element_value) {
#ifdef CSOAP_USE_TINYXML
bool SoapResponse::FromXmlBody(pugi::xml_node xbody) {
assert(!result_name_.empty());
TiXmlDocument xdoc;
xdoc.Parse(content.c_str());
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);
if (xdoc.Error()) {
return false;
pugi::xml_node xresult = xml::GetChildNoNS(xresponse, result_name_);
if (xresult) {
result_ = xresult.text().get();
return true;
}
}
TiXmlElement* xroot = xdoc.RootElement();
soapenv_ns_ = xml::GetNsPrefix(xroot);
TiXmlElement* xbody = xml::GetChild(xroot, soapenv_ns_, "Body");
if (xbody == NULL) {
return false;
}
TiXmlElement* xmessage = xml::GetChildNoNS(xbody, message_name);
if (xmessage == NULL) {
return false;
}
TiXmlElement* xelement = xml::GetChildNoNS(xmessage, element_name);
if (xelement == NULL) {
return false;
}
const char* text = xelement->GetText();
if (text != NULL) {
*element_value = text;
} else {
*element_value = "";
}
#else
pugi::xml_document xdoc;
pugi::xml_parse_result result = xdoc.load_string(content.c_str());
if (!result) {
return false;
}
pugi::xml_node xroot = xdoc.document_element();
soapenv_ns_ = xml::GetNsPrefix(xroot);
pugi::xml_node xbody = xml::GetChild(xroot, soapenv_ns_, "Body");
if (!xbody) {
return false;
}
pugi::xml_node xmessage = xml::GetChildNoNS(xbody, message_name);
if (!xmessage) {
return false;
}
pugi::xml_node xelement = xml::GetChildNoNS(xmessage, element_name);
if (!xelement) {
return false;
}
*element_value = xelement.text().get();
#endif // CSOAP_USE_TINYXML
return true;
return false;
}
} // namespace csoap
+31 -18
View File
@@ -1,31 +1,44 @@
#ifndef CSOAP_RESPONSE_H_
#define CSOAP_RESPONSE_H_
#ifndef CSOAP_SOAP_RESPONSE_H_
#define CSOAP_SOAP_RESPONSE_H_
#include <string>
#include "csoap/soap_message.h"
namespace csoap {
// SOAP response.
// Used to parse the SOAP response XML which is returned as the HTTP response
// body.
class SoapResponse {
class SoapResponse : public SoapMessage {
public:
SoapResponse();
bool Parse(const std::string& content,
const std::string& message_name,
const std::string& element_name,
std::string* element_value);
const std::string& soapenv_ns() const {
return soapenv_ns_;
// Could be "Price" for an operation/method like "GetXyzPrice".
// Really depend on the service.
// Most services use a general name "Result".
CLIENT_API void set_result_name(const std::string& result_name) {
result_name_ = result_name;
}
CLIENT_API const std::string& result() const {
return result_;
}
SERVER_API void set_result(const std::string& result) {
result_ = result;
}
protected:
void ToXmlBody(pugi::xml_node xbody) override;
bool FromXmlBody(pugi::xml_node xbody) override;
private:
// Soap envelope namespace in the response XML.
std::string soapenv_ns_;
// TODO: Support multiple results.
// Result XML node name.
// Used to parse the response XML from client side.
std::string result_name_;
// Result value.
std::string result_;
};
} // namespace csoap
#endif // CSOAP_RESPONSE_H_
#endif // CSOAP_SOAP_RESPONSE_H_
+1
View File
@@ -0,0 +1 @@
#include "csoap/soap_service.h"
+35
View File
@@ -0,0 +1,35 @@
#ifndef CSOAP_SOAP_SERVICE_H_
#define CSOAP_SOAP_SERVICE_H_
#include <string>
#include <memory>
namespace csoap {
class SoapRequest;
class SoapResponse;
// Base class for your SOAP service.
class SoapService {
public:
SoapService() {
}
virtual ~SoapService() {
}
// Handle SOAP request, output the response.
virtual bool Handle(const SoapRequest& request,
SoapResponse* soap_response) = 0;
protected:
// URL used to match the request.
// E.g., "/", "/SomeService", etc.
std::string url_;
};
typedef std::shared_ptr<SoapService> SoapServicePtr;
} // namespace csoap
#endif // CSOAP_SOAP_SERVICE_H_
+41 -89
View File
@@ -3,104 +3,46 @@
namespace csoap {
namespace xml {
#ifdef CSOAP_USE_TINYXML
void SplitName(const pugi::xml_node& xnode,
std::string* prefix,
std::string* name) {
std::string full_name = xnode.name();
std::string GetNsPrefix(const TiXmlElement* xnode) {
std::string node_name = xnode->Value();
#else
std::string GetNsPrefix(const pugi::xml_node& xnode) {
std::string node_name = xnode.name();
#endif
size_t pos = node_name.find(':');
size_t pos = full_name.find(':');
if (pos != std::string::npos) {
return node_name.substr(0, pos);
}
return "";
}
#ifdef CSOAP_USE_TINYXML
TiXmlElement* AppendChild(TiXmlNode* xnode,
const std::string& ns,
const std::string& name) {
std::string ns_name = ns + ":" + name;
TiXmlElement* xchild = new TiXmlElement(ns_name.c_str());
xnode->LinkEndChild(xchild);
return xchild;
}
TiXmlElement* GetChild(TiXmlElement* xnode,
const std::string& ns,
const std::string& name) {
return xnode->FirstChildElement((ns + ":" + name).c_str());
}
TiXmlElement* GetChildNoNS(TiXmlElement* xnode, const std::string& name) {
TiXmlElement* xchild = xnode->FirstChildElement();
while (xchild != NULL) {
std::string child_name = xchild->Value();
// Remove NS prefix.
size_t pos = child_name.find(':');
if (pos != std::string::npos) {
child_name = child_name.substr(pos + 1);
if (prefix != NULL) {
*prefix = full_name.substr(0, pos);
}
if (child_name == name) {
return xchild;
if (name != NULL) {
*name = full_name.substr(pos + 1);
}
xchild = xchild->NextSiblingElement();
}
return NULL;
}
void AddAttr(TiXmlElement* xnode,
const std::string& ns,
const std::string& name,
const std::string& value) {
std::string ns_name = ns + ":" + name;
xnode->SetAttribute(ns_name.c_str(), value.c_str());
}
void SetText(TiXmlElement* xnode, const std::string& text) {
if (xnode->FirstChild() == NULL) {
xnode->LinkEndChild(new TiXmlText(text.c_str()));
} else {
xnode->ReplaceChild(xnode->FirstChild(), TiXmlText(text.c_str()));
if (prefix != NULL) {
*prefix = "";
}
if (name != NULL) {
*name = full_name;
}
}
}
bool PrettyPrintXml(std::ostream& os,
const std::string& xml_string,
const char* indent) {
TiXmlDocument xdoc;
xdoc.Parse(xml_string.c_str());
if (xdoc.Error()) {
os << "Invalid XML" << std::endl;
return false;
}
TiXmlPrinter xprinter;
xprinter.SetIndent(indent);
xdoc.Accept(&xprinter);
os << xprinter.CStr();
return true;
std::string GetPrefix(const pugi::xml_node& xnode) {
std::string ns_prefix;
SplitName(xnode, &ns_prefix, nullptr);
return ns_prefix;
}
#else
std::string GetNameNoPrefix(const pugi::xml_node& xnode) {
std::string name;
SplitName(xnode, nullptr, &name);
return name;
}
pugi::xml_node AppendChild(pugi::xml_node& xnode,
const std::string& ns,
const std::string& name) {
std::string ns_name = ns + ":" + name;
return xnode.append_child(ns_name.c_str());
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,
@@ -138,6 +80,18 @@ void AddAttr(pugi::xml_node& xnode,
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) {
@@ -147,11 +101,9 @@ bool PrettyPrintXml(std::ostream& os,
return false;
}
xdoc.print(os, indent);
xdoc.save(os, indent);
return true;
}
#endif
} // namespace xml
} // namespace csoap
+38 -56
View File
@@ -1,79 +1,63 @@
#ifndef CSOAP_XML_H_
#define CSOAP_XML_H_
#include <string>
#ifdef CSOAP_USE_TINYXML
#include "tinyxml/tinyxml.h"
#else
#include "pugixml/pugixml.hpp"
#endif
// XML utilities.
#include <string>
#include "pugixml/pugixml.hpp"
namespace csoap {
namespace xml {
#ifdef CSOAP_USE_TINYXML
// 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.
// Example:
// Node name: soapenv:Envelope
// NS prefix: soapenv
std::string GetNsPrefix(const TiXmlElement* xnode);
// E.g., if the node name is "soapenv:Envelope", NS prefix will be "soapenv".
std::string GetPrefix(const pugi::xml_node& xnode);
// Append a child with the given name which is prefixed by a namespace.
// 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".
TiXmlElement* AppendChild(TiXmlNode* xnode,
const std::string& ns,
const std::string& name);
TiXmlElement* GetChild(TiXmlElement* xnode,
const std::string& ns,
const std::string& name);
TiXmlElement* GetChildNoNS(TiXmlElement* xnode, const std::string& name);
// Add an attribute with the given name which is prefixed by a namespace.
void AddAttr(TiXmlElement* xnode,
const std::string& ns,
const std::string& name,
const std::string& value);
void SetText(TiXmlElement* xnode, const std::string& text);
bool PrettyPrintXml(std::ostream& os,
const std::string& xml_string,
const char* indent = " ");
#else // PugiXml
// Get the namespace prefix from node name.
// Example:
// Node name: soapenv:Envelope
// NS prefix: soapenv
std::string GetNsPrefix(const pugi::xml_node& xnode);
// Append 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 AppendChild(pugi::xml_node& xnode,
const std::string& ns,
const std::string& name);
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);
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:
@@ -99,9 +83,7 @@ private:
bool PrettyPrintXml(std::ostream& os,
const std::string& xml_string,
const char* indent = " ");
#endif // CSOAP_USE_TINYXML
const char* indent = "\t");
} // namespace xml
} // namespace csoap