Support to create soap server. (draft)
This commit is contained in:
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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_
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#include "csoap/soap_service.h"
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user