diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c99676d..2f7abc02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -341,7 +341,7 @@ else() endif () add_executable(dvmhost ${dvmhost_SRC}) target_include_directories(dvmhost PRIVATE .) -target_link_libraries(dvmhost PRIVATE Threads::Threads util) +target_link_libraries(dvmhost PRIVATE asio::asio Threads::Threads util) set(CPACK_GENERATOR "DEB") set(CPACK_PACKAGE_NAME "dvmhost") @@ -393,7 +393,7 @@ else() endif () add_executable(dvmtests ${dvmhost_SRC} ${dvmtests_SRC}) target_compile_definitions(dvmtests PUBLIC -DCATCH2_TEST_COMPILATION) -target_link_libraries(dvmtests PRIVATE Catch2::Catch2WithMain Threads::Threads util) +target_link_libraries(dvmtests PRIVATE Catch2::Catch2WithMain asio::asio Threads::Threads util) target_include_directories(dvmtests PRIVATE .) endif (ENABLE_TESTS) diff --git a/host/Host.cpp b/host/Host.cpp index 463c1dc9..8c2f1399 100644 --- a/host/Host.cpp +++ b/host/Host.cpp @@ -722,6 +722,10 @@ int Host::run() setState(STATE_IDLE); } + if (m_remoteControl != nullptr) { + m_remoteControl->setProtocols(dmr.get(), p25.get(), nxdn.get()); + } + ::LogInfoEx(LOG_HOST, "Host is performing late initialization and warmup"); // perform early pumping of the modem clock (this is so the DSP has time to setup its buffers), @@ -2345,7 +2349,7 @@ bool Host::createNetwork() // initialize network remote command if (rconEnable) { - m_remoteControl = new RemoteControl(rconAddress, rconPort, rconPassword, rconDebug); + m_remoteControl = new RemoteControl(rconAddress, rconPort, rconPassword, this, rconDebug); m_remoteControl->setLookups(m_ridLookup, m_tidLookup); bool ret = m_remoteControl->open(); if (!ret) { diff --git a/network/RemoteControl.cpp b/network/RemoteControl.cpp index 8bff252f..ca2765d4 100644 --- a/network/RemoteControl.cpp +++ b/network/RemoteControl.cpp @@ -36,12 +36,16 @@ #include "modem/Modem.h" #include "host/Host.h" #include "network/UDPSocket.h" +#include "network/json/json.h" #include "RemoteControl.h" #include "HostMain.h" #include "Log.h" +#include "Thread.h" #include "Utils.h" using namespace network; +using namespace network::rest; +using namespace network::rest::http; using namespace modem; #include @@ -100,13 +104,22 @@ std::string string_format(const std::string& format, FormatArgs ... args) /// Network Hostname/IP address to connect to. /// Network port number. /// Authentication password. +/// Instance of the Host class. /// -RemoteControl::RemoteControl(const std::string& address, uint16_t port, const std::string& password, bool debug) : +RemoteControl::RemoteControl(const std::string& address, uint16_t port, const std::string& password, Host* host, bool debug) : + m_dispatcher(), + m_restServer(address, (uint16_t)(port + 1U)/*port*/), m_socket(address, port), m_p25MFId(p25::P25_MFG_STANDARD), m_password(password), m_passwordHash(nullptr), - m_debug(debug) + m_debug(debug), + m_host(host), + m_dmr(nullptr), + m_p25(nullptr), + m_nxdn(nullptr), + m_ridLookup(nullptr), + m_tidLookup(nullptr) { assert(!address.empty()); assert(port > 0U); @@ -145,6 +158,19 @@ void RemoteControl::setLookups(lookups::RadioIdLookup* ridLookup, lookups::Talkg m_tidLookup = tidLookup; } +/// +/// Sets the instances of the digital radio protocols. +/// +/// Instance of the DMR Control class. +/// Instance of the P25 Control class. +/// Instance of the NXDN Control class. +void RemoteControl::setProtocols(dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn) +{ + m_dmr = dmr; + m_p25 = p25; + m_nxdn = nxdn; +} + /// /// Process remote network command data. /// @@ -850,6 +876,10 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx /// bool RemoteControl::open() { + initializeEndpoints(); + m_restServer.setHandler(m_dispatcher); + + run(); return m_socket.open(); } @@ -858,6 +888,9 @@ bool RemoteControl::open() /// void RemoteControl::close() { + m_restServer.stop(); + wait(); + m_socket.close(); } @@ -865,6 +898,28 @@ void RemoteControl::close() // Private Class Members // --------------------------------------------------------------------------- +/// +/// Helper to initialize REST API endpoints. +/// +void RemoteControl::initializeEndpoints() +{ + m_dispatcher.match("/version") + .get([](HTTPReply& reply, const RequestMatch& match) { + json::object response = json::object(); + response["version"].set(std::string((__PROG_NAME__ " " __VER__ " (" DESCR_DMR DESCR_P25 DESCR_NXDN "CW Id, Network) (built " __BUILD__ ")"))); + + reply.reply(response); + }); +} + +/// +/// +/// +void RemoteControl::entry() +{ + m_restServer.run(); +} + /// /// Helper to write response to client. /// diff --git a/network/RemoteControl.h b/network/RemoteControl.h index 29ae84bf..ea037b14 100644 --- a/network/RemoteControl.h +++ b/network/RemoteControl.h @@ -33,8 +33,11 @@ #include "Defines.h" #include "network/UDPSocket.h" +#include "network/rest/RequestDispatcher.h" +#include "network/rest/http/HTTPServer.h" #include "lookups/RadioIdLookup.h" #include "lookups/TalkgroupIdLookup.h" +#include "Thread.h" #include #include @@ -117,15 +120,17 @@ namespace nxdn { class HOST_SW_API Control; } // Implements the remote control networking logic. // --------------------------------------------------------------------------- -class HOST_SW_API RemoteControl { +class HOST_SW_API RemoteControl : private Thread { public: /// Initializes a new instance of the RemoteControl class. - RemoteControl(const std::string& address, uint16_t port, const std::string& password, bool debug); + RemoteControl(const std::string& address, uint16_t port, const std::string& password, Host* host, bool debug); /// Finalizes a instance of the RemoteControl class. ~RemoteControl(); /// Sets the instances of the Radio ID and Talkgroup ID lookup tables. void setLookups(::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup); + /// Sets the instances of the digital radio protocols. + void setProtocols(dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn); /// Process remote network command data. void process(Host* host, dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn); @@ -137,6 +142,10 @@ public: void close(); private: + typedef network::rest::RequestDispatcher RESTDispatcherType; + RESTDispatcherType m_dispatcher; + network::rest::http::HTTPServer m_restServer; + network::UDPSocket m_socket; uint8_t m_p25MFId; @@ -144,9 +153,20 @@ private: uint8_t* m_passwordHash; bool m_debug; + Host* m_host; + dmr::Control* m_dmr; + p25::Control* m_p25; + nxdn::Control* m_nxdn; + ::lookups::RadioIdLookup* m_ridLookup; ::lookups::TalkgroupIdLookup* m_tidLookup; + /// Helper to initialize REST API endpoints. + void initializeEndpoints(); + + /// + virtual void entry(); + /// Helper to send response to client. void writeResponse(std::string reply, sockaddr_storage address, uint32_t addrLen); diff --git a/network/rest/Dispatcher.h b/network/rest/Dispatcher.h deleted file mode 100644 index e8b2c4fd..00000000 --- a/network/rest/Dispatcher.h +++ /dev/null @@ -1,177 +0,0 @@ -/** -* Digital Voice Modem - Host Software -* GPLv2 Open Source. Use is subject to license terms. -* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -* -* @package DVM / Host Software -* -*/ -// -// Based on code from the CRUD project. (https://github.com/venediktov/CRUD) -// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html) -// -/* -* Copyright (c) 2003-2013 Christopher M. Kohlhoff -* Copyright (C) 2023 by Bryan Biedenkapp N2PLL -* -* Permission is hereby granted, free of charge, to any person or organization -* obtaining a copy of the software and accompanying documentation covered by -* this license (the “Software”) to use, reproduce, display, distribute, execute, -* and transmit the Software, and to prepare derivative works of the Software, and -* to permit third-parties to whom the Software is furnished to do so, all subject -* to the following: -* -* The copyright notices in the Software and this entire statement, including the -* above license grant, this restriction and the following disclaimer, must be included -* in all copies of the Software, in whole or in part, and all derivative works of the -* Software, unless such copies or derivative works are solely in the form of -* machine-executable object code generated by a source language processor. -* -* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -* PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE -* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN -* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -#if !defined(__REST__DISPATHER_H__) -#define __REST__DISPATHER_H__ - -#include "Defines.h" - -#include -#include -#include -#include -#include - -namespace rest { - // --------------------------------------------------------------------------- - // Structure Declaration - // - // --------------------------------------------------------------------------- - - template - struct RequestMatch : Matched - { - /// Initializes a new instance of the RequestMatch structure. - RequestMatch(const Matched& m, const std::string& d) : Matched(m) , data(d) { /* stub */ } - - std::string data; - }; - - // --------------------------------------------------------------------------- - // Structure Declaration - // - // --------------------------------------------------------------------------- - - template - struct RequestMatcher { - typedef std::function&)> RequestHandlerType; - typedef RequestMatcher selfType; - - /// Initializes a new instance of the RequestMatch structure. - explicit RequestMatcher(const Regex& expression) : m_expression(expression) { /* stub */ } - - /// - selfType& get(RequestHandlerType handler) { - m_handlers["GET"] = handler; - return *this; - } - /// - selfType& post(RequestHandlerType handler) { - m_handlers["POST"] = handler; - return *this; - } - /// - selfType& put(RequestHandlerType handler) { - m_handlers["PUT"] = handler; - return *this; - } - /// - selfType& del(RequestHandlerType handler) { - m_handlers["DELETE"] = handler; - return *this; - } - /// - selfType& options(RequestHandlerType handler) { - m_handlers["OPTIONS"] = handler; - return *this; - } - - /// - template - void handleRequest(const Request& request, Response& response, const Matched &what) { - // dispatching to matching based on handler - RequestMatch match(what, request.data); - auto& handler = m_handlers[request.method]; - if (handler) { - handler(response, match); - } - } - - private: - Regex m_expression; - std::map m_handlers; - }; - - // --------------------------------------------------------------------------- - // Class Declaration - // This class implements RESTful web request dispatching. - // --------------------------------------------------------------------------- - - template - class RequestDispatcher { - typedef RequestMatcher MatcherType; - typedef std::shared_ptr MatcherTypePtr; - public: - /// Initializes a new instance of the RequestDispatcher class. - RequestDispatcher() : m_basePath() { /* stub */ } - /// Initializes a new instance of the RequestDispatcher class. - RequestDispatcher(const std::string &basePath) : m_basePath(basePath) { /* stub */ } - - /// - MatcherType& match(const Expression& expression) - { - MatcherTypePtr& p = m_matchers[expression]; - if(!p) { - p = std::make_shared(expression); - } - - return *p; - } - - /// - template - typename std::enable_if::value, void>::type - handleRequest(const Request& request, Response& response) - { - for (const auto& matcher : m_matchers) { - Match what; - if (std::regex_match(request.uri.c_str(), what, matcher.first)) { - matcher.second->handle_request(request, response, what); - } - } - } - - /// - template - typename std::enable_if::value, void>::type - handleRequest(const Request& request, Response& response) - { - for ( const auto& matcher : m_matchers) { - Match what; - if (request.uri.find(matcher.first) != std::string::npos) { - what = matcher.first; - matcher.second->handle_request(request, response, what); - } - } - } - - private: - std::string m_basePath; - std::map m_matchers; - }; -} // namespace rest - -#endif // __REST__DISPATHER_H__ diff --git a/network/rest/RequestDispatcher.h b/network/rest/RequestDispatcher.h new file mode 100644 index 00000000..ef803a94 --- /dev/null +++ b/network/rest/RequestDispatcher.h @@ -0,0 +1,181 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the CRUD project. (https://github.com/venediktov/CRUD) +// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html) +// +/* +* Copyright (c) 2003-2013 Christopher M. Kohlhoff +* Copyright (C) 2023 by Bryan Biedenkapp N2PLL +* +* Permission is hereby granted, free of charge, to any person or organization +* obtaining a copy of the software and accompanying documentation covered by +* this license (the “Software”) to use, reproduce, display, distribute, execute, +* and transmit the Software, and to prepare derivative works of the Software, and +* to permit third-parties to whom the Software is furnished to do so, all subject +* to the following: +* +* The copyright notices in the Software and this entire statement, including the +* above license grant, this restriction and the following disclaimer, must be included +* in all copies of the Software, in whole or in part, and all derivative works of the +* Software, unless such copies or derivative works are solely in the form of +* machine-executable object code generated by a source language processor. +* +* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +* PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE +* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN +* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#if !defined(__REST__DISPATCHER_H__) +#define __REST__DISPATCHER_H__ + +#include "Defines.h" +#include "network/rest/http/HTTPRequest.h" +#include "network/rest/http/HTTPReply.h" + +#include +#include +#include +#include +#include + +namespace network { + namespace rest { + // --------------------------------------------------------------------------- + // Structure Declaration + // + // --------------------------------------------------------------------------- + + struct RequestMatch : std::smatch + { + /// Initializes a new instance of the RequestMatch structure. + RequestMatch(const std::smatch& m, const std::string& d) : std::smatch(m), data(d) { /* stub */ } + + std::string data; + }; + + // --------------------------------------------------------------------------- + // Structure Declaration + // + // --------------------------------------------------------------------------- + + template + struct RequestMatcher { + typedef std::function RequestHandlerType; + + /// Initializes a new instance of the RequestMatcher structure. + explicit RequestMatcher(const std::string& expression) : m_expression(expression), m_isRegEx(false) { /* stub */ } + + /// + RequestMatcher& get(RequestHandlerType handler) { + m_handlers["GET"] = handler; + return *this; + } + /// + RequestMatcher& post(RequestHandlerType handler) { + m_handlers["POST"] = handler; + return *this; + } + /// + RequestMatcher& put(RequestHandlerType handler) { + m_handlers["PUT"] = handler; + return *this; + } + /// + RequestMatcher& del(RequestHandlerType handler) { + m_handlers["DELETE"] = handler; + return *this; + } + /// + RequestMatcher& options(RequestHandlerType handler) { + m_handlers["OPTIONS"] = handler; + return *this; + } + + bool regex() const { return m_isRegEx; } + void setRegEx(bool regEx) { m_isRegEx = regEx; } + + /// + template + void handleRequest(const Request& request, Reply& reply, const std::smatch &what) { + // dispatching to matching based on handler + RequestMatch match(what, request.data); + auto& handler = m_handlers[request.method]; + if (handler) { + handler(reply, match); + } + } + + private: + std::string m_expression; + bool m_isRegEx; + std::map m_handlers; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements RESTful web request dispatching. + // --------------------------------------------------------------------------- + + template + class RequestDispatcher { + typedef RequestMatcher MatcherType; + public: + /// Initializes a new instance of the RequestDispatcher class. + RequestDispatcher() : m_basePath() { /* stub */ } + /// Initializes a new instance of the RequestDispatcher class. + RequestDispatcher(const std::string& basePath) : m_basePath(basePath) { /* stub */ } + + /// + MatcherType& match(const std::string& expression) + { + MatcherTypePtr& p = m_matchers[expression]; + if (!p) { + p = std::make_shared(expression); + } + + return *p; + } + + /// + void handleRequest(const Request& request, Reply& reply) + { + for (const auto& matcher : m_matchers) { + std::smatch what; + if (!matcher.second->regex()) { + if (request.uri.find(matcher.first) != std::string::npos) { + //what = matcher.first; + matcher.second->handleRequest(request, reply, what); + } else { + reply = http::HTTPReply::stockReply(http::HTTPReply::BAD_REQUEST); + } + } else { + if (std::regex_match(request.uri, what, std::regex(matcher.first))) { + matcher.second->handleRequest(request, reply, what); + } else { + reply = http::HTTPReply::stockReply(http::HTTPReply::BAD_REQUEST); + } + } + } + } + + private: + typedef std::shared_ptr MatcherTypePtr; + + std::string m_basePath; + std::map m_matchers; + }; + + typedef RequestDispatcher DefaultRequestDispatcher; + } // namespace rest +} // namespace network + +#endif // __REST__DISPATCHER_H__ diff --git a/network/rest/http/Connection.h b/network/rest/http/Connection.h index fdd5d60e..1757f37f 100644 --- a/network/rest/http/Connection.h +++ b/network/rest/http/Connection.h @@ -48,127 +48,130 @@ #include #include -namespace rest { - namespace server { +namespace network { + namespace rest { + namespace http { - // --------------------------------------------------------------------------- - // Class Prototypes - // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- - template class ConnectionManager; - - // --------------------------------------------------------------------------- - // Class Declaration - // This class represents a single connection from a client. - // --------------------------------------------------------------------------- + template class ConnectionManager; - template - class Connection : public std::enable_shared_from_this> - { - typedef Connection selfType; - typedef std::shared_ptr selfTypePtr; - typedef ConnectionManager ConnectionManagerType; - public: - /// Initializes a new instance of the Connection class. - explicit Connection(asio::ip::tcp::socket socket, ConnectionManagerType& manager, RequestHandlerType& handler, - bool persistent = false) : - m_socket(std::move(socket)), - m_connectionManager(manager), - m_requestHandler(handler), - m_persistent(persistent) - { - /* stub */ - } - /// Initializes a copy instance of the Connection class. - Connection(const Connection&) = delete; + // --------------------------------------------------------------------------- + // Class Declaration + // This class represents a single connection from a client. + // --------------------------------------------------------------------------- - /// - Connection& operator=(const Connection&) = delete; - - /// Start the first asynchronous operation for the connection. - void start() { read(); } - /// Stop all asynchronous operations associated with the connection. - void stop() { m_socket.close(); } - private: - /// Perform an asynchronous read operation. - void read() + template + class Connection : public std::enable_shared_from_this> { - if (!m_persistent) { - auto self(this->shared_from_this()); + typedef Connection selfType; + typedef std::shared_ptr selfTypePtr; + typedef ConnectionManager ConnectionManagerType; + public: + /// Initializes a new instance of the Connection class. + explicit Connection(asio::ip::tcp::socket socket, ConnectionManagerType& manager, RequestHandlerType& handler, + bool persistent = false) : + m_socket(std::move(socket)), + m_connectionManager(manager), + m_requestHandler(handler), + m_persistent(persistent) + { + /* stub */ } + /// Initializes a copy instance of the Connection class. + Connection(const Connection&) = delete; + + /// + Connection& operator=(const Connection&) = delete; + + /// Start the first asynchronous operation for the connection. + void start() { read(); } + /// Stop all asynchronous operations associated with the connection. + void stop() { m_socket.close(); } + private: + /// Perform an asynchronous read operation. + void read() + { + if (!m_persistent) { + auto self(this->shared_from_this()); + } - m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) { - if (!ec) { - HTTPRequestLexer::ResultType result; - char* data; + m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) { + if (!ec) { + HTTPRequestLexer::ResultType result; + char* data; - std::tie(result, data) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred); - auto itr = std::find_if(m_request.headers.begin(), m_request.headers.end(), [](const header &h) { return h.name == "content-length"; }); - if (itr != request_.headers.end()) { - m_request.data = std::string(data, std::static_cast(itr->value)); - } + std::tie(result, data) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred); + auto header = std::find_if(m_request.headers.begin(), m_request.headers.end(), [](const HTTPHeader& h) { return h.name == "content-length"; }); + if (header != m_request.headers.end()) { + size_t length = (size_t)::strtoul(header->value.c_str(), NULL, 10); + m_request.data = std::string(data, length); + } - if (result == HTTPRequestLexer::GOOD) { - m_requestHandler.handleRequest(m_request, m_reply); - write(); + if (result == HTTPRequestLexer::GOOD) { + m_requestHandler.handleRequest(m_request, m_reply); + write(); + } + else if (result == HTTPRequestLexer::BAD) { + m_reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST); + write(); + } + else { + read(); + } } - else if (result == HTTPRequestLexer::BAD) { - m_reply = reply::stockReply(HTTPReply::BAD_REQUEST); - write(); - } - else { - read(); + else if (ec != asio::error::operation_aborted) { + m_connectionManager.stop(this->shared_from_this()); } - } - else if (ec != asio::error::operation_aborted) { - m_connectionManager.stop(this->shared_from_this()); - } - }); - } - - /// Perform an asynchronous write operation. - void write() - { - if (!m_persistent) { - auto self(this->shared_from_this()); - } else { - m_reply.headers.emplace_back("Connection:", "keep-alive"); + }); } - - asio::async_write(m_socket, m_reply.to_buffers(), [=](asio::error_code ec, std::size_t) { - if (m_persistent) { - m_lexer.reset(); - m_reply.headers.resize(0); - m_reply.status = HTTPReply::OK; - m_reply.content = ""; - m_request = HTTPRequest(); - read(); + + /// Perform an asynchronous write operation. + void write() + { + if (!m_persistent) { + auto self(this->shared_from_this()); + } else { + m_reply.headers.emplace_back("Connection:", "keep-alive"); } - else - { - if (!ec) { - // initiate graceful connection closure - asio::error_code ignored_ec; - m_socket.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec); + + asio::async_write(m_socket, m_reply.toBuffers(), [=](asio::error_code ec, std::size_t) { + if (m_persistent) { + m_lexer.reset(); + m_reply.headers.resize(0); + m_reply.status = HTTPReply::OK; + m_reply.content = ""; + m_request = HTTPRequest(); + read(); } - - if (ec != asio::error::operation_aborted) { - m_connectionManager.stop(this->shared_from_this()); + else + { + if (!ec) { + // initiate graceful connection closure + asio::error_code ignored_ec; + m_socket.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec); + } + + if (ec != asio::error::operation_aborted) { + m_connectionManager.stop(this->shared_from_this()); + } } - } - }); - } - - asio::ip::tcp::socket m_socket; - ConnectionManagerType& m_connectionManager; - RequestHandlerType& m_requestHandler; - std::array m_buffer; - HTTPRequest m_request; - HTTPRequestLexer m_lexer; - HTTPReply m_reply; - bool m_persistent; - }; - } // namespace server -} // namespace rest + }); + } + + asio::ip::tcp::socket m_socket; + ConnectionManagerType& m_connectionManager; + RequestHandlerType& m_requestHandler; + std::array m_buffer; + HTTPRequest m_request; + HTTPRequestLexer m_lexer; + HTTPReply m_reply; + bool m_persistent; + }; + } // namespace http + } // namespace rest +} // namespace network #endif // __REST_HTTP__CONNECTION_H__ diff --git a/network/rest/http/ConnectionManager.h b/network/rest/http/ConnectionManager.h index e14a9679..6d8f855e 100644 --- a/network/rest/http/ConnectionManager.h +++ b/network/rest/http/ConnectionManager.h @@ -42,63 +42,65 @@ #include #include -namespace rest { - namespace server { - - // --------------------------------------------------------------------------- - // Class Declaration - // Manages open connections so that they may be cleanly stopped when the server - // needs to shut down. - // --------------------------------------------------------------------------- - - template - class ConnectionManager - { - public: - /// Initializes a new instance of the ConnectionManager class. - ConnectionManager() { /* stub */ } - /// Initializes a copy instance of the ConnectionManager class. - ConnectionManager(const ConnectionManager&) = delete; +namespace network { + namespace rest { + namespace http { + + // --------------------------------------------------------------------------- + // Class Declaration + // Manages open connections so that they may be cleanly stopped when the server + // needs to shut down. + // --------------------------------------------------------------------------- - /// - ConnectionManager& operator=(const ConnectionManager&) = delete; - - /// Add the specified connection to the manager and start it. - void start(ConnectionPtr c) + template + class ConnectionManager { - std::lock_guard guard(m_lock); + public: + /// Initializes a new instance of the ConnectionManager class. + ConnectionManager() { /* stub */ } + /// Initializes a copy instance of the ConnectionManager class. + ConnectionManager(const ConnectionManager&) = delete; + + /// + ConnectionManager& operator=(const ConnectionManager&) = delete; + + /// Add the specified connection to the manager and start it. + void start(ConnectionPtr c) { - m_connections.insert(c); + std::lock_guard guard(m_lock); + { + m_connections.insert(c); + } + c->start(); } - c->start(); - } - - /// Stop the specified connection. - void stop(connection_ptr c) - { - std::lock_guard guard(m_lock); + + /// Stop the specified connection. + void stop(ConnectionPtr c) { - m_connections.erase(c); + std::lock_guard guard(m_lock); + { + m_connections.erase(c); + } + c->stop(); + } + + /// Stop all connections. + void stopAll() + { + for (auto c : m_connections) + c->stop(); + + std::lock_guard guard(m_lock); + m_connections.clear(); } - c->stop(); - } - /// Stop all connections. - void stopAll() - { - for (auto c : m_connections) - c->stop(); + private: + std::set m_connections; + std::mutex m_lock; + }; + } // namespace http + } // namespace rest +} // namespace network - std::lock_guard guard(m_lock); - m_connections.clear(); - } - - private: - std::set m_connections; - std::mutex m_lock; - }; - } // namespace server -} // namespace rest - #endif // __REST_HTTP__CONNECTION_MANAGER_H__ \ No newline at end of file diff --git a/network/rest/http/HTTPHeader.h b/network/rest/http/HTTPHeader.h index 5af9d919..506e518b 100644 --- a/network/rest/http/HTTPHeader.h +++ b/network/rest/http/HTTPHeader.h @@ -41,26 +41,28 @@ #include -namespace rest { - namespace server { +namespace network { + namespace rest { + namespace http { - // --------------------------------------------------------------------------- - // Structure Declaration - // This class implements a model for an HTTP header. - // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Structure Declaration + // This class implements a model for an HTTP header. + // --------------------------------------------------------------------------- - struct HTTPHeader - { - std::string name; - std::string value; - - /// Initializes a new instance of the HTTPHeader struct. - HTTPHeader() { /* stub */ } - /// Initializes a new instance of the HTTPHeader struct. - HTTPHeader(const std::string& name, const std::string& value) : name{name}, value{value} { /* stub */ } - }; + struct HTTPHeader + { + std::string name; + std::string value; + + /// Initializes a new instance of the HTTPHeader struct. + HTTPHeader() { /* stub */ } + /// Initializes a new instance of the HTTPHeader struct. + HTTPHeader(const std::string& name, const std::string& value) : name(name), value(value) { /* stub */ } + }; - } // namespace server -} // namespace rest + } // namespace http + } // namespace rest +} // namespace network #endif // __REST_HTTP__HTTP_HEADER_H__ diff --git a/network/rest/http/HTTPReply.cpp b/network/rest/http/HTTPReply.cpp index c1a7bd6f..b2d76c2d 100644 --- a/network/rest/http/HTTPReply.cpp +++ b/network/rest/http/HTTPReply.cpp @@ -37,7 +37,7 @@ #include "Defines.h" #include "network/rest/http/HTTPReply.h" -using namespace rest::server; +using namespace network::rest::http; #include @@ -106,24 +106,6 @@ namespace misc_strings { const char crlf[] = { '\r', '\n' }; } // namespace misc_strings -std::vector HTTPReply::toBuffers() -{ - std::vector buffers; - buffers.push_back(status_strings::toBuffer(status)); - - for (std::size_t i = 0; i < headers.size(); ++i) { - HTTPHeader& h = headers[i]; - buffers.push_back(asio::buffer(h.name)); - buffers.push_back(asio::buffer(misc_strings::name_value_separator)); - buffers.push_back(asio::buffer(h.value)); - buffers.push_back(asio::buffer(misc_strings::crlf)); - } - - buffers.push_back(asio::buffer(misc_strings::crlf)); - buffers.push_back(asio::buffer(content)); - return buffers; -} - namespace stock_replies { const char ok[] = ""; const char created[] = @@ -244,24 +226,91 @@ namespace stock_replies { } } // namespace stock_replies -HTTPReply HTTPReply::stockReply(HTTPReply::StatusType status, const char* mime) +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Convert the reply into a vector of buffers. The buffers do not own the +/// underlying memory blocks, therefore the reply object must remain valid and +/// not be changed until the write operation has completed. +/// +std::vector HTTPReply::toBuffers() +{ + std::vector buffers; + buffers.push_back(status_strings::toBuffer(status)); + + for (std::size_t i = 0; i < headers.size(); ++i) { + HTTPHeader& h = headers[i]; + buffers.push_back(asio::buffer(h.name)); + buffers.push_back(asio::buffer(misc_strings::name_value_separator)); + buffers.push_back(asio::buffer(h.value)); + buffers.push_back(asio::buffer(misc_strings::crlf)); + } + + buffers.push_back(asio::buffer(misc_strings::crlf)); + buffers.push_back(asio::buffer(content)); + return buffers; +} + +/// +/// +/// +/// +/// +void HTTPReply::reply(json::object obj, HTTPReply::StatusType s) +{ + json::value v = json::value(obj); + std::string json = v.serialize(); + reply(json, s, "text/json"); +} + +/// +/// +/// +/// +/// +/// +void HTTPReply::reply(std::string c, HTTPReply::StatusType s, std::string contentType) +{ + content = c; + status = s; + ensureDefaultHeaders(contentType); +} + +// --------------------------------------------------------------------------- +// Static Members +// --------------------------------------------------------------------------- + +/// +/// Get a stock reply. +/// +/// +/// +HTTPReply HTTPReply::stockReply(HTTPReply::StatusType status, const std::string contentType) { HTTPReply rep; rep.status = status; if (status != HTTPReply::NO_CONTENT) { rep.content = stock_replies::to_string(status); - rep.headers.resize(2); - rep.headers[0].name = "Content-Length"; - rep.headers[0].value = std::to_string(rep.content.size()); - rep.headers[1].name = "Content-Type"; - rep.headers[1].value = mime; + rep.ensureDefaultHeaders(contentType); } return rep; } -HTTPReply& operator<<(HTTPReply& r, const std::string &value) { - r.content.append(value); - return r; +// --------------------------------------------------------------------------- +// Private Members +// --------------------------------------------------------------------------- + +/// +/// +/// +/// +void HTTPReply::ensureDefaultHeaders(std::string contentType) +{ + headers.push_back(HTTPHeader("Content-Length", std::to_string(content.size()))); + headers.push_back(HTTPHeader("Content-Type", contentType)); + headers.push_back(HTTPHeader("Server", std::string((__EXE_NAME__ "/" __VER__)))); } diff --git a/network/rest/http/HTTPReply.h b/network/rest/http/HTTPReply.h index 967b950b..6b9ce8e8 100644 --- a/network/rest/http/HTTPReply.h +++ b/network/rest/http/HTTPReply.h @@ -38,6 +38,7 @@ #define __REST_HTTP__HTTP_REPLY_H__ #include "Defines.h" +#include "network/json/json.h" #include "network/rest/http/HTTPHeader.h" #include @@ -45,49 +46,61 @@ #include -namespace rest { - namespace server { +namespace network { + namespace rest { + namespace http { - // --------------------------------------------------------------------------- - // Structure Declaration - // This struct implements a model of a reply to be sent to a HTTP client. - // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Structure Declaration + // This struct implements a model of a reply to be sent to a HTTP client. + // --------------------------------------------------------------------------- - struct HTTPReply - { - enum StatusType { - OK = 200, - CREATED = 201, - ACCEPTED = 202, - NO_CONTENT = 204, - MULTIPLE_CHOICES = 300, - MOVED_PERMANENTLY = 301, - MOVED_TEMPORARILY = 302, - NOT_MODIFIED = 304, - BAD_REQUEST = 400, - UNAUTHORIZED = 401, - FORBIDDEN = 403, - NOT_FOUND = 404, - INTERNAL_SERVER_ERROR = 500, - NOT_IMPLEMENTED = 501, - BAD_GATEWAY = 502, - SERVICE_UNAVAILABLE = 503 - } status; + struct HTTPReply + { + /// + /// HTTP Status/Response Codes + /// + enum StatusType { + OK = 200, + CREATED = 201, + ACCEPTED = 202, + NO_CONTENT = 204, + MULTIPLE_CHOICES = 300, + MOVED_PERMANENTLY = 301, + MOVED_TEMPORARILY = 302, + NOT_MODIFIED = 304, + BAD_REQUEST = 400, + UNAUTHORIZED = 401, + FORBIDDEN = 403, + NOT_FOUND = 404, + INTERNAL_SERVER_ERROR = 500, + NOT_IMPLEMENTED = 501, + BAD_GATEWAY = 502, + SERVICE_UNAVAILABLE = 503 + } status; - std::vector headers; - std::string content; + std::vector headers; + std::string content; - /// Convert the reply into a vector of buffers. The buffers do not own the - /// underlying memory blocks, therefore the reply object must remain valid and - /// not be changed until the write operation has completed. - std::vector toBuffers(); + /// Convert the reply into a vector of buffers. The buffers do not own the + /// underlying memory blocks, therefore the reply object must remain valid and + /// not be changed until the write operation has completed. + std::vector toBuffers(); - /// Get a stock reply. - static HTTPReply stockReply(StatusType status, const char* mime = "text/html"); - }; + /// Prepares reply for transmission by finalizing status and content type. + void reply(json::object obj, StatusType status = OK); + /// Prepares reply for transmission by finalizing status and content type. + void reply(std::string content, StatusType status = OK, std::string contentType = "text/html"); - HTTPReply& operator<<(HTTPReply& r, const std::string& value); - } // namespace server -} // namespace rest + /// Get a stock reply. + static HTTPReply stockReply(StatusType status, std::string contentType = "text/html"); + + private: + /// + void ensureDefaultHeaders(std::string contentType = "text/html"); + }; + } // namespace http + } // namespace rest +} // namespace network #endif // __REST_HTTP__HTTP_REPLY_H__ diff --git a/network/rest/http/HTTPRequest.h b/network/rest/http/HTTPRequest.h index 52677e0a..e3ec8c38 100644 --- a/network/rest/http/HTTPRequest.h +++ b/network/rest/http/HTTPRequest.h @@ -43,26 +43,28 @@ #include #include -namespace rest { - namespace server { +namespace network { + namespace rest { + namespace http { - // --------------------------------------------------------------------------- - // Structure Declaration - // This struct implements a model of a request received from a HTTP client. - // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Structure Declaration + // This struct implements a model of a request received from a HTTP client. + // --------------------------------------------------------------------------- - struct HTTPRequest - { - std::string method; - std::string uri; + struct HTTPRequest + { + std::string method; + std::string uri; - int httpVersionMajor; - int httpVersionMinor; - - std::vector headers; - std::string data; - }; - } // namespace server -} // namespace http + int httpVersionMajor; + int httpVersionMinor; + + std::vector headers; + std::string data; + }; + } // namespace http + } // namespace rest +} // namespace network #endif // __REST_HTTP__HTTP_REQUEST_H__ diff --git a/network/rest/http/HTTPRequestHandler.cpp b/network/rest/http/HTTPRequestHandler.cpp index 05351b55..17019680 100644 --- a/network/rest/http/HTTPRequestHandler.cpp +++ b/network/rest/http/HTTPRequestHandler.cpp @@ -39,7 +39,7 @@ #include "network/rest/http/HTTPRequest.h" #include "network/rest/http/HTTPReply.h" -using namespace rest::server; +using namespace network::rest::http; #include #include diff --git a/network/rest/http/HTTPRequestHandler.h b/network/rest/http/HTTPRequestHandler.h index ff66ba0e..b6522f47 100644 --- a/network/rest/http/HTTPRequestHandler.h +++ b/network/rest/http/HTTPRequestHandler.h @@ -41,47 +41,49 @@ #include -namespace rest { - namespace server { +namespace network { + namespace rest { + namespace http { - // --------------------------------------------------------------------------- - // Class Prototypes - // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- - struct HTTPReply; - struct HTTPRequest; - - // --------------------------------------------------------------------------- - // Class Declaration - // This class implements the common handler for all incoming requests. - // --------------------------------------------------------------------------- + struct HTTPReply; + struct HTTPRequest; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements the common handler for all incoming requests. + // --------------------------------------------------------------------------- - class HTTPRequestHandler - { - public: - /// Initializes a new instance of the HTTPRequestHandler class. - explicit HTTPRequestHandler(const std::string& docRoot); - /// Initializes a copy instance of the HTTPRequestHandler class. - HTTPRequestHandler(const HTTPRequestHandler&) = delete; - /// - HTTPRequestHandler(HTTPRequestHandler&&) = default; - - /// - HTTPRequestHandler& operator=(const HTTPRequestHandler&) = delete; - /// - HTTPRequestHandler& operator=(HTTPRequestHandler&&) = default; + class HTTPRequestHandler + { + public: + /// Initializes a new instance of the HTTPRequestHandler class. + explicit HTTPRequestHandler(const std::string& docRoot); + /// Initializes a copy instance of the HTTPRequestHandler class. + HTTPRequestHandler(const HTTPRequestHandler&) = delete; + /// + HTTPRequestHandler(HTTPRequestHandler&&) = default; + + /// + HTTPRequestHandler& operator=(const HTTPRequestHandler&) = delete; + /// + HTTPRequestHandler& operator=(HTTPRequestHandler&&) = default; - /// Handle a request and produce a reply. - void handleRequest(const HTTPRequest& req, HTTPReply& reply); + /// Handle a request and produce a reply. + void handleRequest(const HTTPRequest& req, HTTPReply& reply); - private: - /// Perform URL-decoding on a string. Returns false if the encoding was - /// invalid. - static bool urlDecode(const std::string& in, std::string& out); + private: + /// Perform URL-decoding on a string. Returns false if the encoding was + /// invalid. + static bool urlDecode(const std::string& in, std::string& out); - std::string m_docRoot; - }; - } // namespace server -} // namespace rest + std::string m_docRoot; + }; + } // namespace http + } // namespace rest +} // namespace network #endif // __REST_HTTP__HTTP_REQUEST_HANDLER_H__ diff --git a/network/rest/http/HTTPRequestLexer.cpp b/network/rest/http/HTTPRequestLexer.cpp index 1873393a..3aeb6280 100644 --- a/network/rest/http/HTTPRequestLexer.cpp +++ b/network/rest/http/HTTPRequestLexer.cpp @@ -38,7 +38,7 @@ #include "network/rest/http/HTTPRequestLexer.h" #include "network/rest/http/HTTPRequest.h" -using namespace rest::server; +using namespace network::rest::http; #include diff --git a/network/rest/http/HTTPRequestLexer.h b/network/rest/http/HTTPRequestLexer.h index 2beaf44e..1ae7f882 100644 --- a/network/rest/http/HTTPRequestLexer.h +++ b/network/rest/http/HTTPRequestLexer.h @@ -39,84 +39,86 @@ #include -namespace rest { - namespace server { +namespace network { + namespace rest { + namespace http { - // --------------------------------------------------------------------------- - // Class Prototypes - // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- - struct HTTPRequest; - - // --------------------------------------------------------------------------- - // Class Declaration - // This class implements the lexer for incoming requests. - // --------------------------------------------------------------------------- + struct HTTPRequest; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements the lexer for incoming requests. + // --------------------------------------------------------------------------- - class HTTPRequestLexer - { - public: - enum ResultType { GOOD, BAD, INDETERMINATE }; + class HTTPRequestLexer + { + public: + enum ResultType { GOOD, BAD, INDETERMINATE }; - /// Initializes a new instance of the HTTPRequestLexer class. - HTTPRequestLexer(); + /// Initializes a new instance of the HTTPRequestLexer class. + HTTPRequestLexer(); - /// Reset to initial parser state. - void reset(); + /// Reset to initial parser state. + void reset(); - /// Parse some data. The enum return value is good when a complete request has - /// been parsed, bad if the data is invalid, indeterminate when more data is - /// required. The InputIterator return value indicates how much of the input - /// has been consumed. - template - std::tuple parse(HTTPRequest& req, InputIterator begin, InputIterator end) - { - while (begin != end) { - ResultType result = consume(req, *begin++); - if (result == GOOD || result == BAD) - return std::make_tuple(result, begin); + /// Parse some data. The enum return value is good when a complete request has + /// been parsed, bad if the data is invalid, indeterminate when more data is + /// required. The InputIterator return value indicates how much of the input + /// has been consumed. + template + std::tuple parse(HTTPRequest& req, InputIterator begin, InputIterator end) + { + while (begin != end) { + ResultType result = consume(req, *begin++); + if (result == GOOD || result == BAD) + return std::make_tuple(result, begin); + } + return std::make_tuple(INDETERMINATE, begin); } - return std::make_tuple(INDETERMINATE, begin); - } - private: - /// Handle the next character of input. - ResultType consume(HTTPRequest& req, char input); + private: + /// Handle the next character of input. + ResultType consume(HTTPRequest& req, char input); - /// Check if a byte is an HTTP character. - static bool isChar(int c); - /// Check if a byte is an HTTP control character. - static bool isControl(int c); - /// Check if a byte is an HTTP special character. - static bool isSpecial(int c); - /// Check if a byte is an digit. - static bool isDigit(int c); + /// Check if a byte is an HTTP character. + static bool isChar(int c); + /// Check if a byte is an HTTP control character. + static bool isControl(int c); + /// Check if a byte is an HTTP special character. + static bool isSpecial(int c); + /// Check if a byte is an digit. + static bool isDigit(int c); - enum state - { - METHOD_START, - METHOD, - URI, - HTTP_VERSION_H, - HTTP_VERSION_T_1, - HTTP_VERSION_T_2, - HTTP_VERSION_P, - HTTP_VERSION_SLASH, - HTTP_VERSION_MAJOR_START, - HTTP_VERSION_MAJOR, - HTTP_VERSION_MINOR_START, - HTTP_VERSION_MINOR, - EXPECTING_NEWLINE_1, - HEADER_LINE_START, - HEADER_LWS, - HEADER_NAME, - SPACE_BEFORE_HEADER_VALUE, - HEADER_VALUE, - EXPECTING_NEWLINE_2, - EXPECTING_NEWLINE_3 - } m_state; - }; - } // namespace server -} // namespace rest + enum state + { + METHOD_START, + METHOD, + URI, + HTTP_VERSION_H, + HTTP_VERSION_T_1, + HTTP_VERSION_T_2, + HTTP_VERSION_P, + HTTP_VERSION_SLASH, + HTTP_VERSION_MAJOR_START, + HTTP_VERSION_MAJOR, + HTTP_VERSION_MINOR_START, + HTTP_VERSION_MINOR, + EXPECTING_NEWLINE_1, + HEADER_LINE_START, + HEADER_LWS, + HEADER_NAME, + SPACE_BEFORE_HEADER_VALUE, + HEADER_VALUE, + EXPECTING_NEWLINE_2, + EXPECTING_NEWLINE_3 + } m_state; + }; + } // namespace http + } // namespace rest +} // namespace network #endif // __REST_HTTP__HTTP_REQUEST_PARSER_H__ diff --git a/network/rest/http/HTTPServer.h b/network/rest/http/HTTPServer.h index 6d0538cf..d222913d 100644 --- a/network/rest/http/HTTPServer.h +++ b/network/rest/http/HTTPServer.h @@ -34,13 +34,15 @@ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - #if !defined(__REST_HTTP__HTTP_SERVER_H__) +#if !defined(__REST_HTTP__HTTP_SERVER_H__) #define __REST_HTTP__HTTP_SERVER_H__ #include "Defines.h" -#include "network/rest/Connection.h" -#include "network/rest/ConnectionManager.h" -#include "network/rest/HTTPRequestHandler.h" +#include "network/rest/http/Connection.h" +#include "network/rest/http/ConnectionManager.h" +#include "network/rest/http/HTTPRequestHandler.h" +#include "network/rest/http/HTTPReply.h" +#include "network/rest/http/HTTPRequest.h" #include @@ -50,111 +52,104 @@ #include #include -namespace rest { - namespace server { +namespace network { + namespace rest { + namespace http { - // --------------------------------------------------------------------------- - // Class Declaration - // This class implements top-level routines of the HTTP server. - // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements top-level routines of the HTTP server. + // --------------------------------------------------------------------------- - template class ConnectionImpl = Connection> - class HTTPServer { - public: - /// Initializes a new instance of the HTTPServer class. - template - explicit HTTPServer(const std::string& address, const std::string& port, Handler&& handler) : - m_ioService(), - m_signals(m_ioService), - m_acceptor(m_ioService), - m_connectionManager(), - m_socket(m_ioService), - m_requestHandler(std::forward(handler)) - { - // register to handle the signals that indicate when the server should exit - // it is safe to register for the same signal multiple times in a program, - // provided all registration for the specified signal is made through ASIO - m_signals.add(SIGINT); - m_signals.add(SIGTERM); -#if defined(SIGQUIT) - m_signals.add(SIGQUIT); -#endif - - awaitStop(); - - // open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR) - asio::ip::tcp::resolver resolver(m_ioService); - asio::ip::tcp::endpoint endpoint = *resolver.resolve({address, port}); + template class ConnectionImpl = Connection> + class HTTPServer { + public: + /// Initializes a new instance of the HTTPServer class. + explicit HTTPServer(const std::string& address, uint16_t port) : + m_ioService(), + m_acceptor(m_ioService), + m_connectionManager(), + m_socket(m_ioService), + m_requestHandler() + { + // open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR) + asio::ip::address ipAddress = asio::ip::address::from_string(address); + asio::ip::tcp::endpoint endpoint(ipAddress, port); - m_acceptor.open(endpoint.protocol()); - m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true)); - m_acceptor.set_option(asio::socket_base::keep_alive(true)); - m_acceptor.bind(endpoint); - m_acceptor.listen(); - - accept(); - } - /// Initializes a copy instance of the HTTPServer class. - HTTPServer(const HTTPServer&) = delete; - - /// - HTTPServer& operator=(const HTTPServer&) = delete; - - /// Run the servers ASIO IO service loop. - void run() - { - // the run() call will block until all asynchronous operations - // have finished; while the server is running, there is always at least one - // asynchronous operation outstanding: the asynchronous accept call waiting - // for new incoming connections - m_ioService.run(); - } - - private: - /// Perform an asynchronous accept operation. - void accept() - { - m_acceptor.async_accept(m_socket, [this](asio::error_code ec) { - // check whether the server was stopped by a signal before this - // completion handler had a chance to run - if (!m_acceptor.is_open()) { - return; - } - - if (!ec) { - m_connectionManager.start(std::make_shared(std::move(m_socket), m_connectionManager, m_requestHandler)); - } + m_acceptor.open(endpoint.protocol()); + m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true)); + m_acceptor.set_option(asio::socket_base::keep_alive(true)); + m_acceptor.bind(endpoint); + m_acceptor.listen(); accept(); - }); - } - - /// Wait for a request to stop the server. - void awaitStop() - { - m_signals.async_wait([this](asio::error_code /*ec*/, int /*signo*/) { + } + /// Initializes a copy instance of the HTTPServer class. + HTTPServer(const HTTPServer&) = delete; + + /// + HTTPServer& operator=(const HTTPServer&) = delete; + + /// Helper to set the HTTP request handlers. + template + void setHandler(Handler&& handler) + { + m_requestHandler = RequestHandlerType(std::forward(handler)); + } + + /// Run the servers ASIO IO service loop. + void run() + { + // the run() call will block until all asynchronous operations + // have finished; while the server is running, there is always at least one + // asynchronous operation outstanding: the asynchronous accept call waiting + // for new incoming connections + m_ioService.run(); + } + + /// Helper to stop running ASIO IO services. + void stop() + { // the server is stopped by cancelling all outstanding asynchronous // operations; once all operations have finished the m_ioService::run() // call will exit m_acceptor.close(); m_connectionManager.stopAll(); - }); - } - - typedef ConnectionImpl ConnectionType; - typedef std::shared_ptr ConnectionTypePtr; - - asio::io_service m_ioService; - asio::signal_set m_signals; - asio::ip::tcp::acceptor m_acceptor; - - ConnectionManager m_connectionManager; - - asio::ip::tcp::socket m_socket; - - RequestHandlerType m_requestHandler; - }; - } // namespace server -} // namespace rest + } + + private: + /// Perform an asynchronous accept operation. + void accept() + { + m_acceptor.async_accept(m_socket, [this](asio::error_code ec) { + // check whether the server was stopped by a signal before this + // completion handler had a chance to run + if (!m_acceptor.is_open()) { + return; + } + + if (!ec) { + m_connectionManager.start(std::make_shared(std::move(m_socket), m_connectionManager, m_requestHandler)); + } + + accept(); + }); + } + + typedef ConnectionImpl ConnectionType; + typedef std::shared_ptr ConnectionTypePtr; + + asio::io_service m_ioService; + asio::ip::tcp::acceptor m_acceptor; + + ConnectionManager m_connectionManager; + + asio::ip::tcp::socket m_socket; + + RequestHandlerType m_requestHandler; + }; + } // namespace http + } // namespace rest +} // namespace network #endif // __REST_HTTP__HTTP_SERVER_H__