Rework the parsing of soap response.

master
Chunting Gu 7 years ago
parent f5fe4e67f7
commit 76417c3703

@ -6,6 +6,8 @@
#include "example/common/book_xml.h" #include "example/common/book_xml.h"
static const std::string kResultName = "Result";
static void PrintSeparateLine() { static void PrintSeparateLine() {
std::cout << "--------------------------------"; std::cout << "--------------------------------";
std::cout << "--------------------------------"; std::cout << "--------------------------------";
@ -16,7 +18,6 @@ BookClient::BookClient(const std::string& host, const std::string& port)
: soap_client_(host, port), code_(0) { : soap_client_(host, port), code_(0) {
soap_client_.set_url("/book"); soap_client_.set_url("/book");
soap_client_.set_service_ns({ "ser", "http://www.example.com/book/" }); soap_client_.set_service_ns({ "ser", "http://www.example.com/book/" });
soap_client_.set_result_name("Result");
// Customize response XML format. // Customize response XML format.
soap_client_.set_format_raw(false); soap_client_.set_format_raw(false);
@ -91,7 +92,6 @@ bool BookClient::Call0(const std::string& operation, std::string* result_str) {
return Call(operation, {}, result_str); return Call(operation, {}, result_str);
} }
bool BookClient::Call1(const std::string& operation, bool BookClient::Call1(const std::string& operation,
webcc::SoapParameter&& parameter, webcc::SoapParameter&& parameter,
std::string* result_str) { std::string* result_str) {
@ -104,7 +104,8 @@ bool BookClient::Call1(const std::string& operation,
bool BookClient::Call(const std::string& operation, bool BookClient::Call(const std::string& operation,
std::vector<webcc::SoapParameter>&& parameters, std::vector<webcc::SoapParameter>&& parameters,
std::string* result_str) { std::string* result_str) {
if (!soap_client_.Request(operation, std::move(parameters), result_str)) { if (!soap_client_.Request(operation, std::move(parameters), kResultName,
result_str)) {
PrintError(); PrintError();
return false; return false;
} }

@ -5,6 +5,8 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
static const std::string kResultName = "Result";
class CalcClient { class CalcClient {
public: public:
CalcClient(const std::string& host, const std::string& port) CalcClient(const std::string& host, const std::string& port)
@ -15,7 +17,6 @@ class CalcClient {
soap_client_.set_service_ns({ soap_client_.set_service_ns({
"ser", "http://www.example.com/calculator/" "ser", "http://www.example.com/calculator/"
}); });
soap_client_.set_result_name("Result");
// Customize request XML format. // Customize request XML format.
soap_client_.set_format_raw(false); soap_client_.set_format_raw(false);
@ -54,7 +55,8 @@ class CalcClient {
}; };
std::string result_str; std::string result_str;
if (!soap_client_.Request(operation, std::move(parameters), &result_str)) { if (!soap_client_.Request(operation, std::move(parameters), kResultName,
&result_str)) {
PrintError(); PrintError();
return false; return false;
} }

@ -5,6 +5,8 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
static const std::string kResultName = "Result";
class CalcClient { class CalcClient {
public: public:
// NOTE: Parasoft's calculator service uses SOAP V1.1. // NOTE: Parasoft's calculator service uses SOAP V1.1.
@ -16,7 +18,6 @@ class CalcClient {
soap_client_.set_service_ns({ soap_client_.set_service_ns({
"cal", "http://www.parasoft.com/wsdl/calculator/" "cal", "http://www.parasoft.com/wsdl/calculator/"
}); });
soap_client_.set_result_name("Result");
// Customize request XML format. // Customize request XML format.
soap_client_.set_format_raw(false); soap_client_.set_format_raw(false);
@ -39,6 +40,11 @@ class CalcClient {
return Calc("divide", "numerator", "denominator", x, y, result); return Calc("divide", "numerator", "denominator", x, y, result);
} }
// Only for testing purpose.
bool Unknown(double x, double y, double* result) {
return Calc("unknown", "x", "y", x, y, result);
}
private: private:
bool Calc(const std::string& operation, bool Calc(const std::string& operation,
const std::string& x_name, const std::string& y_name, const std::string& x_name, const std::string& y_name,
@ -50,7 +56,8 @@ class CalcClient {
}; };
std::string result_str; std::string result_str;
if (!soap_client_.Request(operation, std::move(parameters), &result_str)) { if (!soap_client_.Request(operation, std::move(parameters), kResultName,
&result_str)) {
PrintError(); PrintError();
return false; return false;
} }
@ -69,7 +76,12 @@ class CalcClient {
if (soap_client_.timed_out()) { if (soap_client_.timed_out()) {
std::cout << " (timed out)"; std::cout << " (timed out)";
} }
std::cout << std::endl; std::cout << std::endl;
if (soap_client_.fault()) {
std::cout << *soap_client_.fault() << std::endl;
}
} }
webcc::SoapClient soap_client_; webcc::SoapClient soap_client_;
@ -102,5 +114,7 @@ int main() {
printf("divide: %.1f\n", result); printf("divide: %.1f\n", result);
} }
calc.Unknown(x, y, &result);
return 0; return 0;
} }

@ -38,6 +38,8 @@ const char* DescribeError(Error error) {
return "Socket write error"; return "Socket write error";
case kHttpError: case kHttpError:
return "HTTP error"; return "HTTP error";
case kServerError:
return "Server error";
case kXmlError: case kXmlError:
return "XML error"; return "XML error";
default: default:

@ -70,21 +70,31 @@ struct HttpStatus {
kNotModified = 304, kNotModified = 304,
kBadRequest = 400, kBadRequest = 400,
kNotFound = 404, kNotFound = 404,
InternalServerError = 500, kInternalServerError = 500,
kNotImplemented = 501, kNotImplemented = 501,
kServiceUnavailable = 503, kServiceUnavailable = 503,
}; };
}; };
// Error codes. // Client side error codes.
enum Error { enum Error {
kNoError = 0, kNoError = 0, // i.e., OK
kHostResolveError, kHostResolveError,
kEndpointConnectError, kEndpointConnectError,
kHandshakeError, kHandshakeError, // HTTPS handshake
kSocketReadError, kSocketReadError,
kSocketWriteError, kSocketWriteError,
// HTTP error.
// E.g., failed to parse HTTP response (invalid content length, etc.).
kHttpError, kHttpError,
// Server error.
// E.g., HTTP status 500 + SOAP Fault element.
kServerError,
// XML parsing error.
kXmlError, kXmlError,
}; };

@ -39,7 +39,7 @@ const std::string& ToString(int status) {
case HttpStatus::kNotFound: case HttpStatus::kNotFound:
return NOT_FOUND; return NOT_FOUND;
case HttpStatus::InternalServerError: case HttpStatus::kInternalServerError:
return INTERNAL_SERVER_ERROR; return INTERNAL_SERVER_ERROR;
case HttpStatus::kNotImplemented: case HttpStatus::kNotImplemented:

@ -97,7 +97,7 @@ void SoapAsyncClient::ResponseHandler(SoapResponseHandler soap_response_handler,
if (!soap_response.FromXml(http_response->content())) { if (!soap_response.FromXml(http_response->content())) {
soap_response_handler("", kXmlError, false); soap_response_handler("", kXmlError, false);
} else { } else {
soap_response_handler(soap_response.result_moved(), kNoError, false); //soap_response_handler(soap_response.result_moved(), kNoError, false);
} }
} }
} }

@ -3,6 +3,8 @@
#include <cassert> #include <cassert>
#include <utility> // for move() #include <utility> // for move()
#include "boost/algorithm/string.hpp"
#include "webcc/soap_request.h" #include "webcc/soap_request.h"
#include "webcc/soap_response.h" #include "webcc/soap_response.h"
@ -24,12 +26,12 @@ SoapClient::SoapClient(const std::string& host, const std::string& port,
bool SoapClient::Request(const std::string& operation, bool SoapClient::Request(const std::string& operation,
std::vector<SoapParameter>&& parameters, std::vector<SoapParameter>&& parameters,
std::string* result) { SoapResponse::Parser parser) {
assert(service_ns_.IsValid()); assert(service_ns_.IsValid());
assert(!url_.empty() && !host_.empty()); assert(!url_.empty() && !host_.empty());
assert(!result_name_.empty());
error_ = kNoError; error_ = kNoError;
fault_.reset();
SoapRequest soap_request; SoapRequest soap_request;
@ -73,16 +75,35 @@ bool SoapClient::Request(const std::string& operation,
} }
SoapResponse soap_response; SoapResponse soap_response;
soap_response.set_result_name(result_name_); soap_response.set_operation(operation);
soap_response.set_parser(parser);
if (!soap_response.FromXml(http_client_.response()->content())) { if (!soap_response.FromXml(http_client_.response()->content())) {
if (soap_response.fault()) {
fault_ = soap_response.fault();
error_ = kServerError;
} else {
error_ = kXmlError; error_ = kXmlError;
return false;
} }
*result = soap_response.result_moved(); return false;
}
return true; return true;
} }
bool SoapClient::Request(const std::string& operation,
std::vector<SoapParameter>&& parameters,
const std::string& result_name,
std::string* result) {
auto parser = [result, &result_name](pugi::xml_node xnode) {
if (boost::iequals(soap_xml::GetNameNoPrefix(xnode), result_name)) {
soap_xml::GetText(xnode, result);
}
return false; // Stop next call.
};
return Request(operation, std::move(parameters), parser);
}
} // namespace webcc } // namespace webcc

@ -7,6 +7,7 @@
#include "webcc/http_client.h" #include "webcc/http_client.h"
#include "webcc/soap_globals.h" #include "webcc/soap_globals.h"
#include "webcc/soap_parameter.h" #include "webcc/soap_parameter.h"
#include "webcc/soap_response.h"
namespace webcc { namespace webcc {
@ -31,10 +32,6 @@ class SoapClient {
service_ns_ = service_ns; service_ns_ = service_ns;
} }
void set_result_name(const std::string& result_name) {
result_name_ = result_name;
}
void set_format_raw(bool format_raw) { void set_format_raw(bool format_raw) {
format_raw_ = format_raw; format_raw_ = format_raw;
} }
@ -45,14 +42,30 @@ class SoapClient {
bool Request(const std::string& operation, bool Request(const std::string& operation,
std::vector<SoapParameter>&& parameters, std::vector<SoapParameter>&& parameters,
SoapResponse::Parser parser);
// Shortcut for responses with single result node.
// The name of the single result node is specified by |result_name|.
// The text of the result node will be set to |result|.
bool Request(const std::string& operation,
std::vector<SoapParameter>&& parameters,
const std::string& result_name,
std::string* result); std::string* result);
// HTTP status code (200, 500, etc.) in the response.
int http_status() const {
assert(http_client_.response());
return http_client_.response()->status();
}
bool timed_out() const { bool timed_out() const {
return http_client_.timed_out(); return http_client_.timed_out();
} }
Error error() const { return error_; } Error error() const { return error_; }
std::shared_ptr<SoapFault> fault() const { return fault_; }
private: private:
std::string host_; std::string host_;
std::string port_; // Leave this empty to use default 80. std::string port_; // Leave this empty to use default 80.
@ -65,10 +78,6 @@ class SoapClient {
// Namespace for your web service. // Namespace for your web service.
SoapNamespace service_ns_; SoapNamespace service_ns_;
// Response result XML node name.
// E.g., "Result".
std::string result_name_;
// Format request XML without any indentation or line breaks. // Format request XML without any indentation or line breaks.
bool format_raw_; bool format_raw_;
@ -79,6 +88,8 @@ class SoapClient {
HttpClient http_client_; HttpClient http_client_;
Error error_; Error error_;
std::shared_ptr<SoapFault> fault_;
}; };
} // namespace webcc } // namespace webcc

@ -1,5 +1,7 @@
#include "webcc/soap_globals.h" #include "webcc/soap_globals.h"
#include <ostream>
namespace webcc { namespace webcc {
const std::string kSoapAction = "SOAPAction"; const std::string kSoapAction = "SOAPAction";
@ -30,4 +32,13 @@ const SoapNamespace kSoapEnvNamespaceV12{
"http://www.w3.org/2003/05/soap-envelope" "http://www.w3.org/2003/05/soap-envelope"
}; };
std::ostream& operator<<(std::ostream& os, const SoapFault& fault) {
os << "Fault: {" << std::endl
<< " faultcode: " << fault.faultcode << std::endl
<< " faultstring: " << fault.faultstring << std::endl
<< " detail: " << fault.detail << std::endl
<< "}";
return os;
}
} // namespace webcc } // namespace webcc

@ -1,6 +1,7 @@
#ifndef WEBCC_SOAP_GLOBALS_H_ #ifndef WEBCC_SOAP_GLOBALS_H_
#define WEBCC_SOAP_GLOBALS_H_ #define WEBCC_SOAP_GLOBALS_H_
#include <iosfwd>
#include <string> #include <string>
namespace webcc { namespace webcc {
@ -35,6 +36,14 @@ struct SoapNamespace {
extern const SoapNamespace kSoapEnvNamespaceV11; extern const SoapNamespace kSoapEnvNamespaceV11;
extern const SoapNamespace kSoapEnvNamespaceV12; extern const SoapNamespace kSoapEnvNamespaceV12;
struct SoapFault {
std::string faultcode;
std::string faultstring;
std::string detail;
};
std::ostream& operator<<(std::ostream& os, const SoapFault& fault);
} // namespace webcc } // namespace webcc
#endif // WEBCC_SOAP_GLOBALS_H_ #endif // WEBCC_SOAP_GLOBALS_H_

@ -31,11 +31,11 @@ class SoapMessage {
operation_ = operation; operation_ = operation;
} }
// Convert to SOAP request XML. // Convert to SOAP XML.
void ToXml(bool format_raw, const std::string& indent, void ToXml(bool format_raw, const std::string& indent,
std::string* xml_string); std::string* xml_string);
// Parse from SOAP request XML. // Parse from SOAP XML.
bool FromXml(const std::string& xml_string); bool FromXml(const std::string& xml_string);
protected: protected:

@ -7,6 +7,8 @@
namespace webcc { namespace webcc {
void SoapResponse::ToXmlBody(pugi::xml_node xbody) { void SoapResponse::ToXmlBody(pugi::xml_node xbody) {
assert(!result_name_.empty());
pugi::xml_node xop = soap_xml::AddChild(xbody, service_ns_.name, pugi::xml_node xop = soap_xml::AddChild(xbody, service_ns_.name,
operation_ + "Response"); operation_ + "Response");
soap_xml::AddNSAttr(xop, service_ns_.name, service_ns_.url); soap_xml::AddNSAttr(xop, service_ns_.name, service_ns_.url);
@ -15,28 +17,62 @@ void SoapResponse::ToXmlBody(pugi::xml_node xbody) {
result_name_); result_name_);
// xresult.text().set() also works for PCDATA. // xresult.text().set() also works for PCDATA.
// TODO: Add SetText() to soap_xml.h|cpp.
xresult.append_child(is_cdata_ ? pugi::node_cdata : pugi::node_pcdata) xresult.append_child(is_cdata_ ? pugi::node_cdata : pugi::node_pcdata)
.set_value(result_.c_str()); .set_value(result_.c_str());
} }
bool SoapResponse::FromXmlBody(pugi::xml_node xbody) { bool SoapResponse::FromXmlBody(pugi::xml_node xbody) {
assert(!result_name_.empty()); // Check Fault element.
pugi::xml_node xfault = soap_xml::GetChildNoNS(xbody, "Fault");
// TODO: service_ns_.url
if (xfault) {
fault_.reset(new SoapFault);
pugi::xml_node xfaultcode = soap_xml::GetChildNoNS(xfault, "faultcode");
pugi::xml_node xfaultstring = soap_xml::GetChildNoNS(xfault, "faultstring");
pugi::xml_node xdetail = soap_xml::GetChildNoNS(xfault, "detail");
if (xfaultcode) {
fault_->faultcode = xfaultcode.text().as_string();
}
if (xfaultstring) {
fault_->faultstring = xfaultstring.text().as_string();
}
if (xdetail) {
fault_->detail = xdetail.text().as_string();
}
return false;
}
// Check Response element.
pugi::xml_node xresponse = soap_xml::GetChildNoNS(xbody,
operation_ + "Response");
if (!xresponse) {
return false;
}
pugi::xml_node xresponse = xbody.first_child();
if (xresponse) {
soap_xml::SplitName(xresponse, &service_ns_.name, nullptr); soap_xml::SplitName(xresponse, &service_ns_.name, nullptr);
service_ns_.url = soap_xml::GetNSAttr(xresponse, service_ns_.name); service_ns_.url = soap_xml::GetNSAttr(xresponse, service_ns_.name);
pugi::xml_node xresult = soap_xml::GetChildNoNS(xresponse, result_name_); // Call result parser on each child of the response node.
if (xresult) { if (parser_) {
// The value of the first child node of type PCDATA/CDATA. pugi::xml_node xchild = xresponse.first_child();
// xresult.text().get/as_string() also works. while (xchild) {
result_ = xresult.child_value(); if (!parser_(xchild)) {
return true; break;
}
xchild = xchild.next_sibling();
} }
} }
return false; return true;
} }
} // namespace webcc } // namespace webcc

@ -1,10 +1,13 @@
#ifndef WEBCC_SOAP_RESPONSE_H_ #ifndef WEBCC_SOAP_RESPONSE_H_
#define WEBCC_SOAP_RESPONSE_H_ #define WEBCC_SOAP_RESPONSE_H_
#include <functional>
#include <memory>
#include <string> #include <string>
#include <utility> // for move() #include <utility> // for move()
#include "webcc/soap_message.h" #include "webcc/soap_message.h"
#include "webcc/soap_xml.h"
namespace webcc { namespace webcc {
@ -12,6 +15,39 @@ class SoapResponse : public SoapMessage {
public: public:
SoapResponse() : is_cdata_(false) {} SoapResponse() : is_cdata_(false) {}
// Response result parser.
// Called on each child of the response node.
// Example:
// - SOAP action: xxx
// - Given SOAP response:
// <soap:Body>
// <n:xxxResponse xmlns:n="...">
// <n:aaa>Blaah</aaa>
// <n:bbb>Blaah</bbb>
// <n:ccc>Blaah</ccc>
// </n:xxxResponse>
// <soap:Body>
// The parser will be called in the following sequence:
// - parser(xaaa); // return true
// - parser(xbbb); // return true
// - parser(xccc);
// If any of the parser returns false, the call sequence will be stopped:
// - parser(xaaa); // return false
// <stopped>
// Then you can get the expected result by parsing the node one by one.
// When you implement the parser, you normally need to check the node name,
// the following helper can extract the name without any namespace prefix:
// webcc::soap_xml::GetNameNoPrefix(xnode);
typedef std::function<bool(pugi::xml_node)> Parser;
void set_parser(Parser parser) {
parser_ = parser;
}
std::shared_ptr<SoapFault> fault() const {
return fault_;
}
// Could be "Price" for an operation/method like "GetXyzPrice". // Could be "Price" for an operation/method like "GetXyzPrice".
// Really depend on the service. // Really depend on the service.
// Most services use a general name "Result". // Most services use a general name "Result".
@ -19,35 +55,37 @@ class SoapResponse : public SoapMessage {
result_name_ = result_name; result_name_ = result_name;
} }
// Server only.
void set_result(const std::string& result, bool is_cdata) { void set_result(const std::string& result, bool is_cdata) {
result_ = result; result_ = result;
is_cdata_ = is_cdata; is_cdata_ = is_cdata;
} }
// Server only.
void set_result_moved(std::string&& result, bool is_cdata) { void set_result_moved(std::string&& result, bool is_cdata) {
result_ = std::move(result); result_ = std::move(result);
is_cdata_ = is_cdata; is_cdata_ = is_cdata;
} }
std::string result_moved() {
return std::move(result_);
}
protected: protected:
void ToXmlBody(pugi::xml_node xbody) override; void ToXmlBody(pugi::xml_node xbody) override;
bool FromXmlBody(pugi::xml_node xbody) override; bool FromXmlBody(pugi::xml_node xbody) override;
private: private:
// NOTE: // Fault element if any.
// Multiple results might be necessary. But for most cases, single result std::shared_ptr<SoapFault> fault_;
// should be enough, because an API normally returns only one value.
// Response result parser.
Parser parser_;
// Result XML node name. // Result XML node name.
// Used to parse the response XML from client side. // Used to parse the response XML from client side.
// TODO
std::string result_name_; std::string result_name_;
// Result value. // Result value.
// TODO
std::string result_; std::string result_;
// CDATA result. // CDATA result.

@ -7,7 +7,7 @@ void SplitName(const pugi::xml_node& xnode, std::string* prefix,
std::string* name) { std::string* name) {
std::string full_name = xnode.name(); std::string full_name = xnode.name();
size_t pos = full_name.find(':'); std::size_t pos = full_name.find(':');
if (pos != std::string::npos) { if (pos != std::string::npos) {
if (prefix != nullptr) { if (prefix != nullptr) {
@ -38,6 +38,19 @@ std::string GetNameNoPrefix(const pugi::xml_node& xnode) {
return name; return name;
} }
// NOTE:
// The following 3 ways all work for PCDATA and CDATA:
// - xnode.text().get()
// - xnode.text().as_string()
// - xnode.child_value()
std::string GetText(const pugi::xml_node& xnode) {
return xnode.child_value();
}
void GetText(const pugi::xml_node& xnode, std::string* text) {
*text = xnode.child_value();
}
pugi::xml_node AddChild(pugi::xml_node xnode, pugi::xml_node AddChild(pugi::xml_node xnode,
const std::string& ns, const std::string& name) { const std::string& ns, const std::string& name) {
return xnode.append_child((ns + ":" + name).c_str()); return xnode.append_child((ns + ":" + name).c_str());
@ -55,7 +68,7 @@ pugi::xml_node GetChildNoNS(const pugi::xml_node& xnode,
std::string child_name = xchild.name(); std::string child_name = xchild.name();
// Remove NS prefix. // Remove NS prefix.
size_t pos = child_name.find(':'); std::size_t pos = child_name.find(':');
if (pos != std::string::npos) { if (pos != std::string::npos) {
child_name = child_name.substr(pos + 1); child_name = child_name.substr(pos + 1);
} }

@ -23,6 +23,15 @@ std::string GetPrefix(const pugi::xml_node& xnode);
// Get the node name without namespace prefix. // Get the node name without namespace prefix.
std::string GetNameNoPrefix(const pugi::xml_node& xnode); std::string GetNameNoPrefix(const pugi::xml_node& xnode);
// Get node text (applicable for both PCDATA and CDATA).
// E.g., given the following node:
// <Name>Chunting Gu</Name>
// GetText returns "Chunting Gu".
std::string GetText(const pugi::xml_node& xnode);
// Output parameter version GetText.
void GetText(const pugi::xml_node& xnode, std::string* text);
// Add a child with the given name which is prefixed by a namespace. // Add a child with the given name which is prefixed by a namespace.
// E.g., AppendChild(xnode, "soapenv", "Envelope") will append a child with // E.g., AppendChild(xnode, "soapenv", "Envelope") will append a child with
// name "soapenv:Envelope". // name "soapenv:Envelope".

Loading…
Cancel
Save