make REST API actually work (just /version for now), the REST API port is currently fixed (RCON port + 1);

pull/19/head
Bryan Biedenkapp 3 years ago
parent fb81fac04f
commit 3dc1e9c309

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

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

@ -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 <cstdio>
@ -100,13 +104,22 @@ std::string string_format(const std::string& format, FormatArgs ... args)
/// <param name="address">Network Hostname/IP address to connect to.</param>
/// <param name="port">Network port number.</param>
/// <param name="password">Authentication password.</param>
/// <param name="host">Instance of the Host class.</param>
/// <param name="debug"></param>
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;
}
/// <summary>
/// Sets the instances of the digital radio protocols.
/// </summary>
/// <param name="dmr">Instance of the DMR Control class.</param>
/// <param name="p25">Instance of the P25 Control class.</param>
/// <param name="nxdn">Instance of the NXDN Control class.</param>
void RemoteControl::setProtocols(dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn)
{
m_dmr = dmr;
m_p25 = p25;
m_nxdn = nxdn;
}
/// <summary>
/// Process remote network command data.
/// </summary>
@ -850,6 +876,10 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx
/// <returns></returns>
bool RemoteControl::open()
{
initializeEndpoints();
m_restServer.setHandler(m_dispatcher);
run();
return m_socket.open();
}
@ -858,6 +888,9 @@ bool RemoteControl::open()
/// </summary>
void RemoteControl::close()
{
m_restServer.stop();
wait();
m_socket.close();
}
@ -865,6 +898,28 @@ void RemoteControl::close()
// Private Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Helper to initialize REST API endpoints.
/// </summary>
void RemoteControl::initializeEndpoints()
{
m_dispatcher.match("/version")
.get([](HTTPReply& reply, const RequestMatch& match) {
json::object response = json::object();
response["version"].set<std::string>(std::string((__PROG_NAME__ " " __VER__ " (" DESCR_DMR DESCR_P25 DESCR_NXDN "CW Id, Network) (built " __BUILD__ ")")));
reply.reply(response);
});
}
/// <summary>
///
/// </summary>
void RemoteControl::entry()
{
m_restServer.run();
}
/// <summary>
/// Helper to write response to client.
/// </summary>

@ -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 <vector>
#include <string>
@ -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:
/// <summary>Initializes a new instance of the RemoteControl class.</summary>
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);
/// <summary>Finalizes a instance of the RemoteControl class.</summary>
~RemoteControl();
/// <summary>Sets the instances of the Radio ID and Talkgroup ID lookup tables.</summary>
void setLookups(::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup);
/// <summary>Sets the instances of the digital radio protocols.</summary>
void setProtocols(dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn);
/// <summary>Process remote network command data.</summary>
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<network::rest::http::HTTPRequest, network::rest::http::HTTPReply> RESTDispatcherType;
RESTDispatcherType m_dispatcher;
network::rest::http::HTTPServer<RESTDispatcherType> 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;
/// <summary>Helper to initialize REST API endpoints.</summary>
void initializeEndpoints();
/// <summary></summary>
virtual void entry();
/// <summary>Helper to send response to client.</summary>
void writeResponse(std::string reply, sockaddr_storage address, uint32_t addrLen);

@ -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 <functional>
#include <map>
#include <string>
#include <regex>
#include <memory>
namespace rest {
// ---------------------------------------------------------------------------
// Structure Declaration
//
// ---------------------------------------------------------------------------
template<typename Matched>
struct RequestMatch : Matched
{
/// <summary>Initializes a new instance of the RequestMatch structure.</summary>
RequestMatch(const Matched& m, const std::string& d) : Matched(m) , data(d) { /* stub */ }
std::string data;
};
// ---------------------------------------------------------------------------
// Structure Declaration
//
// ---------------------------------------------------------------------------
template<typename Response, typename Regex, typename Matched>
struct RequestMatcher {
typedef std::function<void(Response&, const RequestMatch<Matched>&)> RequestHandlerType;
typedef RequestMatcher<Response, Regex, Matched> selfType;
/// <summary>Initializes a new instance of the RequestMatch structure.</summary>
explicit RequestMatcher(const Regex& expression) : m_expression(expression) { /* stub */ }
/// <summary></summary>
selfType& get(RequestHandlerType handler) {
m_handlers["GET"] = handler;
return *this;
}
/// <summary></summary>
selfType& post(RequestHandlerType handler) {
m_handlers["POST"] = handler;
return *this;
}
/// <summary></summary>
selfType& put(RequestHandlerType handler) {
m_handlers["PUT"] = handler;
return *this;
}
/// <summary></summary>
selfType& del(RequestHandlerType handler) {
m_handlers["DELETE"] = handler;
return *this;
}
/// <summary></summary>
selfType& options(RequestHandlerType handler) {
m_handlers["OPTIONS"] = handler;
return *this;
}
/// <summary></summary>
template<typename Request>
void handleRequest(const Request& request, Response& response, const Matched &what) {
// dispatching to matching based on handler
RequestMatch<Matched> match(what, request.data);
auto& handler = m_handlers[request.method];
if (handler) {
handler(response, match);
}
}
private:
Regex m_expression;
std::map<std::string, RequestHandlerType> m_handlers;
};
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements RESTful web request dispatching.
// ---------------------------------------------------------------------------
template<typename Request, typename Response, typename Match = std::cmatch, typename Expression = std::regex>
class RequestDispatcher {
typedef RequestMatcher<Response, Expression, Match> MatcherType;
typedef std::shared_ptr<MatcherType> MatcherTypePtr;
public:
/// <summary>Initializes a new instance of the RequestDispatcher class.</summary>
RequestDispatcher() : m_basePath() { /* stub */ }
/// <summary>Initializes a new instance of the RequestDispatcher class.</summary>
RequestDispatcher(const std::string &basePath) : m_basePath(basePath) { /* stub */ }
/// <summary></summary>
MatcherType& match(const Expression& expression)
{
MatcherTypePtr& p = m_matchers[expression];
if(!p) {
p = std::make_shared<crud_matcher_type>(expression);
}
return *p;
}
/// <summary></summary>
template<typename E = Expression>
typename std::enable_if<!std::is_same<E, std::string>::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);
}
}
}
/// <summary></summary>
template<typename E = Expression>
typename std::enable_if<std::is_same<E, std::string>::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<Expression, MatcherTypePtr> m_matchers;
};
} // namespace rest
#endif // __REST__DISPATHER_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 <functional>
#include <map>
#include <string>
#include <regex>
#include <memory>
namespace network {
namespace rest {
// ---------------------------------------------------------------------------
// Structure Declaration
//
// ---------------------------------------------------------------------------
struct RequestMatch : std::smatch
{
/// <summary>Initializes a new instance of the RequestMatch structure.</summary>
RequestMatch(const std::smatch& m, const std::string& d) : std::smatch(m), data(d) { /* stub */ }
std::string data;
};
// ---------------------------------------------------------------------------
// Structure Declaration
//
// ---------------------------------------------------------------------------
template<typename Reply>
struct RequestMatcher {
typedef std::function<void(Reply&, const RequestMatch&)> RequestHandlerType;
/// <summary>Initializes a new instance of the RequestMatcher structure.</summary>
explicit RequestMatcher(const std::string& expression) : m_expression(expression), m_isRegEx(false) { /* stub */ }
/// <summary></summary>
RequestMatcher<Reply>& get(RequestHandlerType handler) {
m_handlers["GET"] = handler;
return *this;
}
/// <summary></summary>
RequestMatcher<Reply>& post(RequestHandlerType handler) {
m_handlers["POST"] = handler;
return *this;
}
/// <summary></summary>
RequestMatcher<Reply>& put(RequestHandlerType handler) {
m_handlers["PUT"] = handler;
return *this;
}
/// <summary></summary>
RequestMatcher<Reply>& del(RequestHandlerType handler) {
m_handlers["DELETE"] = handler;
return *this;
}
/// <summary></summary>
RequestMatcher<Reply>& options(RequestHandlerType handler) {
m_handlers["OPTIONS"] = handler;
return *this;
}
bool regex() const { return m_isRegEx; }
void setRegEx(bool regEx) { m_isRegEx = regEx; }
/// <summary></summary>
template<typename Request>
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<std::string, RequestHandlerType> m_handlers;
};
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements RESTful web request dispatching.
// ---------------------------------------------------------------------------
template<typename Request = http::HTTPRequest, typename Reply = http::HTTPReply>
class RequestDispatcher {
typedef RequestMatcher<Reply> MatcherType;
public:
/// <summary>Initializes a new instance of the RequestDispatcher class.</summary>
RequestDispatcher() : m_basePath() { /* stub */ }
/// <summary>Initializes a new instance of the RequestDispatcher class.</summary>
RequestDispatcher(const std::string& basePath) : m_basePath(basePath) { /* stub */ }
/// <summary></summary>
MatcherType& match(const std::string& expression)
{
MatcherTypePtr& p = m_matchers[expression];
if (!p) {
p = std::make_shared<MatcherType>(expression);
}
return *p;
}
/// <summary></summary>
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<MatcherType> MatcherTypePtr;
std::string m_basePath;
std::map<std::string, MatcherTypePtr> m_matchers;
};
typedef RequestDispatcher<http::HTTPRequest, http::HTTPReply> DefaultRequestDispatcher;
} // namespace rest
} // namespace network
#endif // __REST__DISPATCHER_H__

@ -48,127 +48,130 @@
#include <iterator>
#include <asio.hpp>
namespace rest {
namespace server {
namespace network {
namespace rest {
namespace http {
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------
template<class> class ConnectionManager;
// ---------------------------------------------------------------------------
// Class Declaration
// This class represents a single connection from a client.
// ---------------------------------------------------------------------------
template<class> class ConnectionManager;
template <typename RequestHandlerType>
class Connection : public std::enable_shared_from_this<Connection<RequestHandlerType>>
{
typedef Connection<RequestHandlerType> selfType;
typedef std::shared_ptr<selfType> selfTypePtr;
typedef ConnectionManager<selfTypePtr> ConnectionManagerType;
public:
/// <summary>Initializes a new instance of the Connection class.</summary>
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 */
}
/// <summary>Initializes a copy instance of the Connection class.</summary>
Connection(const Connection&) = delete;
// ---------------------------------------------------------------------------
// Class Declaration
// This class represents a single connection from a client.
// ---------------------------------------------------------------------------
/// <summary></summary>
Connection& operator=(const Connection&) = delete;
/// <summary>Start the first asynchronous operation for the connection.</summary>
void start() { read(); }
/// <summary>Stop all asynchronous operations associated with the connection.</summary>
void stop() { m_socket.close(); }
private:
/// <summary>Perform an asynchronous read operation.</summary>
void read()
template <typename RequestHandlerType>
class Connection : public std::enable_shared_from_this<Connection<RequestHandlerType>>
{
if (!m_persistent) {
auto self(this->shared_from_this());
typedef Connection<RequestHandlerType> selfType;
typedef std::shared_ptr<selfType> selfTypePtr;
typedef ConnectionManager<selfTypePtr> ConnectionManagerType;
public:
/// <summary>Initializes a new instance of the Connection class.</summary>
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 */
}
/// <summary>Initializes a copy instance of the Connection class.</summary>
Connection(const Connection&) = delete;
/// <summary></summary>
Connection& operator=(const Connection&) = delete;
/// <summary>Start the first asynchronous operation for the connection.</summary>
void start() { read(); }
/// <summary>Stop all asynchronous operations associated with the connection.</summary>
void stop() { m_socket.close(); }
private:
/// <summary>Perform an asynchronous read operation.</summary>
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<long>(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());
}
});
}
/// <summary>Perform an asynchronous write operation.</summary>
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();
/// <summary>Perform an asynchronous write operation.</summary>
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<char, 8192> 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<char, 8192> 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__

@ -42,63 +42,65 @@
#include <set>
#include <mutex>
namespace rest {
namespace server {
// ---------------------------------------------------------------------------
// Class Declaration
// Manages open connections so that they may be cleanly stopped when the server
// needs to shut down.
// ---------------------------------------------------------------------------
template<typename ConnectionPtr>
class ConnectionManager
{
public:
/// <summary>Initializes a new instance of the ConnectionManager class.</summary>
ConnectionManager() { /* stub */ }
/// <summary>Initializes a copy instance of the ConnectionManager class.</summary>
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.
// ---------------------------------------------------------------------------
/// <summary></summary>
ConnectionManager& operator=(const ConnectionManager&) = delete;
/// <summary>Add the specified connection to the manager and start it.</summary>
void start(ConnectionPtr c)
template<typename ConnectionPtr>
class ConnectionManager
{
std::lock_guard<std::mutex> guard(m_lock);
public:
/// <summary>Initializes a new instance of the ConnectionManager class.</summary>
ConnectionManager() { /* stub */ }
/// <summary>Initializes a copy instance of the ConnectionManager class.</summary>
ConnectionManager(const ConnectionManager&) = delete;
/// <summary></summary>
ConnectionManager& operator=(const ConnectionManager&) = delete;
/// <summary>Add the specified connection to the manager and start it.</summary>
void start(ConnectionPtr c)
{
m_connections.insert(c);
std::lock_guard<std::mutex> guard(m_lock);
{
m_connections.insert(c);
}
c->start();
}
c->start();
}
/// <summary>Stop the specified connection.</summary>
void stop(connection_ptr c)
{
std::lock_guard<std::mutex> guard(m_lock);
/// <summary>Stop the specified connection.</summary>
void stop(ConnectionPtr c)
{
m_connections.erase(c);
std::lock_guard<std::mutex> guard(m_lock);
{
m_connections.erase(c);
}
c->stop();
}
/// <summary>Stop all connections.</summary>
void stopAll()
{
for (auto c : m_connections)
c->stop();
std::lock_guard<std::mutex> guard(m_lock);
m_connections.clear();
}
c->stop();
}
/// <summary>Stop all connections.</summary>
void stopAll()
{
for (auto c : m_connections)
c->stop();
private:
std::set<ConnectionPtr> m_connections;
std::mutex m_lock;
};
} // namespace http
} // namespace rest
} // namespace network
std::lock_guard<std::mutex> guard(m_lock);
m_connections.clear();
}
private:
std::set<ConnectionPtr> m_connections;
std::mutex m_lock;
};
} // namespace server
} // namespace rest
#endif // __REST_HTTP__CONNECTION_MANAGER_H__

@ -41,26 +41,28 @@
#include <string>
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;
/// <summary>Initializes a new instance of the HTTPHeader struct.</summary>
HTTPHeader() { /* stub */ }
/// <summary>Initializes a new instance of the HTTPHeader struct.</summary>
HTTPHeader(const std::string& name, const std::string& value) : name{name}, value{value} { /* stub */ }
};
struct HTTPHeader
{
std::string name;
std::string value;
/// <summary>Initializes a new instance of the HTTPHeader struct.</summary>
HTTPHeader() { /* stub */ }
/// <summary>Initializes a new instance of the HTTPHeader struct.</summary>
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__

@ -37,7 +37,7 @@
#include "Defines.h"
#include "network/rest/http/HTTPReply.h"
using namespace rest::server;
using namespace network::rest::http;
#include <string>
@ -106,24 +106,6 @@ namespace misc_strings {
const char crlf[] = { '\r', '\n' };
} // namespace misc_strings
std::vector<asio::const_buffer> HTTPReply::toBuffers()
{
std::vector<asio::const_buffer> 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
// ---------------------------------------------------------------------------
/// <summary>
/// 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.
/// </summary>
std::vector<asio::const_buffer> HTTPReply::toBuffers()
{
std::vector<asio::const_buffer> 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;
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <param name="s"></param>
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");
}
/// <summary>
///
/// </summary>
/// <param name="c"></param>
/// <param name="s"></param>
/// <param name="contentType"></param>
void HTTPReply::reply(std::string c, HTTPReply::StatusType s, std::string contentType)
{
content = c;
status = s;
ensureDefaultHeaders(contentType);
}
// ---------------------------------------------------------------------------
// Static Members
// ---------------------------------------------------------------------------
/// <summary>
/// Get a stock reply.
/// </summary>
/// <param name="status"></param>
/// <param name="contentType"></param>
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
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="contentType"></param>
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__))));
}

@ -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 <string>
@ -45,49 +46,61 @@
#include <asio.hpp>
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
{
/// <summary>
/// HTTP Status/Response Codes
/// </summary>
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<HTTPHeader> headers;
std::string content;
std::vector<HTTPHeader> headers;
std::string content;
/// <summary>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.</summary>
std::vector<asio::const_buffer> toBuffers();
/// <summary>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.</summary>
std::vector<asio::const_buffer> toBuffers();
/// <summary>Get a stock reply.</summary>
static HTTPReply stockReply(StatusType status, const char* mime = "text/html");
};
/// <summary>Prepares reply for transmission by finalizing status and content type.</summary>
void reply(json::object obj, StatusType status = OK);
/// <summary>Prepares reply for transmission by finalizing status and content type.</summary>
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
/// <summary>Get a stock reply.</summary>
static HTTPReply stockReply(StatusType status, std::string contentType = "text/html");
private:
/// <summary></summary>
void ensureDefaultHeaders(std::string contentType = "text/html");
};
} // namespace http
} // namespace rest
} // namespace network
#endif // __REST_HTTP__HTTP_REPLY_H__

@ -43,26 +43,28 @@
#include <string>
#include <vector>
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<HTTPHeader> headers;
std::string data;
};
} // namespace server
} // namespace http
int httpVersionMajor;
int httpVersionMinor;
std::vector<HTTPHeader> headers;
std::string data;
};
} // namespace http
} // namespace rest
} // namespace network
#endif // __REST_HTTP__HTTP_REQUEST_H__

@ -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 <fstream>
#include <sstream>

@ -41,47 +41,49 @@
#include <string>
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:
/// <summary>Initializes a new instance of the HTTPRequestHandler class.</summary>
explicit HTTPRequestHandler(const std::string& docRoot);
/// <summary>Initializes a copy instance of the HTTPRequestHandler class.</summary>
HTTPRequestHandler(const HTTPRequestHandler&) = delete;
/// <summary></summary>
HTTPRequestHandler(HTTPRequestHandler&&) = default;
/// <summary></summary>
HTTPRequestHandler& operator=(const HTTPRequestHandler&) = delete;
/// <summary></summary>
HTTPRequestHandler& operator=(HTTPRequestHandler&&) = default;
class HTTPRequestHandler
{
public:
/// <summary>Initializes a new instance of the HTTPRequestHandler class.</summary>
explicit HTTPRequestHandler(const std::string& docRoot);
/// <summary>Initializes a copy instance of the HTTPRequestHandler class.</summary>
HTTPRequestHandler(const HTTPRequestHandler&) = delete;
/// <summary></summary>
HTTPRequestHandler(HTTPRequestHandler&&) = default;
/// <summary></summary>
HTTPRequestHandler& operator=(const HTTPRequestHandler&) = delete;
/// <summary></summary>
HTTPRequestHandler& operator=(HTTPRequestHandler&&) = default;
/// <summary>Handle a request and produce a reply.</summary>
void handleRequest(const HTTPRequest& req, HTTPReply& reply);
/// <summary>Handle a request and produce a reply.</summary>
void handleRequest(const HTTPRequest& req, HTTPReply& reply);
private:
/// <summary>Perform URL-decoding on a string. Returns false if the encoding was
/// invalid.</summary>
static bool urlDecode(const std::string& in, std::string& out);
private:
/// <summary>Perform URL-decoding on a string. Returns false if the encoding was
/// invalid.</summary>
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__

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

@ -39,84 +39,86 @@
#include <tuple>
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 };
/// <summary>Initializes a new instance of the HTTPRequestLexer class.</summary>
HTTPRequestLexer();
/// <summary>Initializes a new instance of the HTTPRequestLexer class.</summary>
HTTPRequestLexer();
/// <summary>Reset to initial parser state.</summary>
void reset();
/// <summary>Reset to initial parser state.</summary>
void reset();
/// <summary>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.</summary>
template <typename InputIterator>
std::tuple<ResultType, InputIterator> 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);
/// <summary>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.</summary>
template <typename InputIterator>
std::tuple<ResultType, InputIterator> 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:
/// <summary>Handle the next character of input.</summary>
ResultType consume(HTTPRequest& req, char input);
private:
/// <summary>Handle the next character of input.</summary>
ResultType consume(HTTPRequest& req, char input);
/// <summary>Check if a byte is an HTTP character.</summary>
static bool isChar(int c);
/// <summary>Check if a byte is an HTTP control character.</summary>
static bool isControl(int c);
/// <summary>Check if a byte is an HTTP special character.</summary>
static bool isSpecial(int c);
/// <summary>Check if a byte is an digit.</summary>
static bool isDigit(int c);
/// <summary>Check if a byte is an HTTP character.</summary>
static bool isChar(int c);
/// <summary>Check if a byte is an HTTP control character.</summary>
static bool isControl(int c);
/// <summary>Check if a byte is an HTTP special character.</summary>
static bool isSpecial(int c);
/// <summary>Check if a byte is an digit.</summary>
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__

@ -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 <asio.hpp>
@ -50,111 +52,104 @@
#include <utility>
#include <memory>
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<typename RequestHandlerType, template<class> class ConnectionImpl = Connection>
class HTTPServer {
public:
/// <summary>Initializes a new instance of the HTTPServer class.</summary>
template<typename Handler>
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>(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<typename RequestHandlerType, template<class> class ConnectionImpl = Connection>
class HTTPServer {
public:
/// <summary>Initializes a new instance of the HTTPServer class.</summary>
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();
}
/// <summary>Initializes a copy instance of the HTTPServer class.</summary>
HTTPServer(const HTTPServer&) = delete;
/// <summary></summary>
HTTPServer& operator=(const HTTPServer&) = delete;
/// <summary>Run the servers ASIO IO service loop.</summary>
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:
/// <summary>Perform an asynchronous accept operation.</summary>
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<connection_type>(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();
});
}
/// <summary>Wait for a request to stop the server.</summary>
void awaitStop()
{
m_signals.async_wait([this](asio::error_code /*ec*/, int /*signo*/) {
}
/// <summary>Initializes a copy instance of the HTTPServer class.</summary>
HTTPServer(const HTTPServer&) = delete;
/// <summary></summary>
HTTPServer& operator=(const HTTPServer&) = delete;
/// <summary>Helper to set the HTTP request handlers.</summary>
template<typename Handler>
void setHandler(Handler&& handler)
{
m_requestHandler = RequestHandlerType(std::forward<Handler>(handler));
}
/// <summary>Run the servers ASIO IO service loop.</summary>
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();
}
/// <summary>Helper to stop running ASIO IO services.</summary>
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<RequestHandlerType> ConnectionType;
typedef std::shared_ptr<ConnectionType> ConnectionTypePtr;
asio::io_service m_ioService;
asio::signal_set m_signals;
asio::ip::tcp::acceptor m_acceptor;
ConnectionManager<ConnectionTypePtr> m_connectionManager;
asio::ip::tcp::socket m_socket;
RequestHandlerType m_requestHandler;
};
} // namespace server
} // namespace rest
}
private:
/// <summary>Perform an asynchronous accept operation.</summary>
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<ConnectionType>(std::move(m_socket), m_connectionManager, m_requestHandler));
}
accept();
});
}
typedef ConnectionImpl<RequestHandlerType> ConnectionType;
typedef std::shared_ptr<ConnectionType> ConnectionTypePtr;
asio::io_service m_ioService;
asio::ip::tcp::acceptor m_acceptor;
ConnectionManager<ConnectionTypePtr> m_connectionManager;
asio::ip::tcp::socket m_socket;
RequestHandlerType m_requestHandler;
};
} // namespace http
} // namespace rest
} // namespace network
#endif // __REST_HTTP__HTTP_SERVER_H__

Loading…
Cancel
Save

Powered by TurnKey Linux.