Support CDATA for SOAP request and response; Rename Parameter to SoapParameter; Add book server and client example for SOAP.

master
Adam Gu 7 years ago
parent 23d948cebb
commit 223e59cc7a

@ -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()

@ -0,0 +1,61 @@
#include "example/common/book.h"
#include <algorithm>
#include <iostream>
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<Book>::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<Book>::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);
}

@ -0,0 +1,47 @@
#ifndef EXAMPLE_COMMON_BOOK_H_
#define EXAMPLE_COMMON_BOOK_H_
#include <list>
#include <string>
// 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<Book>& 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<Book>::const_iterator FindBook(const std::string& id) const;
std::list<Book>::iterator FindBook(const std::string& id);
// Allocate a new book ID.
std::string NewID() const;
std::list<Book> books_;
};
#endif // EXAMPLE_COMMON_BOOK_H_

@ -0,0 +1,147 @@
#include "example/common/book_xml.h"
#include <cassert>
#include <functional>
#include <sstream>
#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<Book>* 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<Book>& 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<void(pugi::xml_node*)> 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<Book>& books) {
return __NewResultXml(code, message,
std::bind(BookListToXml, books, std::placeholders::_1));
}

@ -0,0 +1,111 @@
#ifndef EXAMPLE_COMMON_BOOK_XML_H_
#define EXAMPLE_COMMON_BOOK_XML_H_
#include <list>
#include <string>
#include "pugixml/pugixml.hpp"
struct Book;
// -----------------------------------------------------------------------------
// Convert the following XML node to a book object.
// <book>
// <id>1</id>
// <title>1984</title>
// <price>12.3</price>
// </book>
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.
// <books>
// <book>
// <id>1</id>
// <title>1984</title>
// <price>12.3</price>
// </book>
// ...
// </books>
bool XmlToBookList(pugi::xml_node xbooks, std::list<Book>* books);
// Convert a list of book objects to XML and append to the given parent.
void BookListToXml(const std::list<Book>& books, pugi::xml_node* xparent);
// Convert the following XML string to a book object.
// <book>
// <id>1</id>
// <title>1984</title>
// <price>12.3</price>
// </book>
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:
// <webcc type = "result">
// <status code = "{code}" message = "{message}">
// </webcc>
// 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.,
// <webcc type = "result">
// <status code = "{code}" message = "{message}">
// <book>
// <id>{book.id}</id>
// <title>{book.title}</title>
// <price>{book.price}</price>
// </book>
// </webcc>
// Create a result XML as below:
// <webcc type = "result">
// <status code = "{code}" message = "{message}">
// </webcc>
std::string NewResultXml(int code, const char* message);
// Create a result XML as below:
// <webcc type = "result">
// <status code = "{code}" message = "{message}">
// <{node}>
// <{key}>{value}</{key}>
// </{node}>
// </webcc>
std::string NewResultXml(int code, const char* message, const char* node,
const char* key, const char* value);
// Create a result XML as below:
// <webcc type = "result">
// <status code = "{code}" message = "{message}">
// <book>
// <id>{book.id}</id>
// <title>{book.title}</title>
// <price>{book.price}</price>
// </book>
// </webcc>
std::string NewResultXml(int code, const char* message, const Book& book);
// Create a result XML as below:
// <webcc type = "result">
// <status code = "{code}" message = "{message}">
// <books>
// <book>
// <id>{book.id}</id>
// <title>{book.title}</title>
// <price>{book.price}</price>
// </book>
// ...
// </books>
// </webcc>
std::string NewResultXml(int code, const char* message,
const std::list<Book>& books);
#endif // EXAMPLE_COMMON_BOOK_XML_H_

@ -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}")

@ -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}")

@ -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":

@ -1,4 +1,4 @@
#include "example/rest_book_server/book_services.h"
#include "example/rest_book_server/services.h"
#include <iostream>
#include <list>
@ -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<Book>& 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<Book>::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<Book>::iterator FindBook(const std::string& id) {
return std::find_if(books_.begin(), books_.end(),
[&id](Book& book) { return book.id == id; });
}
std::list<Book> 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<std::string>& 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<std::string>& 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);
}

@ -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 <string>
#include <vector>
#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_

@ -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}")

@ -0,0 +1,136 @@
#include "example/soap_book_client/book_client.h"
#include <iostream>
#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<Book>* 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<webcc::SoapParameter> parameters{
{ std::move(parameter) }
};
return CallX(operation, std::move(parameters), result_str);
}
bool BookClient::CallX(const std::string& operation,
std::vector<webcc::SoapParameter>&& 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<bool(pugi::xml_node)> 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;
}

@ -0,0 +1,53 @@
#ifndef EXAMPLE_SOAP_BOOK_CLIENT_H_
#define EXAMPLE_SOAP_BOOK_CLIENT_H_
#include <functional>
#include <string>
#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<Book>* 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<webcc::SoapParameter>&& parameters,
std::string* result_str);
bool ParseResultXml(const std::string& result_xml,
std::function<bool(pugi::xml_node)> callback);
// Last status.
int code_;
std::string message_;
};
#endif // EXAMPLE_SOAP_BOOK_CLIENT_H_

@ -0,0 +1,65 @@
#include <iostream>
#include "webcc/logger.h"
#include "example/soap_book_client/book_client.h"
void Help(const char* argv0) {
std::cout << "Usage: " << argv0 << " <host> <port>" << 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<Book> 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;
}

@ -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}")

@ -0,0 +1,220 @@
#include "example/soap_book_server/book_service.h"
#include <iostream>
#include <list>
#include <sstream>
#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:
// <soap:Envelope xmlns:soap="...">
// <soap:Body>
// <ser:CreateBook xmlns:ser="..." />
// <ser:book>
// <![CDATA[
// <book>
// <title>1984</title>
// <price>12.3</price>
// </book>
// ]]>
// </ser:book>
// </ser:CreateBook>
// </soap:Body>
// </soap:Envelope>
// Response SOAP envelope:
// <soap:Envelope xmlns:soap="...">
// <soap:Body>
// <ser:CreateBookResponse xmlns:ser="...">
// <ser:Result>
// <![CDATA[
// <webcc type = "response">
// <status code = "0" message = "ok">
// <book>
// <id>1</id>
// </book>
// </webcc>
// ]]>
// </ser:Result>
// </ser:CreateBookResponse>
// </soap:Body>
// </soap:Envelope>
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:
// <soap:Envelope xmlns:soap="...">
// <soap:Body>
// <ser:GetBook xmlns:ser="..." />
// <ser:id>1</ser:id>
// </ser:GetBook>
// </soap:Body>
// </soap:Envelope>
// Response SOAP envelope:
// <soap:Envelope xmlns:soap="...">
// <soap:Body>
// <ser:GetBookResponse xmlns:ser="...">
// <ser:Result>
// <![CDATA[
// <webcc type = "response">
// <status code = "0" message = "ok">
// <book>
// <id>1</id>
// <title>1984</title>
// <price>12.3</price>
// </book>
// </webcc>
// ]]>
// </ser:Result>
// </ser:GetBookResponse>
// </soap:Body>
// </soap:Envelope>
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:
// <soap:Envelope xmlns:soap="...">
// <soap:Body>
// <ser:ListBooks xmlns:ser="..." />
// </soap:Body>
// </soap:Envelope>
// Response SOAP envelope:
// <soap:Envelope xmlns:soap="...">
// <soap:Body>
// <ser:ListBooksResponse xmlns:ser="...">
// <ser:Result>
// <![CDATA[
// <webcc type = "response">
// <status code = "0" message = "ok">
// <books>
// <book>
// <id>1</id>
// <title>1984</title>
// <price>12.3</price>
// </book>
// ...
// </books>
// </webcc>
// ]]>
// </ser:Result>
// </ser:ListBooksResponse>
// </soap:Body>
// </soap:Envelope>
const std::list<Book>& 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:
// <soap:Envelope xmlns:soap="...">
// <soap:Body>
// <ser:DeleteBook xmlns:ser="..." />
// <ser:id>1</ser:id>
// </ser:DeleteBook>
// </soap:Body>
// </soap:Envelope>
// Response SOAP envelope:
// <soap:Envelope xmlns:soap="...">
// <soap:Body>
// <ser:DeleteBookResponse xmlns:ser="...">
// <ser:Result>
// <![CDATA[
// <webcc type = "response">
// <status code = "0" message = "ok">
// </webcc>
// ]]>
// </ser:Result>
// </ser:DeleteBookResponse>
// </soap:Body>
// </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;
}

@ -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_

@ -0,0 +1,40 @@
#include <iostream>
#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 << " <port>" << 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::uint16_t>(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<BookService>(), "/book");
server.Run();
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
return 1;
}
return 0;
}

@ -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}")

@ -1,89 +0,0 @@
#include "example/soap_calc_client/calc_client.h"
#include <iostream>
#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<webcc::Parameter> 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;
}

@ -1,35 +0,0 @@
#ifndef CALC_CLIENT_H_
#define CALC_CLIENT_H_
#include <string>
#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_

@ -1,13 +1,95 @@
#include <iostream>
#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<webcc::SoapParameter> 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 << " <host> <port>" << 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;
}

@ -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}")

@ -0,0 +1,97 @@
#include <iostream>
#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<webcc::SoapParameter> 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;
}

@ -3,21 +3,10 @@
#include <functional>
#include <string>
// 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<double(double, double)> 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;
}

@ -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);
};

@ -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 << " <port>" << 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<CalcService>(), "/calculator");
server.Run();
} catch (const std::exception& e) {

@ -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<std::string>& url_sub_matches,
const webcc::UrlQuery& query,

@ -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})

@ -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

@ -2,7 +2,6 @@
#define WEBCC_GLOBALS_H_
#include <string>
#include <vector>
// -----------------------------------------------------------------------------
// 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_

@ -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<Parameter>&& parameters,
std::vector<SoapParameter>&& 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) {

@ -4,8 +4,8 @@
#include <string>
#include <vector>
#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<Parameter>&& parameters,
std::vector<SoapParameter>&& 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

@ -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) {

@ -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);

@ -0,0 +1,79 @@
#ifndef WEBCC_SOAP_PARAMETER_H_
#define WEBCC_SOAP_PARAMETER_H_
#include <string>
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_

@ -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();

@ -5,6 +5,7 @@
#include <vector>
#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<Parameter> parameters_;
std::vector<SoapParameter> parameters_;
};
} // namespace webcc

@ -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);
}

@ -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<std::string, SoapServicePtr> 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

@ -1,6 +1,7 @@
#include "webcc/soap_response.h"
#include <cassert>
#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;
}
}

@ -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

@ -5,8 +5,8 @@
#include <string>
#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

@ -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;
}

@ -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();
}

@ -2,7 +2,6 @@
#include <algorithm>
#include <sstream>
#include <utility> // 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; });
}
// -----------------------------------------------------------------------------

@ -9,9 +9,10 @@
#include <map>
#include <string>
#include <utility>
#include <vector>
#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<Parameter> Parameters;
typedef std::pair<std::string, std::string> SoapParameter;
typedef std::vector<SoapParameter> Parameters;
UrlQuery() = default;

Loading…
Cancel
Save