From 223e59cc7a7a1c95490ea602aa167c2498655ab5 Mon Sep 17 00:00:00 2001 From: Adam Gu Date: Thu, 19 Jul 2018 11:19:51 +0800 Subject: [PATCH] Support CDATA for SOAP request and response; Rename Parameter to SoapParameter; Add book server and client example for SOAP. --- CMakeLists.txt | 3 + example/common/book.cc | 61 +++++ example/common/book.h | 47 ++++ example/common/book_xml.cc | 147 ++++++++++++ example/common/book_xml.h | 111 +++++++++ example/rest_book_client/CMakeLists.txt | 10 +- example/rest_book_server/CMakeLists.txt | 15 +- example/rest_book_server/main.cc | 2 +- .../{book_services.cc => services.cc} | 109 ++------- .../{book_services.h => services.h} | 14 +- example/soap_book_client/CMakeLists.txt | 15 ++ example/soap_book_client/book_client.cc | 136 +++++++++++ example/soap_book_client/book_client.h | 53 +++++ example/soap_book_client/main.cc | 65 ++++++ example/soap_book_server/CMakeLists.txt | 15 ++ example/soap_book_server/book_service.cc | 220 ++++++++++++++++++ example/soap_book_server/book_service.h | 25 ++ example/soap_book_server/main.cc | 40 ++++ example/soap_calc_client/CMakeLists.txt | 9 +- example/soap_calc_client/calc_client.cc | 89 ------- example/soap_calc_client/calc_client.h | 35 --- example/soap_calc_client/main.cc | 93 +++++++- .../soap_calc_client_parasoft/CMakeLists.txt | 6 + .../calculator.wsdl | 0 example/soap_calc_client_parasoft/main.cc | 97 ++++++++ example/soap_calc_server/calc_service.cc | 35 +-- example/soap_calc_server/calc_service.h | 4 +- example/soap_calc_server/main.cc | 7 +- unittest/rest_service_manager_test.cc | 2 +- webcc/CMakeLists.txt | 15 +- webcc/globals.cc | 38 --- webcc/globals.h | 38 --- webcc/soap_client.cc | 13 +- webcc/soap_client.h | 32 ++- webcc/soap_message.cc | 21 +- webcc/soap_message.h | 6 +- webcc/soap_parameter.h | 79 +++++++ webcc/soap_request.cc | 16 +- webcc/soap_request.h | 7 +- webcc/soap_request_handler.cc | 2 +- webcc/soap_request_handler.h | 16 +- webcc/soap_response.cc | 10 +- webcc/soap_response.h | 12 +- webcc/soap_server.h | 10 +- webcc/soap_xml.cc | 2 +- webcc/soap_xml.h | 8 +- webcc/url.cc | 9 +- webcc/url.h | 6 +- 48 files changed, 1394 insertions(+), 411 deletions(-) create mode 100644 example/common/book.cc create mode 100644 example/common/book.h create mode 100644 example/common/book_xml.cc create mode 100644 example/common/book_xml.h rename example/rest_book_server/{book_services.cc => services.cc} (61%) rename example/rest_book_server/{book_services.h => services.h} (82%) create mode 100644 example/soap_book_client/CMakeLists.txt create mode 100644 example/soap_book_client/book_client.cc create mode 100644 example/soap_book_client/book_client.h create mode 100644 example/soap_book_client/main.cc create mode 100644 example/soap_book_server/CMakeLists.txt create mode 100644 example/soap_book_server/book_service.cc create mode 100644 example/soap_book_server/book_service.h create mode 100644 example/soap_book_server/main.cc delete mode 100644 example/soap_calc_client/calc_client.cc delete mode 100644 example/soap_calc_client/calc_client.h create mode 100644 example/soap_calc_client_parasoft/CMakeLists.txt rename example/{soap_calc_client => soap_calc_client_parasoft}/calculator.wsdl (100%) create mode 100644 example/soap_calc_client_parasoft/main.cc create mode 100644 webcc/soap_parameter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 846d813..0a47bb6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,6 +133,9 @@ if(WEBCC_BUILD_EXAMPLE) if(WEBCC_ENABLE_SOAP) add_subdirectory(${PROJECT_SOURCE_DIR}/example/soap_calc_server) add_subdirectory(${PROJECT_SOURCE_DIR}/example/soap_calc_client) + add_subdirectory(${PROJECT_SOURCE_DIR}/example/soap_calc_client_parasoft) + add_subdirectory(${PROJECT_SOURCE_DIR}/example/soap_book_server) + add_subdirectory(${PROJECT_SOURCE_DIR}/example/soap_book_client) endif() endif() diff --git a/example/common/book.cc b/example/common/book.cc new file mode 100644 index 0000000..5191347 --- /dev/null +++ b/example/common/book.cc @@ -0,0 +1,61 @@ +#include "example/common/book.h" + +#include +#include + +const Book kNullBook{}; + +std::ostream& operator<<(std::ostream& os, const Book& book) { + os << "{ " << book.id << ", " << book.title << ", " << book.price << " }"; + return os; +} + +const Book& BookStore::GetBook(const std::string& id) const { + auto it = FindBook(id); + return (it == books_.end() ? kNullBook : *it); +} + +std::string BookStore::AddBook(const Book& book) { + std::string id = NewID(); + books_.push_back({ id, book.title, book.price }); + return id; +} + +bool BookStore::UpdateBook(const Book& book) { + auto it = FindBook(book.id); + if (it != books_.end()) { + it->title = book.title; + it->price = book.price; + return true; + } + return false; +} + +bool BookStore::DeleteBook(const std::string& id) { + auto it = FindBook(id); + + if (it != books_.end()) { + books_.erase(it); + return true; + } + + return false; +} + +std::list::const_iterator BookStore::FindBook(const std::string& id) + const { + return std::find_if(books_.begin(), books_.end(), + [&id](const Book& book) { return book.id == id; }); +} + +std::list::iterator BookStore::FindBook(const std::string& id) { + return std::find_if(books_.begin(), books_.end(), + [&id](Book& book) { return book.id == id; }); +} + +std::string BookStore::NewID() const { + static int s_id_counter = 0; + + ++s_id_counter; + return std::to_string(s_id_counter); +} diff --git a/example/common/book.h b/example/common/book.h new file mode 100644 index 0000000..8d47da9 --- /dev/null +++ b/example/common/book.h @@ -0,0 +1,47 @@ +#ifndef EXAMPLE_COMMON_BOOK_H_ +#define EXAMPLE_COMMON_BOOK_H_ + +#include +#include + +// In-memory test data. +// There should be some database in a real product. + +struct Book { + std::string id; + std::string title; + double price; + + bool IsNull() const { return id.empty(); } +}; + +std::ostream& operator<<(std::ostream& os, const Book& book); + +extern const Book kNullBook; + +class BookStore { + public: + const std::list& books() const { return books_; } + + const Book& GetBook(const std::string& id) const; + + // Add a book, return the ID. + // NOTE: The ID of the input book will be ignored so should be empty. + std::string AddBook(const Book& book); + + bool UpdateBook(const Book& book); + + bool DeleteBook(const std::string& id); + + private: + std::list::const_iterator FindBook(const std::string& id) const; + + std::list::iterator FindBook(const std::string& id); + + // Allocate a new book ID. + std::string NewID() const; + + std::list books_; +}; + +#endif // EXAMPLE_COMMON_BOOK_H_ diff --git a/example/common/book_xml.cc b/example/common/book_xml.cc new file mode 100644 index 0000000..56f6373 --- /dev/null +++ b/example/common/book_xml.cc @@ -0,0 +1,147 @@ +#include "example/common/book_xml.h" + +#include +#include +#include + +#include "example/common/book.h" + +// ----------------------------------------------------------------------------- + +// Print a XML node to string. +static std::string PrintXml(pugi::xml_node xnode, bool format_raw = true, + const char* indent = "") { + std::stringstream ss; + unsigned int flags = format_raw ? pugi::format_raw : pugi::format_indent; + xnode.print(ss, indent, flags); + return ss.str(); +} + +// ----------------------------------------------------------------------------- + +bool XmlToBook(pugi::xml_node xbook, Book* book) { + assert(xbook.name() == std::string("book")); + + book->id = xbook.child("id").text().as_string(); + book->title = xbook.child("title").text().as_string(); + book->price = xbook.child("price").text().as_double(); + + return true; +} + +void BookToXml(const Book& book, pugi::xml_node* xparent) { + pugi::xml_node xbook = xparent->append_child("book"); + + xbook.append_child("id").text().set(book.id.c_str()); + xbook.append_child("title").text().set(book.title.c_str()); + xbook.append_child("price").text().set(book.price); +} + +bool XmlToBookList(pugi::xml_node xbooks, std::list* books) { + assert(xbooks.name() == std::string("books")); + + pugi::xml_node xbook = xbooks.child("book"); + + while (xbook) { + Book book{ + xbook.child("id").text().as_string(), + xbook.child("title").text().as_string(), + xbook.child("price").text().as_double() + }; + books->push_back(book); + + xbook = xbook.next_sibling("book"); + } + + return true; +} + +void BookListToXml(const std::list& books, pugi::xml_node* xparent) { + pugi::xml_node xbooks = xparent->append_child("books"); + + for (const Book& book : books) { + BookToXml(book, &xbooks); + } +} + +bool XmlStringToBook(const std::string& xml_string, Book* book) { + pugi::xml_document xdoc; + if (!xdoc.load_string(xml_string.c_str())) { + return false; + } + + pugi::xml_node xbook = xdoc.document_element(); + if (!xbook) { + return false; + } + + if (xbook.name() != std::string("book")) { + return false; + } + + return XmlToBook(xbook, book); +} + +std::string BookToXmlString(const Book& book, bool format_raw, + const char* indent) { + pugi::xml_document xdoc; + BookToXml(book, &xdoc); + return PrintXml(xdoc); +} + +// ----------------------------------------------------------------------------- + +std::string NewRequestXml(const Book& book) { + pugi::xml_document xdoc; + + pugi::xml_node xwebcc = xdoc.append_child("webcc"); + xwebcc.append_attribute("type") = "request"; + + BookToXml(book, &xwebcc); + + return PrintXml(xdoc, false, " "); +} + +// ----------------------------------------------------------------------------- + +static std::string __NewResultXml(int code, const char* message, + std::function callback) { + pugi::xml_document xdoc; + + pugi::xml_node xwebcc = xdoc.append_child("webcc"); + xwebcc.append_attribute("type") = "response"; + + pugi::xml_node xstatus = xwebcc.append_child("status"); + xstatus.append_attribute("code") = code; + xstatus.append_attribute("message") = message; + + if (callback) { + callback(&xwebcc); + } + + return PrintXml(xdoc, false, " "); +} + +std::string NewResultXml(int code, const char* message) { + return __NewResultXml(code, message, {}); +} + +std::string NewResultXml(int code, const char* message, const char* node, + const char* key, const char* value) { + auto callback = [node, key, value](pugi::xml_node* xparent) { + pugi::xml_node xnode = xparent->append_child(node); + xnode.append_child(key).text() = value; + }; + return __NewResultXml(code, message, callback); +} + +std::string NewResultXml(int code, const char* message, const Book& book) { + return __NewResultXml(code, message, + std::bind(BookToXml, book, std::placeholders::_1)); +} + +std::string NewResultXml(int code, const char* message, + const std::list& books) { + return __NewResultXml(code, message, + std::bind(BookListToXml, books, std::placeholders::_1)); +} diff --git a/example/common/book_xml.h b/example/common/book_xml.h new file mode 100644 index 0000000..02cc737 --- /dev/null +++ b/example/common/book_xml.h @@ -0,0 +1,111 @@ +#ifndef EXAMPLE_COMMON_BOOK_XML_H_ +#define EXAMPLE_COMMON_BOOK_XML_H_ + +#include +#include + +#include "pugixml/pugixml.hpp" + +struct Book; + +// ----------------------------------------------------------------------------- + +// Convert the following XML node to a book object. +// +// 1 +// 1984 +// 12.3 +// +bool XmlToBook(pugi::xml_node xbook, Book* book); + +// Convert a book object to XML and append to the given parent. +void BookToXml(const Book& book, pugi::xml_node* xparent); + +// Convert the following XML node to a list of book objects. +// +// +// 1 +// 1984 +// 12.3 +// +// ... +// +bool XmlToBookList(pugi::xml_node xbooks, std::list* books); + +// Convert a list of book objects to XML and append to the given parent. +void BookListToXml(const std::list& books, pugi::xml_node* xparent); + +// Convert the following XML string to a book object. +// +// 1 +// 1984 +// 12.3 +// +bool XmlStringToBook(const std::string& xml_string, Book* book); + +// Convert a book object to XML string. +std::string BookToXmlString(const Book& book, bool format_raw = true, + const char* indent = ""); + +// ----------------------------------------------------------------------------- + +// This example defines its own result XML which will be embedded into the SOAP +// envolope as CDATA. The general schema of this result XML is: +// +// +// +// The "status" node is mandatory, you should define proper status codes and +// messages according to your needs. +// Additional data is attached as the sibling of "status" node, e.g., +// +// +// +// {book.id} +// {book.title} +// {book.price} +// +// + +// Create a result XML as below: +// +// +// +std::string NewResultXml(int code, const char* message); + +// Create a result XML as below: +// +// +// <{node}> +// <{key}>{value} +// +// +std::string NewResultXml(int code, const char* message, const char* node, + const char* key, const char* value); + +// Create a result XML as below: +// +// +// +// {book.id} +// {book.title} +// {book.price} +// +// +std::string NewResultXml(int code, const char* message, const Book& book); + +// Create a result XML as below: +// +// +// +// +// {book.id} +// {book.title} +// {book.price} +// +// ... +// +// +std::string NewResultXml(int code, const char* message, + const std::list& books); + +#endif // EXAMPLE_COMMON_BOOK_XML_H_ diff --git a/example/rest_book_client/CMakeLists.txt b/example/rest_book_client/CMakeLists.txt index e77120f..68c799a 100644 --- a/example/rest_book_client/CMakeLists.txt +++ b/example/rest_book_client/CMakeLists.txt @@ -1,4 +1,8 @@ -add_executable(rest_book_client main.cc) +set(TARGET_NAME rest_book_client) -target_link_libraries(rest_book_client webcc jsoncpp ${Boost_LIBRARIES}) -target_link_libraries(rest_book_client "${CMAKE_THREAD_LIBS_INIT}") +set(SRCS main.cc) + +add_executable(${TARGET_NAME} ${SRCS}) + +target_link_libraries(${TARGET_NAME} webcc jsoncpp ${Boost_LIBRARIES}) +target_link_libraries(${TARGET_NAME} "${CMAKE_THREAD_LIBS_INIT}") diff --git a/example/rest_book_server/CMakeLists.txt b/example/rest_book_server/CMakeLists.txt index a848101..f3a9b03 100644 --- a/example/rest_book_server/CMakeLists.txt +++ b/example/rest_book_server/CMakeLists.txt @@ -1,6 +1,13 @@ -set(SRCS book_services.cc book_services.h main.cc) +set(TARGET_NAME rest_book_server) -add_executable(rest_book_server ${SRCS}) +set(SRCS + ../common/book.cc + ../common/book.h + services.cc + services.h + main.cc) -target_link_libraries(rest_book_server webcc jsoncpp ${Boost_LIBRARIES}) -target_link_libraries(rest_book_server "${CMAKE_THREAD_LIBS_INIT}") +add_executable(${TARGET_NAME} ${SRCS}) + +target_link_libraries(${TARGET_NAME} webcc jsoncpp ${Boost_LIBRARIES}) +target_link_libraries(${TARGET_NAME} "${CMAKE_THREAD_LIBS_INIT}") diff --git a/example/rest_book_server/main.cc b/example/rest_book_server/main.cc index 0433bd8..ce0179c 100644 --- a/example/rest_book_server/main.cc +++ b/example/rest_book_server/main.cc @@ -3,7 +3,7 @@ #include "webcc/logger.h" #include "webcc/rest_server.h" -#include "example/rest_book_server/book_services.h" +#include "example/rest_book_server/services.h" // In order to run with VLD, please copy the following files to the example // output folder from "third_party\win32\bin": diff --git a/example/rest_book_server/book_services.cc b/example/rest_book_server/services.cc similarity index 61% rename from example/rest_book_server/book_services.cc rename to example/rest_book_server/services.cc index 909d385..308acee 100644 --- a/example/rest_book_server/book_services.cc +++ b/example/rest_book_server/services.cc @@ -1,4 +1,4 @@ -#include "example/rest_book_server/book_services.h" +#include "example/rest_book_server/services.h" #include #include @@ -7,95 +7,21 @@ #include "json/json.h" #include "webcc/logger.h" -// ----------------------------------------------------------------------------- - -// In-memory test data. -// There should be some database in a real product. - -struct Book { - std::string id; - std::string title; - double price; - - bool IsNull() const { - return id.empty(); - } - - Json::Value ToJson() const { - Json::Value root; - root["id"] = id; - root["title"] = title; - root["price"] = price; - return root; - } -}; - -std::ostream& operator<<(std::ostream& os, const Book& book) { - os << "{ " << book.id << ", " << book.title << ", " << book.price << " }"; - return os; -} - -static const Book kNullBook{}; - -class BookStore { - public: - BookStore() = default; - - const std::list& books() const { return books_; } - - const Book& GetBook(const std::string& id) const { - auto it = FindBook(id); - return (it == books_.end() ? kNullBook : *it); - } - - bool AddBook(const Book& new_book) { - if (FindBook(new_book.id) == books_.end()) { - books_.push_back(new_book); - return true; - } - return false; - } - - bool UpdateBook(const Book& book) { - auto it = FindBook(book.id); - if (it != books_.end()) { - it->title = book.title; - it->price = book.price; - return true; - } - return false; - } - - bool DeleteBook(const std::string& id) { - auto it = FindBook(id); - - if (it != books_.end()) { - books_.erase(it); - return true; - } - - return false; - } - - private: - std::list::const_iterator FindBook(const std::string& id) const { - return std::find_if(books_.begin(), books_.end(), - [&id](const Book& book) { return book.id == id; }); - } +#include "example/common/book.h" - std::list::iterator FindBook(const std::string& id) { - return std::find_if(books_.begin(), books_.end(), - [&id](Book& book) { return book.id == id; }); - } - - std::list books_; -}; +// ----------------------------------------------------------------------------- static BookStore g_book_store; -// ----------------------------------------------------------------------------- +static Json::Value BookToJson(const Book& book) { + Json::Value root; + root["id"] = book.id; + root["title"] = book.title; + root["price"] = book.price; + return root; +} -static bool BookFromJson(const std::string& json, Book* book) { +static bool JsonToBook(const std::string& json, Book* book) { Json::Value root; Json::CharReaderBuilder builder; std::stringstream stream(json); @@ -112,6 +38,8 @@ static bool BookFromJson(const std::string& json, Book* book) { return true; } +// ----------------------------------------------------------------------------- + // Return all books as a JSON array. // TODO: Support query parameters. bool BookListService::Get(const webcc::UrlQuery& /* query */, @@ -123,7 +51,7 @@ bool BookListService::Get(const webcc::UrlQuery& /* query */, Json::Value root(Json::arrayValue); for (const Book& book : g_book_store.books()) { - root.append(book.ToJson()); + root.append(BookToJson(book)); } Json::StreamWriterBuilder builder; @@ -142,8 +70,9 @@ bool BookListService::Post(const std::string& request_content, } Book book; - if (BookFromJson(request_content, &book)) { - return g_book_store.AddBook(book); + if (JsonToBook(request_content, &book)) { + g_book_store.AddBook(book); // TODO: return ID + return true; } return false; @@ -168,7 +97,7 @@ bool BookDetailService::Get(const std::vector& url_sub_matches, const Book& book = g_book_store.GetBook(book_id); if (!book.IsNull()) { Json::StreamWriterBuilder builder; - *response_content = Json::writeString(builder, book.ToJson()); + *response_content = Json::writeString(builder, BookToJson(book)); return true; } @@ -191,7 +120,7 @@ bool BookDetailService::Put(const std::vector& url_sub_matches, const std::string& book_id = url_sub_matches[0]; Book book; - if (BookFromJson(request_content, &book)) { + if (JsonToBook(request_content, &book)) { book.id = book_id; return g_book_store.UpdateBook(book); } diff --git a/example/rest_book_server/book_services.h b/example/rest_book_server/services.h similarity index 82% rename from example/rest_book_server/book_services.h rename to example/rest_book_server/services.h index 61be687..437a2d2 100644 --- a/example/rest_book_server/book_services.h +++ b/example/rest_book_server/services.h @@ -1,5 +1,8 @@ -#ifndef BOOK_SERVICES_H_ -#define BOOK_SERVICES_H_ +#ifndef EXAMPLE_REST_BOOK_SERVER_SERVICES_H_ +#define EXAMPLE_REST_BOOK_SERVER_SERVICES_H_ + +#include +#include #include "webcc/rest_service.h" @@ -13,7 +16,7 @@ // The query parameters could be regular expressions. class BookListService : public webcc::RestListService { public: - BookListService(int sleep_seconds) : sleep_seconds_(sleep_seconds) { + explicit BookListService(int sleep_seconds) : sleep_seconds_(sleep_seconds) { } protected: @@ -39,7 +42,8 @@ class BookListService : public webcc::RestListService { // contains the matched book ID. class BookDetailService : public webcc::RestDetailService { public: - BookDetailService(int sleep_seconds) : sleep_seconds_(sleep_seconds) { + explicit BookDetailService(int sleep_seconds) + : sleep_seconds_(sleep_seconds) { } protected: @@ -58,4 +62,4 @@ class BookDetailService : public webcc::RestDetailService { int sleep_seconds_ = 0; }; -#endif // BOOK_SERVICE_H_ +#endif // EXAMPLE_REST_BOOK_SERVER_SERVICES_H_ diff --git a/example/soap_book_client/CMakeLists.txt b/example/soap_book_client/CMakeLists.txt new file mode 100644 index 0000000..68860f2 --- /dev/null +++ b/example/soap_book_client/CMakeLists.txt @@ -0,0 +1,15 @@ +set(TARGET_NAME soap_book_client) + +set(SRCS + ../common/book.cc + ../common/book.h + ../common/book_xml.cc + ../common/book_xml.h + book_client.cc + book_client.h + main.cc) + +add_executable(${TARGET_NAME} ${SRCS}) + +target_link_libraries(${TARGET_NAME} webcc pugixml ${Boost_LIBRARIES}) +target_link_libraries(${TARGET_NAME} "${CMAKE_THREAD_LIBS_INIT}") diff --git a/example/soap_book_client/book_client.cc b/example/soap_book_client/book_client.cc new file mode 100644 index 0000000..1af9fc1 --- /dev/null +++ b/example/soap_book_client/book_client.cc @@ -0,0 +1,136 @@ +#include "example/soap_book_client/book_client.h" + +#include + +#include "webcc/logger.h" + +#include "example/common/book_xml.h" + +static void PrintSeparateLine() { + std::cout << "--------------------------------"; + std::cout << "--------------------------------"; + std::cout << std::endl; +} + +BookClient::BookClient(const std::string& host, const std::string& port) + : webcc::SoapClient(host, port), code_(0) { + url_ = "/book"; + service_ns_ = { "ser", "http://www.example.com/book/" }; + result_name_ = "Result"; + + // Customize response XML format. + format_raw_ = false; + indent_str_ = " "; +} + +bool BookClient::CreateBook(const std::string& title, double price, std::string* id) { + PrintSeparateLine(); + std::cout << "CreateBook: " << title << ", " << price << std::endl; + + webcc::SoapParameter parameter{ + "book", + BookToXmlString({ "", title, price }), + true, // as_cdata + }; + std::string result_xml; + if (!Call1("CreateBook", std::move(parameter), &result_xml)) { + return false; + } + + auto callback = [id](pugi::xml_node xnode) { + *id = xnode.child("book").child("id").text().as_string(); + return !id->empty(); + }; + return ParseResultXml(result_xml, callback); +} + +bool BookClient::GetBook(const std::string& id, Book* book) { + PrintSeparateLine(); + std::cout << "GetBook: " << id << std::endl; + + std::string result_xml; + if (!Call1("GetBook", { "id", id }, &result_xml)) { + return false; + } + + auto callback = [book](pugi::xml_node xnode) { + return XmlToBook(xnode.child("book"), book); + }; + return ParseResultXml(result_xml, callback); +} + +bool BookClient::ListBooks(std::list* books) { + PrintSeparateLine(); + std::cout << "ListBooks" << std::endl; + + std::string result_xml; + if (!Call0("ListBooks", &result_xml)) { + return false; + } + + auto callback = [books](pugi::xml_node xnode) { + return XmlToBookList(xnode.child("books"), books); + }; + return ParseResultXml(result_xml, callback); +} + +bool BookClient::DeleteBook(const std::string& id) { + PrintSeparateLine(); + std::cout << "DeleteBook: " << id << std::endl; + + std::string result_xml; + if (!Call1("DeleteBook", { "id", id }, &result_xml)) { + return false; + } + + return ParseResultXml(result_xml, {}); +} + +bool BookClient::Call0(const std::string& operation, std::string* result_str) { + return CallX(operation, {}, result_str); +} + + +bool BookClient::Call1(const std::string& operation, webcc::SoapParameter&& parameter, + std::string* result_str) { + std::vector parameters{ + { std::move(parameter) } + }; + return CallX(operation, std::move(parameters), result_str); +} + +bool BookClient::CallX(const std::string& operation, + std::vector&& parameters, + std::string* result_str) { + webcc::Error error = webcc::SoapClient::Call(operation, + std::move(parameters), + result_str); + + if (error != webcc::kNoError) { + LOG_ERRO("Operation '%s' failed: %s", + operation, webcc::DescribeError(error)); + return false; + } + + return true; +} + +bool BookClient::ParseResultXml(const std::string& result_xml, + std::function callback) { + pugi::xml_document xdoc; + if (!xdoc.load_string(result_xml.c_str())) { + return false; + } + + pugi::xml_node xwebcc = xdoc.document_element(); + + pugi::xml_node xstatus = xwebcc.child("status"); + code_ = xstatus.attribute("code").as_int(); + message_ = xstatus.attribute("message").as_string(); + + if (callback) { + return callback(xwebcc); + } + + return true; +} diff --git a/example/soap_book_client/book_client.h b/example/soap_book_client/book_client.h new file mode 100644 index 0000000..db6738c --- /dev/null +++ b/example/soap_book_client/book_client.h @@ -0,0 +1,53 @@ +#ifndef EXAMPLE_SOAP_BOOK_CLIENT_H_ +#define EXAMPLE_SOAP_BOOK_CLIENT_H_ + +#include +#include + +#include "webcc/soap_client.h" + +#include "example/common/book.h" + +class BookClient : public webcc::SoapClient { + public: + BookClient(const std::string& host, const std::string& port); + + ~BookClient() override = default; + + int code() const { return code_; } + const std::string& message() const { return message_; } + + // Create a book. + bool CreateBook(const std::string& title, double price, std::string* id); + + // Get a book by ID. + bool GetBook(const std::string& id, Book* book); + + // List all books. + bool ListBooks(std::list* books); + + // Delete a book by ID. + bool DeleteBook(const std::string& id); + + private: + // Call with 0 parameter. + bool Call0(const std::string& operation, std::string* result_str); + + // Call with 1 parameter. + bool Call1(const std::string& operation, webcc::SoapParameter&& parameter, + std::string* result_str); + + // Simple wrapper of webcc::SoapClient::Call() to log error if any. + bool CallX(const std::string& operation, + std::vector&& parameters, + std::string* result_str); + + bool ParseResultXml(const std::string& result_xml, + std::function callback); + + // Last status. + int code_; + std::string message_; +}; + +#endif // EXAMPLE_SOAP_BOOK_CLIENT_H_ diff --git a/example/soap_book_client/main.cc b/example/soap_book_client/main.cc new file mode 100644 index 0000000..87e072e --- /dev/null +++ b/example/soap_book_client/main.cc @@ -0,0 +1,65 @@ +#include + +#include "webcc/logger.h" + +#include "example/soap_book_client/book_client.h" + +void Help(const char* argv0) { + std::cout << "Usage: " << argv0 << " " << std::endl; + std::cout << " E.g.," << std::endl; + std::cout << " " << argv0 << " localhost 8080" << std::endl; +} + +int main(int argc, char* argv[]) { + if (argc < 3) { + Help(argv[0]); + return 1; + } + + WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); + + std::string host = argv[1]; + std::string port = argv[2]; + + BookClient client(host, port); + + std::string id1; + if (!client.CreateBook("1984", 12.3, &id1)) { + std::cerr << "Failed to create book." << std::endl; + return 2; + } + + std::cout << "Book ID: " << id1 << std::endl; + + std::string id2; + if (!client.CreateBook("1Q84", 32.1, &id2)) { + std::cerr << "Failed to create book." << std::endl; + return 2; + } + + std::cout << "Book ID: " << id2 << std::endl; + + Book book; + if (!client.GetBook(id1, &book)) { + std::cerr << "Failed to get book." << std::endl; + return 2; + } + + std::cout << "Book: " << book << std::endl; + + std::list books; + if (!client.ListBooks(&books)) { + std::cerr << "Failed to list books." << std::endl; + return 2; + } + + for (const Book& book : books) { + std::cout << "Book: " << book << std::endl; + } + + if (client.DeleteBook(id1)) { + std::cout << "Book deleted: " << id1 << std::endl; + } + + return 0; +} diff --git a/example/soap_book_server/CMakeLists.txt b/example/soap_book_server/CMakeLists.txt new file mode 100644 index 0000000..9df6349 --- /dev/null +++ b/example/soap_book_server/CMakeLists.txt @@ -0,0 +1,15 @@ +set(TARGET_NAME soap_book_server) + +set(SRCS + ../common/book.cc + ../common/book.h + ../common/book_xml.cc + ../common/book_xml.h + book_service.cc + book_service.h + main.cc) + +add_executable(${TARGET_NAME} ${SRCS}) + +target_link_libraries(${TARGET_NAME} webcc pugixml ${Boost_LIBRARIES}) +target_link_libraries(${TARGET_NAME} "${CMAKE_THREAD_LIBS_INIT}") diff --git a/example/soap_book_server/book_service.cc b/example/soap_book_server/book_service.cc new file mode 100644 index 0000000..962cd57 --- /dev/null +++ b/example/soap_book_server/book_service.cc @@ -0,0 +1,220 @@ +#include "example/soap_book_server/book_service.h" + +#include +#include +#include + +#include "webcc/logger.h" +#include "webcc/soap_request.h" +#include "webcc/soap_response.h" + +#include "example/common/book.h" +#include "example/common/book_xml.h" + +// ----------------------------------------------------------------------------- + +static BookStore g_book_store; + +// ----------------------------------------------------------------------------- + +bool BookService::Handle(const webcc::SoapRequest& soap_request, + webcc::SoapResponse* soap_response) { + const std::string& operation = soap_request.operation(); + + soap_response->set_soapenv_ns(webcc::kSoapEnvNamespace); + soap_response->set_service_ns({ + "ser", + "http://www.example.com/book/" + }); + + soap_response->set_operation(operation); + soap_response->set_result_name("Result"); + + if (operation == "CreateBook") { + return CreateBook(soap_request, soap_response); + + } else if (operation == "GetBook") { + return GetBook(soap_request, soap_response); + + } else if (operation == "ListBooks") { + return ListBooks(soap_request, soap_response); + + } else if (operation == "DeleteBook") { + return DeleteBook(soap_request, soap_response); + + } else { + LOG_ERRO("Operation '%s' is not supported.", operation.c_str()); + return false; + } + + return false; +} + +bool BookService::CreateBook(const webcc::SoapRequest& soap_request, + webcc::SoapResponse* soap_response) { + // Request SOAP envelope: + // + // + // + // + // + // 1984 + // 12.3 + // + // ]]> + // + // + // + // + + // Response SOAP envelope: + // + // + // + // + // + // + // + // 1 + // + // + // ]]> + // + // + // + // + + const std::string& title = soap_request.GetParameter("title"); + + const std::string& book_xml = soap_request.GetParameter("book"); + + Book book; + XmlStringToBook(book_xml, &book); // TODO: Error handling + + std::string id = g_book_store.AddBook(book); + + std::string response_xml = NewResultXml(0, "ok", "book", "id", + id.c_str()); + + soap_response->set_result_moved(std::move(response_xml), true); + + return true; +} + +bool BookService::GetBook(const webcc::SoapRequest& soap_request, + webcc::SoapResponse* soap_response) { + // Request SOAP envelope: + // + // + // + // 1 + // + // + // + + // Response SOAP envelope: + // + // + // + // + // + // + // + // 1 + // 1984 + // 12.3 + // + // + // ]]> + // + // + // + // + + const std::string& id = soap_request.GetParameter("id"); + + const Book& book = g_book_store.GetBook(id); + + soap_response->set_result_moved(NewResultXml(0, "ok", book), true); + + return true; +} + +bool BookService::ListBooks(const webcc::SoapRequest& soap_request, + webcc::SoapResponse* soap_response) { + // Request SOAP envelope: + // + // + // + // + // + + // Response SOAP envelope: + // + // + // + // + // + // + // + // + // 1 + // 1984 + // 12.3 + // + // ... + // + // + // ]]> + // + // + // + // + + const std::list& books = g_book_store.books(); + + soap_response->set_result_moved(NewResultXml(0, "ok", books), true); + + return true; +} + +bool BookService::DeleteBook(const webcc::SoapRequest& soap_request, + webcc::SoapResponse* soap_response) { + // Request SOAP envelope: + // + // + // + // 1 + // + // + // + + // Response SOAP envelope: + // + // + // + // + // + // + // + // ]]> + // + // + // + // + + const std::string& id = soap_request.GetParameter("id"); + + if (g_book_store.DeleteBook(id)) { + soap_response->set_result_moved(NewResultXml(0, "ok"), true); + } else { + soap_response->set_result_moved(NewResultXml(1, "error"), true); + } + + return true; +} diff --git a/example/soap_book_server/book_service.h b/example/soap_book_server/book_service.h new file mode 100644 index 0000000..b4f3ac7 --- /dev/null +++ b/example/soap_book_server/book_service.h @@ -0,0 +1,25 @@ +#ifndef EXAMPLE_SOAP_BOOK_SERVER_BOOK_SERVICE_H_ +#define EXAMPLE_SOAP_BOOK_SERVER_BOOK_SERVICE_H_ + +#include "webcc/soap_service.h" + +class BookService : public webcc::SoapService { + public: + bool Handle(const webcc::SoapRequest& soap_request, + webcc::SoapResponse* soap_response) override; + + private: + bool CreateBook(const webcc::SoapRequest& soap_request, + webcc::SoapResponse* soap_response); + + bool GetBook(const webcc::SoapRequest& soap_request, + webcc::SoapResponse* soap_response); + + bool ListBooks(const webcc::SoapRequest& soap_request, + webcc::SoapResponse* soap_response); + + bool DeleteBook(const webcc::SoapRequest& soap_request, + webcc::SoapResponse* soap_response); +}; + +#endif // EXAMPLE_SOAP_BOOK_SERVER_BOOK_SERVICE_H_ diff --git a/example/soap_book_server/main.cc b/example/soap_book_server/main.cc new file mode 100644 index 0000000..610112f --- /dev/null +++ b/example/soap_book_server/main.cc @@ -0,0 +1,40 @@ +#include + +#include "webcc/logger.h" +#include "webcc/soap_server.h" + +#include "example/soap_book_server/book_service.h" + +void Help(const char* argv0) { + std::cout << "Usage: " << argv0 << " " << std::endl; + std::cout << " E.g.," << std::endl; + std::cout << " " << argv0 << " 8080" << std::endl; +} + +int main(int argc, char* argv[]) { + if (argc < 2) { + Help(argv[0]); + return 1; + } + + WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); + + std::uint16_t port = static_cast(std::atoi(argv[1])); + std::size_t workers = 2; + + try { + webcc::SoapServer server(port, workers); + + // Customize response XML format. + server.set_format_raw(false); + server.set_indent_str(" "); + + server.Bind(std::make_shared(), "/book"); + server.Run(); + } catch (const std::exception& e) { + std::cerr << "Exception: " << e.what() << std::endl; + return 1; + } + + return 0; +} diff --git a/example/soap_calc_client/CMakeLists.txt b/example/soap_calc_client/CMakeLists.txt index a94250e..2cfb33d 100644 --- a/example/soap_calc_client/CMakeLists.txt +++ b/example/soap_calc_client/CMakeLists.txt @@ -1,7 +1,6 @@ -set(SRCS calc_client.cc calc_client.h main.cc) +set(TARGET_NAME soap_calc_client) -add_executable(soap_calc_client ${SRCS}) - -target_link_libraries(soap_calc_client webcc pugixml ${Boost_LIBRARIES}) -target_link_libraries(soap_calc_client "${CMAKE_THREAD_LIBS_INIT}") +add_executable(${TARGET_NAME} main.cc) +target_link_libraries(${TARGET_NAME} webcc pugixml ${Boost_LIBRARIES}) +target_link_libraries(${TARGET_NAME} "${CMAKE_THREAD_LIBS_INIT}") diff --git a/example/soap_calc_client/calc_client.cc b/example/soap_calc_client/calc_client.cc deleted file mode 100644 index 2c9eedc..0000000 --- a/example/soap_calc_client/calc_client.cc +++ /dev/null @@ -1,89 +0,0 @@ -#include "example/soap_calc_client/calc_client.h" - -#include - -#include "webcc/logger.h" - -// Set to 0 to test our own calculator server created with webcc. -#define ACCESS_PARASOFT 0 - -CalcClient::CalcClient() { - Init(); -} - -bool CalcClient::Add(double x, double y, double* result) { - return Calc("add", "x", "y", x, y, result); -} - -bool CalcClient::Subtract(double x, double y, double* result) { - return Calc("subtract", "x", "y", x, y, result); -} - -bool CalcClient::Multiply(double x, double y, double* result) { - return Calc("multiply", "x", "y", x, y, result); -} - -bool CalcClient::Divide(double x, double y, double* result) { - // ParaSoft's Calculator Service uses different parameter names for Divide. -#if ACCESS_PARASOFT - return Calc("divide", "numerator", "denominator", x, y, result); -#else - return Calc("divide", "x", "y", x, y, result); -#endif -} - -bool CalcClient::NotExist(double x, double y, double* result) { - return Calc("not_exist", "x", "y", x, y, result); -} - -void CalcClient::Init() { - // Override the default timeout. - timeout_seconds_ = 5; - -#if ACCESS_PARASOFT - url_ = "/glue/calculator"; - host_ = "ws1.parasoft.com"; - port_ = ""; // Default to "80". - service_ns_ = { "cal", "http://www.parasoft.com/wsdl/calculator/" }; - result_name_ = "Result"; -#else - url_ = "/calculator"; - host_ = "localhost"; - port_ = "8080"; - service_ns_ = { "ser", "http://www.example.com/calculator/" }; - result_name_ = "Result"; -#endif -} - -bool CalcClient::Calc(const std::string& operation, - const std::string& x_name, - const std::string& y_name, - double x, - double y, - double* result) { - // Prepare parameters. - std::vector parameters{ - { x_name, x }, - { y_name, y } - }; - - // Make the call. - std::string result_str; - webcc::Error error = Call(operation, std::move(parameters), &result_str); - - // Error handling if any. - if (error != webcc::kNoError) { - LOG_ERRO("Operation '%s' failed: %s", operation.c_str(), - webcc::DescribeError(error)); - return false; - } - - // Convert the result from string to double. - try { - *result = std::stod(result_str); - } catch (const std::exception&) { - return false; - } - - return true; -} diff --git a/example/soap_calc_client/calc_client.h b/example/soap_calc_client/calc_client.h deleted file mode 100644 index aacb45b..0000000 --- a/example/soap_calc_client/calc_client.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef CALC_CLIENT_H_ -#define CALC_CLIENT_H_ - -#include - -#include "webcc/soap_client.h" - -class CalcClient : public webcc::SoapClient { -public: - CalcClient(); - - bool Add(double x, double y, double* result); - - bool Subtract(double x, double y, double* result); - - bool Multiply(double x, double y, double* result); - - bool Divide(double x, double y, double* result); - - // For testing purpose. - bool NotExist(double x, double y, double* 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, - double x, - double y, - double* result); -}; - -#endif // CALC_CLIENT_H_ diff --git a/example/soap_calc_client/main.cc b/example/soap_calc_client/main.cc index ace8d45..8270253 100644 --- a/example/soap_calc_client/main.cc +++ b/example/soap_calc_client/main.cc @@ -1,13 +1,95 @@ #include #include "webcc/logger.h" +#include "webcc/soap_client.h" -#include "example/soap_calc_client/calc_client.h" +// ----------------------------------------------------------------------------- + +class CalcClient : public webcc::SoapClient { + public: + CalcClient(const std::string& host, const std::string& port) + : webcc::SoapClient(host, port) { + timeout_seconds_ = 5; // Override the default timeout. + + url_ = "/calculator"; + service_ns_ = { "ser", "http://www.example.com/calculator/" }; + result_name_ = "Result"; + + // Customize request XML format. + format_raw_ = false; + indent_str_ = " "; + } + + bool Add(double x, double y, double* result) { + return Calc("add", "x", "y", x, y, result); + } + + bool Subtract(double x, double y, double* result) { + return Calc("subtract", "x", "y", x, y, result); + } + + bool Multiply(double x, double y, double* result) { + return Calc("multiply", "x", "y", x, y, result); + } + + bool Divide(double x, double y, double* result) { + return Calc("divide", "x", "y", x, y, result); + } + + // Only for testing purpose. + bool Unknown(double x, double y, double* result) { + return Calc("unknown", "x", "y", x, y, result); + } + + protected: + bool Calc(const std::string& operation, + const std::string& x_name, const std::string& y_name, + double x, double y, + double* result) { + std::vector parameters{ + { x_name, x }, + { y_name, y } + }; + + std::string result_str; + webcc::Error error = Call(operation, std::move(parameters), &result_str); + + if (error != webcc::kNoError) { + LOG_ERRO("Operation '%s' failed: %s", operation.c_str(), + webcc::DescribeError(error)); + return false; + } + + try { + *result = std::stod(result_str); + } catch (const std::exception&) { + return false; + } + + return true; + } +}; + +// ----------------------------------------------------------------------------- + +void Help(const char* argv0) { + std::cout << "Usage: " << argv0 << " " << std::endl; + std::cout << " E.g.," << std::endl; + std::cout << " " << argv0 << " localhost 8080" << std::endl; +} + +int main(int argc, char* argv[]) { + if (argc < 3) { + Help(argv[0]); + return 1; + } -int main() { WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); - CalcClient calc; + std::string host = argv[1]; + std::string port = argv[2]; + + CalcClient calc(host, port); double x = 1.0; double y = 2.0; @@ -29,10 +111,7 @@ int main() { printf("divide: %.1f\n", result); } - // Try to call a non-existing operation. - if (calc.NotExist(x, y, &result)) { - printf("not_exist: %.1f\n", result); - } + calc.Unknown(x, y, &result); return 0; } diff --git a/example/soap_calc_client_parasoft/CMakeLists.txt b/example/soap_calc_client_parasoft/CMakeLists.txt new file mode 100644 index 0000000..27fd017 --- /dev/null +++ b/example/soap_calc_client_parasoft/CMakeLists.txt @@ -0,0 +1,6 @@ +set(TARGET_NAME soap_calc_client_parasoft) + +add_executable(${TARGET_NAME} main.cc) + +target_link_libraries(${TARGET_NAME} webcc pugixml ${Boost_LIBRARIES}) +target_link_libraries(${TARGET_NAME} "${CMAKE_THREAD_LIBS_INIT}") diff --git a/example/soap_calc_client/calculator.wsdl b/example/soap_calc_client_parasoft/calculator.wsdl similarity index 100% rename from example/soap_calc_client/calculator.wsdl rename to example/soap_calc_client_parasoft/calculator.wsdl diff --git a/example/soap_calc_client_parasoft/main.cc b/example/soap_calc_client_parasoft/main.cc new file mode 100644 index 0000000..f08b782 --- /dev/null +++ b/example/soap_calc_client_parasoft/main.cc @@ -0,0 +1,97 @@ +#include + +#include "webcc/logger.h" +#include "webcc/soap_client.h" + +// ----------------------------------------------------------------------------- + +class CalcClient : public webcc::SoapClient { + public: + CalcClient(const std::string& host, const std::string& port) + : webcc::SoapClient(host, port) { + timeout_seconds_ = 5; // Override the default timeout. + + url_ = "/glue/calculator"; + service_ns_ = { "cal", "http://www.parasoft.com/wsdl/calculator/" }; + result_name_ = "Result"; + + // Customize request XML format. + format_raw_ = false; + indent_str_ = " "; + } + + bool Add(double x, double y, double* result) { + return Calc("add", "x", "y", x, y, result); + } + + bool Subtract(double x, double y, double* result) { + return Calc("subtract", "x", "y", x, y, result); + } + + bool Multiply(double x, double y, double* result) { + return Calc("multiply", "x", "y", x, y, result); + } + + bool Divide(double x, double y, double* result) { + return Calc("divide", "numerator", "denominator", x, y, result); + } + + protected: + bool Calc(const std::string& operation, + const std::string& x_name, const std::string& y_name, + double x, double y, + double* result) { + std::vector parameters{ + { x_name, x }, + { y_name, y } + }; + + std::string result_str; + webcc::Error error = Call(operation, std::move(parameters), &result_str); + + if (error != webcc::kNoError) { + LOG_ERRO("Operation '%s' failed: %s", operation.c_str(), + webcc::DescribeError(error)); + return false; + } + + try { + *result = std::stod(result_str); + } catch (const std::exception&) { + return false; + } + + return true; + } +}; + +// ----------------------------------------------------------------------------- + +int main() { + WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); + + // Default port 80. + CalcClient calc("ws1.parasoft.com", ""); + + double x = 1.0; + double y = 2.0; + double result = 0.0; + + if (calc.Add(x, y, &result)) { + printf("add: %.1f\n", result); + } + + if (calc.Subtract(x, y, &result)) { + printf("subtract: %.1f\n", result); + } + + if (calc.Multiply(x, y, &result)) { + printf("multiply: %.1f\n", result); + } + + if (calc.Divide(x, y, &result)) { + printf("divide: %.1f\n", result); + } + + return 0; +} diff --git a/example/soap_calc_server/calc_service.cc b/example/soap_calc_server/calc_service.cc index 52770e8..ae20f70 100644 --- a/example/soap_calc_server/calc_service.cc +++ b/example/soap_calc_server/calc_service.cc @@ -3,21 +3,10 @@ #include #include -// Sleep several seconds for the client to test timeout control. -#define SLEEP_FOR_TIMEOUT_TEST 0 - -#if SLEEP_FOR_TIMEOUT_TEST -#include "boost/thread/thread.hpp" -#endif - #include "webcc/logger.h" #include "webcc/soap_request.h" #include "webcc/soap_response.h" -#if SLEEP_FOR_TIMEOUT_TEST -static const int kSleepSeconds = 3; -#endif - bool CalcService::Handle(const webcc::SoapRequest& soap_request, webcc::SoapResponse* soap_response) { double x = 0.0; @@ -26,22 +15,22 @@ bool CalcService::Handle(const webcc::SoapRequest& soap_request, return false; } - const std::string& op = soap_request.operation(); + const std::string& operation = soap_request.operation(); - LOG_INFO("Soap operation '%s': %.2f, %.2f", op.c_str(), x, y); + LOG_INFO("Soap operation '%s': %.2f, %.2f", operation.c_str(), x, y); std::function calc; - if (op == "add") { + if (operation == "add") { calc = [](double x, double y) { return x + y; }; - } else if (op == "subtract") { + } else if (operation == "subtract") { calc = [](double x, double y) { return x - y; }; - } else if (op == "multiply") { + } else if (operation == "multiply") { calc = [](double x, double y) { return x * y; }; - } else if (op == "divide") { + } else if (operation == "divide") { calc = [](double x, double y) { return x / y; }; if (y == 0.0) { @@ -49,7 +38,7 @@ bool CalcService::Handle(const webcc::SoapRequest& soap_request, return false; } } else { - LOG_ERRO("Operation '%s' is not supported.", op.c_str()); + LOG_ERRO("Operation '%s' is not supported.", operation.c_str()); return false; } @@ -68,13 +57,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(std::to_string(result)); - -#if SLEEP_FOR_TIMEOUT_TEST - LOG_INFO("Sleep %d seconds for the client to test timeout control.", - kSleepSeconds); - boost::this_thread::sleep_for(boost::chrono::seconds(kSleepSeconds)); -#endif + soap_response->set_result_moved(std::to_string(result), false); return true; } @@ -85,7 +68,7 @@ bool CalcService::GetParameters(const webcc::SoapRequest& soap_request, *x = std::stod(soap_request.GetParameter("x")); *y = std::stod(soap_request.GetParameter("y")); } catch (const std::exception& e) { - LOG_ERRO("Parameter cast error: %s", e.what()); + LOG_ERRO("SoapParameter cast error: %s", e.what()); return false; } diff --git a/example/soap_calc_server/calc_service.h b/example/soap_calc_server/calc_service.h index 615dd86..deb3321 100644 --- a/example/soap_calc_server/calc_service.h +++ b/example/soap_calc_server/calc_service.h @@ -4,14 +4,14 @@ #include "webcc/soap_service.h" class CalcService : public webcc::SoapService { -public: + public: CalcService() = default; ~CalcService() override = default; bool Handle(const webcc::SoapRequest& soap_request, webcc::SoapResponse* soap_response) override; -private: + private: bool GetParameters(const webcc::SoapRequest& soap_request, double* x, double* y); }; diff --git a/example/soap_calc_server/main.cc b/example/soap_calc_server/main.cc index 8ef21a5..3d48c40 100644 --- a/example/soap_calc_server/main.cc +++ b/example/soap_calc_server/main.cc @@ -5,7 +5,7 @@ #include "example/soap_calc_server/calc_service.h" -static void Help(const char* argv0) { +void Help(const char* argv0) { std::cout << "Usage: " << argv0 << " " << std::endl; std::cout << " E.g.," << std::endl; std::cout << " " << argv0 << " 8080" << std::endl; @@ -24,6 +24,11 @@ int main(int argc, char* argv[]) { try { webcc::SoapServer server(port, workers); + + // Customize response XML format. + server.set_format_raw(false); + server.set_indent_str(" "); + server.Bind(std::make_shared(), "/calculator"); server.Run(); } catch (const std::exception& e) { diff --git a/unittest/rest_service_manager_test.cc b/unittest/rest_service_manager_test.cc index f111da6..1b72cde 100644 --- a/unittest/rest_service_manager_test.cc +++ b/unittest/rest_service_manager_test.cc @@ -2,7 +2,7 @@ #include "gtest/gtest.h" class TestRestService : public webcc::RestService { -public: + public: bool Handle(const std::string& http_method, const std::vector& url_sub_matches, const webcc::UrlQuery& query, diff --git a/webcc/CMakeLists.txt b/webcc/CMakeLists.txt index c8248ef..1d39292 100644 --- a/webcc/CMakeLists.txt +++ b/webcc/CMakeLists.txt @@ -54,19 +54,20 @@ if(WEBCC_ENABLE_SOAP) # SOAP specific sources. set(SOAP_SRCS soap_client.cc + soap_client.h + soap_message.cc soap_message.h - soap_request_handler.cc + soap_parameter.h + soap_response.cc soap_response.h - soap_xml.cc - soap_client.h soap_request.cc + soap_request.h + soap_request_handler.cc soap_request_handler.h soap_server.h - soap_xml.h - soap_message.cc - soap_request.h - soap_response.cc soap_service.h + soap_xml.cc + soap_xml.h ) set(SRCS ${SRCS} ${SOAP_SRCS}) diff --git a/webcc/globals.cc b/webcc/globals.cc index 60d397f..be9cb25 100644 --- a/webcc/globals.cc +++ b/webcc/globals.cc @@ -54,42 +54,4 @@ const char* DescribeError(Error error) { } } -// ----------------------------------------------------------------------------- - -Parameter::Parameter(const std::string& key, const char* value) - : key_(key), value_(value) { -} - -Parameter::Parameter(const std::string& key, const std::string& value) - : key_(key), value_(value) { -} - -Parameter::Parameter(const std::string& key, std::string&& value) - : key_(key), value_(std::move(value)) { -} - -Parameter::Parameter(const std::string& key, int value) - : key_(key), value_(std::to_string(value)) { -} - -Parameter::Parameter(const std::string& key, double value) - : key_(key), value_(std::to_string(value)) { -} - -Parameter::Parameter(const std::string& key, bool value) - : key_(key), value_(value ? "true" : "false") { -} - -Parameter::Parameter(Parameter&& rhs) - : key_(std::move(rhs.key_)), value_(std::move(rhs.value_)) { -} - -Parameter& Parameter::operator=(Parameter&& rhs) { - if (&rhs != this) { - key_ = std::move(rhs.key_); - value_ = std::move(rhs.value_); - } - return *this; -} - } // namespace webcc diff --git a/webcc/globals.h b/webcc/globals.h index 6e36995..3145501 100644 --- a/webcc/globals.h +++ b/webcc/globals.h @@ -2,7 +2,6 @@ #define WEBCC_GLOBALS_H_ #include -#include // ----------------------------------------------------------------------------- // Macros @@ -86,43 +85,6 @@ enum Error { // Return a descriptive message for the given error code. const char* DescribeError(Error error); -// ----------------------------------------------------------------------------- - -// Key-value parameter. -class Parameter { - public: - Parameter() = default; - Parameter(const Parameter&) = default; - Parameter& operator=(const Parameter&) = default; - - Parameter(const std::string& key, const char* value); - Parameter(const std::string& key, const std::string& value); - Parameter(const std::string& key, std::string&& value); - Parameter(const std::string& key, int value); - Parameter(const std::string& key, double value); - Parameter(const std::string& key, bool value); - - // Use "= default" if drop the support of VS 2013. - Parameter(Parameter&& rhs); - - // Use "= default" if drop the support of VS 2013. - Parameter& operator=(Parameter&& rhs); - - const std::string& key() const { return key_; } - const std::string& value() const { return value_; } - - const char* c_key() const { return key_.c_str(); } - const char* c_value() const { return value_.c_str(); } - - std::string ToString() const { - return key_ + "=" + value_; - } - - private: - std::string key_; - std::string value_; -}; - } // namespace webcc #endif // WEBCC_GLOBALS_H_ diff --git a/webcc/soap_client.cc b/webcc/soap_client.cc index 4879302..881ffd1 100644 --- a/webcc/soap_client.cc +++ b/webcc/soap_client.cc @@ -11,8 +11,13 @@ namespace webcc { +SoapClient::SoapClient(const std::string& host, const std::string& port) + : host_(host), port_(port), + format_raw_(true), timeout_seconds_(0), timed_out_(false) { +} + Error SoapClient::Call(const std::string& operation, - std::vector&& parameters, + std::vector&& parameters, std::string* result) { assert(service_ns_.IsValid()); assert(!url_.empty() && !host_.empty()); @@ -29,12 +34,12 @@ Error SoapClient::Call(const std::string& operation, soap_request.set_operation(operation); - for (Parameter& p : parameters) { + for (SoapParameter& p : parameters) { soap_request.AddParameter(std::move(p)); } std::string http_content; - soap_request.ToXml(&http_content); + soap_request.ToXml(format_raw_, indent_str_, &http_content); HttpRequest http_request; @@ -46,8 +51,6 @@ Error SoapClient::Call(const std::string& operation, http_request.SetHeader(kSoapAction, operation); http_request.UpdateStartLine(); - HttpResponse http_response; - HttpClient http_client; if (timeout_seconds_ > 0) { diff --git a/webcc/soap_client.h b/webcc/soap_client.h index b4d519b..72ded96 100644 --- a/webcc/soap_client.h +++ b/webcc/soap_client.h @@ -4,8 +4,8 @@ #include #include -#include "webcc/globals.h" #include "webcc/soap_message.h" +#include "webcc/soap_parameter.h" namespace webcc { @@ -17,22 +17,21 @@ class SoapClient { bool timed_out() const { return timed_out_; } - protected: - SoapClient() : timeout_seconds_(0), timed_out_(false) { + void set_format_raw(bool format_raw) { format_raw_ = format_raw; } + + void set_indent_str(const std::string& indent_str) { + indent_str_ = indent_str; } + protected: + SoapClient(const std::string& host, const std::string& port); + // A generic wrapper to make a call. // NOTE: The parameters should be movable. Error Call(const std::string& operation, - std::vector&& parameters, + std::vector&& parameters, std::string* result); - // Timeout in seconds; only effective when > 0. - int timeout_seconds_; - - // If the error was caused by timeout or not. - bool timed_out_; - SoapNamespace soapenv_ns_; // SOAP envelope namespace. SoapNamespace service_ns_; // Namespace for your web service. @@ -45,6 +44,19 @@ class SoapClient { // Response result XML node name. // E.g., "Result". std::string result_name_; + + // Format request XML without any indentation or line breaks. + bool format_raw_; + + // Indent string for request XML. + // Applicable when |format_raw_| is false. + std::string indent_str_; + + // Timeout in seconds; only effective when > 0. + int timeout_seconds_; + + // If the error was caused by timeout or not. + bool timed_out_; }; } // namespace webcc diff --git a/webcc/soap_message.cc b/webcc/soap_message.cc index 54a194e..b5e928e 100644 --- a/webcc/soap_message.cc +++ b/webcc/soap_message.cc @@ -11,19 +11,12 @@ const SoapNamespace kSoapEnvNamespace{ "http://schemas.xmlsoap.org/soap/envelope/" }; -void SoapMessage::ToXml(std::string* xml_string) { - assert(soapenv_ns_.IsValid() && - service_ns_.IsValid() && - !operation_.empty()); +void SoapMessage::ToXml(bool format_raw, const std::string& indent, + std::string* xml_string) { + assert(soapenv_ns_.IsValid() && service_ns_.IsValid() && !operation_.empty()); pugi::xml_document xdoc; - // TODO(Adam): - // When save with format_default, declaration will be generated - // automatically but without encoding. - // pugi::xml_node xdecl = xdoc.prepend_child(pugi::node_declaration); - // xdecl.append_attribute("version").set_value("1.0"); - pugi::xml_node xroot = soap_xml::AddChild(xdoc, soapenv_ns_.name, "Envelope"); soap_xml::AddNSAttr(xroot, soapenv_ns_.name, soapenv_ns_.url); @@ -32,8 +25,12 @@ void SoapMessage::ToXml(std::string* xml_string) { ToXmlBody(xbody); - soap_xml::XmlStrRefWriter writer(xml_string); - xdoc.save(writer, "\t", pugi::format_default, pugi::encoding_utf8); + soap_xml::XmlStringWriter writer(xml_string); + + unsigned int flags = format_raw ? pugi::format_raw : pugi::format_indent; + + // Use print() instead of save() for no declaration or BOM. + xdoc.print(writer, indent.c_str(), flags, pugi::encoding_utf8); } bool SoapMessage::FromXml(const std::string& xml_string) { diff --git a/webcc/soap_message.h b/webcc/soap_message.h index e7f5240..71dfa8c 100644 --- a/webcc/soap_message.h +++ b/webcc/soap_message.h @@ -11,8 +11,7 @@ namespace webcc { // XML namespace name/url pair. // E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" } -class SoapNamespace { - public: +struct SoapNamespace { std::string name; std::string url; @@ -47,7 +46,8 @@ class SoapMessage { } // Convert to SOAP request XML. - void ToXml(std::string* xml_string); + void ToXml(bool format_raw, const std::string& indent, + std::string* xml_string); // Parse from SOAP request XML. bool FromXml(const std::string& xml_string); diff --git a/webcc/soap_parameter.h b/webcc/soap_parameter.h new file mode 100644 index 0000000..0c04d80 --- /dev/null +++ b/webcc/soap_parameter.h @@ -0,0 +1,79 @@ +#ifndef WEBCC_SOAP_PARAMETER_H_ +#define WEBCC_SOAP_PARAMETER_H_ + +#include + +namespace webcc { + +// Key-value SOAP parameter. +class SoapParameter { + public: + SoapParameter() : as_cdata_(false) { + } + + SoapParameter(const SoapParameter&) = default; + SoapParameter& operator=(const SoapParameter&) = default; + + SoapParameter(const std::string& key, const char* value) + : key_(key), value_(value), + as_cdata_(false) { + } + + SoapParameter(const std::string& key, const std::string& value, + bool as_cdata = false) + : key_(key), value_(value), as_cdata_(as_cdata) { + } + + SoapParameter(const std::string& key, std::string&& value, + bool as_cdata = false) + : key_(key), value_(std::move(value)), as_cdata_(as_cdata) { + } + + SoapParameter(const std::string& key, int value) + : key_(key), value_(std::to_string(value)), + as_cdata_(false) { + } + + SoapParameter(const std::string& key, double value) + : key_(key), value_(std::to_string(value)), + as_cdata_(false) { + } + + SoapParameter(const std::string& key, bool value) + : key_(key), value_(value ? "true" : "false"), + as_cdata_(false) { + } + + // Use "= default" if drop the support of VS 2013. + SoapParameter(SoapParameter&& rhs) + : key_(std::move(rhs.key_)), value_(std::move(rhs.value_)), + as_cdata_(rhs.as_cdata_) { + } + + // Use "= default" if drop the support of VS 2013. + SoapParameter& operator=(SoapParameter&& rhs) { + if (&rhs != this) { + key_ = std::move(rhs.key_); + value_ = std::move(rhs.value_); + as_cdata_ = rhs.as_cdata_; + } + return *this; + } + + const std::string& key() const { return key_; } + const std::string& value() const { return value_; } + + const char* c_key() const { return key_.c_str(); } + const char* c_value() const { return value_.c_str(); } + + bool as_cdata() const { return as_cdata_; } + + private: + std::string key_; + std::string value_; + bool as_cdata_; +}; + +} // namespace webcc + +#endif // WEBCC_SOAP_PARAMETER_H_ diff --git a/webcc/soap_request.cc b/webcc/soap_request.cc index ea7332b..ac25a2b 100644 --- a/webcc/soap_request.cc +++ b/webcc/soap_request.cc @@ -4,16 +4,16 @@ namespace webcc { -void SoapRequest::AddParameter(const Parameter& parameter) { +void SoapRequest::AddParameter(const SoapParameter& parameter) { parameters_.push_back(parameter); } -void SoapRequest::AddParameter(Parameter&& parameter) { +void SoapRequest::AddParameter(SoapParameter&& parameter) { parameters_.push_back(std::move(parameter)); } const std::string& SoapRequest::GetParameter(const std::string& key) const { - for (const Parameter& p : parameters_) { + for (const SoapParameter& p : parameters_) { if (p.key() == key) { return p.value(); } @@ -27,9 +27,12 @@ void SoapRequest::ToXmlBody(pugi::xml_node xbody) { pugi::xml_node xop = soap_xml::AddChild(xbody, service_ns_.name, operation_); soap_xml::AddNSAttr(xop, service_ns_.name, service_ns_.url); - for (Parameter& p : parameters_) { + for (SoapParameter& p : parameters_) { pugi::xml_node xparam = soap_xml::AddChild(xop, service_ns_.name, p.key()); - xparam.text().set(p.value().c_str()); + + // xparam.text().set() also works for PCDATA. + xparam.append_child(p.as_cdata() ? pugi::node_cdata : pugi::node_pcdata) + .set_value(p.c_value()); } } @@ -46,7 +49,8 @@ bool SoapRequest::FromXmlBody(pugi::xml_node xbody) { while (xparameter) { parameters_.push_back({ soap_xml::GetNameNoPrefix(xparameter), - std::string(xparameter.text().as_string()) + // xparameter.text().get/as_string() also works. + std::string(xparameter.child_value()) }); xparameter = xparameter.next_sibling(); diff --git a/webcc/soap_request.h b/webcc/soap_request.h index 33bc305..cf145e0 100644 --- a/webcc/soap_request.h +++ b/webcc/soap_request.h @@ -5,6 +5,7 @@ #include #include "webcc/soap_message.h" +#include "webcc/soap_parameter.h" namespace webcc { @@ -13,9 +14,9 @@ namespace webcc { // request body. class SoapRequest : public SoapMessage { public: - void AddParameter(const Parameter& parameter); + void AddParameter(const SoapParameter& parameter); - void AddParameter(Parameter&& parameter); + void AddParameter(SoapParameter&& parameter); // Get parameter value by key. const std::string& GetParameter(const std::string& key) const; @@ -25,7 +26,7 @@ class SoapRequest : public SoapMessage { bool FromXmlBody(pugi::xml_node xbody) override; private: - std::vector parameters_; + std::vector parameters_; }; } // namespace webcc diff --git a/webcc/soap_request_handler.cc b/webcc/soap_request_handler.cc index 149e41f..77beb7a 100644 --- a/webcc/soap_request_handler.cc +++ b/webcc/soap_request_handler.cc @@ -36,7 +36,7 @@ void SoapRequestHandler::HandleConnection(HttpConnectionPtr connection) { } std::string content; - soap_response.ToXml(&content); + soap_response.ToXml(format_raw_, indent_str_, &content); connection->SetResponseContent(std::move(content), kTextXmlUtf8); connection->SendResponse(HttpStatus::kOK); } diff --git a/webcc/soap_request_handler.h b/webcc/soap_request_handler.h index 42239d2..273fc69 100644 --- a/webcc/soap_request_handler.h +++ b/webcc/soap_request_handler.h @@ -10,9 +10,16 @@ namespace webcc { class SoapRequestHandler : public HttpRequestHandler { public: - SoapRequestHandler() = default; + SoapRequestHandler() : format_raw_(true) {} + ~SoapRequestHandler() override = default; + void set_format_raw(bool format_raw) { format_raw_ = format_raw; } + + void set_indent_str(const std::string& indent_str) { + indent_str_ = indent_str; + } + bool Bind(SoapServicePtr service, const std::string& url); private: @@ -22,6 +29,13 @@ class SoapRequestHandler : public HttpRequestHandler { typedef std::map UrlServiceMap; UrlServiceMap url_service_map_; + + // Format response XML without any indentation or line breaks. + bool format_raw_; + + // Indent string for response XML. + // Applicable when |format_raw_| is false. + std::string indent_str_; }; } // namespace webcc diff --git a/webcc/soap_response.cc b/webcc/soap_response.cc index 0a2cec4..108e5df 100644 --- a/webcc/soap_response.cc +++ b/webcc/soap_response.cc @@ -1,6 +1,7 @@ #include "webcc/soap_response.h" #include + #include "webcc/soap_xml.h" namespace webcc { @@ -12,7 +13,10 @@ void SoapResponse::ToXmlBody(pugi::xml_node xbody) { pugi::xml_node xresult = soap_xml::AddChild(xop, service_ns_.name, result_name_); - xresult.text().set(result_.c_str()); + + // xresult.text().set() also works for PCDATA. + xresult.append_child(is_cdata_ ? pugi::node_cdata : pugi::node_pcdata) + .set_value(result_.c_str()); } bool SoapResponse::FromXmlBody(pugi::xml_node xbody) { @@ -25,7 +29,9 @@ bool SoapResponse::FromXmlBody(pugi::xml_node xbody) { pugi::xml_node xresult = soap_xml::GetChildNoNS(xresponse, result_name_); if (xresult) { - result_ = xresult.text().get(); + // The value of the first child node of type PCDATA/CDATA. + // xresult.text().get/as_string() also works. + result_ = xresult.child_value(); return true; } } diff --git a/webcc/soap_response.h b/webcc/soap_response.h index a7499dd..ff61f29 100644 --- a/webcc/soap_response.h +++ b/webcc/soap_response.h @@ -8,9 +8,10 @@ namespace webcc { -// SOAP response. class SoapResponse : public SoapMessage { public: + SoapResponse() : is_cdata_(false) {} + // Could be "Price" for an operation/method like "GetXyzPrice". // Really depend on the service. // Most services use a general name "Result". @@ -18,12 +19,14 @@ class SoapResponse : public SoapMessage { result_name_ = result_name; } - void set_result(const std::string& result) { + void set_result(const std::string& result, bool is_cdata) { result_ = result; + is_cdata_ = is_cdata; } - void set_result(std::string&& result) { + void set_result_moved(std::string&& result, bool is_cdata) { result_ = std::move(result); + is_cdata_ = is_cdata; } std::string result_moved() { @@ -46,6 +49,9 @@ class SoapResponse : public SoapMessage { // Result value. std::string result_; + + // CDATA result. + bool is_cdata_; }; } // namespace webcc diff --git a/webcc/soap_server.h b/webcc/soap_server.h index 043371b..e664ca9 100644 --- a/webcc/soap_server.h +++ b/webcc/soap_server.h @@ -5,8 +5,8 @@ #include -#include "webcc/soap_request_handler.h" #include "webcc/http_server.h" +#include "webcc/soap_request_handler.h" namespace webcc { @@ -18,6 +18,14 @@ class SoapServer : public HttpServer { ~SoapServer() override = default; + void set_format_raw(bool format_raw) { + request_handler_.set_format_raw(format_raw); + } + + void set_indent_str(const std::string& indent_str) { + request_handler_.set_indent_str(indent_str); + } + // Bind a SOAP service to the given URL path. // The |url| path must start with "/", e.g., "/calculator". // Binding to the same URL multiple times is allowed, but only the last diff --git a/webcc/soap_xml.cc b/webcc/soap_xml.cc index 5ac7f9b..fb5afc3 100644 --- a/webcc/soap_xml.cc +++ b/webcc/soap_xml.cc @@ -94,7 +94,7 @@ bool PrettyPrint(std::ostream& os, const std::string& xml_string, return false; } - xdoc.save(os, indent); + xdoc.print(os, indent); return true; } diff --git a/webcc/soap_xml.h b/webcc/soap_xml.h index 4f6b938..13b8537 100644 --- a/webcc/soap_xml.h +++ b/webcc/soap_xml.h @@ -58,11 +58,11 @@ std::string GetNSAttr(const pugi::xml_node& xnode, // pugi::xml_document xdoc; // ... // std::string xml_string; -// XmlStrRefWriter writer(&xml_string); -// xdoc.save(writer, "\t", pugi::format_default, pugi::encoding_utf8); -class XmlStrRefWriter : public pugi::xml_writer { +// XmlStringWriter writer(&xml_string); +// xdoc.save/print(writer); +class XmlStringWriter : public pugi::xml_writer { public: - explicit XmlStrRefWriter(std::string* result) : result_(result) { + explicit XmlStringWriter(std::string* result) : result_(result) { result_->clear(); } diff --git a/webcc/url.cc b/webcc/url.cc index b8c6a89..29612d1 100644 --- a/webcc/url.cc +++ b/webcc/url.cc @@ -2,7 +2,6 @@ #include #include -#include // for move() namespace webcc { @@ -89,7 +88,7 @@ void UrlQuery::Remove(const std::string& key) { const std::string& UrlQuery::Get(const std::string& key) const { auto it = Find(key); if (it != parameters_.end()) { - return it->value(); + return it->second; } static const std::string kEmptyValue; @@ -101,11 +100,11 @@ std::string UrlQuery::ToString() const { return ""; } - std::string str = parameters_[0].ToString(); + std::string str = parameters_[0].first + "=" + parameters_[0].second; for (std::size_t i = 1; i < parameters_.size(); ++i) { str += "&"; - str += parameters_[i].ToString(); + str += parameters_[i].first + "=" + parameters_[i].second; } return str; @@ -114,7 +113,7 @@ std::string UrlQuery::ToString() const { UrlQuery::ConstIterator UrlQuery::Find(const std::string& key) const { return std::find_if(parameters_.begin(), parameters_.end(), - [&key](const Parameter& p) { return p.key() == key; }); + [&key](const SoapParameter& p) { return p.first == key; }); } // ----------------------------------------------------------------------------- diff --git a/webcc/url.h b/webcc/url.h index 2a3cd36..e6a915b 100644 --- a/webcc/url.h +++ b/webcc/url.h @@ -9,9 +9,10 @@ #include #include +#include #include -#include "webcc/globals.h" +//#include "webcc/globals.h" namespace webcc { @@ -20,7 +21,8 @@ namespace webcc { // URL query parameters. class UrlQuery { public: - typedef std::vector Parameters; + typedef std::pair SoapParameter; + typedef std::vector Parameters; UrlQuery() = default;