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,8 +48,9 @@
#include <iterator>
#include <asio.hpp>
namespace rest {
namespace server {
namespace network {
namespace rest {
namespace http {
// ---------------------------------------------------------------------------
// Class Prototypes
@ -103,9 +104,10 @@ namespace rest {
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));
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) {
@ -113,7 +115,7 @@ namespace rest {
write();
}
else if (result == HTTPRequestLexer::BAD) {
m_reply = reply::stockReply(HTTPReply::BAD_REQUEST);
m_reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST);
write();
}
else {
@ -135,7 +137,7 @@ namespace rest {
m_reply.headers.emplace_back("Connection:", "keep-alive");
}
asio::async_write(m_socket, m_reply.to_buffers(), [=](asio::error_code ec, std::size_t) {
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);
@ -168,7 +170,8 @@ namespace rest {
HTTPReply m_reply;
bool m_persistent;
};
} // namespace server
} // namespace rest
} // namespace http
} // namespace rest
} // namespace network
#endif // __REST_HTTP__CONNECTION_H__

@ -42,8 +42,9 @@
#include <set>
#include <mutex>
namespace rest {
namespace server {
namespace network {
namespace rest {
namespace http {
// ---------------------------------------------------------------------------
// Class Declaration
@ -74,7 +75,7 @@ namespace rest {
}
/// <summary>Stop the specified connection.</summary>
void stop(connection_ptr c)
void stop(ConnectionPtr c)
{
std::lock_guard<std::mutex> guard(m_lock);
{
@ -97,8 +98,9 @@ namespace rest {
std::set<ConnectionPtr> m_connections;
std::mutex m_lock;
};
} // namespace server
} // namespace rest
} // namespace http
} // namespace rest
} // namespace network
#endif // __REST_HTTP__CONNECTION_MANAGER_H__

@ -41,8 +41,9 @@
#include <string>
namespace rest {
namespace server {
namespace network {
namespace rest {
namespace http {
// ---------------------------------------------------------------------------
// Structure Declaration
@ -57,10 +58,11 @@ namespace rest {
/// <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 */ }
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,8 +46,9 @@
#include <asio.hpp>
namespace rest {
namespace server {
namespace network {
namespace rest {
namespace http {
// ---------------------------------------------------------------------------
// Structure Declaration
@ -55,6 +57,9 @@ namespace rest {
struct HTTPReply
{
/// <summary>
/// HTTP Status/Response Codes
/// </summary>
enum StatusType {
OK = 200,
CREATED = 201,
@ -82,12 +87,20 @@ namespace rest {
/// not be changed until the write operation has completed.</summary>
std::vector<asio::const_buffer> toBuffers();
/// <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");
/// <summary>Get a stock reply.</summary>
static HTTPReply stockReply(StatusType status, const char* mime = "text/html");
};
static HTTPReply stockReply(StatusType status, std::string contentType = "text/html");
HTTPReply& operator<<(HTTPReply& r, const std::string& value);
} // namespace server
} // namespace rest
private:
/// <summary></summary>
void ensureDefaultHeaders(std::string contentType = "text/html");
};
} // namespace http
} // namespace rest
} // namespace network
#endif // __REST_HTTP__HTTP_REPLY_H__

@ -43,8 +43,9 @@
#include <string>
#include <vector>
namespace rest {
namespace server {
namespace network {
namespace rest {
namespace http {
// ---------------------------------------------------------------------------
// Structure Declaration
@ -62,7 +63,8 @@ namespace rest {
std::vector<HTTPHeader> headers;
std::string data;
};
} // namespace server
} // namespace http
} // 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,8 +41,9 @@
#include <string>
namespace rest {
namespace server {
namespace network {
namespace rest {
namespace http {
// ---------------------------------------------------------------------------
// Class Prototypes
@ -81,7 +82,8 @@ namespace rest {
std::string m_docRoot;
};
} // namespace server
} // namespace rest
} // 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,8 +39,9 @@
#include <tuple>
namespace rest {
namespace server {
namespace network {
namespace rest {
namespace http {
// ---------------------------------------------------------------------------
// Class Prototypes
@ -116,7 +117,8 @@ namespace rest {
EXPECTING_NEWLINE_3
} m_state;
};
} // namespace server
} // namespace rest
} // 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,8 +52,9 @@
#include <utility>
#include <memory>
namespace rest {
namespace server {
namespace network {
namespace rest {
namespace http {
// ---------------------------------------------------------------------------
// Class Declaration
@ -62,29 +65,16 @@ namespace rest {
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) :
explicit HTTPServer(const std::string& address, uint16_t port) :
m_ioService(),
m_signals(m_ioService),
m_acceptor(m_ioService),
m_connectionManager(),
m_socket(m_ioService),
m_requestHandler(std::forward<Handler>(handler))
m_requestHandler()
{
// 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});
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));
@ -100,6 +90,13 @@ namespace rest {
/// <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()
{
@ -110,6 +107,16 @@ namespace rest {
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();
}
private:
/// <summary>Perform an asynchronous accept operation.</summary>
void accept()
@ -122,30 +129,17 @@ namespace rest {
}
if (!ec) {
m_connectionManager.start(std::make_shared<connection_type>(std::move(m_socket), m_connectionManager, m_requestHandler));
m_connectionManager.start(std::make_shared<ConnectionType>(std::move(m_socket), m_connectionManager, m_requestHandler));
}
accept();
});
}
/// <summary>Wait for a request to stop the server.</summary>
void awaitStop()
{
m_signals.async_wait([this](asio::error_code /*ec*/, int /*signo*/) {
// 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;
@ -154,7 +148,8 @@ namespace rest {
RequestHandlerType m_requestHandler;
};
} // namespace server
} // namespace rest
} // namespace http
} // namespace rest
} // namespace network
#endif // __REST_HTTP__HTTP_SERVER_H__

Loading…
Cancel
Save

Powered by TurnKey Linux.