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"
static const std::string kResultName = "Result";
static void PrintSeparateLine() {
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_.set_url("/book");
soap_client_.set_service_ns({ "ser", "http://www.example.com/book/" });
soap_client_.set_result_name("Result");
// Customize response XML format.
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);
}
bool BookClient::Call1(const std::string& operation,
webcc::SoapParameter&& parameter,
std::string* result_str) {
@ -104,7 +104,8 @@ bool BookClient::Call1(const std::string& operation,
bool BookClient::Call(const std::string& operation,
std::vector<webcc::SoapParameter>&& parameters,
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();
return false;
}

@ -95,7 +95,7 @@ bool BookService::CreateBook(const webcc::SoapRequest& soap_request,
std::string id = g_book_store.AddBook(book);
std::string response_xml = NewResultXml(0, "ok", "book", "id",
id.c_str());
id.c_str());
soap_response->set_result_moved(std::move(response_xml), true);

@ -5,6 +5,8 @@
// -----------------------------------------------------------------------------
static const std::string kResultName = "Result";
class CalcClient {
public:
CalcClient(const std::string& host, const std::string& port)
@ -15,7 +17,6 @@ class CalcClient {
soap_client_.set_service_ns({
"ser", "http://www.example.com/calculator/"
});
soap_client_.set_result_name("Result");
// Customize request XML format.
soap_client_.set_format_raw(false);
@ -54,7 +55,8 @@ class CalcClient {
};
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();
return false;
}

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

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

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

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

@ -97,7 +97,7 @@ void SoapAsyncClient::ResponseHandler(SoapResponseHandler soap_response_handler,
if (!soap_response.FromXml(http_response->content())) {
soap_response_handler("", kXmlError, false);
} 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 <utility> // for move()
#include "boost/algorithm/string.hpp"
#include "webcc/soap_request.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,
std::vector<SoapParameter>&& parameters,
std::string* result) {
SoapResponse::Parser parser) {
assert(service_ns_.IsValid());
assert(!url_.empty() && !host_.empty());
assert(!result_name_.empty());
error_ = kNoError;
fault_.reset();
SoapRequest soap_request;
@ -73,16 +75,35 @@ bool SoapClient::Request(const std::string& operation,
}
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())) {
error_ = kXmlError;
if (soap_response.fault()) {
fault_ = soap_response.fault();
error_ = kServerError;
} else {
error_ = kXmlError;
}
return false;
}
*result = soap_response.result_moved();
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

@ -7,6 +7,7 @@
#include "webcc/http_client.h"
#include "webcc/soap_globals.h"
#include "webcc/soap_parameter.h"
#include "webcc/soap_response.h"
namespace webcc {
@ -31,10 +32,6 @@ class SoapClient {
service_ns_ = service_ns;
}
void set_result_name(const std::string& result_name) {
result_name_ = result_name;
}
void set_format_raw(bool format_raw) {
format_raw_ = format_raw;
}
@ -45,14 +42,30 @@ class SoapClient {
bool Request(const std::string& operation,
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);
// 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 {
return http_client_.timed_out();
}
Error error() const { return error_; }
std::shared_ptr<SoapFault> fault() const { return fault_; }
private:
std::string host_;
std::string port_; // Leave this empty to use default 80.
@ -65,10 +78,6 @@ class SoapClient {
// Namespace for your web service.
SoapNamespace service_ns_;
// Response result XML node name.
// E.g., "Result".
std::string result_name_;
// Format request XML without any indentation or line breaks.
bool format_raw_;
@ -79,6 +88,8 @@ class SoapClient {
HttpClient http_client_;
Error error_;
std::shared_ptr<SoapFault> fault_;
};
} // namespace webcc

@ -1,5 +1,7 @@
#include "webcc/soap_globals.h"
#include <ostream>
namespace webcc {
const std::string kSoapAction = "SOAPAction";
@ -30,4 +32,13 @@ const SoapNamespace kSoapEnvNamespaceV12{
"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

@ -1,6 +1,7 @@
#ifndef WEBCC_SOAP_GLOBALS_H_
#define WEBCC_SOAP_GLOBALS_H_
#include <iosfwd>
#include <string>
namespace webcc {
@ -35,6 +36,14 @@ struct SoapNamespace {
extern const SoapNamespace kSoapEnvNamespaceV11;
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
#endif // WEBCC_SOAP_GLOBALS_H_

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

@ -7,6 +7,8 @@
namespace webcc {
void SoapResponse::ToXmlBody(pugi::xml_node xbody) {
assert(!result_name_.empty());
pugi::xml_node xop = soap_xml::AddChild(xbody, service_ns_.name,
operation_ + "Response");
soap_xml::AddNSAttr(xop, service_ns_.name, service_ns_.url);
@ -15,28 +17,62 @@ void SoapResponse::ToXmlBody(pugi::xml_node xbody) {
result_name_);
// 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)
.set_value(result_.c_str());
}
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;
}
soap_xml::SplitName(xresponse, &service_ns_.name, nullptr);
service_ns_.url = soap_xml::GetNSAttr(xresponse, service_ns_.name);
pugi::xml_node xresponse = xbody.first_child();
if (xresponse) {
soap_xml::SplitName(xresponse, &service_ns_.name, nullptr);
service_ns_.url = soap_xml::GetNSAttr(xresponse, service_ns_.name);
pugi::xml_node xresult = soap_xml::GetChildNoNS(xresponse, result_name_);
if (xresult) {
// The value of the first child node of type PCDATA/CDATA.
// xresult.text().get/as_string() also works.
result_ = xresult.child_value();
return true;
// Call result parser on each child of the response node.
if (parser_) {
pugi::xml_node xchild = xresponse.first_child();
while (xchild) {
if (!parser_(xchild)) {
break;
}
xchild = xchild.next_sibling();
}
}
return false;
return true;
}
} // namespace webcc

@ -1,10 +1,13 @@
#ifndef WEBCC_SOAP_RESPONSE_H_
#define WEBCC_SOAP_RESPONSE_H_
#include <functional>
#include <memory>
#include <string>
#include <utility> // for move()
#include "webcc/soap_message.h"
#include "webcc/soap_xml.h"
namespace webcc {
@ -12,6 +15,39 @@ class SoapResponse : public SoapMessage {
public:
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".
// Really depend on the service.
// Most services use a general name "Result".
@ -19,35 +55,37 @@ class SoapResponse : public SoapMessage {
result_name_ = result_name;
}
// Server only.
void set_result(const std::string& result, bool is_cdata) {
result_ = result;
is_cdata_ = is_cdata;
}
// Server only.
void set_result_moved(std::string&& result, bool is_cdata) {
result_ = std::move(result);
is_cdata_ = is_cdata;
}
std::string result_moved() {
return std::move(result_);
}
protected:
void ToXmlBody(pugi::xml_node xbody) override;
bool FromXmlBody(pugi::xml_node xbody) override;
private:
// NOTE:
// Multiple results might be necessary. But for most cases, single result
// should be enough, because an API normally returns only one value.
// Fault element if any.
std::shared_ptr<SoapFault> fault_;
// Response result parser.
Parser parser_;
// Result XML node name.
// Used to parse the response XML from client side.
// TODO
std::string result_name_;
// Result value.
// TODO
std::string result_;
// CDATA result.

@ -7,7 +7,7 @@ void SplitName(const pugi::xml_node& xnode, std::string* prefix,
std::string* 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 (prefix != nullptr) {
@ -38,6 +38,19 @@ std::string GetNameNoPrefix(const pugi::xml_node& xnode) {
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,
const std::string& ns, const std::string& name) {
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();
// Remove NS prefix.
size_t pos = child_name.find(':');
std::size_t pos = child_name.find(':');
if (pos != std::string::npos) {
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.
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.
// E.g., AppendChild(xnode, "soapenv", "Envelope") will append a child with
// name "soapenv:Envelope".

Loading…
Cancel
Save