Many improvements (fix start line parsing; remove keep-alive; refine demo; ...

master
Adam Gu 8 years ago
parent 032a5a96a1
commit eac855ae32

@ -4,6 +4,39 @@ namespace csoap {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
const char* GetErrorMessage(ErrorCode error_code) {
switch (error_code) {
case kHostResolveError:
return "Cannot resolve the host.";
case kEndpointConnectError:
return "Cannot connect to remote endpoint.";
case kSocketReadError:
return "Socket read error.";
case kSocketWriteError:
return "Socket write error.";
case kHttpStartLineError:
return "[HTTP Response] Start line is invalid.";
case kHttpStatusError:
return "[HTTP Response] Status is not OK.";
case kHttpContentLengthError:
return "[HTTP Response] Content-Length is invalid or missing.";
case kXmlError:
return "XML error";
default:
return "No error";
}
}
////////////////////////////////////////////////////////////////////////////////
Parameter::Parameter(const std::string& key, const std::string& value) Parameter::Parameter(const std::string& key, const std::string& value)
: key_(key) { : key_(key) {
value_ = LexicalCast<std::string>(value, ""); value_ = LexicalCast<std::string>(value, "");

@ -25,6 +25,32 @@ To LexicalCast(const From& input, const To& default_output) {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
enum ErrorCode {
kNoError = 0, // OK
kHostResolveError,
kEndpointConnectError,
kSocketReadError,
kSocketWriteError,
// Invalid start line in the HTTP response.
kHttpStartLineError,
// Status is not 200 in the HTTP response.
kHttpStatusError,
// Invalid or missing Content-Length in the HTTP response.
kHttpContentLengthError,
kXmlError,
};
// Return a descriptive message for the given error code.
const char* GetErrorMessage(ErrorCode error_code);
////////////////////////////////////////////////////////////////////////////////
// XML namespace name/url pair. // XML namespace name/url pair.
// E.g., { "soapenv", "http://schemas.xmlsoap.org/soap/envelope/" } // E.g., { "soapenv", "http://schemas.xmlsoap.org/soap/envelope/" }
class Namespace { class Namespace {

@ -4,8 +4,8 @@
// Include all csoap headers. // Include all csoap headers.
#include "csoap/http_client.h" #include "csoap/http_client.h"
#include "csoap/soap_request_envelope.h" #include "csoap/soap_request.h"
#include "csoap/soap_response_parser.h" #include "csoap/soap_response.h"
#include "csoap/xml.h" #include "csoap/xml.h"
#endif // CSOAP_CSOAP_H_ #endif // CSOAP_CSOAP_H_

@ -14,16 +14,22 @@ static const std::string kCRLF = "\r\n";
// NOTE: // NOTE:
// Each header field consists of a name followed by a colon (":") and the // Each header field consists of a name followed by a colon (":") and the
// field value. Field names are case-insensitive. // field value. Field names are case-insensitive.
// See http://stackoverflow.com/questions/5258977/are-http-headers-case-sensitive // See https://stackoverflow.com/a/5259004
static const std::string kFieldContentTypeName = "Content-Type"; static const std::string kFieldContentTypeName = "Content-Type";
static const std::string kFieldContentLengthName = "Content-Length"; static const std::string kFieldContentLengthName = "Content-Length";
static const size_t kInvalidContentLength = std::string::npos;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// 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
HttpRequest::HttpRequest(HttpVersion version) HttpRequest::HttpRequest(HttpVersion version)
: version_(version) : version_(version)
, keep_alive_(true) , content_length_(0) {
, content_length_(std::string::npos) {
} }
void HttpRequest::ToString(std::string& req_string) const { void HttpRequest::ToString(std::string& req_string) const {
@ -42,13 +48,17 @@ void HttpRequest::ToString(std::string& req_string) const {
// Header fields // Header fields
req_string += kFieldContentTypeName;
req_string += ": ";
if (!content_type_.empty()) { if (!content_type_.empty()) {
req_string += kFieldContentTypeName;
req_string += ": ";
req_string += content_type_; req_string += content_type_;
req_string += kCRLF; } else {
req_string += "text/xml; charset=utf-8";
} }
req_string += kCRLF;
req_string += kFieldContentLengthName; req_string += kFieldContentLengthName;
req_string += ": "; req_string += ": ";
req_string += LexicalCast<std::string>(content_length_, "0"); req_string += LexicalCast<std::string>(content_length_, "0");
@ -66,25 +76,20 @@ void HttpRequest::ToString(std::string& req_string) const {
} }
req_string += kCRLF; req_string += kCRLF;
if (keep_alive_) { req_string += kCRLF; // End of Headers.
req_string += "Connection: Keep-Alive";
req_string += kCRLF;
}
req_string += kCRLF;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
HttpResponse::HttpResponse() HttpResponse::HttpResponse()
: status_(0) : status_(0)
, content_length_(0) , content_length_(kInvalidContentLength)
, start_line_parsed_(false) , start_line_parsed_(false)
, header_parsed_(false) , header_parsed_(false)
, finished_(false) { , finished_(false) {
} }
void HttpResponse::Parse(const char* data, size_t len) { ErrorCode HttpResponse::Parse(const char* data, size_t len) {
if (header_parsed_) { if (header_parsed_) {
// Add the data to the content. // Add the data to the content.
content_.append(data, len); content_.append(data, len);
@ -92,10 +97,9 @@ void HttpResponse::Parse(const char* data, size_t len) {
if (content_.length() >= content_length_) { if (content_.length() >= content_length_) {
// All content has been read. // All content has been read.
finished_ = true; finished_ = true;
return;
} }
return; return kNoError;
} }
pending_data_.append(data, len); pending_data_.append(data, len);
@ -108,7 +112,7 @@ void HttpResponse::Parse(const char* data, size_t len) {
} }
if (pos == off) { // End of headers. if (pos == off) { // End of headers.
off = pos + 2; // Skip "\r\n". off = pos + 2; // Skip CRLF.
header_parsed_ = true; header_parsed_ = true;
break; break;
} }
@ -116,17 +120,30 @@ void HttpResponse::Parse(const char* data, size_t len) {
std::string line = pending_data_.substr(off, pos - off); std::string line = pending_data_.substr(off, pos - off);
if (!start_line_parsed_) { if (!start_line_parsed_) {
ParseStartLine(line); // TODO: Error handling.
start_line_parsed_ = true; start_line_parsed_ = true;
ErrorCode error = ParseStartLine(line);
if (error != kNoError) {
return error;
}
} else { } else {
ParseHeaderField(line); // 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 "\r\n". off = pos + 2; // Skip CRLF.
} }
if (header_parsed_) { if (header_parsed_) {
// Headers just ended. // Headers just ended.
if (content_length_ == kInvalidContentLength) {
// No Content-Length?
return kHttpContentLengthError;
}
content_ += pending_data_.substr(off); content_ += pending_data_.substr(off);
if (content_.length() >= content_length_) { if (content_.length() >= content_length_) {
@ -137,55 +154,68 @@ void HttpResponse::Parse(const char* data, size_t len) {
// Save the unparsed piece for next parsing. // Save the unparsed piece for next parsing.
pending_data_ = pending_data_.substr(off); pending_data_ = pending_data_.substr(off);
} }
return kNoError;
} }
bool HttpResponse::ParseStartLine(const std::string& line) { ErrorCode HttpResponse::ParseStartLine(const std::string& line) {
std::vector<std::string> parts; size_t off = 0;
boost::split(parts, line, boost::is_any_of(" "), boost::token_compress_on);
size_t pos = line.find(' ');
if (pos == std::string::npos) {
return kHttpStartLineError;
}
// HTTP version
off = pos + 1; // Skip space.
if (parts.size() != 3) { pos = line.find(' ', off);
return false; if (pos == std::string::npos) {
return kHttpStartLineError;
} }
// Status code
std::string status_str = line.substr(off, pos - off);
try { try {
status_ = boost::lexical_cast<int>(parts[1]); status_ = boost::lexical_cast<int>(status_str);
} catch (boost::bad_lexical_cast&) { } catch (boost::bad_lexical_cast&) {
return false; return kHttpStartLineError;
} }
reason_ = parts[2]; off = pos + 1; // Skip space.
reason_ = line.substr(off);
return true; if (status_ != kHttpOK) {
return kHttpStatusError;
}
return kNoError;
} }
bool HttpResponse::ParseHeaderField(const std::string& line) { void HttpResponse::ParseContentLength(const std::string& line) {
size_t pos = line.find(':'); size_t pos = line.find(':');
if (pos == std::string::npos) { if (pos == std::string::npos) {
return false; return;
} }
std::string name = line.substr(0, pos); std::string name = line.substr(0, pos);
++pos; // Skip ':'. if (boost::iequals(name, kFieldContentLengthName)) {
while (line[pos] == ' ') { // Skip spaces. ++pos; // Skip ':'.
++pos; while (line[pos] == ' ') { // Skip spaces.
} ++pos;
}
std::string value = line.substr(pos); std::string value = line.substr(pos);
if (boost::iequals(name, kFieldContentTypeName)) {
content_type_ = value;
} else if (boost::iequals(name, kFieldContentLengthName)) {
try { try {
content_length_ = boost::lexical_cast<size_t>(value); content_length_ = boost::lexical_cast<size_t>(value);
} catch (boost::bad_lexical_cast&) { } catch (boost::bad_lexical_cast&) {
// TODO // TODO
} }
} else {
// Unsupported, ignore.
} }
return true;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -193,11 +223,11 @@ bool HttpResponse::ParseHeaderField(const std::string& line) {
HttpClient::HttpClient() { HttpClient::HttpClient() {
} }
HttpClient::~HttpClient() { ErrorCode HttpClient::SendRequest(const HttpRequest& request,
} const std::string& body,
HttpResponse* response) {
assert(response != NULL);
bool HttpClient::SendRequest(const HttpRequest& request,
const std::string& body) {
using boost::asio::ip::tcp; using boost::asio::ip::tcp;
tcp::socket socket(io_service_); tcp::socket socket(io_service_);
@ -211,17 +241,17 @@ bool HttpClient::SendRequest(const HttpRequest& request,
tcp::resolver::query query(request.host(), port); tcp::resolver::query query(request.host(), port);
boost::system::error_code error; boost::system::error_code ec;
tcp::resolver::iterator it = resolver.resolve(query, error); tcp::resolver::iterator it = resolver.resolve(query, ec);
if (error) { if (ec) {
return false; return kHostResolveError;
} }
socket.connect(*it, error); socket.connect(*it, ec);
if (error) { if (ec) {
return false; return kEndpointConnectError;
} }
std::string request_str; std::string request_str;
@ -231,23 +261,31 @@ bool HttpClient::SendRequest(const HttpRequest& request,
boost::asio::write(socket, boost::asio::buffer(request_str)); boost::asio::write(socket, boost::asio::buffer(request_str));
boost::asio::write(socket, boost::asio::buffer(body)); boost::asio::write(socket, boost::asio::buffer(body));
} catch (boost::system::system_error&) { } catch (boost::system::system_error&) {
return false; return kSocketWriteError;
} }
// Read and parse HTTP response. // Read and parse HTTP response.
while (!response_.finished()) { // We must stop trying to read some once all content has been received,
try { // because some servers will block extra call to read_some().
size_t len = socket.read_some(boost::asio::buffer(bytes_)); while (!response->finished()) {
response_.Parse(bytes_.data(), len); size_t len = socket.read_some(boost::asio::buffer(bytes_), ec);
} catch (boost::system::system_error&) { if (len == 0 || ec) {
// Should be EOF, but ... return kSocketReadError;
break; }
// 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 (error != kNoError) {
return error;
} }
} }
return true; return kNoError;
} }
} // namespace csoap } // namespace csoap

@ -3,15 +3,7 @@
#include <string> #include <string>
#include "boost/asio.hpp" #include "boost/asio.hpp"
#include "csoap/common.h"
// 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 { namespace csoap {
@ -22,9 +14,10 @@ enum HttpVersion {
kHttpV11, kHttpV11,
}; };
//enum HttpStatus { enum HttpStatus {
// kHttpOK = 200, kHttpOK = 200,
//}; kHttpNotFound = 404,
};
enum HeaderField { enum HeaderField {
kHeaderContentType, kHeaderContentType,
@ -37,15 +30,21 @@ enum HeaderField {
// HTTP request. // HTTP request.
// NOTE: // NOTE:
// - Only POST method is supported. // - Only POST method is supported.
// See http://stackoverflow.com/questions/26339317/do-soap-web-services-support-only-post-http-method // See https://stackoverflow.com/a/26339467
class HttpRequest { class HttpRequest {
public: public:
HttpRequest(HttpVersion version); HttpRequest(HttpVersion version);
void set_uri(const std::string& uri) { // Set the URL for the HTTP request start line.
url_ = uri; // 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) { void set_content_type(const std::string& content_type) {
content_type_ = content_type; content_type_ = content_type;
} }
@ -54,10 +53,6 @@ public:
content_length_ = content_length; content_length_ = content_length;
} }
void set_keep_alive(bool keep_alive) {
keep_alive_ = keep_alive;
}
const std::string& host() const { const std::string& host() const {
return host_; return host_;
} }
@ -67,7 +62,7 @@ public:
} }
// \param host Descriptive host name or numeric IP address. // \param host Descriptive host name or numeric IP address.
// \param port Numeric port number. // \param port Numeric port number, "80" will be used if it's empty.
void set_host(const std::string& host, const std::string& port) { void set_host(const std::string& host, const std::string& port) {
host_ = host; host_ = host;
port_ = port; port_ = port;
@ -84,7 +79,8 @@ private:
HttpVersion version_; HttpVersion version_;
// Request URL. // Request URL.
// A complete URL naming the requested resource, or the path component of the URL. // A complete URL naming the requested resource, or the path component of
// the URL.
std::string url_; std::string url_;
std::string content_type_; std::string content_type_;
@ -93,8 +89,6 @@ private:
std::string host_; std::string host_;
std::string port_; std::string port_;
bool keep_alive_;
std::string soap_action_; std::string soap_action_;
}; };
@ -104,7 +98,7 @@ class HttpResponse {
public: public:
HttpResponse(); HttpResponse();
void Parse(const char* data, size_t len); ErrorCode Parse(const char* data, size_t len);
bool finished() const { bool finished() const {
return finished_; return finished_;
@ -124,18 +118,18 @@ public:
private: private:
// Parse start line, e.g., "HTTP/1.1 200 OK". // Parse start line, e.g., "HTTP/1.1 200 OK".
bool ParseStartLine(const std::string& line); ErrorCode ParseStartLine(const std::string& line);
// Parse a header line, e.g., "Content-Length: 19". void ParseContentLength(const std::string& line);
bool ParseHeaderField(const std::string& line);
private: private:
int status_; // HTTP status, e.g., 200. int status_; // HTTP status, e.g., 200.
std::string reason_; std::string reason_;
std::string content_type_;
size_t content_length_; size_t content_length_;
std::string content_; std::string content_;
ErrorCode error_;
// Data waiting to be parsed. // Data waiting to be parsed.
std::string pending_data_; std::string pending_data_;
@ -150,20 +144,14 @@ private:
class HttpClient { class HttpClient {
public: public:
HttpClient(); HttpClient();
~HttpClient();
bool SendRequest(const HttpRequest& request, ErrorCode SendRequest(const HttpRequest& request,
const std::string& body); const std::string& body,
HttpResponse* response);
const HttpResponse& response() const {
return response_;
}
private: private:
boost::asio::io_service io_service_; boost::asio::io_service io_service_;
std::array<char, 1024> bytes_; std::array<char, 1024> bytes_;
HttpResponse response_;
}; };
} // namespace csoap } // namespace csoap

@ -0,0 +1,51 @@
#include "csoap/soap_request.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);
}
////////////////////////////////////////////////////////////////////////////////
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));
}
void SoapRequest::AddParameter(const Parameter& parameter) {
parameters_.push_back(parameter);
}
void SoapRequest::ToXmlString(std::string* xml_string) {
pugi::xml_document xdoc;
pugi::xml_node xroot = xml::AppendChild(xdoc, soapenv_ns_.name, "Envelope");
AppendAttrNS(xroot, soapenv_ns_);
AppendAttrNS(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_);
for (Parameter& p : parameters_) {
pugi::xml_node xparam = xml::AppendChild(xop, service_ns_.name, 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

@ -0,0 +1,48 @@
#ifndef CSOAP_SOAP_REQUEST_H_
#define CSOAP_SOAP_REQUEST_H_
#include <string>
#include <vector>
#include "csoap/common.h"
namespace csoap {
// SOAP request.
// Used to compose the SOAP request envelope XML which will be sent as the HTTP
// request body.
class SoapRequest {
public:
explicit SoapRequest(const std::string& operation);
// 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;
}
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);
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_;
};
} // namespace csoap
#endif // CSOAP_SOAP_REQUEST_H_

@ -1,67 +0,0 @@
#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

@ -1,45 +0,0 @@
#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_

@ -1,16 +1,16 @@
#include "csoap/soap_response_parser.h" #include "csoap/soap_response.h"
#include "csoap/xml.h" #include "csoap/xml.h"
namespace csoap { namespace csoap {
SoapResponseParser::SoapResponseParser() { SoapResponse::SoapResponse() {
} }
bool SoapResponseParser::Parse(const std::string& content, bool SoapResponse::Parse(const std::string& content,
const std::string& message_name, const std::string& message_name,
const std::string& element_name, const std::string& element_name,
std::string* element_value) { std::string* element_value) {
pugi::xml_document xdoc; pugi::xml_document xdoc;
pugi::xml_parse_result result = xdoc.load_string(content.c_str()); pugi::xml_parse_result result = xdoc.load_string(content.c_str());

@ -1,18 +1,17 @@
#ifndef CSOAP_RESPONSE_PARSER_H_ #ifndef CSOAP_RESPONSE_H_
#define CSOAP_RESPONSE_PARSER_H_ #define CSOAP_RESPONSE_H_
#include <string> #include <string>
namespace csoap { namespace csoap {
class SoapResponseParser { // SOAP response.
// Used to parse the SOAP response XML which is returned as the HTTP response
// body.
class SoapResponse {
public: public:
SoapResponseParser(); SoapResponse();
// <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, bool Parse(const std::string& content,
const std::string& message_name, const std::string& message_name,
const std::string& element_name, const std::string& element_name,
@ -29,4 +28,4 @@ private:
} // namespace csoap } // namespace csoap
#endif // CSOAP_RESPONSE_PARSER_H_ #endif // CSOAP_RESPONSE_H_

@ -11,13 +11,43 @@ Calculator::Calculator() {
} }
bool Calculator::Add(float x, float y, float* result) { bool Calculator::Add(float x, float y, float* result) {
return Calc("add", "x", "y", x, y, result);
}
bool Calculator::Subtract(float x, float y, float* result) {
return Calc("subtract", "x", "y", x, y, result);
}
bool Calculator::Multiply(float x, float y, float* result) {
return Calc("multiply", "x", "y", x, y, result);
}
bool Calculator::Divide(float x, float y, float* result) {
return Calc("divide", "numerator", "denominator", x, y, result);
}
void Calculator::Init() {
url_ = "/glue/calculator";
host_ = "ws1.parasoft.com";
port_ = ""; // Use default: 80
service_ns_ = { "ser", "http://www.parasoft.com/wsdl/calculator/" };
}
bool Calculator::Calc(const std::string& operation,
const std::string& x_name,
const std::string& y_name,
float x,
float y,
float* result) {
csoap::Parameter parameters[] = { csoap::Parameter parameters[] = {
{ "x", x }, { x_name, x },
{ "y", y } { y_name, y }
}; };
std::string result_str; std::string result_str;
if (!Call("add", parameters, 2, &result_str)) { if (!Call(operation, parameters, 2, &result_str)) {
return false; return false;
} }
@ -30,64 +60,55 @@ bool Calculator::Add(float x, float y, float* result) {
return true; return true;
} }
void Calculator::Init() {
soap_envelope_ns_ = { "soapenv", "http://schemas.xmlsoap.org/soap/envelope/" };
service_ns_ = { "ser", "http://www.parasoft.com/wsdl/calculator/" };
host_ = "ws1.parasoft.com";
port_ = ""; // Use default: 80
}
bool Calculator::Call(const std::string& operation, bool Calculator::Call(const std::string& operation,
const csoap::Parameter* parameters, const csoap::Parameter* parameters,
size_t count, size_t count,
std::string* result) { std::string* result) {
csoap::SoapRequestEnvelope req_envelope(operation); csoap::SoapRequest soap_request(operation);
req_envelope.SetNamespace(csoap::SoapRequestEnvelope::kSoapEnvelopeNS, soap_envelope_ns_); soap_request.set_service_ns(service_ns_);
req_envelope.SetNamespace(csoap::SoapRequestEnvelope::kServiceNS, service_ns_);
for (size_t i = 0; i < count; ++i) { for (size_t i = 0; i < count; ++i) {
req_envelope.AddParameter(parameters[i]); soap_request.AddParameter(parameters[i]);
} }
std::string request_body; std::string http_request_body;
req_envelope.ToXmlString(&request_body); soap_request.ToXmlString(&http_request_body);
csoap::HttpRequest http_request(csoap::kHttpV11); csoap::HttpRequest http_request(csoap::kHttpV11);
http_request.set_uri("http://ws1.parasoft.com/glue/calculator"); http_request.set_url(url_);
http_request.set_content_type("text/xml; charset=utf-8"); http_request.set_content_length(http_request_body.size());
http_request.set_content_length(request_body.size());
http_request.set_host(host_, port_); http_request.set_host(host_, port_);
http_request.set_keep_alive(true);
http_request.set_soap_action(operation); http_request.set_soap_action(operation);
csoap::HttpResponse http_response;
csoap::HttpClient http_client; csoap::HttpClient http_client;
csoap::ErrorCode ec = http_client.SendRequest(http_request,
http_request_body,
&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;
}
if (!http_client.SendRequest(http_request, request_body)) {
std::cerr << "Failed to send HTTP request." << std::endl;
return false; return false;
} }
const csoap::HttpResponse& http_response = http_client.response(); csoap::SoapResponse soap_response;
std::cout << http_response.status() << " " << http_response.reason() << std::endl;
csoap::SoapResponseParser soap_response_parser;
std::string rsp_message_name = operation + "Response"; std::string rsp_message_name = operation + "Response";
std::string rsp_element_name = "Result"; std::string rsp_element_name = "Result";
soap_response_parser.Parse(http_response.content(), return soap_response.Parse(http_response.content(),
rsp_message_name, rsp_message_name,
rsp_element_name, rsp_element_name,
result); result);
//std::cout << "return:\n" << *result << std::endl;
return true;
} }
} // namespace demo } // namespace demo

@ -15,21 +15,39 @@ public:
bool Add(float x, float y, float* result); bool Add(float x, float y, float* result);
bool Subtract(float x, float y, float* result);
bool Multiply(float x, float y, float* result);
bool Divide(float x, float y, float* result);
protected: protected:
void Init(); void Init();
// A more concrete wrapper to make a call.
bool Calc(const std::string& operation,
const std::string& x_name,
const std::string& y_name,
float x,
float y,
float* result);
// A generic wrapper to make a call.
bool Call(const std::string& operation, bool Call(const std::string& operation,
const csoap::Parameter* parameters, const csoap::Parameter* parameters,
size_t count, size_t count,
std::string* result); std::string* result);
protected: protected:
std::string url_; // Request URL // 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 host_;
std::string port_; std::string port_; // Leave this empty to use default 80.
csoap::Namespace soap_envelope_ns_; // The namespace of your service.
csoap::Namespace service_ns_; csoap::Namespace service_ns_;
}; };

@ -4,12 +4,31 @@
int main() { int main() {
demo::Calculator calculator; demo::Calculator calculator;
float x = 1.0;
float y = 2.0;
float result = 0.0; float result = 0.0;
if (!calculator.Add(1.0, 2.0, &result)) {
std::cerr << "Failed to call web service." << std::endl; if (calculator.Add(x, y, &result)) {
} else { printf("add: %.1f\n", result);
std::cout << "Add result: " << std::showpoint << result << std::endl; }
if (calculator.Subtract(x, y, &result)) {
printf("subtract: %.1f\n", result);
}
if (calculator.Multiply(x, y, &result)) {
printf("multiply: %.1f\n", result);
}
if (calculator.Divide(x, y, &result)) {
printf("divide: %.1f\n", result);
} }
return 0; return 0;
} }
// Output:
// add: 3.0
// subtract: -1.0
// multiply: 2.0
// divide: 0.5

Loading…
Cancel
Save