From eac855ae329d4f2ff660fc34e07c04349e11ebe5 Mon Sep 17 00:00:00 2001 From: Adam Gu Date: Sat, 27 May 2017 17:11:22 +0800 Subject: [PATCH] Many improvements (fix start line parsing; remove keep-alive; refine demo; ... --- src/csoap/common.cc | 33 ++++ src/csoap/common.h | 26 +++ src/csoap/csoap.h | 4 +- src/csoap/http_client.cc | 166 +++++++++++------- src/csoap/http_client.h | 62 +++---- src/csoap/soap_request.cc | 51 ++++++ src/csoap/soap_request.h | 48 +++++ src/csoap/soap_request_envelope.cc | 67 ------- src/csoap/soap_request_envelope.h | 45 ----- ...ap_response_parser.cc => soap_response.cc} | 12 +- ...soap_response_parser.h => soap_response.h} | 19 +- src/demo/calculator.cc | 89 ++++++---- src/demo/calculator.h | 24 ++- src/demo/main.cc | 27 ++- 14 files changed, 401 insertions(+), 272 deletions(-) create mode 100644 src/csoap/soap_request.cc create mode 100644 src/csoap/soap_request.h delete mode 100644 src/csoap/soap_request_envelope.cc delete mode 100644 src/csoap/soap_request_envelope.h rename src/csoap/{soap_response_parser.cc => soap_response.cc} (70%) rename src/csoap/{soap_response_parser.h => soap_response.h} (52%) diff --git a/src/csoap/common.cc b/src/csoap/common.cc index 5dc4358..480904f 100644 --- a/src/csoap/common.cc +++ b/src/csoap/common.cc @@ -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) : key_(key) { value_ = LexicalCast(value, ""); diff --git a/src/csoap/common.h b/src/csoap/common.h index afe7dd8..9e3632d 100644 --- a/src/csoap/common.h +++ b/src/csoap/common.h @@ -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. // E.g., { "soapenv", "http://schemas.xmlsoap.org/soap/envelope/" } class Namespace { diff --git a/src/csoap/csoap.h b/src/csoap/csoap.h index a418db0..5daeef1 100644 --- a/src/csoap/csoap.h +++ b/src/csoap/csoap.h @@ -4,8 +4,8 @@ // Include all csoap headers. #include "csoap/http_client.h" -#include "csoap/soap_request_envelope.h" -#include "csoap/soap_response_parser.h" +#include "csoap/soap_request.h" +#include "csoap/soap_response.h" #include "csoap/xml.h" #endif // CSOAP_CSOAP_H_ diff --git a/src/csoap/http_client.cc b/src/csoap/http_client.cc index 09ef919..03c2ab3 100644 --- a/src/csoap/http_client.cc +++ b/src/csoap/http_client.cc @@ -14,16 +14,22 @@ 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 +// See https://stackoverflow.com/a/5259004 static const std::string kFieldContentTypeName = "Content-Type"; 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) - : version_(version) - , keep_alive_(true) - , content_length_(std::string::npos) { + : version_(version) + , content_length_(0) { } void HttpRequest::ToString(std::string& req_string) const { @@ -42,13 +48,17 @@ void HttpRequest::ToString(std::string& req_string) const { // Header fields + req_string += kFieldContentTypeName; + req_string += ": "; + if (!content_type_.empty()) { - req_string += kFieldContentTypeName; - req_string += ": "; req_string += content_type_; - req_string += kCRLF; + } else { + req_string += "text/xml; charset=utf-8"; } + req_string += kCRLF; + req_string += kFieldContentLengthName; req_string += ": "; req_string += LexicalCast(content_length_, "0"); @@ -66,25 +76,20 @@ void HttpRequest::ToString(std::string& req_string) const { } req_string += kCRLF; - if (keep_alive_) { - req_string += "Connection: Keep-Alive"; - req_string += kCRLF; - } - - req_string += kCRLF; + req_string += kCRLF; // End of Headers. } //////////////////////////////////////////////////////////////////////////////// HttpResponse::HttpResponse() : status_(0) - , content_length_(0) + , content_length_(kInvalidContentLength) , start_line_parsed_(false) , header_parsed_(false) , finished_(false) { } -void HttpResponse::Parse(const char* data, size_t len) { +ErrorCode HttpResponse::Parse(const char* data, size_t len) { if (header_parsed_) { // Add the data to the content. content_.append(data, len); @@ -92,10 +97,9 @@ void HttpResponse::Parse(const char* data, size_t len) { if (content_.length() >= content_length_) { // All content has been read. finished_ = true; - return; } - return; + return kNoError; } pending_data_.append(data, len); @@ -108,7 +112,7 @@ void HttpResponse::Parse(const char* data, size_t len) { } if (pos == off) { // End of headers. - off = pos + 2; // Skip "\r\n". + off = pos + 2; // Skip CRLF. header_parsed_ = true; break; } @@ -116,17 +120,30 @@ void HttpResponse::Parse(const char* data, size_t len) { std::string line = pending_data_.substr(off, pos - off); if (!start_line_parsed_) { - ParseStartLine(line); // TODO: Error handling. start_line_parsed_ = true; + ErrorCode error = ParseStartLine(line); + if (error != kNoError) { + return error; + } } 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_) { // Headers just ended. + + if (content_length_ == kInvalidContentLength) { + // No Content-Length? + return kHttpContentLengthError; + } + content_ += pending_data_.substr(off); 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. pending_data_ = pending_data_.substr(off); } + + return kNoError; } -bool HttpResponse::ParseStartLine(const std::string& line) { - std::vector parts; - boost::split(parts, line, boost::is_any_of(" "), boost::token_compress_on); +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. - if (parts.size() != 3) { - return false; + 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(parts[1]); + status_ = boost::lexical_cast(status_str); } 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(':'); if (pos == std::string::npos) { - return false; + return; } std::string name = line.substr(0, pos); - ++pos; // Skip ':'. - while (line[pos] == ' ') { // Skip spaces. - ++pos; - } + if (boost::iequals(name, kFieldContentLengthName)) { + ++pos; // Skip ':'. + 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 { content_length_ = boost::lexical_cast(value); } catch (boost::bad_lexical_cast&) { // TODO } - } else { - // Unsupported, ignore. } - - return true; } //////////////////////////////////////////////////////////////////////////////// @@ -193,11 +223,11 @@ bool HttpResponse::ParseHeaderField(const std::string& line) { 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; tcp::socket socket(io_service_); @@ -211,17 +241,17 @@ bool HttpClient::SendRequest(const HttpRequest& request, tcp::resolver::query query(request.host(), port); - boost::system::error_code error; - tcp::resolver::iterator it = resolver.resolve(query, error); + boost::system::error_code ec; + tcp::resolver::iterator it = resolver.resolve(query, ec); - if (error) { - return false; + if (ec) { + return kHostResolveError; } - socket.connect(*it, error); + socket.connect(*it, ec); - if (error) { - return false; + if (ec) { + return kEndpointConnectError; } 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(body)); } catch (boost::system::system_error&) { - return false; + return kSocketWriteError; } // Read and parse HTTP response. - while (!response_.finished()) { - try { - size_t len = socket.read_some(boost::asio::buffer(bytes_)); - response_.Parse(bytes_.data(), len); + // 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); - } catch (boost::system::system_error&) { - // Should be EOF, but ... - break; + if (len == 0 || ec) { + return kSocketReadError; + } + + // 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 diff --git a/src/csoap/http_client.h b/src/csoap/http_client.h index c74da23..c5b7f08 100644 --- a/src/csoap/http_client.h +++ b/src/csoap/http_client.h @@ -3,15 +3,7 @@ #include #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. +#include "csoap/common.h" namespace csoap { @@ -22,9 +14,10 @@ enum HttpVersion { kHttpV11, }; -//enum HttpStatus { -// kHttpOK = 200, -//}; +enum HttpStatus { + kHttpOK = 200, + kHttpNotFound = 404, +}; enum HeaderField { kHeaderContentType, @@ -37,15 +30,21 @@ enum HeaderField { // HTTP request. // NOTE: // - 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 { public: HttpRequest(HttpVersion version); - void set_uri(const std::string& uri) { - url_ = uri; + // 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; } @@ -54,10 +53,6 @@ public: content_length_ = content_length; } - void set_keep_alive(bool keep_alive) { - keep_alive_ = keep_alive; - } - const std::string& host() const { return host_; } @@ -67,7 +62,7 @@ public: } // \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) { host_ = host; port_ = port; @@ -84,7 +79,8 @@ private: HttpVersion version_; // 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 content_type_; @@ -93,8 +89,6 @@ private: std::string host_; std::string port_; - bool keep_alive_; - std::string soap_action_; }; @@ -104,7 +98,7 @@ class HttpResponse { public: HttpResponse(); - void Parse(const char* data, size_t len); + ErrorCode Parse(const char* data, size_t len); bool finished() const { return finished_; @@ -124,18 +118,18 @@ public: private: // 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". - bool ParseHeaderField(const std::string& line); + void ParseContentLength(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_; + ErrorCode error_; + // Data waiting to be parsed. std::string pending_data_; @@ -150,20 +144,14 @@ private: class HttpClient { public: HttpClient(); - ~HttpClient(); - bool SendRequest(const HttpRequest& request, - const std::string& body); - - const HttpResponse& response() const { - return response_; - } + ErrorCode SendRequest(const HttpRequest& request, + const std::string& body, + HttpResponse* response); private: boost::asio::io_service io_service_; std::array bytes_; - - HttpResponse response_; }; } // namespace csoap diff --git a/src/csoap/soap_request.cc b/src/csoap/soap_request.cc new file mode 100644 index 0000000..f1f57df --- /dev/null +++ b/src/csoap/soap_request.cc @@ -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 diff --git a/src/csoap/soap_request.h b/src/csoap/soap_request.h new file mode 100644 index 0000000..9558619 --- /dev/null +++ b/src/csoap/soap_request.h @@ -0,0 +1,48 @@ +#ifndef CSOAP_SOAP_REQUEST_H_ +#define CSOAP_SOAP_REQUEST_H_ + +#include +#include + +#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 parameters_; +}; + +} // namespace csoap + +#endif // CSOAP_SOAP_REQUEST_H_ diff --git a/src/csoap/soap_request_envelope.cc b/src/csoap/soap_request_envelope.cc deleted file mode 100644 index 5a9c550..0000000 --- a/src/csoap/soap_request_envelope.cc +++ /dev/null @@ -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 diff --git a/src/csoap/soap_request_envelope.h b/src/csoap/soap_request_envelope.h deleted file mode 100644 index a6021f3..0000000 --- a/src/csoap/soap_request_envelope.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef CSOAP_SOAP_REQUEST_ENVELOPE_H_ -#define CSOAP_SOAP_REQUEST_ENVELOPE_H_ - -#include -#include - -#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 parameters_; -}; - -} // namespace csoap - -#endif // CSOAP_SOAP_REQUEST_ENVELOPE_H_ diff --git a/src/csoap/soap_response_parser.cc b/src/csoap/soap_response.cc similarity index 70% rename from src/csoap/soap_response_parser.cc rename to src/csoap/soap_response.cc index a0459fc..90c6606 100644 --- a/src/csoap/soap_response_parser.cc +++ b/src/csoap/soap_response.cc @@ -1,16 +1,16 @@ -#include "csoap/soap_response_parser.h" +#include "csoap/soap_response.h" #include "csoap/xml.h" namespace csoap { -SoapResponseParser::SoapResponseParser() { +SoapResponse::SoapResponse() { } -bool SoapResponseParser::Parse(const std::string& content, - const std::string& message_name, - const std::string& element_name, - std::string* element_value) { +bool SoapResponse::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()); diff --git a/src/csoap/soap_response_parser.h b/src/csoap/soap_response.h similarity index 52% rename from src/csoap/soap_response_parser.h rename to src/csoap/soap_response.h index c8cb7a5..027afaf 100644 --- a/src/csoap/soap_response_parser.h +++ b/src/csoap/soap_response.h @@ -1,18 +1,17 @@ -#ifndef CSOAP_RESPONSE_PARSER_H_ -#define CSOAP_RESPONSE_PARSER_H_ +#ifndef CSOAP_RESPONSE_H_ +#define CSOAP_RESPONSE_H_ #include namespace csoap { -class SoapResponseParser { +// SOAP response. +// Used to parse the SOAP response XML which is returned as the HTTP response +// body. +class SoapResponse { public: - SoapResponseParser(); - - // - // - // - // ... + SoapResponse(); + bool Parse(const std::string& content, const std::string& message_name, const std::string& element_name, @@ -29,4 +28,4 @@ private: } // namespace csoap -#endif // CSOAP_RESPONSE_PARSER_H_ +#endif // CSOAP_RESPONSE_H_ diff --git a/src/demo/calculator.cc b/src/demo/calculator.cc index e6bd173..4760fb9 100644 --- a/src/demo/calculator.cc +++ b/src/demo/calculator.cc @@ -11,13 +11,43 @@ Calculator::Calculator() { } 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[] = { - { "x", x }, - { "y", y } + { x_name, x }, + { y_name, y } }; std::string result_str; - if (!Call("add", parameters, 2, &result_str)) { + if (!Call(operation, parameters, 2, &result_str)) { return false; } @@ -30,64 +60,55 @@ bool Calculator::Add(float x, float y, float* result) { 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, const csoap::Parameter* parameters, size_t count, std::string* result) { - csoap::SoapRequestEnvelope req_envelope(operation); + csoap::SoapRequest soap_request(operation); - req_envelope.SetNamespace(csoap::SoapRequestEnvelope::kSoapEnvelopeNS, soap_envelope_ns_); - req_envelope.SetNamespace(csoap::SoapRequestEnvelope::kServiceNS, service_ns_); + soap_request.set_service_ns(service_ns_); for (size_t i = 0; i < count; ++i) { - req_envelope.AddParameter(parameters[i]); + soap_request.AddParameter(parameters[i]); } - std::string request_body; - req_envelope.ToXmlString(&request_body); + std::string http_request_body; + soap_request.ToXmlString(&http_request_body); csoap::HttpRequest http_request(csoap::kHttpV11); - http_request.set_uri("http://ws1.parasoft.com/glue/calculator"); - http_request.set_content_type("text/xml; charset=utf-8"); - http_request.set_content_length(request_body.size()); + http_request.set_url(url_); + http_request.set_content_length(http_request_body.size()); http_request.set_host(host_, port_); - http_request.set_keep_alive(true); - http_request.set_soap_action(operation); + csoap::HttpResponse http_response; + 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; } - const csoap::HttpResponse& http_response = http_client.response(); - - std::cout << http_response.status() << " " << http_response.reason() << std::endl; - - csoap::SoapResponseParser soap_response_parser; + csoap::SoapResponse soap_response; std::string rsp_message_name = operation + "Response"; std::string rsp_element_name = "Result"; - soap_response_parser.Parse(http_response.content(), + return soap_response.Parse(http_response.content(), rsp_message_name, rsp_element_name, result); - - //std::cout << "return:\n" << *result << std::endl; - - return true; } } // namespace demo diff --git a/src/demo/calculator.h b/src/demo/calculator.h index cb36009..0650cb4 100644 --- a/src/demo/calculator.h +++ b/src/demo/calculator.h @@ -15,21 +15,39 @@ public: 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: 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, const csoap::Parameter* parameters, size_t count, std::string* result); 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 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_; }; diff --git a/src/demo/main.cc b/src/demo/main.cc index 61e4004..18d9512 100644 --- a/src/demo/main.cc +++ b/src/demo/main.cc @@ -4,12 +4,31 @@ int main() { demo::Calculator calculator; + float x = 1.0; + float y = 2.0; float result = 0.0; - if (!calculator.Add(1.0, 2.0, &result)) { - std::cerr << "Failed to call web service." << std::endl; - } else { - std::cout << "Add result: " << std::showpoint << result << std::endl; + + if (calculator.Add(x, y, &result)) { + printf("add: %.1f\n", result); + } + + 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; } + +// Output: +// add: 3.0 +// subtract: -1.0 +// multiply: 2.0 +// divide: 0.5