From b02ea902c9e9e808feaf980d51a4b44dd492396e Mon Sep 17 00:00:00 2001 From: Chunting Gu Date: Thu, 27 Sep 2018 17:05:29 +0800 Subject: [PATCH] Improve the response result composition. --- example/soap_book_client/book_client.cc | 4 +- example/soap_book_server/book_service.cc | 13 +-- example/soap_calc_server/calc_service.cc | 4 +- webcc/soap_async_client.cc | 2 +- webcc/soap_response.cc | 39 ++++---- webcc/soap_response.h | 121 ++++++++++++++++------- webcc/soap_xml.cc | 5 + webcc/soap_xml.h | 3 + 8 files changed, 126 insertions(+), 65 deletions(-) diff --git a/example/soap_book_client/book_client.cc b/example/soap_book_client/book_client.cc index 99bfb86..716a022 100644 --- a/example/soap_book_client/book_client.cc +++ b/example/soap_book_client/book_client.cc @@ -6,7 +6,7 @@ #include "example/common/book_xml.h" -static const std::string kResultName = "Result"; +static const std::string kResult = "Result"; static void PrintSeparateLine() { std::cout << "--------------------------------"; @@ -104,7 +104,7 @@ bool BookClient::Call1(const std::string& operation, bool BookClient::Call(const std::string& operation, std::vector&& parameters, std::string* result_str) { - if (!soap_client_.Request(operation, std::move(parameters), kResultName, + if (!soap_client_.Request(operation, std::move(parameters), kResult, result_str)) { PrintError(); return false; diff --git a/example/soap_book_server/book_service.cc b/example/soap_book_server/book_service.cc index b8f7dea..e059ae9 100644 --- a/example/soap_book_server/book_service.cc +++ b/example/soap_book_server/book_service.cc @@ -15,6 +15,8 @@ static BookStore g_book_store; +static const std::string kResult = "Result"; + // ----------------------------------------------------------------------------- bool BookService::Handle(const webcc::SoapRequest& soap_request, @@ -27,7 +29,6 @@ bool BookService::Handle(const webcc::SoapRequest& soap_request, }); soap_response->set_operation(operation); - soap_response->set_result_name("Result"); if (operation == "CreateBook") { return CreateBook(soap_request, soap_response); @@ -97,7 +98,7 @@ bool BookService::CreateBook(const webcc::SoapRequest& soap_request, std::string response_xml = NewResultXml(0, "ok", "book", "id", id.c_str()); - soap_response->set_result_moved(std::move(response_xml), true); + soap_response->set_simple_result(kResult, std::move(response_xml), true); return true; } @@ -137,7 +138,7 @@ bool BookService::GetBook(const webcc::SoapRequest& soap_request, const Book& book = g_book_store.GetBook(id); - soap_response->set_result_moved(NewResultXml(0, "ok", book), true); + soap_response->set_simple_result(kResult, NewResultXml(0, "ok", book), true); return true; } @@ -176,7 +177,7 @@ bool BookService::ListBooks(const webcc::SoapRequest& soap_request, const std::list& books = g_book_store.books(); - soap_response->set_result_moved(NewResultXml(0, "ok", books), true); + soap_response->set_simple_result(kResult, NewResultXml(0, "ok", books), true); return true; } @@ -210,9 +211,9 @@ bool BookService::DeleteBook(const webcc::SoapRequest& soap_request, const std::string& id = soap_request.GetParameter("id"); if (g_book_store.DeleteBook(id)) { - soap_response->set_result_moved(NewResultXml(0, "ok"), true); + soap_response->set_simple_result(kResult, NewResultXml(0, "ok"), true); } else { - soap_response->set_result_moved(NewResultXml(1, "error"), true); + soap_response->set_simple_result(kResult, NewResultXml(1, "error"), true); } return true; diff --git a/example/soap_calc_server/calc_service.cc b/example/soap_calc_server/calc_service.cc index 2a35e4f..766cc27 100644 --- a/example/soap_calc_server/calc_service.cc +++ b/example/soap_calc_server/calc_service.cc @@ -58,9 +58,7 @@ bool CalcService::Handle(const webcc::SoapRequest& soap_request, }); soap_response->set_operation(soap_request.operation()); - - soap_response->set_result_name("Result"); - soap_response->set_result_moved(std::to_string(result), false); + soap_response->set_simple_result("Result", std::to_string(result), false); return true; } diff --git a/webcc/soap_async_client.cc b/webcc/soap_async_client.cc index a142b7c..df22969 100644 --- a/webcc/soap_async_client.cc +++ b/webcc/soap_async_client.cc @@ -92,7 +92,7 @@ void SoapAsyncClient::ResponseHandler(SoapResponseHandler soap_response_handler, soap_response_handler("", error, timed_out); } else { SoapResponse soap_response; - soap_response.set_result_name(result_name_); + //soap_response.set_result_name(result_name_); if (!soap_response.FromXml(http_response->content())) { soap_response_handler("", kXmlError, false); diff --git a/webcc/soap_response.cc b/webcc/soap_response.cc index fb0dece..3ddd915 100644 --- a/webcc/soap_response.cc +++ b/webcc/soap_response.cc @@ -7,22 +7,23 @@ 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); - - pugi::xml_node xresult = soap_xml::AddChild(xop, service_ns_.name, - 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()); + pugi::xml_node xresponse = soap_xml::AddChild(xbody, service_ns_.name, + operation_ + "Response"); + soap_xml::AddNSAttr(xresponse, service_ns_.name, service_ns_.url); + + if (simple_result_) { + pugi::xml_node xresult = soap_xml::AddChild(xresponse, service_ns_.name, + simple_result_->name); + soap_xml::SetText(xresult, simple_result_->value, simple_result_->is_cdata); + } else { + assert(composer_); + (*composer_)(xresponse); + } } bool SoapResponse::FromXmlBody(pugi::xml_node xbody) { + assert(parser_); + // Check Fault element. pugi::xml_node xfault = soap_xml::GetChildNoNS(xbody, "Fault"); @@ -62,14 +63,12 @@ bool SoapResponse::FromXmlBody(pugi::xml_node xbody) { service_ns_.url = soap_xml::GetNSAttr(xresponse, service_ns_.name); // 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(); + pugi::xml_node xchild = xresponse.first_child(); + while (xchild) { + if (!parser_(xchild)) { + break; } + xchild = xchild.next_sibling(); } return true; diff --git a/webcc/soap_response.h b/webcc/soap_response.h index 07b8ad1..a679027 100644 --- a/webcc/soap_response.h +++ b/webcc/soap_response.h @@ -13,8 +13,6 @@ namespace webcc { class SoapResponse : public SoapMessage { public: - SoapResponse() : is_cdata_(false) {} - // Response result parser. // Called on each child of the response node. // Example: @@ -28,45 +26,107 @@ class SoapResponse : public SoapMessage { // // // The parser will be called in the following sequence: - // - parser(xaaa); // return true - // - parser(xbbb); // return true - // - parser(xccc); + // - parser(aaa); // return true + // - parser(bbb); // return true + // - parser(ccc); // If any of the parser returns false, the call sequence will be stopped: - // - parser(xaaa); // return false + // - parser(aaa); // return false // // 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); + // webcc::soap_xml::GetNameNoPrefix(node); typedef std::function Parser; + // Response result composer. + // Called on the response node. + // Example: + // - SOAP action: xxx + // - Given SOAP response: + // + // + // + // The composer will be called as: + // - composer(xxxResponse); + // The composer then add proper children to xxxResponse as the result. + class Composer { + public: + void operator()(pugi::xml_node xresponse) { + Compose(xresponse); + } + private: + virtual void Compose(pugi::xml_node xresponse) = 0; + }; + + typedef std::shared_ptr ComposerPtr; + + // Simple result means there's only one child of the response node. + // Normally, the value of the result is a string (could be CDATA to embed an + // XML string). + // Given SOAP action "xxx", the response could be: + // - Plain text as result value: + // + // + // 2.0 + // + // + // - CDATA as result value: + // + // + // + // + // + // + // 1 + // + // + // ]]> + // + // + // + struct SimpleResult { + std::string name; // Result XML node name. + std::string value; // Result value. + bool is_cdata; // CDATA result. + }; + + typedef std::shared_ptr SimpleResultPtr; + + SoapResponse() : parser_(nullptr) {} + + // Set the parser to parse the result. + // Client only. void set_parser(Parser parser) { parser_ = parser; } - std::shared_ptr fault() const { - return fault_; + // Set the composer to compose the result. + // Composer will be ignored if the simple result is provided. + void set_composer(ComposerPtr composer) { + composer_ = composer; } - // Could be "Price" for an operation/method like "GetXyzPrice". - // Really depend on the service. - // Most services use a general name "Result". - void set_result_name(const std::string& result_name) { - result_name_ = result_name; + // Set to compose from simple result. + void set_simple_result(SimpleResultPtr simple_result) { + simple_result_ = simple_result; } - // Server only. - void set_result(const std::string& result, bool is_cdata) { - result_ = result; - is_cdata_ = is_cdata; + // Set to compose from simple result (a shortcut). + void set_simple_result(const std::string& name, + std::string&& value, + bool is_cdata) { + simple_result_.reset(new SimpleResult{ + name, std::move(value), is_cdata + }); } - // Server only. - void set_result_moved(std::string&& result, bool is_cdata) { - result_ = std::move(result); - is_cdata_ = is_cdata; + std::shared_ptr fault() const { + return fault_; } + // TODO: Set fault from server. + protected: void ToXmlBody(pugi::xml_node xbody) override; @@ -76,20 +136,15 @@ class SoapResponse : public SoapMessage { // Fault element if any. std::shared_ptr fault_; - // Response result parser. + // Result parser (for client). 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_; + // Result composer (for server). + // Ignored if |simple_result_| is provided. + ComposerPtr composer_; - // CDATA result. - bool is_cdata_; + // Simple result (for server). + SimpleResultPtr simple_result_; }; } // namespace webcc diff --git a/webcc/soap_xml.cc b/webcc/soap_xml.cc index d1a940b..214b2bc 100644 --- a/webcc/soap_xml.cc +++ b/webcc/soap_xml.cc @@ -51,6 +51,11 @@ void GetText(const pugi::xml_node& xnode, std::string* text) { *text = xnode.child_value(); } +void SetText(pugi::xml_node xnode, const std::string& text, bool is_cdata) { + xnode.append_child(is_cdata ? pugi::node_cdata : pugi::node_pcdata) + .set_value(text.c_str()); +} + pugi::xml_node AddChild(pugi::xml_node xnode, const std::string& ns, const std::string& name) { return xnode.append_child((ns + ":" + name).c_str()); diff --git a/webcc/soap_xml.h b/webcc/soap_xml.h index 3118ed0..b47fcfb 100644 --- a/webcc/soap_xml.h +++ b/webcc/soap_xml.h @@ -32,6 +32,9 @@ std::string GetText(const pugi::xml_node& xnode); // Output parameter version GetText. void GetText(const pugi::xml_node& xnode, std::string* text); +// Set node text. +void SetText(pugi::xml_node xnode, const std::string& text, bool is_cdata); + // 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".