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 () endif ()
add_executable(dvmhost ${dvmhost_SRC}) add_executable(dvmhost ${dvmhost_SRC})
target_include_directories(dvmhost PRIVATE .) 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_GENERATOR "DEB")
set(CPACK_PACKAGE_NAME "dvmhost") set(CPACK_PACKAGE_NAME "dvmhost")
@ -393,7 +393,7 @@ else()
endif () endif ()
add_executable(dvmtests ${dvmhost_SRC} ${dvmtests_SRC}) add_executable(dvmtests ${dvmhost_SRC} ${dvmtests_SRC})
target_compile_definitions(dvmtests PUBLIC -DCATCH2_TEST_COMPILATION) 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 .) target_include_directories(dvmtests PRIVATE .)
endif (ENABLE_TESTS) endif (ENABLE_TESTS)

@ -722,6 +722,10 @@ int Host::run()
setState(STATE_IDLE); 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"); ::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), // 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 // initialize network remote command
if (rconEnable) { 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); m_remoteControl->setLookups(m_ridLookup, m_tidLookup);
bool ret = m_remoteControl->open(); bool ret = m_remoteControl->open();
if (!ret) { if (!ret) {

@ -36,12 +36,16 @@
#include "modem/Modem.h" #include "modem/Modem.h"
#include "host/Host.h" #include "host/Host.h"
#include "network/UDPSocket.h" #include "network/UDPSocket.h"
#include "network/json/json.h"
#include "RemoteControl.h" #include "RemoteControl.h"
#include "HostMain.h" #include "HostMain.h"
#include "Log.h" #include "Log.h"
#include "Thread.h"
#include "Utils.h" #include "Utils.h"
using namespace network; using namespace network;
using namespace network::rest;
using namespace network::rest::http;
using namespace modem; using namespace modem;
#include <cstdio> #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="address">Network Hostname/IP address to connect to.</param>
/// <param name="port">Network port number.</param> /// <param name="port">Network port number.</param>
/// <param name="password">Authentication password.</param> /// <param name="password">Authentication password.</param>
/// <param name="host">Instance of the Host class.</param>
/// <param name="debug"></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_socket(address, port),
m_p25MFId(p25::P25_MFG_STANDARD), m_p25MFId(p25::P25_MFG_STANDARD),
m_password(password), m_password(password),
m_passwordHash(nullptr), 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(!address.empty());
assert(port > 0U); assert(port > 0U);
@ -145,6 +158,19 @@ void RemoteControl::setLookups(lookups::RadioIdLookup* ridLookup, lookups::Talkg
m_tidLookup = tidLookup; 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> /// <summary>
/// Process remote network command data. /// Process remote network command data.
/// </summary> /// </summary>
@ -850,6 +876,10 @@ void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25, nx
/// <returns></returns> /// <returns></returns>
bool RemoteControl::open() bool RemoteControl::open()
{ {
initializeEndpoints();
m_restServer.setHandler(m_dispatcher);
run();
return m_socket.open(); return m_socket.open();
} }
@ -858,6 +888,9 @@ bool RemoteControl::open()
/// </summary> /// </summary>
void RemoteControl::close() void RemoteControl::close()
{ {
m_restServer.stop();
wait();
m_socket.close(); m_socket.close();
} }
@ -865,6 +898,28 @@ void RemoteControl::close()
// Private Class Members // 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> /// <summary>
/// Helper to write response to client. /// Helper to write response to client.
/// </summary> /// </summary>

@ -33,8 +33,11 @@
#include "Defines.h" #include "Defines.h"
#include "network/UDPSocket.h" #include "network/UDPSocket.h"
#include "network/rest/RequestDispatcher.h"
#include "network/rest/http/HTTPServer.h"
#include "lookups/RadioIdLookup.h" #include "lookups/RadioIdLookup.h"
#include "lookups/TalkgroupIdLookup.h" #include "lookups/TalkgroupIdLookup.h"
#include "Thread.h"
#include <vector> #include <vector>
#include <string> #include <string>
@ -117,15 +120,17 @@ namespace nxdn { class HOST_SW_API Control; }
// Implements the remote control networking logic. // Implements the remote control networking logic.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
class HOST_SW_API RemoteControl { class HOST_SW_API RemoteControl : private Thread {
public: public:
/// <summary>Initializes a new instance of the RemoteControl class.</summary> /// <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> /// <summary>Finalizes a instance of the RemoteControl class.</summary>
~RemoteControl(); ~RemoteControl();
/// <summary>Sets the instances of the Radio ID and Talkgroup ID lookup tables.</summary> /// <summary>Sets the instances of the Radio ID and Talkgroup ID lookup tables.</summary>
void setLookups(::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup); 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> /// <summary>Process remote network command data.</summary>
void process(Host* host, dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn); void process(Host* host, dmr::Control* dmr, p25::Control* p25, nxdn::Control* nxdn);
@ -137,6 +142,10 @@ public:
void close(); void close();
private: 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; network::UDPSocket m_socket;
uint8_t m_p25MFId; uint8_t m_p25MFId;
@ -144,9 +153,20 @@ private:
uint8_t* m_passwordHash; uint8_t* m_passwordHash;
bool m_debug; bool m_debug;
Host* m_host;
dmr::Control* m_dmr;
p25::Control* m_p25;
nxdn::Control* m_nxdn;
::lookups::RadioIdLookup* m_ridLookup; ::lookups::RadioIdLookup* m_ridLookup;
::lookups::TalkgroupIdLookup* m_tidLookup; ::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> /// <summary>Helper to send response to client.</summary>
void writeResponse(std::string reply, sockaddr_storage address, uint32_t addrLen); 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 <iterator>
#include <asio.hpp> #include <asio.hpp>
namespace rest { namespace network {
namespace server { namespace rest {
namespace http {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Class Prototypes // Class Prototypes
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
template<class> class ConnectionManager; template<class> class ConnectionManager;
// ---------------------------------------------------------------------------
// Class Declaration
// This class represents a single connection from a client.
// ---------------------------------------------------------------------------
template <typename RequestHandlerType> // ---------------------------------------------------------------------------
class Connection : public std::enable_shared_from_this<Connection<RequestHandlerType>> // Class Declaration
{ // This class represents a single connection from a client.
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> template <typename RequestHandlerType>
Connection& operator=(const Connection&) = delete; class Connection : public std::enable_shared_from_this<Connection<RequestHandlerType>>
/// <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) { typedef Connection<RequestHandlerType> selfType;
auto self(this->shared_from_this()); 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) { m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) {
if (!ec) { if (!ec) {
HTTPRequestLexer::ResultType result; HTTPRequestLexer::ResultType result;
char* data; char* data;
std::tie(result, data) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred); 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"; }); auto header = std::find_if(m_request.headers.begin(), m_request.headers.end(), [](const HTTPHeader& h) { return h.name == "content-length"; });
if (itr != request_.headers.end()) { if (header != m_request.headers.end()) {
m_request.data = std::string(data, std::static_cast<long>(itr->value)); size_t length = (size_t)::strtoul(header->value.c_str(), NULL, 10);
} m_request.data = std::string(data, length);
}
if (result == HTTPRequestLexer::GOOD) { if (result == HTTPRequestLexer::GOOD) {
m_requestHandler.handleRequest(m_request, m_reply); m_requestHandler.handleRequest(m_request, m_reply);
write(); write();
}
else if (result == HTTPRequestLexer::BAD) {
m_reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST);
write();
}
else {
read();
}
} }
else if (result == HTTPRequestLexer::BAD) { else if (ec != asio::error::operation_aborted) {
m_reply = reply::stockReply(HTTPReply::BAD_REQUEST); m_connectionManager.stop(this->shared_from_this());
write();
}
else {
read();
} }
} });
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) { /// <summary>Perform an asynchronous write operation.</summary>
if (m_persistent) { void write()
m_lexer.reset(); {
m_reply.headers.resize(0); if (!m_persistent) {
m_reply.status = HTTPReply::OK; auto self(this->shared_from_this());
m_reply.content = ""; } else {
m_request = HTTPRequest(); m_reply.headers.emplace_back("Connection:", "keep-alive");
read();
} }
else
{ asio::async_write(m_socket, m_reply.toBuffers(), [=](asio::error_code ec, std::size_t) {
if (!ec) { if (m_persistent) {
// initiate graceful connection closure m_lexer.reset();
asio::error_code ignored_ec; m_reply.headers.resize(0);
m_socket.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec); m_reply.status = HTTPReply::OK;
m_reply.content = "";
m_request = HTTPRequest();
read();
} }
else
if (ec != asio::error::operation_aborted) { {
m_connectionManager.stop(this->shared_from_this()); 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;
asio::ip::tcp::socket m_socket; ConnectionManagerType& m_connectionManager;
ConnectionManagerType& m_connectionManager; RequestHandlerType& m_requestHandler;
RequestHandlerType& m_requestHandler; std::array<char, 8192> m_buffer;
std::array<char, 8192> m_buffer; HTTPRequest m_request;
HTTPRequest m_request; HTTPRequestLexer m_lexer;
HTTPRequestLexer m_lexer; HTTPReply m_reply;
HTTPReply m_reply; bool m_persistent;
bool m_persistent; };
}; } // namespace http
} // namespace server } // namespace rest
} // namespace rest } // namespace network
#endif // __REST_HTTP__CONNECTION_H__ #endif // __REST_HTTP__CONNECTION_H__

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

@ -41,26 +41,28 @@
#include <string> #include <string>
namespace rest { namespace network {
namespace server { namespace rest {
namespace http {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Structure Declaration // Structure Declaration
// This class implements a model for an HTTP header. // This class implements a model for an HTTP header.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
struct HTTPHeader struct HTTPHeader
{ {
std::string name; std::string name;
std::string value; std::string value;
/// <summary>Initializes a new instance of the HTTPHeader struct.</summary> /// <summary>Initializes a new instance of the HTTPHeader struct.</summary>
HTTPHeader() { /* stub */ } HTTPHeader() { /* stub */ }
/// <summary>Initializes a new instance of the HTTPHeader struct.</summary> /// <summary>Initializes a new instance of the HTTPHeader struct.</summary>
HTTPHeader(const std::string& name, const std::string& value) : name{name}, value{value} { /* stub */ } HTTPHeader(const std::string& name, const std::string& value) : name(name), value(value) { /* stub */ }
}; };
} // namespace server } // namespace http
} // namespace rest } // namespace rest
} // namespace network
#endif // __REST_HTTP__HTTP_HEADER_H__ #endif // __REST_HTTP__HTTP_HEADER_H__

@ -37,7 +37,7 @@
#include "Defines.h" #include "Defines.h"
#include "network/rest/http/HTTPReply.h" #include "network/rest/http/HTTPReply.h"
using namespace rest::server; using namespace network::rest::http;
#include <string> #include <string>
@ -106,24 +106,6 @@ namespace misc_strings {
const char crlf[] = { '\r', '\n' }; const char crlf[] = { '\r', '\n' };
} // namespace misc_strings } // 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 { namespace stock_replies {
const char ok[] = ""; const char ok[] = "";
const char created[] = const char created[] =
@ -244,24 +226,91 @@ namespace stock_replies {
} }
} // 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; HTTPReply rep;
rep.status = status; rep.status = status;
if (status != HTTPReply::NO_CONTENT) { if (status != HTTPReply::NO_CONTENT) {
rep.content = stock_replies::to_string(status); rep.content = stock_replies::to_string(status);
rep.headers.resize(2); rep.ensureDefaultHeaders(contentType);
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;
} }
return rep; return rep;
} }
HTTPReply& operator<<(HTTPReply& r, const std::string &value) { // ---------------------------------------------------------------------------
r.content.append(value); // Private Members
return r; // ---------------------------------------------------------------------------
/// <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__ #define __REST_HTTP__HTTP_REPLY_H__
#include "Defines.h" #include "Defines.h"
#include "network/json/json.h"
#include "network/rest/http/HTTPHeader.h" #include "network/rest/http/HTTPHeader.h"
#include <string> #include <string>
@ -45,49 +46,61 @@
#include <asio.hpp> #include <asio.hpp>
namespace rest { namespace network {
namespace server { namespace rest {
namespace http {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Structure Declaration // Structure Declaration
// This struct implements a model of a reply to be sent to a HTTP client. // This struct implements a model of a reply to be sent to a HTTP client.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
struct HTTPReply struct HTTPReply
{ {
enum StatusType { /// <summary>
OK = 200, /// HTTP Status/Response Codes
CREATED = 201, /// </summary>
ACCEPTED = 202, enum StatusType {
NO_CONTENT = 204, OK = 200,
MULTIPLE_CHOICES = 300, CREATED = 201,
MOVED_PERMANENTLY = 301, ACCEPTED = 202,
MOVED_TEMPORARILY = 302, NO_CONTENT = 204,
NOT_MODIFIED = 304, MULTIPLE_CHOICES = 300,
BAD_REQUEST = 400, MOVED_PERMANENTLY = 301,
UNAUTHORIZED = 401, MOVED_TEMPORARILY = 302,
FORBIDDEN = 403, NOT_MODIFIED = 304,
NOT_FOUND = 404, BAD_REQUEST = 400,
INTERNAL_SERVER_ERROR = 500, UNAUTHORIZED = 401,
NOT_IMPLEMENTED = 501, FORBIDDEN = 403,
BAD_GATEWAY = 502, NOT_FOUND = 404,
SERVICE_UNAVAILABLE = 503 INTERNAL_SERVER_ERROR = 500,
} status; NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503
} status;
std::vector<HTTPHeader> headers; std::vector<HTTPHeader> headers;
std::string content; std::string content;
/// <summary>Convert the reply into a vector of buffers. The buffers do not own the /// <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 /// underlying memory blocks, therefore the reply object must remain valid and
/// not be changed until the write operation has completed.</summary> /// not be changed until the write operation has completed.</summary>
std::vector<asio::const_buffer> toBuffers(); std::vector<asio::const_buffer> toBuffers();
/// <summary>Get a stock reply.</summary> /// <summary>Prepares reply for transmission by finalizing status and content type.</summary>
static HTTPReply stockReply(StatusType status, const char* mime = "text/html"); 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); /// <summary>Get a stock reply.</summary>
} // namespace server static HTTPReply stockReply(StatusType status, std::string contentType = "text/html");
} // namespace rest
private:
/// <summary></summary>
void ensureDefaultHeaders(std::string contentType = "text/html");
};
} // namespace http
} // namespace rest
} // namespace network
#endif // __REST_HTTP__HTTP_REPLY_H__ #endif // __REST_HTTP__HTTP_REPLY_H__

@ -43,26 +43,28 @@
#include <string> #include <string>
#include <vector> #include <vector>
namespace rest { namespace network {
namespace server { namespace rest {
namespace http {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Structure Declaration // Structure Declaration
// This struct implements a model of a request received from a HTTP client. // This struct implements a model of a request received from a HTTP client.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
struct HTTPRequest struct HTTPRequest
{ {
std::string method; std::string method;
std::string uri; std::string uri;
int httpVersionMajor; int httpVersionMajor;
int httpVersionMinor; int httpVersionMinor;
std::vector<HTTPHeader> headers; std::vector<HTTPHeader> headers;
std::string data; std::string data;
}; };
} // namespace server } // namespace http
} // namespace http } // namespace rest
} // namespace network
#endif // __REST_HTTP__HTTP_REQUEST_H__ #endif // __REST_HTTP__HTTP_REQUEST_H__

@ -39,7 +39,7 @@
#include "network/rest/http/HTTPRequest.h" #include "network/rest/http/HTTPRequest.h"
#include "network/rest/http/HTTPReply.h" #include "network/rest/http/HTTPReply.h"
using namespace rest::server; using namespace network::rest::http;
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>

@ -41,47 +41,49 @@
#include <string> #include <string>
namespace rest { namespace network {
namespace server { namespace rest {
namespace http {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Class Prototypes // Class Prototypes
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
struct HTTPReply; struct HTTPReply;
struct HTTPRequest; struct HTTPRequest;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Class Declaration // Class Declaration
// This class implements the common handler for all incoming requests. // This class implements the common handler for all incoming requests.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
class HTTPRequestHandler class HTTPRequestHandler
{ {
public: public:
/// <summary>Initializes a new instance of the HTTPRequestHandler class.</summary> /// <summary>Initializes a new instance of the HTTPRequestHandler class.</summary>
explicit HTTPRequestHandler(const std::string& docRoot); explicit HTTPRequestHandler(const std::string& docRoot);
/// <summary>Initializes a copy instance of the HTTPRequestHandler class.</summary> /// <summary>Initializes a copy instance of the HTTPRequestHandler class.</summary>
HTTPRequestHandler(const HTTPRequestHandler&) = delete; HTTPRequestHandler(const HTTPRequestHandler&) = delete;
/// <summary></summary> /// <summary></summary>
HTTPRequestHandler(HTTPRequestHandler&&) = default; HTTPRequestHandler(HTTPRequestHandler&&) = default;
/// <summary></summary> /// <summary></summary>
HTTPRequestHandler& operator=(const HTTPRequestHandler&) = delete; HTTPRequestHandler& operator=(const HTTPRequestHandler&) = delete;
/// <summary></summary> /// <summary></summary>
HTTPRequestHandler& operator=(HTTPRequestHandler&&) = default; HTTPRequestHandler& operator=(HTTPRequestHandler&&) = default;
/// <summary>Handle a request and produce a reply.</summary> /// <summary>Handle a request and produce a reply.</summary>
void handleRequest(const HTTPRequest& req, HTTPReply& reply); void handleRequest(const HTTPRequest& req, HTTPReply& reply);
private: private:
/// <summary>Perform URL-decoding on a string. Returns false if the encoding was /// <summary>Perform URL-decoding on a string. Returns false if the encoding was
/// invalid.</summary> /// invalid.</summary>
static bool urlDecode(const std::string& in, std::string& out); static bool urlDecode(const std::string& in, std::string& out);
std::string m_docRoot; std::string m_docRoot;
}; };
} // namespace server } // namespace http
} // namespace rest } // namespace rest
} // namespace network
#endif // __REST_HTTP__HTTP_REQUEST_HANDLER_H__ #endif // __REST_HTTP__HTTP_REQUEST_HANDLER_H__

@ -38,7 +38,7 @@
#include "network/rest/http/HTTPRequestLexer.h" #include "network/rest/http/HTTPRequestLexer.h"
#include "network/rest/http/HTTPRequest.h" #include "network/rest/http/HTTPRequest.h"
using namespace rest::server; using namespace network::rest::http;
#include <cctype> #include <cctype>

@ -39,84 +39,86 @@
#include <tuple> #include <tuple>
namespace rest { namespace network {
namespace server { namespace rest {
namespace http {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Class Prototypes // Class Prototypes
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
struct HTTPRequest; struct HTTPRequest;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Class Declaration // Class Declaration
// This class implements the lexer for incoming requests. // This class implements the lexer for incoming requests.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
class HTTPRequestLexer class HTTPRequestLexer
{ {
public: public:
enum ResultType { GOOD, BAD, INDETERMINATE }; enum ResultType { GOOD, BAD, INDETERMINATE };
/// <summary>Initializes a new instance of the HTTPRequestLexer class.</summary> /// <summary>Initializes a new instance of the HTTPRequestLexer class.</summary>
HTTPRequestLexer(); HTTPRequestLexer();
/// <summary>Reset to initial parser state.</summary> /// <summary>Reset to initial parser state.</summary>
void reset(); void reset();
/// <summary>Parse some data. The enum return value is good when a complete request has /// <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 /// been parsed, bad if the data is invalid, indeterminate when more data is
/// required. The InputIterator return value indicates how much of the input /// required. The InputIterator return value indicates how much of the input
/// has been consumed.</summary> /// has been consumed.</summary>
template <typename InputIterator> template <typename InputIterator>
std::tuple<ResultType, InputIterator> parse(HTTPRequest& req, InputIterator begin, InputIterator end) std::tuple<ResultType, InputIterator> parse(HTTPRequest& req, InputIterator begin, InputIterator end)
{ {
while (begin != end) { while (begin != end) {
ResultType result = consume(req, *begin++); ResultType result = consume(req, *begin++);
if (result == GOOD || result == BAD) if (result == GOOD || result == BAD)
return std::make_tuple(result, begin); return std::make_tuple(result, begin);
}
return std::make_tuple(INDETERMINATE, begin);
} }
return std::make_tuple(INDETERMINATE, begin);
}
private: private:
/// <summary>Handle the next character of input.</summary> /// <summary>Handle the next character of input.</summary>
ResultType consume(HTTPRequest& req, char input); ResultType consume(HTTPRequest& req, char input);
/// <summary>Check if a byte is an HTTP character.</summary> /// <summary>Check if a byte is an HTTP character.</summary>
static bool isChar(int c); static bool isChar(int c);
/// <summary>Check if a byte is an HTTP control character.</summary> /// <summary>Check if a byte is an HTTP control character.</summary>
static bool isControl(int c); static bool isControl(int c);
/// <summary>Check if a byte is an HTTP special character.</summary> /// <summary>Check if a byte is an HTTP special character.</summary>
static bool isSpecial(int c); static bool isSpecial(int c);
/// <summary>Check if a byte is an digit.</summary> /// <summary>Check if a byte is an digit.</summary>
static bool isDigit(int c); static bool isDigit(int c);
enum state enum state
{ {
METHOD_START, METHOD_START,
METHOD, METHOD,
URI, URI,
HTTP_VERSION_H, HTTP_VERSION_H,
HTTP_VERSION_T_1, HTTP_VERSION_T_1,
HTTP_VERSION_T_2, HTTP_VERSION_T_2,
HTTP_VERSION_P, HTTP_VERSION_P,
HTTP_VERSION_SLASH, HTTP_VERSION_SLASH,
HTTP_VERSION_MAJOR_START, HTTP_VERSION_MAJOR_START,
HTTP_VERSION_MAJOR, HTTP_VERSION_MAJOR,
HTTP_VERSION_MINOR_START, HTTP_VERSION_MINOR_START,
HTTP_VERSION_MINOR, HTTP_VERSION_MINOR,
EXPECTING_NEWLINE_1, EXPECTING_NEWLINE_1,
HEADER_LINE_START, HEADER_LINE_START,
HEADER_LWS, HEADER_LWS,
HEADER_NAME, HEADER_NAME,
SPACE_BEFORE_HEADER_VALUE, SPACE_BEFORE_HEADER_VALUE,
HEADER_VALUE, HEADER_VALUE,
EXPECTING_NEWLINE_2, EXPECTING_NEWLINE_2,
EXPECTING_NEWLINE_3 EXPECTING_NEWLINE_3
} m_state; } m_state;
}; };
} // namespace server } // namespace http
} // namespace rest } // namespace rest
} // namespace network
#endif // __REST_HTTP__HTTP_REQUEST_PARSER_H__ #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 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN 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__ #define __REST_HTTP__HTTP_SERVER_H__
#include "Defines.h" #include "Defines.h"
#include "network/rest/Connection.h" #include "network/rest/http/Connection.h"
#include "network/rest/ConnectionManager.h" #include "network/rest/http/ConnectionManager.h"
#include "network/rest/HTTPRequestHandler.h" #include "network/rest/http/HTTPRequestHandler.h"
#include "network/rest/http/HTTPReply.h"
#include "network/rest/http/HTTPRequest.h"
#include <asio.hpp> #include <asio.hpp>
@ -50,111 +52,104 @@
#include <utility> #include <utility>
#include <memory> #include <memory>
namespace rest { namespace network {
namespace server { namespace rest {
namespace http {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Class Declaration // Class Declaration
// This class implements top-level routines of the HTTP server. // This class implements top-level routines of the HTTP server.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
template<typename RequestHandlerType, template<class> class ConnectionImpl = Connection> template<typename RequestHandlerType, template<class> class ConnectionImpl = Connection>
class HTTPServer { class HTTPServer {
public: public:
/// <summary>Initializes a new instance of the HTTPServer class.</summary> /// <summary>Initializes a new instance of the HTTPServer class.</summary>
template<typename Handler> explicit HTTPServer(const std::string& address, uint16_t port) :
explicit HTTPServer(const std::string& address, const std::string& port, Handler&& handler) : m_ioService(),
m_ioService(), m_acceptor(m_ioService),
m_signals(m_ioService), m_connectionManager(),
m_acceptor(m_ioService), m_socket(m_ioService),
m_connectionManager(), m_requestHandler()
m_socket(m_ioService), {
m_requestHandler(std::forward<Handler>(handler)) // open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR)
{ asio::ip::address ipAddress = asio::ip::address::from_string(address);
// register to handle the signals that indicate when the server should exit asio::ip::tcp::endpoint endpoint(ipAddress, port);
// 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});
m_acceptor.open(endpoint.protocol()); m_acceptor.open(endpoint.protocol());
m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true)); m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true));
m_acceptor.set_option(asio::socket_base::keep_alive(true)); m_acceptor.set_option(asio::socket_base::keep_alive(true));
m_acceptor.bind(endpoint); m_acceptor.bind(endpoint);
m_acceptor.listen(); 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));
}
accept(); accept();
}); }
} /// <summary>Initializes a copy instance of the HTTPServer class.</summary>
HTTPServer(const HTTPServer&) = delete;
/// <summary>Wait for a request to stop the server.</summary>
void awaitStop() /// <summary></summary>
{ HTTPServer& operator=(const HTTPServer&) = delete;
m_signals.async_wait([this](asio::error_code /*ec*/, int /*signo*/) {
/// <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 // the server is stopped by cancelling all outstanding asynchronous
// operations; once all operations have finished the m_ioService::run() // operations; once all operations have finished the m_ioService::run()
// call will exit // call will exit
m_acceptor.close(); m_acceptor.close();
m_connectionManager.stopAll(); m_connectionManager.stopAll();
}); }
}
private:
typedef ConnectionImpl<RequestHandlerType> ConnectionType; /// <summary>Perform an asynchronous accept operation.</summary>
typedef std::shared_ptr<ConnectionType> ConnectionTypePtr; void accept()
{
asio::io_service m_ioService; m_acceptor.async_accept(m_socket, [this](asio::error_code ec) {
asio::signal_set m_signals; // check whether the server was stopped by a signal before this
asio::ip::tcp::acceptor m_acceptor; // completion handler had a chance to run
if (!m_acceptor.is_open()) {
ConnectionManager<ConnectionTypePtr> m_connectionManager; return;
}
asio::ip::tcp::socket m_socket;
if (!ec) {
RequestHandlerType m_requestHandler; m_connectionManager.start(std::make_shared<ConnectionType>(std::move(m_socket), m_connectionManager, m_requestHandler));
}; }
} // namespace server
} // namespace rest 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__ #endif // __REST_HTTP__HTTP_SERVER_H__

Loading…
Cancel
Save

Powered by TurnKey Linux.