From 616f5a3f5ef1e798a38977e1e039977d0c685259 Mon Sep 17 00:00:00 2001 From: Chunting Gu Date: Fri, 15 Mar 2019 14:40:36 +0800 Subject: [PATCH] Reorganize examples; fix soap issues. --- CMakeLists.txt | 13 +- example/CMakeLists.txt | 33 -- example/github_client/CMakeLists.txt | 2 - example/http_client/CMakeLists.txt | 2 - example/rest_book_client/CMakeLists.txt | 20 -- example/rest_book_client/main.cc | 290 ------------------ example/rest_book_server/CMakeLists.txt | 23 -- example/rest_book_server/main.cc | 60 ---- example/rest_book_server/services.cc | 127 -------- example/rest_book_server/services.h | 62 ---- example/soap_book_client/CMakeLists.txt | 22 -- example/soap_book_client/book_client.h | 56 ---- example/soap_book_client/main.cc | 73 ----- example/soap_book_server/CMakeLists.txt | 23 -- example/soap_book_server/book_service.h | 25 -- example/soap_book_server/main.cc | 48 --- example/soap_calc_server/CMakeLists.txt | 6 - example/soap_calc_server/calc_service.h | 15 - example/soap_calc_server/main.cc | 40 --- examples/CMakeLists.txt | 55 ++++ {example => examples}/common/book.cc | 2 +- {example => examples}/common/book.h | 0 {example => examples}/common/book_json.cc | 4 +- {example => examples}/common/book_json.h | 0 {example => examples}/common/book_xml.cc | 4 +- {example => examples}/common/book_xml.h | 0 .../main.cc => examples/github_client.cc | 28 +- .../main.cc => examples/http_client.cc | 27 +- examples/rest_book_client.cc | 282 +++++++++++++++++ examples/rest_book_server.cc | 239 +++++++++++++++ .../soap_book_client.cc | 134 +++++++- .../soap_book_server.cc | 73 ++++- {example => examples}/soap_calc_client.cc | 18 +- .../soap_calc_client_parasoft.cc | 7 +- .../soap_calc_server.cc | 52 +++- unittest/rest_service_manager_test.cc | 42 +-- webcc/CMakeLists.txt | 24 +- webcc/globals.h | 1 - webcc/http_client_session.cc | 19 ++ webcc/http_client_session.h | 8 + webcc/http_response.cc | 3 + webcc/rest_request_handler.cc | 10 +- webcc/rest_service.cc | 8 +- webcc/rest_service.h | 12 +- webcc/rest_service_manager.cc | 8 +- webcc/rest_service_manager.h | 9 +- webcc/soap_client.cc | 25 +- webcc/soap_client.h | 12 +- webcc/soap_request_handler.cc | 4 +- 49 files changed, 959 insertions(+), 1091 deletions(-) delete mode 100644 example/CMakeLists.txt delete mode 100644 example/github_client/CMakeLists.txt delete mode 100644 example/http_client/CMakeLists.txt delete mode 100644 example/rest_book_client/CMakeLists.txt delete mode 100644 example/rest_book_client/main.cc delete mode 100644 example/rest_book_server/CMakeLists.txt delete mode 100644 example/rest_book_server/main.cc delete mode 100644 example/rest_book_server/services.cc delete mode 100644 example/rest_book_server/services.h delete mode 100644 example/soap_book_client/CMakeLists.txt delete mode 100644 example/soap_book_client/book_client.h delete mode 100644 example/soap_book_client/main.cc delete mode 100644 example/soap_book_server/CMakeLists.txt delete mode 100644 example/soap_book_server/book_service.h delete mode 100644 example/soap_book_server/main.cc delete mode 100644 example/soap_calc_server/CMakeLists.txt delete mode 100644 example/soap_calc_server/calc_service.h delete mode 100644 example/soap_calc_server/main.cc create mode 100644 examples/CMakeLists.txt rename {example => examples}/common/book.cc (97%) rename {example => examples}/common/book.h (100%) rename {example => examples}/common/book_json.cc (93%) rename {example => examples}/common/book_json.h (100%) rename {example => examples}/common/book_xml.cc (98%) rename {example => examples}/common/book_xml.h (100%) rename example/github_client/main.cc => examples/github_client.cc (84%) rename example/http_client/main.cc => examples/http_client.cc (84%) create mode 100644 examples/rest_book_client.cc create mode 100644 examples/rest_book_server.cc rename example/soap_book_client/book_client.cc => examples/soap_book_client.cc (52%) rename example/soap_book_server/book_service.cc => examples/soap_book_server.cc (76%) rename {example => examples}/soap_calc_client.cc (87%) rename {example => examples}/soap_calc_client_parasoft.cc (92%) rename example/soap_calc_server/calc_service.cc => examples/soap_calc_server.cc (54%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d7faa7..8c6abbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,6 @@ endif() project(webcc) -option(WEBCC_ENABLE_REST "Enable REST support?" ON) option(WEBCC_ENABLE_SOAP "Enable SOAP support (need pugixml)?" ON) option(WEBCC_ENABLE_UNITTEST "Build unit test?" ON) option(WEBCC_ENABLE_EXAMPLES "Build examples?" ON) @@ -118,14 +117,12 @@ endif() add_subdirectory(webcc) if(WEBCC_ENABLE_EXAMPLES) - if(WEBCC_ENABLE_REST) - # For including jsoncpp as "json/json.h". - include_directories(${THIRD_PARTY_DIR}/src/jsoncpp) - # REST examples need jsoncpp to parse and create JSON. - add_subdirectory(${THIRD_PARTY_DIR}/src/jsoncpp) - endif() + # For including jsoncpp as "json/json.h". + include_directories(${THIRD_PARTY_DIR}/src/jsoncpp) + # REST examples need jsoncpp to parse and create JSON. + add_subdirectory(${THIRD_PARTY_DIR}/src/jsoncpp) - add_subdirectory(example) + add_subdirectory(examples) endif() if(WEBCC_ENABLE_UNITTEST) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt deleted file mode 100644 index dc827a6..0000000 --- a/example/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# Examples - -# Common libraries to link for examples. -set(EXAMPLE_COMMON_LIBS webcc ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} - "${CMAKE_THREAD_LIBS_INIT}") -if(WIN32) - set(EXAMPLE_COMMON_LIBS ${EXAMPLE_COMMON_LIBS} crypt32) -endif() - -if(UNIX) - # Add `-ldl` for Linux to avoid "undefined reference to `dlopen'". - set(EXAMPLE_COMMON_LIBS ${EXAMPLE_COMMON_LIBS} ${CMAKE_DL_LIBS}) -endif() - -add_subdirectory(http_client) - -if(WEBCC_ENABLE_REST) - add_subdirectory(rest_book_server) - # add_subdirectory(rest_book_client) - add_subdirectory(github_client) -endif() - -if(WEBCC_ENABLE_SOAP) - add_subdirectory(soap_calc_server) - add_subdirectory(soap_book_server) - add_subdirectory(soap_book_client) -endif() - -add_executable(soap_calc_client soap_calc_client.cc) -add_executable(soap_calc_client_parasoft soap_calc_client_parasoft.cc) - -target_link_libraries(soap_calc_client ${EXAMPLE_COMMON_LIBS} pugixml) -target_link_libraries(soap_calc_client_parasoft ${EXAMPLE_COMMON_LIBS} pugixml) diff --git a/example/github_client/CMakeLists.txt b/example/github_client/CMakeLists.txt deleted file mode 100644 index 2384bca..0000000 --- a/example/github_client/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_executable(github_client main.cc) -target_link_libraries(github_client ${EXAMPLE_COMMON_LIBS} jsoncpp) diff --git a/example/http_client/CMakeLists.txt b/example/http_client/CMakeLists.txt deleted file mode 100644 index 0907baa..0000000 --- a/example/http_client/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_executable(http_client main.cc) -target_link_libraries(http_client ${EXAMPLE_COMMON_LIBS}) diff --git a/example/rest_book_client/CMakeLists.txt b/example/rest_book_client/CMakeLists.txt deleted file mode 100644 index 4df2c68..0000000 --- a/example/rest_book_client/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -set(TARGET_NAME rest_book_client) - -set(SRCS - ../common/book.cc - ../common/book.h - ../common/book_json.cc - ../common/book_json.h - main.cc) - -add_executable(${TARGET_NAME} ${SRCS}) - -target_link_libraries(${TARGET_NAME} ${EXAMPLE_COMMON_LIBS} jsoncpp) - -# Install VLD DLLs to build dir so that the example can be launched from -# inside VS. -if(WEBCC_ENABLE_VLD) - install(DIRECTORY ${THIRD_PARTY_DIR}/win32/bin/debug/vld/ - CONFIGURATIONS Debug - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Debug) -endif() diff --git a/example/rest_book_client/main.cc b/example/rest_book_client/main.cc deleted file mode 100644 index d50569f..0000000 --- a/example/rest_book_client/main.cc +++ /dev/null @@ -1,290 +0,0 @@ -#include -#include - -#include "json/json.h" - -#include "webcc/logger.h" -#include "webcc/rest_client.h" - -#include "example/common/book.h" -#include "example/common/book_json.h" - -#if (defined(WIN32) || defined(_WIN64)) -#if defined(_DEBUG) && defined(WEBCC_ENABLE_VLD) -#pragma message ("< include vld.h >") -#include "vld/vld.h" -#pragma comment(lib, "vld") -#endif -#endif - -// ----------------------------------------------------------------------------- - -class BookClientBase { -public: - BookClientBase(const std::string& host, const std::string& port, - int timeout_seconds) - : host_(host), port_(port) { - http_client_.SetTimeout(timeout_seconds); - } - - virtual ~BookClientBase() = default; - -public: - // Helper function to make a request. - webcc::HttpRequestPtr MakeRequest(const std::string& method, - const std::string& url, - std::string&& content = "") { - auto request = webcc::HttpRequest::New(method, url, host_, port_); - request->AcceptAppJson(); - if (!content.empty()) { - request->SetContentInAppJsonUtf8(JsonToString(content), true); - } - request->Prepare(); - return request; - } - - // Log the socket communication error. - void LogError() { - if (http_client_.timed_out()) { - LOG_ERRO("%s (timed out)", webcc::DescribeError(http_client_.error())); - } else { - LOG_ERRO(webcc::DescribeError(http_client_.error())); - } - } - - // Check HTTP response status. - bool CheckStatus(webcc::http::Status expected_status) { - int status = http_client_.response_status(); - if (status != expected_status) { - LOG_ERRO("HTTP status error (actual: %d, expected: %d).", - status, expected_status); - return false; - } - return true; - } - - std::string host_; - std::string port_; - webcc::HttpClient http_client_; -}; - -// ----------------------------------------------------------------------------- - -class BookListClient : public BookClientBase { -public: - BookListClient(const std::string& host, const std::string& port, - int timeout_seconds) - : BookClientBase(host, port, timeout_seconds) { - } - - bool ListBooks(std::list* books) { - auto request = MakeRequest(webcc::kHttpGet, "/books"); - - if (!http_client_.Request(*request)) { - // Socket communication error. - LogError(); - return false; - } - - if (!CheckStatus(webcc::http::Status::kOK)) { - // Response HTTP status error. - return false; - } - - Json::Value rsp_json = StringToJson(http_client_.response_content()); - - if (!rsp_json.isArray()) { - return false; // Should be a JSON array of books. - } - - for (Json::ArrayIndex i = 0; i < rsp_json.size(); ++i) { - books->push_back(JsonToBook(rsp_json[i])); - } - - return true; - } - - bool CreateBook(const std::string& title, double price, std::string* id) { - Json::Value req_json; - req_json["title"] = title; - req_json["price"] = price; - - auto request = MakeRequest(webcc::kHttpPost, "/books", - JsonToString(req_json)); - - if (!http_client_.Request(*request)) { - LogError(); - return false; - } - - if (!CheckStatus(webcc::http::Status::kCreated)) { - return false; - } - - Json::Value rsp_json = StringToJson(http_client_.response_content()); - *id = rsp_json["id"].asString(); - - return !id->empty(); - } -}; - -// ----------------------------------------------------------------------------- - -class BookDetailClient : public BookClientBase { -public: - BookDetailClient(const std::string& host, const std::string& port, - int timeout_seconds) - : BookClientBase(host, port, timeout_seconds) { - } - - bool GetBook(const std::string& id, Book* book) { - auto request = MakeRequest(webcc::kHttpGet, "/books/" + id); - - if (!http_client_.Request(*request)) { - LogError(); - return false; - } - - if (!CheckStatus(webcc::http::Status::kOK)) { - return false; - } - - return JsonStringToBook(http_client_.response_content(), book); - } - - bool UpdateBook(const std::string& id, const std::string& title, - double price) { - Json::Value json; - json["title"] = title; - json["price"] = price; - - auto request = MakeRequest(webcc::kHttpPut, "/books/" + id, - JsonToString(json)); - - if (!http_client_.Request(*request)) { - LogError(); - return false; - } - - if (!CheckStatus(webcc::http::Status::kOK)) { - return false; - } - - return true; - } - - bool DeleteBook(const std::string& id) { - auto request = MakeRequest(webcc::kHttpDelete, "/books/" + id); - - if (!http_client_.Request(*request)) { - LogError(); - return false; - } - - if (!CheckStatus(webcc::http::Status::kOK)) { - return false; - } - - return true; - } -}; - -// ----------------------------------------------------------------------------- - -void PrintSeparator() { - std::cout << std::string(80, '-') << std::endl; -} - -void PrintBook(const Book& book) { - std::cout << "Book: " << book << std::endl; -} - -void PrintBookList(const std::list& books) { - std::cout << "Book list: " << books.size() << std::endl; - for (const Book& book : books) { - std::cout << " Book: " << book << std::endl; - } -} - -// ----------------------------------------------------------------------------- - -void Help(const char* argv0) { - std::cout << "Usage: " << argv0 << " [timeout]" << std::endl; - std::cout << " E.g.," << std::endl; - std::cout << " " << argv0 << " localhost 8080" << std::endl; - std::cout << " " << argv0 << " localhost 8080 2" << std::endl; -} - -int main(int argc, char* argv[]) { - if (argc < 3) { - Help(argv[0]); - return 1; - } - - WEBCC_LOG_INIT("", webcc::LOG_CONSOLE_FILE_OVERWRITE); - - std::string host = argv[1]; - std::string port = argv[2]; - - int timeout_seconds = -1; - if (argc > 3) { - timeout_seconds = std::atoi(argv[3]); - } - - BookListClient list_client(host, port, timeout_seconds); - BookDetailClient detail_client(host, port, timeout_seconds); - - PrintSeparator(); - - std::list books; - if (list_client.ListBooks(&books)) { - PrintBookList(books); - } - - PrintSeparator(); - - std::string id; - if (list_client.CreateBook("1984", 12.3, &id)) { - std::cout << "Book ID: " << id << std::endl; - } else { - id = "1"; - std::cout << "Book ID: " << id << " (faked)"<< std::endl; - } - - PrintSeparator(); - - books.clear(); - if (list_client.ListBooks(&books)) { - PrintBookList(books); - } - - PrintSeparator(); - - Book book; - if (detail_client.GetBook(id, &book)) { - PrintBook(book); - } - - //PrintSeparator(); - - //detail_client.UpdateBook(id, "1Q84", 32.1); - - //PrintSeparator(); - - //if (detail_client.GetBook(id, &book)) { - // PrintBook(book); - //} - - //PrintSeparator(); - - //detail_client.DeleteBook(id); - - //PrintSeparator(); - - //books.clear(); - //if (list_client.ListBooks(&books)) { - // PrintBookList(books); - //} - - return 0; -} diff --git a/example/rest_book_server/CMakeLists.txt b/example/rest_book_server/CMakeLists.txt deleted file mode 100644 index 9b89095..0000000 --- a/example/rest_book_server/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -set(TARGET_NAME rest_book_server) - -set(SRCS - ../common/book.cc - ../common/book.h - ../common/book_json.cc - ../common/book_json.h - services.cc - services.h - 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}") - -# Install VLD DLLs to build dir so that the example can be launched from -# inside VS. -if(WEBCC_ENABLE_VLD) - install(DIRECTORY ${THIRD_PARTY_DIR}/win32/bin/debug/vld/ - CONFIGURATIONS Debug - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Debug) -endif() diff --git a/example/rest_book_server/main.cc b/example/rest_book_server/main.cc deleted file mode 100644 index f4d7766..0000000 --- a/example/rest_book_server/main.cc +++ /dev/null @@ -1,60 +0,0 @@ -#include - -#include "webcc/logger.h" -#include "webcc/rest_server.h" - -#include "example/rest_book_server/services.h" - -#if (defined(WIN32) || defined(_WIN64)) -#if defined(_DEBUG) && defined(WEBCC_ENABLE_VLD) -#pragma message ("< include vld.h >") -#include "vld/vld.h" -#pragma comment(lib, "vld") -#endif -#endif - -void Help(const char* argv0) { - std::cout << "Usage: " << argv0 << " [seconds]" << std::endl; - std::cout << "If |seconds| is provided, the server will sleep these seconds " - "before sending back each response." - << std::endl; - std::cout << " E.g.," << std::endl; - std::cout << " " << argv0 << " 8080" << std::endl; - std::cout << " " << argv0 << " 8080 3" << 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])); - - int sleep_seconds = 0; - if (argc >= 3) { - sleep_seconds = std::atoi(argv[2]); - } - - std::size_t workers = 2; - - try { - webcc::RestServer server(port, workers); - - server.Bind(std::make_shared(sleep_seconds), - "/books", false); - - server.Bind(std::make_shared(sleep_seconds), - "/books/(\\d+)", true); - - server.Run(); - - } catch (const std::exception& e) { - std::cerr << "Exception: " << e.what() << std::endl; - return 1; - } - - return 0; -} diff --git a/example/rest_book_server/services.cc b/example/rest_book_server/services.cc deleted file mode 100644 index 6cc1fe1..0000000 --- a/example/rest_book_server/services.cc +++ /dev/null @@ -1,127 +0,0 @@ -#include "example/rest_book_server/services.h" - -#include -#include -#include - -#include "json/json.h" -#include "webcc/logger.h" - -#include "example/common/book.h" -#include "example/common/book_json.h" - -// ----------------------------------------------------------------------------- - -static BookStore g_book_store; - -static void Sleep(int seconds) { - if (seconds > 0) { - LOG_INFO("Sleep %d seconds...", seconds); - std::this_thread::sleep_for(std::chrono::seconds(seconds)); - } -} - -// ----------------------------------------------------------------------------- - -// Return all books as a JSON array. -void BookListService::Get(const webcc::UrlQuery& /*query*/, - webcc::RestResponse* response) { - Sleep(sleep_seconds_); - - Json::Value json(Json::arrayValue); - - for (const Book& book : g_book_store.books()) { - json.append(BookToJson(book)); - } - - response->content = JsonToString(json); - response->status = webcc::http::Status::kOK; -} - -void BookListService::Post(const std::string& request_content, - webcc::RestResponse* response) { - Sleep(sleep_seconds_); - - Book book; - if (JsonStringToBook(request_content, &book)) { - std::string id = g_book_store.AddBook(book); - - Json::Value json; - json["id"] = id; - - response->content = JsonToString(json); - response->status = webcc::http::Status::kCreated; - } else { - // Invalid JSON - response->status = webcc::http::Status::kBadRequest; - } -} - -// ----------------------------------------------------------------------------- - -void BookDetailService::Get(const webcc::UrlSubMatches& url_sub_matches, - const webcc::UrlQuery& query, - webcc::RestResponse* response) { - Sleep(sleep_seconds_); - - if (url_sub_matches.size() != 1) { - // Using kNotFound means the resource specified by the URL cannot be found. - // kBadRequest could be another choice. - response->status = webcc::http::Status::kNotFound; - return; - } - - const std::string& book_id = url_sub_matches[0]; - - const Book& book = g_book_store.GetBook(book_id); - if (book.IsNull()) { - response->status = webcc::http::Status::kNotFound; - return; - } - - response->content = BookToJsonString(book); - response->status = webcc::http::Status::kOK; -} - -void BookDetailService::Put(const webcc::UrlSubMatches& url_sub_matches, - const std::string& request_content, - webcc::RestResponse* response) { - Sleep(sleep_seconds_); - - if (url_sub_matches.size() != 1) { - response->status = webcc::http::Status::kNotFound; - return; - } - - const std::string& book_id = url_sub_matches[0]; - - Book book; - if (!JsonStringToBook(request_content, &book)) { - response->status = webcc::http::Status::kBadRequest; - return; - } - - book.id = book_id; - g_book_store.UpdateBook(book); - - response->status = webcc::http::Status::kOK; -} - -void BookDetailService::Delete(const webcc::UrlSubMatches& url_sub_matches, - webcc::RestResponse* response) { - Sleep(sleep_seconds_); - - if (url_sub_matches.size() != 1) { - response->status = webcc::http::Status::kNotFound; - return; - } - - const std::string& book_id = url_sub_matches[0]; - - if (!g_book_store.DeleteBook(book_id)) { - response->status = webcc::http::Status::kNotFound; - return; - } - - response->status = webcc::http::Status::kOK; -} diff --git a/example/rest_book_server/services.h b/example/rest_book_server/services.h deleted file mode 100644 index f710483..0000000 --- a/example/rest_book_server/services.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef EXAMPLE_REST_BOOK_SERVER_SERVICES_H_ -#define EXAMPLE_REST_BOOK_SERVER_SERVICES_H_ - -#include -#include - -#include "webcc/rest_service.h" - -// ----------------------------------------------------------------------------- - -class BookListService : public webcc::RestListService { -public: - explicit BookListService(int sleep_seconds) - : sleep_seconds_(sleep_seconds) { - } - -public: - // Get a list of books based on query parameters. - void Get(const webcc::UrlQuery& query, webcc::RestResponse* response) final; - - // Create a new book. - void Post(const std::string& request_content, - webcc::RestResponse* response) final; - -private: - // Sleep some seconds before send back the response. - // For testing timeout control in client side. - int sleep_seconds_; -}; - -// ----------------------------------------------------------------------------- - -// The URL is like '/books/{BookID}', and the 'url_sub_matches' parameter -// contains the matched book ID. -class BookDetailService : public webcc::RestDetailService { -public: - explicit BookDetailService(int sleep_seconds) - : sleep_seconds_(sleep_seconds) { - } - -public: - // Get the detailed information of a book. - void Get(const webcc::UrlSubMatches& url_sub_matches, - const webcc::UrlQuery& query, - webcc::RestResponse* response) final; - - // Update a book. - void Put(const webcc::UrlSubMatches& url_sub_matches, - const std::string& request_content, - webcc::RestResponse* response) final; - - // Delete a book. - void Delete(const webcc::UrlSubMatches& url_sub_matches, - webcc::RestResponse* response) final; - -private: - // Sleep some seconds before send back the response. - // For testing timeout control in client side. - int sleep_seconds_; -}; - -#endif // EXAMPLE_REST_BOOK_SERVER_SERVICES_H_ diff --git a/example/soap_book_client/CMakeLists.txt b/example/soap_book_client/CMakeLists.txt deleted file mode 100644 index 81ebbaf..0000000 --- a/example/soap_book_client/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -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} ${EXAMPLE_COMMON_LIBS} pugixml) - -# Install VLD DLLs to build dir so that the example can be launched from -# inside VS. -if(WEBCC_ENABLE_VLD) - install(DIRECTORY ${THIRD_PARTY_DIR}/win32/bin/debug/vld/ - CONFIGURATIONS Debug - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Debug) -endif() diff --git a/example/soap_book_client/book_client.h b/example/soap_book_client/book_client.h deleted file mode 100644 index 734136a..0000000 --- a/example/soap_book_client/book_client.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef EXAMPLE_SOAP_BOOK_CLIENT_H_ -#define EXAMPLE_SOAP_BOOK_CLIENT_H_ - -#include -#include - -#include "pugixml/pugixml.hpp" -#include "webcc/soap_client.h" - -#include "example/common/book.h" - -class BookClient { -public: - BookClient(const std::string& host, const std::string& port); - - 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 SoapClient::Request() to log error if any. - bool Call(const std::string& operation, - std::vector&& parameters, - std::string* result_str); - - void PrintError(); - - bool ParseResultXml(const std::string& result_xml, - std::function callback); - - webcc::SoapClient soap_client_; - - // 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 deleted file mode 100644 index 6c21e60..0000000 --- a/example/soap_book_client/main.cc +++ /dev/null @@ -1,73 +0,0 @@ -#include - -#include "webcc/logger.h" - -#include "example/soap_book_client/book_client.h" - -#if (defined(WIN32) || defined(_WIN64)) -#if defined(_DEBUG) && defined(WEBCC_ENABLE_VLD) -#pragma message ("< include vld.h >") -#include "vld/vld.h" -#pragma comment(lib, "vld") -#endif -#endif - -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 deleted file mode 100644 index 7fed802..0000000 --- a/example/soap_book_server/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -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}") - -# Install VLD DLLs to build dir so that the example can be launched from -# inside VS. -if(WEBCC_ENABLE_VLD) - install(DIRECTORY ${THIRD_PARTY_DIR}/win32/bin/debug/vld/ - CONFIGURATIONS Debug - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Debug) -endif() diff --git a/example/soap_book_server/book_service.h b/example/soap_book_server/book_service.h deleted file mode 100644 index 2c0e133..0000000 --- a/example/soap_book_server/book_service.h +++ /dev/null @@ -1,25 +0,0 @@ -#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 deleted file mode 100644 index 0f73345..0000000 --- a/example/soap_book_server/main.cc +++ /dev/null @@ -1,48 +0,0 @@ -#include - -#include "webcc/logger.h" -#include "webcc/soap_server.h" - -#include "example/soap_book_server/book_service.h" - -#if (defined(WIN32) || defined(_WIN64)) -#if defined(_DEBUG) && defined(WEBCC_ENABLE_VLD) -#pragma message ("< include vld.h >") -#include "vld/vld.h" -#pragma comment(lib, "vld") -#endif -#endif - -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_server/CMakeLists.txt b/example/soap_calc_server/CMakeLists.txt deleted file mode 100644 index 6c82984..0000000 --- a/example/soap_calc_server/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -set(SRCS calc_service.cc calc_service.h main.cc) - -add_executable(soap_calc_server ${SRCS}) - -target_link_libraries(soap_calc_server webcc pugixml ${Boost_LIBRARIES}) -target_link_libraries(soap_calc_server "${CMAKE_THREAD_LIBS_INIT}") diff --git a/example/soap_calc_server/calc_service.h b/example/soap_calc_server/calc_service.h deleted file mode 100644 index d8e5858..0000000 --- a/example/soap_calc_server/calc_service.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef CALC_SERVICE_H_ -#define CALC_SERVICE_H_ - -#include "webcc/soap_service.h" - -class CalcService : public webcc::SoapService { -public: - CalcService() = default; - ~CalcService() override = default; - - bool Handle(const webcc::SoapRequest& soap_request, - webcc::SoapResponse* soap_response) final; -}; - -#endif // CALC_SERVICE_H_ diff --git a/example/soap_calc_server/main.cc b/example/soap_calc_server/main.cc deleted file mode 100644 index 3d48c40..0000000 --- a/example/soap_calc_server/main.cc +++ /dev/null @@ -1,40 +0,0 @@ -#include - -#include "webcc/logger.h" -#include "webcc/soap_server.h" - -#include "example/soap_calc_server/calc_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(), "/calculator"); - server.Run(); - } catch (const std::exception& e) { - std::cerr << "Exception: " << e.what() << std::endl; - return 1; - } - - return 0; -} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..999d210 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,55 @@ +# Examples + +# Common libraries to link for examples. +set(EXAMPLE_COMMON_LIBS webcc ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} + "${CMAKE_THREAD_LIBS_INIT}") +if(WIN32) + set(EXAMPLE_COMMON_LIBS ${EXAMPLE_COMMON_LIBS} crypt32) +endif() + +if(UNIX) + # Add `-ldl` for Linux to avoid "undefined reference to `dlopen'". + set(EXAMPLE_COMMON_LIBS ${EXAMPLE_COMMON_LIBS} ${CMAKE_DL_LIBS}) +endif() + +set(REST_BOOK_SRCS + common/book.cc + common/book.h + common/book_json.cc + common/book_json.h + ) + +add_executable(http_client http_client.cc) +add_executable(github_client github_client.cc) + +target_link_libraries(http_client ${EXAMPLE_COMMON_LIBS}) +target_link_libraries(github_client ${EXAMPLE_COMMON_LIBS} jsoncpp) + +add_executable(rest_book_server rest_book_server.cc ${REST_BOOK_SRCS}) +add_executable(rest_book_client rest_book_client.cc ${REST_BOOK_SRCS}) + +target_link_libraries(rest_book_server ${EXAMPLE_COMMON_LIBS} jsoncpp) +target_link_libraries(rest_book_client ${EXAMPLE_COMMON_LIBS} jsoncpp) + +if(WEBCC_ENABLE_SOAP) + add_executable(soap_calc_server soap_calc_server.cc) + add_executable(soap_calc_client soap_calc_client.cc) + add_executable(soap_calc_client_parasoft soap_calc_client_parasoft.cc) + + target_link_libraries(soap_calc_server ${EXAMPLE_COMMON_LIBS} pugixml) + target_link_libraries(soap_calc_client ${EXAMPLE_COMMON_LIBS} pugixml) + target_link_libraries(soap_calc_client_parasoft ${EXAMPLE_COMMON_LIBS} pugixml) + + set(SOAP_BOOK_SRCS + common/book.cc + common/book.h + common/book_xml.cc + common/book_xml.h + ) + + add_executable(soap_book_server soap_book_server.cc ${SOAP_BOOK_SRCS}) + add_executable(soap_book_client soap_book_client.cc ${SOAP_BOOK_SRCS}) + + target_link_libraries(soap_book_server ${EXAMPLE_COMMON_LIBS} pugixml) + target_link_libraries(soap_book_client ${EXAMPLE_COMMON_LIBS} pugixml) +endif() diff --git a/example/common/book.cc b/examples/common/book.cc similarity index 97% rename from example/common/book.cc rename to examples/common/book.cc index 5191347..62ee25b 100644 --- a/example/common/book.cc +++ b/examples/common/book.cc @@ -1,4 +1,4 @@ -#include "example/common/book.h" +#include "examples/common/book.h" #include #include diff --git a/example/common/book.h b/examples/common/book.h similarity index 100% rename from example/common/book.h rename to examples/common/book.h diff --git a/example/common/book_json.cc b/examples/common/book_json.cc similarity index 93% rename from example/common/book_json.cc rename to examples/common/book_json.cc index 5b18482..c5fd817 100644 --- a/example/common/book_json.cc +++ b/examples/common/book_json.cc @@ -1,11 +1,11 @@ -#include "example/common/book_json.h" +#include "examples/common/book_json.h" #include #include #include "json/json.h" -#include "example/common/book.h" +#include "examples/common/book.h" std::string JsonToString(const Json::Value& json) { Json::StreamWriterBuilder builder; diff --git a/example/common/book_json.h b/examples/common/book_json.h similarity index 100% rename from example/common/book_json.h rename to examples/common/book_json.h diff --git a/example/common/book_xml.cc b/examples/common/book_xml.cc similarity index 98% rename from example/common/book_xml.cc rename to examples/common/book_xml.cc index 56f6373..740721d 100644 --- a/example/common/book_xml.cc +++ b/examples/common/book_xml.cc @@ -1,10 +1,10 @@ -#include "example/common/book_xml.h" +#include "examples/common/book_xml.h" #include #include #include -#include "example/common/book.h" +#include "examples/common/book.h" // ----------------------------------------------------------------------------- diff --git a/example/common/book_xml.h b/examples/common/book_xml.h similarity index 100% rename from example/common/book_xml.h rename to examples/common/book_xml.h diff --git a/example/github_client/main.cc b/examples/github_client.cc similarity index 84% rename from example/github_client/main.cc rename to examples/github_client.cc index cd0f320..62cbbcb 100644 --- a/example/github_client/main.cc +++ b/examples/github_client.cc @@ -63,10 +63,7 @@ void PrettyPrintJsonString(const std::string& str) { // ----------------------------------------------------------------------------- // List public events. -void ListEvents() { - webcc::HttpClientSession session; - session.set_ssl_verify(kSslVerify); - +void ListEvents(webcc::HttpClientSession& session) { try { auto r = session.Get(kUrlRoot + "/events"); PRINT_JSON_STRING(r->content()); @@ -76,10 +73,10 @@ void ListEvents() { } // List the followers of the given user. -void ListUserFollowers(const std::string& user) { - webcc::HttpClientSession session; - session.set_ssl_verify(kSslVerify); - +// Example: +// ListUserFollowers(session, "") +void ListUserFollowers(webcc::HttpClientSession& session, + const std::string& user) { try { auto r = session.Get(kUrlRoot + "/users/" + user + "/followers"); PRINT_JSON_STRING(r->content()); @@ -90,10 +87,11 @@ void ListUserFollowers(const std::string& user) { // List the followers of the current authorized user. // Header syntax: Authorization: -void ListAuthUserFollowers(const std::string& auth) { - webcc::HttpClientSession session; - session.set_ssl_verify(kSslVerify); - +// Example: +// ListAuthUserFollowers(session, "Basic ") +// ListAuthUserFollowers(session, "Token ") +void ListAuthUserFollowers(webcc::HttpClientSession& session, + const std::string& auth) { try { auto r = session.Get(kUrlRoot + "/user/followers", {}, { "Authorization", auth }); @@ -110,11 +108,11 @@ void ListAuthUserFollowers(const std::string& auth) { int main() { WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); - //ListEvents(); + webcc::HttpClientSession session; - //ListUserFollowers(""); + session.set_ssl_verify(kSslVerify); - //ListAuthUserFollowers("Basic "); + ListEvents(session); return 0; } diff --git a/example/http_client/main.cc b/examples/http_client.cc similarity index 84% rename from example/http_client/main.cc rename to examples/http_client.cc index c942406..2a4d069 100644 --- a/example/http_client/main.cc +++ b/examples/http_client.cc @@ -71,6 +71,18 @@ void ExampleHttps() { std::cout << r->content() << std::endl; } +// Example for testing Keep-Alive connection. +// +// Boost.org doesn't support persistent connection so always includes +// "Connection: Close" header in the response. +// Both Google and GitHub support persistent connection but they don't like +// to include "Connection: Keep-Alive" header in the responses. +// +// ExampleKeepAlive("http://httpbin.org/get"); +// ExampleKeepAlive("https://www.boost.org/LICENSE_1_0.txt"); +// ExampleKeepAlive("https://www.google.com"); +// ExampleKeepAlive("https://api.github.com/events"); +// void ExampleKeepAlive(const std::string& url) { HttpClientSession session; @@ -95,21 +107,8 @@ int main() { // Note that the exception handling is mandatory. try { - //ExampleBasic(); - //ExampleArgs(); - //ExampleWrappers(); - //ExampleHttps(); - // Boost.org doesn't support persistent connection so always includes - // "Connection: Close" header in the response. - - // Both Google and GitHub support persistent connection but they don't like - // to include "Connection: Keep-Alive" header in the responses. - - //ExampleKeepAlive("http://httpbin.org/get"); - //ExampleKeepAlive("https://www.boost.org/LICENSE_1_0.txt"); - //ExampleKeepAlive("https://www.google.com"); - //ExampleKeepAlive("https://api.github.com/events"); + ExampleBasic(); } catch (const Exception& e) { std::cout << "Exception: " << e.what() << std::endl; diff --git a/examples/rest_book_client.cc b/examples/rest_book_client.cc new file mode 100644 index 0000000..e37566c --- /dev/null +++ b/examples/rest_book_client.cc @@ -0,0 +1,282 @@ +#include +#include + +#include "json/json.h" + +#include "webcc/http_client_session.h" +#include "webcc/logger.h" + +#include "examples/common/book.h" +#include "examples/common/book_json.h" + +#if (defined(WIN32) || defined(_WIN64)) +#if defined(_DEBUG) && defined(WEBCC_ENABLE_VLD) +#pragma message ("< include vld.h >") +#include "vld/vld.h" +#pragma comment(lib, "vld") +#endif +#endif + +// ----------------------------------------------------------------------------- + +class BookClientBase { +public: + BookClientBase(const std::string& url, int timeout_seconds) : url_(url) { + //session_.SetTimeout(timeout_seconds); + + //session_.set_content_type("application/json"); + session_.set_charset("utf-8"); + } + + virtual ~BookClientBase() = default; + +protected: + // Helper function to make a request. + //webcc::HttpRequestPtr MakeRequest(const std::string& method, + // const std::string& url, + // std::string&& content = "") { + // auto request = webcc::HttpRequest::New(method, url, host_, port_); + // request->AcceptAppJson(); + // if (!content.empty()) { + // request->SetContentInAppJsonUtf8(JsonToString(content), true); + // } + // request->Prepare(); + // return request; + //} + + // Check HTTP response status. + bool CheckStatus(webcc::HttpResponsePtr response, int expected_status) { + int status = response->status(); + if (status != expected_status) { + LOG_ERRO("HTTP status error (actual: %d, expected: %d).", + status, expected_status); + return false; + } + return true; + } + +protected: + std::string url_; + + webcc::HttpClientSession session_; +}; + +// ----------------------------------------------------------------------------- + +class BookListClient : public BookClientBase { +public: + BookListClient(const std::string& url, int timeout_seconds) + : BookClientBase(url, timeout_seconds) { + } + + bool ListBooks(std::list* books) { + try { + auto r = session_.Get(url_ + "/books"); + + if (!CheckStatus(r, webcc::http::Status::kOK)) { + // Response HTTP status error. + return false; + } + + Json::Value rsp_json = StringToJson(r->content()); + + if (!rsp_json.isArray()) { + return false; // Should be a JSON array of books. + } + + for (Json::ArrayIndex i = 0; i < rsp_json.size(); ++i) { + books->push_back(JsonToBook(rsp_json[i])); + } + + return true; + + } catch (const webcc::Exception& e) { + std::cerr << e.what() << std::endl; + return false; + } + } + + bool CreateBook(const std::string& title, double price, std::string* id) { + Json::Value req_json; + req_json["title"] = title; + req_json["price"] = price; + + try { + auto r = session_.Post(url_ + "/books", JsonToString(req_json), true); + + if (!CheckStatus(r, webcc::http::Status::kCreated)) { + return false; + } + + Json::Value rsp_json = StringToJson(r->content()); + *id = rsp_json["id"].asString(); + + return !id->empty(); + + } catch (const webcc::Exception& e) { + std::cerr << e.what() << std::endl; + return false; + } + } +}; + +// ----------------------------------------------------------------------------- + +class BookDetailClient : public BookClientBase { +public: + BookDetailClient(const std::string& url, int timeout_seconds) + : BookClientBase(url, timeout_seconds) { + } + + bool GetBook(const std::string& id, Book* book) { + try { + auto r = session_.Get(url_ + "/books" + id); + + if (!CheckStatus(r, webcc::http::Status::kOK)) { + return false; + } + + return JsonStringToBook(r->content(), book); + + } catch (const webcc::Exception& e) { + std::cerr << e.what() << std::endl; + return false; + } + } + + bool UpdateBook(const std::string& id, const std::string& title, + double price) { + Json::Value json; + json["title"] = title; + json["price"] = price; + + try { + auto r = session_.Put(url_ + "/books" + id, JsonToString(json), true); + + if (!CheckStatus(r, webcc::http::Status::kOK)) { + return false; + } + + return true; + + } catch (const webcc::Exception& e) { + std::cerr << e.what() << std::endl; + return false; + } + } + + bool DeleteBook(const std::string& id) { + try { + auto r = session_.Delete(url_ + "/books/" + id); + + if (!CheckStatus(r, webcc::http::Status::kOK)) { + return false; + } + + return true; + + } catch (const webcc::Exception& e) { + std::cerr << e.what() << std::endl; + return false; + } + } +}; + +// ----------------------------------------------------------------------------- + +void PrintSeparator() { + std::cout << std::string(80, '-') << std::endl; +} + +void PrintBook(const Book& book) { + std::cout << "Book: " << book << std::endl; +} + +void PrintBookList(const std::list& books) { + std::cout << "Book list: " << books.size() << std::endl; + for (const Book& book : books) { + std::cout << " Book: " << book << std::endl; + } +} + +// ----------------------------------------------------------------------------- + +void Help(const char* argv0) { + std::cout << "Usage: " << argv0 << " [timeout]" << std::endl; + std::cout << " E.g.," << std::endl; + std::cout << " " << argv0 << "http://localhost:8080" << std::endl; + std::cout << " " << argv0 << "http://localhost:8080 2" << std::endl; +} + +int main(int argc, char* argv[]) { + if (argc < 2) { + Help(argv[0]); + return 1; + } + + WEBCC_LOG_INIT("", webcc::LOG_CONSOLE_FILE_OVERWRITE); + + std::string url = argv[1]; + + int timeout_seconds = -1; + if (argc > 2) { + timeout_seconds = std::atoi(argv[2]); + } + + BookListClient list_client(url, timeout_seconds); + BookDetailClient detail_client(url, timeout_seconds); + + PrintSeparator(); + + std::list books; + if (list_client.ListBooks(&books)) { + PrintBookList(books); + } + + //PrintSeparator(); + + //std::string id; + //if (list_client.CreateBook("1984", 12.3, &id)) { + // std::cout << "Book ID: " << id << std::endl; + //} else { + // id = "1"; + // std::cout << "Book ID: " << id << " (faked)"<< std::endl; + //} + + //PrintSeparator(); + + //books.clear(); + //if (list_client.ListBooks(&books)) { + // PrintBookList(books); + //} + + //PrintSeparator(); + + //Book book; + //if (detail_client.GetBook(id, &book)) { + // PrintBook(book); + //} + + //PrintSeparator(); + + //detail_client.UpdateBook(id, "1Q84", 32.1); + + //PrintSeparator(); + + //if (detail_client.GetBook(id, &book)) { + // PrintBook(book); + //} + + //PrintSeparator(); + + //detail_client.DeleteBook(id); + + //PrintSeparator(); + + //books.clear(); + //if (list_client.ListBooks(&books)) { + // PrintBookList(books); + //} + + return 0; +} diff --git a/examples/rest_book_server.cc b/examples/rest_book_server.cc new file mode 100644 index 0000000..de44816 --- /dev/null +++ b/examples/rest_book_server.cc @@ -0,0 +1,239 @@ +#include +#include +#include +#include +#include + +#include "json/json.h" + +#include "webcc/logger.h" +#include "webcc/rest_server.h" +#include "webcc/rest_service.h" + +#include "examples/common/book.h" +#include "examples/common/book_json.h" + +#if (defined(WIN32) || defined(_WIN64)) +#if defined(_DEBUG) && defined(WEBCC_ENABLE_VLD) +#pragma message ("< include vld.h >") +#include "vld/vld.h" +#pragma comment(lib, "vld") +#endif +#endif + +// ----------------------------------------------------------------------------- + +static BookStore g_book_store; + +static void Sleep(int seconds) { + if (seconds > 0) { + LOG_INFO("Sleep %d seconds...", seconds); + std::this_thread::sleep_for(std::chrono::seconds(seconds)); + } +} + +// ----------------------------------------------------------------------------- + +class BookListService : public webcc::RestListService { +public: + explicit BookListService(int sleep_seconds) + : sleep_seconds_(sleep_seconds) { + } + +public: + // Get a list of books based on query parameters. + void Get(const webcc::UrlQuery& query, webcc::RestResponse* response) final; + + // Create a new book. + void Post(const std::string& request_content, + webcc::RestResponse* response) final; + +private: + // Sleep some seconds before send back the response. + // For testing timeout control in client side. + int sleep_seconds_; +}; + +// ----------------------------------------------------------------------------- + +// The URL is like '/books/{BookID}', and the 'url_matches' parameter +// contains the matched book ID. +class BookDetailService : public webcc::RestDetailService { +public: + explicit BookDetailService(int sleep_seconds) + : sleep_seconds_(sleep_seconds) { + } + +public: + // Get the detailed information of a book. + void Get(const webcc::UrlMatches& url_matches, + const webcc::UrlQuery& query, + webcc::RestResponse* response) final; + + // Update a book. + void Put(const webcc::UrlMatches& url_matches, + const std::string& request_content, + webcc::RestResponse* response) final; + + // Delete a book. + void Delete(const webcc::UrlMatches& url_matches, + webcc::RestResponse* response) final; + +private: + // Sleep some seconds before send back the response. + // For testing timeout control in client side. + int sleep_seconds_; +}; + +// ----------------------------------------------------------------------------- + +// Return all books as a JSON array. +void BookListService::Get(const webcc::UrlQuery& /*query*/, + webcc::RestResponse* response) { + Sleep(sleep_seconds_); + + Json::Value json(Json::arrayValue); + + for (const Book& book : g_book_store.books()) { + json.append(BookToJson(book)); + } + + response->content = JsonToString(json); + response->status = webcc::http::Status::kOK; +} + +void BookListService::Post(const std::string& request_content, + webcc::RestResponse* response) { + Sleep(sleep_seconds_); + + Book book; + if (JsonStringToBook(request_content, &book)) { + std::string id = g_book_store.AddBook(book); + + Json::Value json; + json["id"] = id; + + response->content = JsonToString(json); + response->status = webcc::http::Status::kCreated; + } else { + // Invalid JSON + response->status = webcc::http::Status::kBadRequest; + } +} + +// ----------------------------------------------------------------------------- + +void BookDetailService::Get(const webcc::UrlMatches& url_matches, + const webcc::UrlQuery& query, + webcc::RestResponse* response) { + Sleep(sleep_seconds_); + + if (url_matches.size() != 1) { + // Using kNotFound means the resource specified by the URL cannot be found. + // kBadRequest could be another choice. + response->status = webcc::http::Status::kNotFound; + return; + } + + const std::string& book_id = url_matches[0]; + + const Book& book = g_book_store.GetBook(book_id); + if (book.IsNull()) { + response->status = webcc::http::Status::kNotFound; + return; + } + + response->content = BookToJsonString(book); + response->status = webcc::http::Status::kOK; +} + +void BookDetailService::Put(const webcc::UrlMatches& url_matches, + const std::string& request_content, + webcc::RestResponse* response) { + Sleep(sleep_seconds_); + + if (url_matches.size() != 1) { + response->status = webcc::http::Status::kNotFound; + return; + } + + const std::string& book_id = url_matches[0]; + + Book book; + if (!JsonStringToBook(request_content, &book)) { + response->status = webcc::http::Status::kBadRequest; + return; + } + + book.id = book_id; + g_book_store.UpdateBook(book); + + response->status = webcc::http::Status::kOK; +} + +void BookDetailService::Delete(const webcc::UrlMatches& url_matches, + webcc::RestResponse* response) { + Sleep(sleep_seconds_); + + if (url_matches.size() != 1) { + response->status = webcc::http::Status::kNotFound; + return; + } + + const std::string& book_id = url_matches[0]; + + if (!g_book_store.DeleteBook(book_id)) { + response->status = webcc::http::Status::kNotFound; + return; + } + + response->status = webcc::http::Status::kOK; +} + +// ----------------------------------------------------------------------------- + +void Help(const char* argv0) { + std::cout << "Usage: " << argv0 << " [seconds]" << std::endl; + std::cout << "If |seconds| is provided, the server will sleep these seconds " + "before sending back each response." + << std::endl; + std::cout << " E.g.," << std::endl; + std::cout << " " << argv0 << " 8080" << std::endl; + std::cout << " " << argv0 << " 8080 3" << 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])); + + int sleep_seconds = 0; + if (argc >= 3) { + sleep_seconds = std::atoi(argv[2]); + } + + std::size_t workers = 2; + + try { + webcc::RestServer server(port, workers); + + server.Bind(std::make_shared(sleep_seconds), + "/books", false); + + server.Bind(std::make_shared(sleep_seconds), + "/books/(\\d+)", true); + + server.Run(); + + } catch (const std::exception& e) { + std::cerr << "Exception: " << e.what() << std::endl; + return 1; + } + + return 0; +} diff --git a/example/soap_book_client/book_client.cc b/examples/soap_book_client.cc similarity index 52% rename from example/soap_book_client/book_client.cc rename to examples/soap_book_client.cc index 39a32a5..09c6552 100644 --- a/example/soap_book_client/book_client.cc +++ b/examples/soap_book_client.cc @@ -1,10 +1,24 @@ -#include "example/soap_book_client/book_client.h" - +#include #include +#include + +#include "pugixml/pugixml.hpp" #include "webcc/logger.h" +#include "webcc/soap_client.h" + +#include "examples/common/book.h" +#include "examples/common/book_xml.h" -#include "example/common/book_xml.h" +#if (defined(WIN32) || defined(_WIN64)) +#if defined(_DEBUG) && defined(WEBCC_ENABLE_VLD) +#pragma message ("< include vld.h >") +#include "vld/vld.h" +#pragma comment(lib, "vld") +#endif +#endif + +// ----------------------------------------------------------------------------- static const std::string kResult = "Result"; @@ -14,9 +28,56 @@ static void PrintSeparateLine() { std::cout << std::endl; } -BookClient::BookClient(const std::string& host, const std::string& port) - : soap_client_(host, port), code_(0) { - soap_client_.set_url("/book"); +// ----------------------------------------------------------------------------- + +class BookClient { +public: + explicit BookClient(const std::string& url); + + 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 SoapClient::Request() to log error if any. + bool Call(const std::string& operation, + std::vector&& parameters, + std::string* result_str); + + void PrintError(); + + bool ParseResultXml(const std::string& result_xml, + std::function callback); + + webcc::SoapClient soap_client_; + + // Last status. + int code_; + std::string message_; +}; + +// ----------------------------------------------------------------------------- + +BookClient::BookClient(const std::string& url) + : soap_client_(url), code_(0) { soap_client_.set_service_ns({ "ser", "http://www.example.com/book/" }); // Customize response XML format. @@ -139,3 +200,64 @@ bool BookClient::ParseResultXml(const std::string& result_xml, return true; } + +// ----------------------------------------------------------------------------- + +void Help(const char* argv0) { + std::cout << "Usage: " << argv0 << " " << std::endl; + std::cout << " E.g.," << std::endl; + std::cout << " " << argv0 << " http://localhost:8080" << std::endl; +} + +int main(int argc, char* argv[]) { + if (argc < 2) { + Help(argv[0]); + return 1; + } + + WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); + + std::string url = argv[1]; + + BookClient client(url + "/book"); + + 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/book_service.cc b/examples/soap_book_server.cc similarity index 76% rename from example/soap_book_server/book_service.cc rename to examples/soap_book_server.cc index e059ae9..6a5d7db 100644 --- a/example/soap_book_server/book_service.cc +++ b/examples/soap_book_server.cc @@ -1,5 +1,3 @@ -#include "example/soap_book_server/book_service.h" - #include #include #include @@ -7,9 +5,19 @@ #include "webcc/logger.h" #include "webcc/soap_request.h" #include "webcc/soap_response.h" +#include "webcc/soap_server.h" +#include "webcc/soap_service.h" + +#include "examples/common/book.h" +#include "examples/common/book_xml.h" -#include "example/common/book.h" -#include "example/common/book_xml.h" +#if (defined(WIN32) || defined(_WIN64)) +#if defined(_DEBUG) && defined(WEBCC_ENABLE_VLD) +#pragma message ("< include vld.h >") +#include "vld/vld.h" +#pragma comment(lib, "vld") +#endif +#endif // ----------------------------------------------------------------------------- @@ -19,6 +27,27 @@ static const std::string kResult = "Result"; // ----------------------------------------------------------------------------- +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); +}; + +// ----------------------------------------------------------------------------- + bool BookService::Handle(const webcc::SoapRequest& soap_request, webcc::SoapResponse* soap_response) { const std::string& operation = soap_request.operation(); @@ -218,3 +247,39 @@ bool BookService::DeleteBook(const webcc::SoapRequest& soap_request, return true; } + +// ----------------------------------------------------------------------------- + +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.cc b/examples/soap_calc_client.cc similarity index 87% rename from example/soap_calc_client.cc rename to examples/soap_calc_client.cc index 283999e..9292b0e 100644 --- a/example/soap_calc_client.cc +++ b/examples/soap_calc_client.cc @@ -7,13 +7,14 @@ static const std::string kResultName = "Result"; +// ----------------------------------------------------------------------------- + class CalcClient { public: - CalcClient(const std::string& host, const std::string& port) - : soap_client_(host, port) { + CalcClient(const std::string& url) + : soap_client_(url) { soap_client_.SetTimeout(5); - soap_client_.set_url("/calculator"); soap_client_.set_service_ns({ "ser", "http://www.example.com/calculator/" }); @@ -84,23 +85,22 @@ private: // ----------------------------------------------------------------------------- void Help(const char* argv0) { - std::cout << "Usage: " << argv0 << " " << std::endl; + std::cout << "Usage: " << argv0 << " " << std::endl; std::cout << " E.g.," << std::endl; - std::cout << " " << argv0 << " localhost 8080" << std::endl; + std::cout << " " << argv0 << "http://localhost:8080" << std::endl; } int main(int argc, char* argv[]) { - if (argc < 3) { + if (argc < 2) { Help(argv[0]); return 1; } WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); - std::string host = argv[1]; - std::string port = argv[2]; + std::string url = argv[1]; - CalcClient calc(host, port); + CalcClient calc(url + "/calculator"); double x = 1.0; double y = 2.0; diff --git a/example/soap_calc_client_parasoft.cc b/examples/soap_calc_client_parasoft.cc similarity index 92% rename from example/soap_calc_client_parasoft.cc rename to examples/soap_calc_client_parasoft.cc index 46ec010..e5887e1 100644 --- a/example/soap_calc_client_parasoft.cc +++ b/examples/soap_calc_client_parasoft.cc @@ -10,11 +10,10 @@ static const std::string kResultName = "Result"; class CalcClient { public: // NOTE: Parasoft's calculator service uses SOAP V1.1. - CalcClient(const std::string& host, const std::string& port) - : soap_client_(host, port, webcc::kSoapV11) { + CalcClient(const std::string& url) + : soap_client_(url, webcc::kSoapV11) { soap_client_.SetTimeout(5); - soap_client_.set_url("/glue/calculator"); soap_client_.set_service_ns({ "cal", "http://www.parasoft.com/wsdl/calculator/" }); @@ -92,7 +91,7 @@ private: int main() { WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); - CalcClient calc("ws1.parasoft.com", ""); // Use default port 80 + CalcClient calc("http://ws1.parasoft.com/glue/calculator"); double x = 1.0; double y = 2.0; diff --git a/example/soap_calc_server/calc_service.cc b/examples/soap_calc_server.cc similarity index 54% rename from example/soap_calc_server/calc_service.cc rename to examples/soap_calc_server.cc index 766cc27..41ae6eb 100644 --- a/example/soap_calc_server/calc_service.cc +++ b/examples/soap_calc_server.cc @@ -1,11 +1,23 @@ -#include "example/soap_calc_server/calc_service.h" - #include +#include #include #include "webcc/logger.h" #include "webcc/soap_request.h" #include "webcc/soap_response.h" +#include "webcc/soap_server.h" +#include "webcc/soap_service.h" + +// ----------------------------------------------------------------------------- + +class CalcService : public webcc::SoapService { +public: + CalcService() = default; + ~CalcService() override = default; + + bool Handle(const webcc::SoapRequest& soap_request, + webcc::SoapResponse* soap_response) final; +}; bool CalcService::Handle(const webcc::SoapRequest& soap_request, webcc::SoapResponse* soap_response) { @@ -62,3 +74,39 @@ bool CalcService::Handle(const webcc::SoapRequest& soap_request, return true; } + +// ----------------------------------------------------------------------------- + +static 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(), "/calculator"); + server.Run(); + } catch (const std::exception& e) { + std::cerr << "Exception: " << e.what() << std::endl; + return 1; + } + + return 0; +} diff --git a/unittest/rest_service_manager_test.cc b/unittest/rest_service_manager_test.cc index a02324d..54e1eda 100644 --- a/unittest/rest_service_manager_test.cc +++ b/unittest/rest_service_manager_test.cc @@ -20,19 +20,19 @@ TEST(RestServiceManager, URL_RegexBasic) { service_manager.AddService(std::make_shared(), "/instance/(\\d+)", true); - std::vector sub_matches; + std::vector matches; std::string url = "/instance/12345"; - webcc::RestServicePtr service = service_manager.GetService(url, &sub_matches); + webcc::RestServicePtr service = service_manager.GetService(url, &matches); EXPECT_TRUE(!!service); - EXPECT_EQ(1, sub_matches.size()); - EXPECT_EQ("12345", sub_matches[0]); + EXPECT_EQ(1, matches.size()); + EXPECT_EQ("12345", matches[0]); url = "/instance/abcde"; - sub_matches.clear(); - service = service_manager.GetService(url, &sub_matches); + matches.clear(); + service = service_manager.GetService(url, &matches); EXPECT_FALSE(!!service); } @@ -43,15 +43,15 @@ TEST(RestServiceManager, URL_Temp) { service_manager.AddService(std::make_shared(), "/instance/([\\w.]+)", true); - std::vector sub_matches; + std::vector matches; std::string url = "/instance/123.45-+6aaa"; - webcc::RestServicePtr service = service_manager.GetService(url, &sub_matches); + webcc::RestServicePtr service = service_manager.GetService(url, &matches); EXPECT_TRUE(!!service); - EXPECT_EQ(1, sub_matches.size()); - EXPECT_EQ("123.45-6aaa", sub_matches[0]); + EXPECT_EQ(1, matches.size()); + EXPECT_EQ("123.45-6aaa", matches[0]); } TEST(RestServiceManager, URL_RegexMultiple) { @@ -61,21 +61,21 @@ TEST(RestServiceManager, URL_RegexMultiple) { "/study/(\\d+)/series/(\\d+)/instance/(\\d+)", true); - std::vector sub_matches; + std::vector matches; std::string url = "/study/1/series/2/instance/3"; - webcc::RestServicePtr service = service_manager.GetService(url, &sub_matches); + webcc::RestServicePtr service = service_manager.GetService(url, &matches); EXPECT_TRUE(!!service); - EXPECT_EQ(3, sub_matches.size()); - EXPECT_EQ("1", sub_matches[0]); - EXPECT_EQ("2", sub_matches[1]); - EXPECT_EQ("3", sub_matches[2]); + EXPECT_EQ(3, matches.size()); + EXPECT_EQ("1", matches[0]); + EXPECT_EQ("2", matches[1]); + EXPECT_EQ("3", matches[2]); url = "/study/a/series/b/instance/c"; - sub_matches.clear(); - service = service_manager.GetService(url, &sub_matches); + matches.clear(); + service = service_manager.GetService(url, &matches); EXPECT_FALSE(!!service); } @@ -86,10 +86,10 @@ TEST(RestServiceManager, URL_NonRegex) { service_manager.AddService(std::make_shared(), "/instances", false); - std::vector sub_matches; + std::vector matches; std::string url = "/instances"; - webcc::RestServicePtr service = service_manager.GetService(url, &sub_matches); + webcc::RestServicePtr service = service_manager.GetService(url, &matches); EXPECT_TRUE(!!service); - EXPECT_EQ(0, sub_matches.size()); + EXPECT_EQ(0, matches.size()); } diff --git a/webcc/CMakeLists.txt b/webcc/CMakeLists.txt index 6edde78..6cfd3de 100644 --- a/webcc/CMakeLists.txt +++ b/webcc/CMakeLists.txt @@ -30,6 +30,10 @@ set(HEADERS http_server.h http_socket.h queue.h + rest_request_handler.h + rest_server.h + rest_service.h + rest_service_manager.h url.h utility.h version.h @@ -51,27 +55,13 @@ set(SOURCES http_server.cc http_socket.cc logger.cc + rest_request_handler.cc + rest_service_manager.cc + rest_service.cc url.cc utility.cc ) -if(WEBCC_ENABLE_REST) - set(REST_HEADERS - rest_request_handler.h - rest_server.h - rest_service.h - rest_service_manager.h - ) - set(REST_SOURCES - rest_request_handler.cc - rest_service_manager.cc - rest_service.cc - ) - - set(HEADERS ${HEADERS} ${REST_HEADERS}) - set(SOURCES ${SOURCES} ${REST_SOURCES}) -endif() - if(WEBCC_ENABLE_SOAP) set(SOAP_HEADERS soap_client.h diff --git a/webcc/globals.h b/webcc/globals.h index 48dafc2..d6d8be0 100644 --- a/webcc/globals.h +++ b/webcc/globals.h @@ -88,7 +88,6 @@ const char* const kTextXml = "text/xml"; } // namespace media_types -// TODO: Rename to encodings? namespace charsets { const char* const kUtf8 = "utf-8"; diff --git a/webcc/http_client_session.cc b/webcc/http_client_session.cc index beb60f4..28ee470 100644 --- a/webcc/http_client_session.cc +++ b/webcc/http_client_session.cc @@ -103,6 +103,25 @@ HttpResponsePtr HttpClientSession::Post(const std::string& url, .headers(std::move(headers))); } +HttpResponsePtr HttpClientSession::Put(const std::string& url, + std::string&& data, bool json, + std::vector&& headers, + HttpRequestArgs&& args) { + return Request(args.method(http::kPut) + .url(url) + .data(std::move(data)) + .json(json) + .headers(std::move(headers))); +} + +HttpResponsePtr HttpClientSession::Delete(const std::string& url, + std::vector&& headers, + HttpRequestArgs&& args) { + return Request(args.method(http::kDelete) + .url(url) + .headers(std::move(headers))); +} + void HttpClientSession::InitHeaders() { headers_.Add(http::headers::kUserAgent, http::UserAgent()); diff --git a/webcc/http_client_session.h b/webcc/http_client_session.h index 77a3c1d..21ecbc3 100644 --- a/webcc/http_client_session.h +++ b/webcc/http_client_session.h @@ -46,6 +46,14 @@ public: std::vector&& headers = {}, HttpRequestArgs&& args = HttpRequestArgs()); + HttpResponsePtr Put(const std::string& url, std::string&& data, bool json, + std::vector&& headers = {}, + HttpRequestArgs&& args = HttpRequestArgs()); + + HttpResponsePtr Delete(const std::string& url, + std::vector&& headers = {}, + HttpRequestArgs&& args = HttpRequestArgs()); + private: void InitHeaders(); diff --git a/webcc/http_response.cc b/webcc/http_response.cc index 831ae2b..35890e8 100644 --- a/webcc/http_response.cc +++ b/webcc/http_response.cc @@ -63,6 +63,9 @@ bool HttpResponse::Prepare() { SetHeader("Server", http::UserAgent()); SetHeader("Date", GetHttpDateTimestamp()); + // TODO: Support Keep-Alive. + SetHeader(http::headers::kConnection, "Close"); + return true; } diff --git a/webcc/rest_request_handler.cc b/webcc/rest_request_handler.cc index 354875f..b105e1c 100644 --- a/webcc/rest_request_handler.cc +++ b/webcc/rest_request_handler.cc @@ -16,21 +16,15 @@ bool RestRequestHandler::Bind(RestServicePtr service, const std::string& url, void RestRequestHandler::HandleConnection(HttpConnectionPtr connection) { const HttpRequest& http_request = connection->request(); - // TODO const Url& url = http_request.url(); - if (url.path().empty()) { - connection->SendResponse(http::Status::kBadRequest); - return; - } - RestRequest rest_request{ http_request.method(), http_request.content(), url.query() }; // Get service by URL path. - RestServicePtr service = - service_manager_.GetService(url.path(), &rest_request.url_sub_matches); + std::string path = "/" + url.path(); + auto service = service_manager_.GetService(path, &rest_request.url_matches); if (!service) { LOG_WARN("No service matches the URL path: %s", url.path().c_str()); diff --git a/webcc/rest_service.cc b/webcc/rest_service.cc index a191cec..1ccb47a 100644 --- a/webcc/rest_service.cc +++ b/webcc/rest_service.cc @@ -23,13 +23,13 @@ void RestListService::Handle(const RestRequest& request, void RestDetailService::Handle(const RestRequest& request, RestResponse* response) { if (request.method == http::kGet) { - Get(request.url_sub_matches, UrlQuery(request.url_query_str), response); + Get(request.url_matches, UrlQuery(request.url_query_str), response); } else if (request.method == http::kPut) { - Put(request.url_sub_matches, request.content, response); + Put(request.url_matches, request.content, response); } else if (request.method == http::kPatch) { - Patch(request.url_sub_matches, request.content, response); + Patch(request.url_matches, request.content, response); } else if (request.method == http::kDelete) { - Delete(request.url_sub_matches, response); + Delete(request.url_matches, response); } else { LOG_ERRO("RestDetailService doesn't support '%s' method.", request.method.c_str()); diff --git a/webcc/rest_service.h b/webcc/rest_service.h index ac8adc0..2cd75c9 100644 --- a/webcc/rest_service.h +++ b/webcc/rest_service.h @@ -21,7 +21,7 @@ namespace webcc { // ----------------------------------------------------------------------------- // Regex sub-matches of the URL. -typedef std::vector UrlSubMatches; +typedef std::vector UrlMatches; struct RestRequest { // HTTP method (GET, POST, etc.). @@ -34,7 +34,7 @@ struct RestRequest { const std::string& url_query_str; // Regex sub-matches of the URL (usually resource ID's). - UrlSubMatches url_sub_matches; + UrlMatches url_matches; }; struct RestResponse { @@ -79,22 +79,22 @@ public: void Handle(const RestRequest& request, RestResponse* response) final; public: - virtual void Get(const UrlSubMatches& url_sub_matches, + virtual void Get(const UrlMatches& url_matches, const UrlQuery& query, RestResponse* response) { } - virtual void Put(const UrlSubMatches& url_sub_matches, + virtual void Put(const UrlMatches& url_matches, const std::string& request_content, RestResponse* response) { } - virtual void Patch(const UrlSubMatches& url_sub_matches, + virtual void Patch(const UrlMatches& url_matches, const std::string& request_content, RestResponse* response) { } - virtual void Delete(const UrlSubMatches& url_sub_matches, + virtual void Delete(const UrlMatches& url_matches, RestResponse* response) { } }; diff --git a/webcc/rest_service_manager.cc b/webcc/rest_service_manager.cc index af5bdf5..76229d3 100644 --- a/webcc/rest_service_manager.cc +++ b/webcc/rest_service_manager.cc @@ -31,9 +31,9 @@ bool RestServiceManager::AddService(RestServicePtr service, } } -RestServicePtr RestServiceManager::GetService( - const std::string& url, std::vector* sub_matches) { - assert(sub_matches != nullptr); +RestServicePtr RestServiceManager::GetService(const std::string& url, + UrlMatches* matches) { + assert(matches != nullptr); for (ServiceItem& item : service_items_) { if (item.is_regex) { @@ -43,7 +43,7 @@ RestServicePtr RestServiceManager::GetService( // Any sub-matches? // NOTE: Start from 1 because match[0] is the whole string itself. for (size_t i = 1; i < match.size(); ++i) { - sub_matches->push_back(match[i].str()); + matches->push_back(match[i].str()); } return item.service; diff --git a/webcc/rest_service_manager.h b/webcc/rest_service_manager.h index 6b031a0..37c0b17 100644 --- a/webcc/rest_service_manager.h +++ b/webcc/rest_service_manager.h @@ -24,12 +24,11 @@ public: bool AddService(RestServicePtr service, const std::string& url, bool is_regex); - // The |sub_matches| is only available when the |url| bound to the - // service is a regular expression and has sub-expressions. + // The |matches| is only available when the |url| bound to the service is a + // regular expression and has sub-expressions. // E.g., the URL bound to the service is "/instances/(\\d+)", now match - // "/instances/12345" against it, you will get one sub-match of "12345". - RestServicePtr GetService(const std::string& url, - std::vector* sub_matches); + // "/instances/12345" against it, you will get one match of "12345". + RestServicePtr GetService(const std::string& url, UrlMatches* matches); private: class ServiceItem { diff --git a/webcc/soap_client.cc b/webcc/soap_client.cc index 18ee245..e10d531 100644 --- a/webcc/soap_client.cc +++ b/webcc/soap_client.cc @@ -10,19 +10,11 @@ namespace webcc { -SoapClient::SoapClient(const std::string& host, const std::string& port, - SoapVersion soap_version, std::size_t buffer_size) - : host_(host), port_(port), soap_version_(soap_version), +SoapClient::SoapClient(const std::string& url, SoapVersion soap_version, + std::size_t buffer_size) + : url_(url), soap_version_(soap_version), http_client_(buffer_size), format_raw_(true), error_(kNoError) { - // Try to extract port from host if it's empty. - if (port_.empty()) { - std::size_t i = host_.find_last_of(':'); - if (i != std::string::npos) { - port_ = host_.substr(i + 1); - host_ = host_.substr(0, i); - } - } } bool SoapClient::Request(const std::string& operation, @@ -30,7 +22,7 @@ bool SoapClient::Request(const std::string& operation, SoapResponse::Parser parser, std::size_t buffer_size) { assert(service_ns_.IsValid()); - assert(!url_.empty() && !host_.empty()); + assert(!url_.empty()); error_ = kNoError; fault_.reset(); @@ -55,14 +47,7 @@ bool SoapClient::Request(const std::string& operation, std::string http_content; soap_request.ToXml(format_raw_, indent_str_, &http_content); - // TODO - std::string url = host_; - url += url_; - if (!port_.empty()) { - url += ":" + port_; - } - - HttpRequest http_request(http::kPost, url); + HttpRequest http_request(http::kPost, url_); http_request.SetContent(std::move(http_content), true); diff --git a/webcc/soap_client.h b/webcc/soap_client.h index ab3da4f..c7d557f 100644 --- a/webcc/soap_client.h +++ b/webcc/soap_client.h @@ -13,9 +13,7 @@ namespace webcc { class SoapClient { public: - // If |port| is empty, |host| will be checked to see if it contains port or - // not (separated by ':'). - explicit SoapClient(const std::string& host, const std::string& port = "", + explicit SoapClient(const std::string& url, SoapVersion soap_version = kSoapV12, std::size_t buffer_size = 0); @@ -28,8 +26,6 @@ public: http_client_.SetTimeout(seconds); } - void set_url(const std::string& url) { url_ = url; } - void set_service_ns(const SoapNamespace& service_ns) { service_ns_ = service_ns; } @@ -71,16 +67,12 @@ public: std::shared_ptr fault() const { return fault_; } private: - std::string host_; - std::string port_; // Leave this empty to use default 80. + std::string url_; SoapVersion soap_version_; HttpClient http_client_; - // Request URL. - std::string url_; - // Namespace for your web service. SoapNamespace service_ns_; diff --git a/webcc/soap_request_handler.cc b/webcc/soap_request_handler.cc index 9dbe579..4e20800 100644 --- a/webcc/soap_request_handler.cc +++ b/webcc/soap_request_handler.cc @@ -16,7 +16,9 @@ bool SoapRequestHandler::Bind(SoapServicePtr service, const std::string& url) { } void SoapRequestHandler::HandleConnection(HttpConnectionPtr connection) { - SoapServicePtr service = GetServiceByUrl(connection->request().url().path()); + std::string path = "/" + connection->request().url().path(); + + SoapServicePtr service = GetServiceByUrl(path); if (!service) { connection->SendResponse(http::Status::kBadRequest); return;