From 4d7089535ba204a4a6586d7115a9aca977467a4a Mon Sep 17 00:00:00 2001 From: Saeed Masoumi Date: Thu, 3 Feb 2022 23:05:24 +0330 Subject: [PATCH 1/2] default rest api reouter added. RestApiRouter sample has been added. --- Release/include/cpprest/http_listener.h | 633 ++++++++++-------- Release/samples/RestApiRouter/apis.h | 46 ++ .../samples/RestApiRouter/restapirouter.cpp | 43 ++ 3 files changed, 438 insertions(+), 284 deletions(-) create mode 100644 Release/samples/RestApiRouter/apis.h create mode 100644 Release/samples/RestApiRouter/restapirouter.cpp diff --git a/Release/include/cpprest/http_listener.h b/Release/include/cpprest/http_listener.h index a5457c0135..78b0bf39ed 100644 --- a/Release/include/cpprest/http_listener.h +++ b/Release/include/cpprest/http_listener.h @@ -20,322 +20,387 @@ #include #endif -#if !defined(_WIN32) || (_WIN32_WINNT >= _WIN32_WINNT_VISTA && !defined(__cplusplus_winrt)) || \ +#if !defined(_WIN32) || (_WIN32_WINNT >= _WIN32_WINNT_VISTA && !defined(__cplusplus_winrt)) || \ defined(CPPREST_FORCE_HTTP_LISTENER_ASIO) namespace web { -namespace http -{ -/// HTTP listener is currently in beta. -namespace experimental -{ -/// HTTP server side library. -namespace listener -{ -/// -/// Configuration class used to set various options when constructing and http_listener instance. -/// -class http_listener_config -{ -public: - /// - /// Create an http_listener configuration with default options. - /// - http_listener_config() : m_timeout(utility::seconds(120)), m_backlog(0) {} - - /// - /// Copy constructor. - /// - /// http_listener_config to copy. - http_listener_config(const http_listener_config& other) - : m_timeout(other.m_timeout) - , m_backlog(other.m_backlog) + namespace http + { + /// HTTP listener is currently in beta. + namespace experimental + { + /// HTTP server side library. + namespace listener + { + /// + /// Configuration class used to set various options when constructing and http_listener instance. + /// + class http_listener_config + { + public: + /// + /// Create an http_listener configuration with default options. + /// + http_listener_config() : m_timeout(utility::seconds(120)), m_backlog(0) {} + + /// + /// Copy constructor. + /// + /// http_listener_config to copy. + http_listener_config(const http_listener_config &other) + : m_timeout(other.m_timeout), m_backlog(other.m_backlog) #if !defined(_WIN32) || defined(CPPREST_FORCE_HTTP_LISTENER_ASIO) - , m_ssl_context_callback(other.m_ssl_context_callback) + , + m_ssl_context_callback(other.m_ssl_context_callback) #endif - { - } - - /// - /// Move constructor. - /// - /// http_listener_config to move from. - http_listener_config(http_listener_config&& other) - : m_timeout(std::move(other.m_timeout)) - , m_backlog(std::move(other.m_backlog)) + { + } + + /// + /// Move constructor. + /// + /// http_listener_config to move from. + http_listener_config(http_listener_config &&other) + : m_timeout(std::move(other.m_timeout)), m_backlog(std::move(other.m_backlog)) #if !defined(_WIN32) || defined(CPPREST_FORCE_HTTP_LISTENER_ASIO) - , m_ssl_context_callback(std::move(other.m_ssl_context_callback)) + , + m_ssl_context_callback(std::move(other.m_ssl_context_callback)) #endif - { - } - - /// - /// Assignment operator. - /// - /// http_listener_config instance. - http_listener_config& operator=(const http_listener_config& rhs) - { - if (this != &rhs) - { - m_timeout = rhs.m_timeout; - m_backlog = rhs.m_backlog; + { + } + + /// + /// Assignment operator. + /// + /// http_listener_config instance. + http_listener_config &operator=(const http_listener_config &rhs) + { + if (this != &rhs) + { + m_timeout = rhs.m_timeout; + m_backlog = rhs.m_backlog; #if !defined(_WIN32) || defined(CPPREST_FORCE_HTTP_LISTENER_ASIO) - m_ssl_context_callback = rhs.m_ssl_context_callback; + m_ssl_context_callback = rhs.m_ssl_context_callback; #endif - } - return *this; - } - - /// - /// Assignment operator. - /// - /// http_listener_config instance. - http_listener_config& operator=(http_listener_config&& rhs) - { - if (this != &rhs) - { - m_timeout = std::move(rhs.m_timeout); - m_backlog = std::move(rhs.m_backlog); + } + return *this; + } + + /// + /// Assignment operator. + /// + /// http_listener_config instance. + http_listener_config &operator=(http_listener_config &&rhs) + { + if (this != &rhs) + { + m_timeout = std::move(rhs.m_timeout); + m_backlog = std::move(rhs.m_backlog); #if !defined(_WIN32) || defined(CPPREST_FORCE_HTTP_LISTENER_ASIO) - m_ssl_context_callback = std::move(rhs.m_ssl_context_callback); + m_ssl_context_callback = std::move(rhs.m_ssl_context_callback); #endif - } - return *this; - } - - /// - /// Get the timeout - /// - /// The timeout (in seconds). - utility::seconds timeout() const { return m_timeout; } - - /// - /// Set the timeout - /// - /// The timeout (in seconds) used for each send and receive operation on the client. - void set_timeout(utility::seconds timeout) { m_timeout = std::move(timeout); } - - /// - /// Get the listen backlog - /// - /// The maximum length of the queue of pending connections, or zero for the implementation - /// default. The implementation may not honour this value. - int backlog() const { return m_backlog; } - - /// - /// Set the listen backlog - /// - /// The maximum length of the queue of pending connections, or zero for the implementation - /// default. The implementation may not honour this value. - void set_backlog(int backlog) { m_backlog = backlog; } + } + return *this; + } + + /// + /// Get the timeout + /// + /// The timeout (in seconds). + utility::seconds timeout() const { return m_timeout; } + + /// + /// Set the timeout + /// + /// The timeout (in seconds) used for each send and receive operation on the client. + void set_timeout(utility::seconds timeout) { m_timeout = std::move(timeout); } + + /// + /// Get the listen backlog + /// + /// The maximum length of the queue of pending connections, or zero for the implementation + /// default. The implementation may not honour this value. + int backlog() const { return m_backlog; } + + /// + /// Set the listen backlog + /// + /// The maximum length of the queue of pending connections, or zero for the implementation + /// default. The implementation may not honour this value. + void set_backlog(int backlog) { m_backlog = backlog; } #if !defined(_WIN32) || defined(CPPREST_FORCE_HTTP_LISTENER_ASIO) - /// - /// Get the callback of ssl context - /// - /// The function defined by the user of http_listener_config to configure a ssl context. - const std::function& get_ssl_context_callback() const - { - return m_ssl_context_callback; - } - - /// - /// Set the callback of ssl context - /// - /// The function to configure a ssl context which will setup https - /// connections. - void set_ssl_context_callback(const std::function& ssl_context_callback) - { - m_ssl_context_callback = ssl_context_callback; - } + /// + /// Get the callback of ssl context + /// + /// The function defined by the user of http_listener_config to configure a ssl context. + const std::function &get_ssl_context_callback() const + { + return m_ssl_context_callback; + } + + /// + /// Set the callback of ssl context + /// + /// The function to configure a ssl context which will setup https + /// connections. + void set_ssl_context_callback(const std::function &ssl_context_callback) + { + m_ssl_context_callback = ssl_context_callback; + } #endif -private: - utility::seconds m_timeout; - int m_backlog; + private: + utility::seconds m_timeout; + int m_backlog; #if !defined(_WIN32) || defined(CPPREST_FORCE_HTTP_LISTENER_ASIO) - std::function m_ssl_context_callback; + std::function m_ssl_context_callback; #endif -}; + }; -namespace details -{ -/// -/// Internal class for pointer to implementation design pattern. -/// -class http_listener_impl -{ -public: - http_listener_impl() : m_closed(true), m_close_task(pplx::task_from_result()) {} + namespace details + { + /// + /// Internal class for pointer to implementation design pattern. + /// + class http_listener_impl + { + public: + http_listener_impl() : m_closed(true), m_close_task(pplx::task_from_result()) {} - _ASYNCRTIMP http_listener_impl(http::uri address); - _ASYNCRTIMP http_listener_impl(http::uri address, http_listener_config config); + _ASYNCRTIMP http_listener_impl(http::uri address); + _ASYNCRTIMP http_listener_impl(http::uri address, http_listener_config config); - _ASYNCRTIMP pplx::task open(); - _ASYNCRTIMP pplx::task close(); + _ASYNCRTIMP pplx::task open(); + _ASYNCRTIMP pplx::task close(); - /// - /// Handler for all requests. The HTTP host uses this to dispatch a message to the pipeline. - /// - /// Only HTTP server implementations should call this API. - _ASYNCRTIMP void handle_request(http::http_request msg); + /// + /// Handler for all requests. The HTTP host uses this to dispatch a message to the pipeline. + /// + /// Only HTTP server implementations should call this API. + _ASYNCRTIMP void handle_request(http::http_request msg); - const http::uri& uri() const { return m_uri; } + const http::uri &uri() const { return m_uri; } - const http_listener_config& configuration() const { return m_config; } + const http_listener_config &configuration() const { return m_config; } - // Handlers - std::function m_all_requests; - std::map> m_supported_methods; + // Handlers + std::function m_all_requests; + std::map> m_supported_methods; -private: - // Default implementation for TRACE and OPTIONS. - void handle_trace(http::http_request message); - void handle_options(http::http_request message); + private: + // Default implementation for TRACE and OPTIONS. + void handle_trace(http::http_request message); + void handle_options(http::http_request message); - // Gets a comma separated string containing the methods supported by this listener. - utility::string_t get_supported_methods() const; + // Gets a comma separated string containing the methods supported by this listener. + utility::string_t get_supported_methods() const; - http::uri m_uri; - http_listener_config m_config; + http::uri m_uri; + http_listener_config m_config; - // Used to record that the listener is closed. - bool m_closed; - pplx::task m_close_task; -}; - -} // namespace details + // Used to record that the listener is closed. + bool m_closed; + pplx::task m_close_task; + }; + } // namespace details /// -/// A class for listening and processing HTTP requests at a specific URI. +/// Rest router implemention. /// -class http_listener -{ -public: - /// - /// Create a listener from a URI. - /// - /// The listener will not have been opened when returned. - /// URI at which the listener should accept requests. - http_listener(http::uri address) - : m_impl(utility::details::make_unique(std::move(address))) - { - } - - /// - /// Create a listener with specified URI and configuration. - /// - /// URI at which the listener should accept requests. - /// Configuration to create listener with. - http_listener(http::uri address, http_listener_config config) - : m_impl(utility::details::make_unique(std::move(address), std::move(config))) - { - } - - /// - /// Default constructor. - /// - /// The resulting listener cannot be used for anything, but is useful to initialize a variable - /// that will later be overwritten with a real listener instance. - http_listener() : m_impl(utility::details::make_unique()) {} - - /// - /// Destructor frees any held resources. - /// - /// Call close() before allowing a listener to be destroyed. - ~http_listener() - { - if (m_impl) - { - // As a safe guard close the listener if not already done. - // Users are required to call close, but this is just a safeguard. - try - { - m_impl->close().wait(); - } - catch (...) - { - } - } - } - - /// - /// Asynchronously open the listener, i.e. start accepting requests. - /// - /// A task that will be completed once this listener is actually opened, accepting requests. - pplx::task open() { return m_impl->open(); } - - /// - /// Asynchronously stop accepting requests and close all connections. - /// - /// A task that will be completed once this listener is actually closed, no longer accepting - /// requests. This function will stop accepting requests and wait for all outstanding handler - /// calls to finish before completing the task. Waiting on the task returned from close() within a handler and - /// blocking waiting for its result will result in a deadlock. - /// - /// Call close() before allowing a listener to be destroyed. - /// - pplx::task close() { return m_impl->close(); } - - /// - /// Add a general handler to support all requests. - /// - /// Function object to be called for all requests. - void support(const std::function& handler) { m_impl->m_all_requests = handler; } - - /// - /// Add support for a specific HTTP method. - /// - /// An HTTP method. - /// Function object to be called for all requests for the given HTTP method. - void support(const http::method& method, const std::function& handler) - { - m_impl->m_supported_methods[method] = handler; - } - - /// - /// Get the URI of the listener. - /// - /// The URI this listener is for. - const http::uri& uri() const { return m_impl->uri(); } - - /// - /// Get the configuration of this listener. - /// - /// Configuration this listener was constructed with. - const http_listener_config& configuration() const { return m_impl->configuration(); } - - /// - /// Move constructor. - /// - /// http_listener instance to construct this one from. - http_listener(http_listener&& other) : m_impl(std::move(other.m_impl)) {} - - /// - /// Move assignment operator. - /// - /// http_listener to replace this one with. - http_listener& operator=(http_listener&& other) - { - if (this != &other) - { - m_impl = std::move(other.m_impl); - } - return *this; - } - -private: - // No copying of listeners. - http_listener(const http_listener& other); - http_listener& operator=(const http_listener& other); - - std::unique_ptr m_impl; -}; - -} // namespace listener -} // namespace experimental -} // namespace http +#if defined(_GLIBCXX_FUNCTIONAL) || defined(_FUNCTIONAL_) +#include +#endif +#if defined(_GLIBCXX_MAP) || defined(_MAP_) +#include +#endif +#if defined(CASA_JSON_H) +#include +#endif + class rest_api_router + { + private: + using action_pointer = std::function; + static std::map routes_container; + static void add(utility::string_t endpoint, action_pointer actionPtr) + { + routes_container.insert(std::pair(endpoint, actionPtr)); + } + static web::json::value execute(utility::string_t endpoint, const web::http::http_request &request) + { + std::map::iterator it; + it = rest_api_router::routes_container.find(endpoint); + if (it == rest_api_router::routes_container.end()) + return web::json::value::null(); + else + return it->second(request); + } + + + public: + template + class register_route + { + public: + using action_pointer = web::json::value (C::*)(const web::http::http_request &); + register_route(utility::string_t endpoint, action_pointer action) + { + C *c = new C; + rest_api_router::add(endpoint, std::bind(action, c, std::placeholders::_1)); + delete (c); + } + }; + static const void route_handler(const web::http::http_request &request) + { + web::json::value result; + result = web::http::experimental::listener::rest_api_router::execute(request.relative_uri().to_string(), request); + if (result != web::json::value::null()) + request.reply(web::http::status_codes::OK, result); + else + request.reply(web::http::status_codes::NotFound); + } + }; + std::map rest_api_router::routes_container; + /// + /// A class for listening and processing HTTP requests at a specific URI. + /// + class http_listener + { + public: + /// + /// Create a listener from a URI. + /// + /// The listener will not have been opened when returned. + /// URI at which the listener should accept requests. + http_listener(http::uri address) + : m_impl(utility::details::make_unique(std::move(address))) + { + } + + /// + /// Create a listener with specified URI and configuration. + /// + /// URI at which the listener should accept requests. + /// Configuration to create listener with. + http_listener(http::uri address, http_listener_config config) + : m_impl(utility::details::make_unique(std::move(address), std::move(config))) + { + } + + /// + /// Default constructor. + /// + /// The resulting listener cannot be used for anything, but is useful to initialize a variable + /// that will later be overwritten with a real listener instance. + http_listener() : m_impl(utility::details::make_unique()) {} + + /// + /// Destructor frees any held resources. + /// + /// Call close() before allowing a listener to be destroyed. + ~http_listener() + { + if (m_impl) + { + // As a safe guard close the listener if not already done. + // Users are required to call close, but this is just a safeguard. + try + { + m_impl->close().wait(); + } + catch (...) + { + } + } + } + + /// + /// Asynchronously open the listener, i.e. start accepting requests. + /// + /// A task that will be completed once this listener is actually opened, accepting requests. + pplx::task open() { return m_impl->open(); } + + /// + /// Asynchronously stop accepting requests and close all connections. + /// + /// A task that will be completed once this listener is actually closed, no longer accepting + /// requests. This function will stop accepting requests and wait for all outstanding handler + /// calls to finish before completing the task. Waiting on the task returned from close() within a handler and + /// blocking waiting for its result will result in a deadlock. + /// + /// Call close() before allowing a listener to be destroyed. + /// + pplx::task close() { return m_impl->close(); } + + /// + /// Add a general handler to support all requests. + /// + /// Function object to be called for all requests. + void support(const std::function &handler) { m_impl->m_all_requests = handler; } + + /// + /// Add support for a specific HTTP method. + /// + /// An HTTP method. + /// Function object to be called for all requests for the given HTTP method. + void support(const http::method &method, const std::function &handler) + { + m_impl->m_supported_methods[method] = handler; + } + + /// + /// Add support for a rest apit routing. + /// + /// Function object to be called onc time for add all users endpoints. + void support_rest_api_routing(const std::function &route_config) + { + route_config(); + m_impl->m_all_requests = &rest_api_router::route_handler; + } + + /// + /// Get the URI of the listener. + /// + /// The URI this listener is for. + const http::uri &uri() const { return m_impl->uri(); } + + /// + /// Get the configuration of this listener. + /// + /// Configuration this listener was constructed with. + const http_listener_config &configuration() const { return m_impl->configuration(); } + + /// + /// Move constructor. + /// + /// http_listener instance to construct this one from. + http_listener(http_listener &&other) : m_impl(std::move(other.m_impl)) {} + + /// + /// Move assignment operator. + /// + /// http_listener to replace this one with. + http_listener &operator=(http_listener &&other) + { + if (this != &other) + { + m_impl = std::move(other.m_impl); + } + return *this; + } + + private: + // No copying of listeners. + http_listener(const http_listener &other); + http_listener &operator=(const http_listener &other); + + std::unique_ptr m_impl; + }; + + } // namespace listener + } // namespace experimental + } // namespace http } // namespace web #endif diff --git a/Release/samples/RestApiRouter/apis.h b/Release/samples/RestApiRouter/apis.h new file mode 100644 index 0000000000..c8e45284e5 --- /dev/null +++ b/Release/samples/RestApiRouter/apis.h @@ -0,0 +1,46 @@ +#pragma once +#ifndef _APIS_SAMPLE +#define _APIS_SAMPLE +#endif +#include +#include +class api1 +{ +public: + web::json::value Action1(const web::http::http_request &request) + { + web::json::value result; + result["method"] = web::json::value::string(request.method()); + result["endpoint"] = web::json::value::string(request.relative_uri().to_string()); + result["body"] = request.extract_json().get(); + return result; + } + web::json::value Action2(const web::http::http_request &request) + { + web::json::value result; + result["method"] = web::json::value::string(request.method()); + result["endpoint"] = web::json::value::string(request.relative_uri().to_string()); + result["body"] = request.extract_json().get(); + return result; + } +}; +class api2 +{ +public: + web::json::value Action1(const web::http::http_request &request) + { + web::json::value result; + result["method"] = web::json::value::string(request.method()); + result["endpoint"] = web::json::value::string(request.relative_uri().to_string()); + result["body"] = request.extract_json().get(); + return result; + } + web::json::value Action2(const web::http::http_request &request) + { + web::json::value result; + result["method"] = web::json::value::string(request.method()); + result["endpoint"] = web::json::value::string(request.relative_uri().to_string()); + result["body"] = request.extract_json().get(); + return result; + } +}; \ No newline at end of file diff --git a/Release/samples/RestApiRouter/restapirouter.cpp b/Release/samples/RestApiRouter/restapirouter.cpp new file mode 100644 index 0000000000..7aa8fb4c17 --- /dev/null +++ b/Release/samples/RestApiRouter/restapirouter.cpp @@ -0,0 +1,43 @@ +#include "apis.h" +#include +#include +using namespace utility; +using namespace web::http; +using namespace web::http::client; +using namespace web::http::experimental::listener; +class Server +{ +private: + static const void route_registeration() + { + rest_api_router::register_route("/api/v1/api1/action1", &api1::Action1); + rest_api_router::register_route("/api/v1/api1/action2", &api1::Action2); + rest_api_router::register_route("/api/v1/api2/action1", &api2::Action2); + rest_api_router::register_route("/api/v1/api2/action2", &api2::Action2); + } + +public: + const void Listen(void) + { + string_t baseUrl = "http://0.0.0.0:7777"; + http_listener baseListener(baseUrl); + baseListener.support_rest_api_routing(route_registeration); + try + { + baseListener.open(); + std::cout << "Server is running on http://0.0.0.0:7777" << std::endl; + std::string line; + std::getline(std::cin, line); + } + catch (const std::exception &e) + { + std::cerr << "Error: " << e.what() << std::endl; + } + } +}; +int main() +{ + Server s; + s.Listen(); + return 0; +} \ No newline at end of file From e954f7fd48c17ddf9bb7d2ab4934080eb87d054f Mon Sep 17 00:00:00 2001 From: Saeed Masoumi Date: Thu, 3 Feb 2022 23:36:44 +0330 Subject: [PATCH 2/2] Update README.md Added "Rest API Route" section. --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index e7d91abeaa..35674e7edd 100644 --- a/README.md +++ b/README.md @@ -75,3 +75,29 @@ We'd love to get your review score, whether good or bad, but even more than that This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +## Rest API Router: +You can find sources under "Release/samples/RestApiRouter". I've created a sample only for showing you how easy it is to use, +I'm using gcc under WSL (debian) and Postman for testing ednpoints: +Compile & Build: +```bash +g++ -std=c++11 restapirouter.cpp -o sample -lcrypto -lcpprest -lpthread +``` +```bash +./sample +``` +![image](https://user-images.githubusercontent.com/56490683/152418716-dcb77f03-4fa1-4891-9714-cfdda05b919e.png) + +Use Postman to send a request (Method: GET , Body: {"name":"test"}, Endpoint: /api/v1/api1/action1): + +![image](https://user-images.githubusercontent.com/56490683/152419500-1c3f3515-45b5-42a8-9c60-0be4a124db7f.png) + +Use Postman to send a request (Method: POST , Body: {"name":"test", "username":"testuser"}, Endpoint: /api/v1/api2/action2): + +![image](https://user-images.githubusercontent.com/56490683/152419758-a303b6ae-da49-445a-967e-ba6e82529ae4.png) + +What if we requested a not defined endpoint: +Use Postman to send a request (Method: POST , Body: {"name":"test", "username":"testuser"}, Endpoint: /api/v1/api2/**action3**): + +![image](https://user-images.githubusercontent.com/56490683/152419929-ba197ee6-112d-458d-8259-ff1035b57b36.png) + +Thats all.