A usable draft.

This commit is contained in:
Adam Gu
2017-05-26 17:23:12 +08:00
parent a21102e963
commit 032a5a96a1
23 changed files with 15426 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
if(UNIX)
add_definitions(-D_GLIBCXX_USE_WCHAR_T -std=c++11)
endif()
file(GLOB SRCS *.cc *.h)
add_library(csoap ${SRCS})
+27
View File
@@ -0,0 +1,27 @@
#include "csoap/common.h"
namespace csoap {
////////////////////////////////////////////////////////////////////////////////
Parameter::Parameter(const std::string& key, const std::string& value)
: key_(key) {
value_ = LexicalCast<std::string>(value, "");
}
Parameter::Parameter(const std::string& key, int value)
: key_(key) {
value_ = LexicalCast<std::string>(value, "");
}
Parameter::Parameter(const std::string& key, float value)
: key_(key) {
value_ = LexicalCast<std::string>(value, "");
}
Parameter::Parameter(const std::string& key, bool value)
: key_(key) {
value_ = LexicalCast<std::string>(value, "");
}
} // namespace csoap
+69
View File
@@ -0,0 +1,69 @@
#ifndef CSOAP_COMMON_H_
#define CSOAP_COMMON_H_
// Common definitions.
#include <string>
#include "boost/lexical_cast.hpp"
namespace csoap {
////////////////////////////////////////////////////////////////////////////////
// Wrapper for boost::lexcical_cast.
// Usage:
// LexicalCast<int>("123", 0);
// LexicalCast<std::string>(123, "");
template <typename To, typename From>
To LexicalCast(const From& input, const To& default_output) {
try {
return boost::lexical_cast<To>(input);
} catch (boost::bad_lexical_cast&) {
return default_output;
}
}
////////////////////////////////////////////////////////////////////////////////
// XML namespace name/url pair.
// E.g., { "soapenv", "http://schemas.xmlsoap.org/soap/envelope/" }
class Namespace {
public:
std::string name;
std::string url;
};
////////////////////////////////////////////////////////////////////////////////
// Parameter in the SOAP request envelope.
class Parameter {
public:
Parameter(const std::string& key, const std::string& value);
Parameter(const std::string& key, int value);
Parameter(const std::string& key, float value);
Parameter(const std::string& key, bool value);
const char* c_key() const {
return key_.c_str();
}
const std::string& key() const {
return key_;
}
const char* c_value() const {
return value_.c_str();
}
const std::string& value() const {
return value_;
}
private:
std::string key_;
std::string value_;
};
} // namespace csoap
#endif // CSOAP_COMMON_H_
+11
View File
@@ -0,0 +1,11 @@
#ifndef CSOAP_CSOAP_H_
#define CSOAP_CSOAP_H_
// Include all csoap headers.
#include "csoap/http_client.h"
#include "csoap/soap_request_envelope.h"
#include "csoap/soap_response_parser.h"
#include "csoap/xml.h"
#endif // CSOAP_CSOAP_H_
+253
View File
@@ -0,0 +1,253 @@
#include "csoap/http_client.h"
#include "boost/bind.hpp"
#include "boost/algorithm/string.hpp"
#include "csoap/common.h"
namespace csoap {
////////////////////////////////////////////////////////////////////////////////
static const std::string kCRLF = "\r\n";
// NOTE:
// Each header field consists of a name followed by a colon (":") and the
// field value. Field names are case-insensitive.
// See http://stackoverflow.com/questions/5258977/are-http-headers-case-sensitive
static const std::string kFieldContentTypeName = "Content-Type";
static const std::string kFieldContentLengthName = "Content-Length";
////////////////////////////////////////////////////////////////////////////////
HttpRequest::HttpRequest(HttpVersion version)
: version_(version)
, keep_alive_(true)
, content_length_(std::string::npos) {
}
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
if (!content_type_.empty()) {
req_string += kFieldContentTypeName;
req_string += ": ";
req_string += content_type_;
req_string += kCRLF;
}
req_string += kFieldContentLengthName;
req_string += ": ";
req_string += LexicalCast<std::string>(content_length_, "0");
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;
if (keep_alive_) {
req_string += "Connection: Keep-Alive";
req_string += kCRLF;
}
req_string += kCRLF;
}
////////////////////////////////////////////////////////////////////////////////
HttpResponse::HttpResponse()
: status_(0)
, content_length_(0)
, start_line_parsed_(false)
, header_parsed_(false)
, finished_(false) {
}
void 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;
}
return;
}
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 "\r\n".
header_parsed_ = true;
break;
}
std::string line = pending_data_.substr(off, pos - off);
if (!start_line_parsed_) {
ParseStartLine(line); // TODO: Error handling.
start_line_parsed_ = true;
} else {
ParseHeaderField(line);
}
off = pos + 2; // Skip "\r\n".
}
if (header_parsed_) {
// Headers just ended.
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);
}
}
bool HttpResponse::ParseStartLine(const std::string& line) {
std::vector<std::string> parts;
boost::split(parts, line, boost::is_any_of(" "), boost::token_compress_on);
if (parts.size() != 3) {
return false;
}
try {
status_ = boost::lexical_cast<int>(parts[1]);
} catch (boost::bad_lexical_cast&) {
return false;
}
reason_ = parts[2];
return true;
}
bool HttpResponse::ParseHeaderField(const std::string& line) {
size_t pos = line.find(':');
if (pos == std::string::npos) {
return false;
}
std::string name = line.substr(0, pos);
++pos; // Skip ':'.
while (line[pos] == ' ') { // Skip spaces.
++pos;
}
std::string value = line.substr(pos);
if (boost::iequals(name, kFieldContentTypeName)) {
content_type_ = value;
} else if (boost::iequals(name, kFieldContentLengthName)) {
try {
content_length_ = boost::lexical_cast<size_t>(value);
} catch (boost::bad_lexical_cast&) {
// TODO
}
} else {
// Unsupported, ignore.
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
HttpClient::HttpClient() {
}
HttpClient::~HttpClient() {
}
bool HttpClient::SendRequest(const HttpRequest& request,
const std::string& body) {
using boost::asio::ip::tcp;
tcp::socket socket(io_service_);
tcp::resolver resolver(io_service_);
std::string port = request.port();
if (port.empty()) {
port = "80";
}
tcp::resolver::query query(request.host(), port);
boost::system::error_code error;
tcp::resolver::iterator it = resolver.resolve(query, error);
if (error) {
return false;
}
socket.connect(*it, error);
if (error) {
return false;
}
std::string request_str;
request.ToString(request_str);
try {
boost::asio::write(socket, boost::asio::buffer(request_str));
boost::asio::write(socket, boost::asio::buffer(body));
} catch (boost::system::system_error&) {
return false;
}
// Read and parse HTTP response.
while (!response_.finished()) {
try {
size_t len = socket.read_some(boost::asio::buffer(bytes_));
response_.Parse(bytes_.data(), len);
} catch (boost::system::system_error&) {
// Should be EOF, but ...
break;
}
}
return true;
}
} // namespace csoap
+171
View File
@@ -0,0 +1,171 @@
#ifndef CSOAP_HTTP_CLIENT_H_
#define CSOAP_HTTP_CLIENT_H_
#include <string>
#include "boost/asio.hpp"
// A little concept about URL (From HTTP The Definitive Guide):
// Say you want to fetch the URL http://www.joes-hardware.com/seasonal/index-fall.html:
// - The first part of the URL(http) is the URL scheme. The scheme tells a web client
// *how* to access the resource. In this case, the URL says to use the HTTP protocol.
// - The second part of the URL (www.joes-hardware.com) is the server location.
// This tells the web client *where* the resource is hosted.
// - The third part of the URL(/seasonal/index-fall.html) is the resource path. The
// path tells *what* particular local resource on the server is being requested.
namespace csoap {
////////////////////////////////////////////////////////////////////////////////
enum HttpVersion {
kHttpV10,
kHttpV11,
};
//enum HttpStatus {
// kHttpOK = 200,
//};
enum HeaderField {
kHeaderContentType,
kHeaderContentLength,
kHeaderHost,
};
////////////////////////////////////////////////////////////////////////////////
// HTTP request.
// NOTE:
// - Only POST method is supported.
// See http://stackoverflow.com/questions/26339317/do-soap-web-services-support-only-post-http-method
class HttpRequest {
public:
HttpRequest(HttpVersion version);
void set_uri(const std::string& uri) {
url_ = uri;
}
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;
}
void set_keep_alive(bool keep_alive) {
keep_alive_ = keep_alive;
}
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.
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_;
bool keep_alive_;
std::string soap_action_;
};
////////////////////////////////////////////////////////////////////////////////
class HttpResponse {
public:
HttpResponse();
void 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".
bool ParseStartLine(const std::string& line);
// Parse a header line, e.g., "Content-Length: 19".
bool ParseHeaderField(const std::string& line);
private:
int status_; // HTTP status, e.g., 200.
std::string reason_;
std::string content_type_;
size_t content_length_;
std::string content_;
// Data waiting to be parsed.
std::string pending_data_;
// Parsing helper flags.
bool start_line_parsed_;
bool header_parsed_;
bool finished_;
};
////////////////////////////////////////////////////////////////////////////////
class HttpClient {
public:
HttpClient();
~HttpClient();
bool SendRequest(const HttpRequest& request,
const std::string& body);
const HttpResponse& response() const {
return response_;
}
private:
boost::asio::io_service io_service_;
std::array<char, 1024> bytes_;
HttpResponse response_;
};
} // namespace csoap
#endif // CSOAP_HTTP_CLIENT_H_
+67
View File
@@ -0,0 +1,67 @@
#include "csoap/soap_request_envelope.h"
#include "csoap/xml.h"
namespace csoap {
////////////////////////////////////////////////////////////////////////////////
// Append "xmlns" attribute.
static void AppendAttrNS(pugi::xml_node& xnode, const Namespace& ns) {
xml::AppendAttr(xnode, "xmlns", ns.name, ns.url);
}
////////////////////////////////////////////////////////////////////////////////
SoapRequestEnvelope::SoapRequestEnvelope(const std::string& operation)
: operation_(operation) {
}
void SoapRequestEnvelope::SetNamespace(NSType ns_type, const Namespace& ns) {
assert(ns_type < kCountNS);
namespaces_[ns_type] = ns;
}
void SoapRequestEnvelope::SetNamespace(NSType ns_type,
const std::string& name,
const std::string& url) {
assert(ns_type < kCountNS);
namespaces_[ns_type].name = name;
namespaces_[ns_type].url = url;
}
void SoapRequestEnvelope::AddParameter(const std::string& key,
const std::string& value) {
parameters_.push_back(Parameter(key, value));
}
void SoapRequestEnvelope::AddParameter(const Parameter& parameter) {
parameters_.push_back(parameter);
}
void SoapRequestEnvelope::ToXmlString(std::string* xml_string) {
std::string& soapenv_ns = namespaces_[kSoapEnvelopeNS].name;
std::string& srv_ns = namespaces_[kServiceNS].name;
pugi::xml_document xdoc;
pugi::xml_node xroot = xml::AppendChild(xdoc, soapenv_ns, "Envelope");
AppendAttrNS(xroot, namespaces_[kSoapEnvelopeNS]);
AppendAttrNS(xroot, namespaces_[kServiceNS]);
xml::AppendChild(xroot, soapenv_ns, "Header");
pugi::xml_node xbody = xml::AppendChild(xroot, soapenv_ns, "Body");
pugi::xml_node xoperation = xml::AppendChild(xbody, srv_ns, operation_);
for (Parameter& p : parameters_) {
pugi::xml_node xparam = xml::AppendChild(xoperation, srv_ns, p.c_key());
xparam.text().set(p.c_value());
}
xml::XmlStrRefWriter writer(xml_string);
xdoc.print(writer, "\t", pugi::format_default, pugi::encoding_utf8);
}
} // namespace csoap
+45
View File
@@ -0,0 +1,45 @@
#ifndef CSOAP_SOAP_REQUEST_ENVELOPE_H_
#define CSOAP_SOAP_REQUEST_ENVELOPE_H_
#include <string>
#include <vector>
#include "csoap/common.h"
namespace csoap {
// SOAP request envelope.
// Used to compose the SOAP envelope XML which will be sent as the HTTP
// request body.
class SoapRequestEnvelope {
public:
enum NSType {
kSoapEnvelopeNS = 0,
kServiceNS,
kCountNS,
};
public:
explicit SoapRequestEnvelope(const std::string& operation);
void SetNamespace(NSType ns_type, const Namespace& ns);
void SetNamespace(NSType ns_type,
const std::string& name,
const std::string& url);
void AddParameter(const std::string& key, const std::string& value);
void AddParameter(const Parameter& parameter);
void ToXmlString(std::string* xml_string);
private:
Namespace namespaces_[kCountNS];
std::string operation_;
std::vector<Parameter> parameters_;
};
} // namespace csoap
#endif // CSOAP_SOAP_REQUEST_ENVELOPE_H_
+46
View File
@@ -0,0 +1,46 @@
#include "csoap/soap_response_parser.h"
#include "csoap/xml.h"
namespace csoap {
SoapResponseParser::SoapResponseParser() {
}
bool SoapResponseParser::Parse(const std::string& content,
const std::string& message_name,
const std::string& element_name,
std::string* element_value) {
pugi::xml_document xdoc;
pugi::xml_parse_result result = xdoc.load_string(content.c_str());
if (!result) {
//std::cout << "Error: " << result.description() << std::endl;
return false;
}
pugi::xml_node xroot = xdoc.document_element();
soapenv_ns_ = csoap::xml::GetNsPrefix(xroot);
pugi::xml_node xbody = csoap::xml::GetChild(xroot, soapenv_ns_, "Body");
if (!xbody) {
return false;
}
pugi::xml_node xmessage = csoap::xml::GetChildNoNS(xbody, message_name);
if (!xmessage) {
return false;
}
pugi::xml_node xelement = csoap::xml::GetChildNoNS(xmessage, element_name);
if (!xelement) {
return false;
}
*element_value = xelement.text().get();
return true;
}
} // namespace csoap
+32
View File
@@ -0,0 +1,32 @@
#ifndef CSOAP_RESPONSE_PARSER_H_
#define CSOAP_RESPONSE_PARSER_H_
#include <string>
namespace csoap {
class SoapResponseParser {
public:
SoapResponseParser();
// <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
// <soapenv:Body>
// <ns:getPatientResponse xmlns:ns="http://service.csdm.carestream.com">
// <ns:return> ...
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_;
}
private:
// Soap envelope namespace in the response XML.
std::string soapenv_ns_;
};
} // namespace csoap
#endif // CSOAP_RESPONSE_PARSER_H_
+62
View File
@@ -0,0 +1,62 @@
#include "csoap/xml.h"
namespace csoap {
namespace xml {
std::string GetNsPrefix(const pugi::xml_node& xnode) {
std::string node_name = xnode.name();
size_t pos = node_name.find(':');
if (pos != std::string::npos) {
return node_name.substr(0, pos);
}
return "";
}
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 GetChild(pugi::xml_node& xnode,
const std::string& ns,
const std::string& name) {
return xnode.child((ns + ":" + name).c_str());
}
pugi::xml_node GetChildNoNS(pugi::xml_node& xnode,
const std::string& name) {
pugi::xml_node xchild = xnode.first_child();
while (xchild) {
std::string child_name = xchild.name();
// Remove NS prefix.
size_t pos = child_name.find(':');
if (pos != std::string::npos) {
child_name = child_name.substr(pos + 1);
}
if (child_name == name) {
return xchild;
}
xchild = xchild.next_sibling();
}
return pugi::xml_node();
}
void AppendAttr(pugi::xml_node& xnode,
const std::string& ns,
const std::string& name,
const std::string& value) {
std::string ns_name = ns + ":" + name;
xnode.append_attribute(ns_name.c_str()) = value.c_str();
}
} // namespace xml
} // namespace csoap
+63
View File
@@ -0,0 +1,63 @@
#ifndef CSOAP_XML_H_
#define CSOAP_XML_H_
#include <string>
#include "pugixml/pugixml.hpp"
// XML utilities.
namespace csoap {
namespace xml {
// 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 GetChild(pugi::xml_node& xnode,
const std::string& ns,
const std::string& name);
pugi::xml_node GetChildNoNS(pugi::xml_node& xnode,
const std::string& name);
// Append a attribute with the given name which is prefixed by a namespace.
void AppendAttr(pugi::xml_node& xnode,
const std::string& ns,
const std::string& name,
const std::string& value);
// An XML writer writing to a referenced string.
// Example:
// pugi::xml_document xdoc;
// ...
// std::string xml_string;
// XmlStrRefWriter writer(&xml_string);
// xdoc.print(writer, " ", pugi::format_default, pugi::encoding_utf8);
class XmlStrRefWriter : public pugi::xml_writer {
public:
explicit XmlStrRefWriter(std::string* result)
: result_(result) {
result_->clear();
}
virtual void write(const void* data, size_t size) override {
result_->append(static_cast<const char*>(data), size);
}
private:
std::string* result_;
};
} // namespace xml
} // namespace csoap
#endif // CSOAP_XML_H_