*BEWARE* this commit alters the RPI_ARM cross-compiler and may break things *BEWARE*; implement support for HTTPS SSL REST API configuration; add contrib binary overlay for the RPI_ARM compiler (to add libssl-dev dependences);
parent
cda6b5965f
commit
31ca17449d
Binary file not shown.
@ -0,0 +1,190 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/**
|
||||||
|
* Digital Voice Modem - Common Library
|
||||||
|
* GPLv2 Open Source. Use is subject to license terms.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* @package DVM / Common Library
|
||||||
|
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#if !defined(__REST_HTTP__SECURE_CLIENT_CONNECTION_H__)
|
||||||
|
#define __REST_HTTP__SECURE_CLIENT_CONNECTION_H__
|
||||||
|
|
||||||
|
#if defined(ENABLE_TCP_SSL)
|
||||||
|
|
||||||
|
#include "common/Defines.h"
|
||||||
|
#include "common/network/rest/http/HTTPLexer.h"
|
||||||
|
#include "common/network/rest/http/HTTPPayload.h"
|
||||||
|
#include "common/Log.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
#include <iterator>
|
||||||
|
#include <asio.hpp>
|
||||||
|
#include <asio/ssl.hpp>
|
||||||
|
|
||||||
|
namespace network
|
||||||
|
{
|
||||||
|
namespace rest
|
||||||
|
{
|
||||||
|
namespace http
|
||||||
|
{
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// This class represents a single connection from a client.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template <typename RequestHandlerType>
|
||||||
|
class SecureClientConnection {
|
||||||
|
public:
|
||||||
|
auto operator=(SecureClientConnection&) -> SecureClientConnection& = delete;
|
||||||
|
auto operator=(SecureClientConnection&&) -> SecureClientConnection& = delete;
|
||||||
|
SecureClientConnection(SecureClientConnection&) = delete;
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the SecureClientConnection class.</summary>
|
||||||
|
explicit SecureClientConnection(asio::ip::tcp::socket socket, asio::ssl::context& context, RequestHandlerType& handler) :
|
||||||
|
m_socket(std::move(socket), context),
|
||||||
|
m_requestHandler(handler),
|
||||||
|
m_lexer(HTTPLexer(true))
|
||||||
|
{
|
||||||
|
m_socket.set_verify_mode(asio::ssl::verify_none);
|
||||||
|
m_socket.set_verify_callback(std::bind(&SecureClientConnection::verify_certificate, this, std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Start the first asynchronous operation for the connection.</summary>
|
||||||
|
void start()
|
||||||
|
{
|
||||||
|
m_socket.handshake(asio::ssl::stream_base::client);
|
||||||
|
read();
|
||||||
|
}
|
||||||
|
/// <summary>Stop all asynchronous operations associated with the connection.</summary>
|
||||||
|
void stop()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ensureNoLinger();
|
||||||
|
if (m_socket.lowest_layer().is_open()) {
|
||||||
|
m_socket.lowest_layer().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(const std::exception&) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Helper to enable the SO_LINGER socket option during shutdown.</summary>
|
||||||
|
void ensureNoLinger()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// enable SO_LINGER timeout 0
|
||||||
|
asio::socket_base::linger linger(true, 0);
|
||||||
|
m_socket.lowest_layer().set_option(linger);
|
||||||
|
}
|
||||||
|
catch(const asio::system_error& e)
|
||||||
|
{
|
||||||
|
asio::error_code ec = e.code();
|
||||||
|
if (ec) {
|
||||||
|
::LogError(LOG_REST, "SecureClientConnection::ensureNoLinger(), %s, code = %u", ec.message().c_str(), ec.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Perform an synchronous write operation.</summary>
|
||||||
|
void send(HTTPPayload request)
|
||||||
|
{
|
||||||
|
request.attachHostHeader(m_socket.lowest_layer().remote_endpoint());
|
||||||
|
write(request);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
/// <summary>Perform an SSL certificate verification.</summary>
|
||||||
|
bool verify_certificate(bool preverified, asio::ssl::verify_context& context)
|
||||||
|
{
|
||||||
|
return true; // ignore always valid
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Perform an asynchronous read operation.</summary>
|
||||||
|
void read()
|
||||||
|
{
|
||||||
|
m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) {
|
||||||
|
if (!ec) {
|
||||||
|
HTTPLexer::ResultType result;
|
||||||
|
char* content;
|
||||||
|
|
||||||
|
std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred);
|
||||||
|
|
||||||
|
std::string contentLength = m_request.headers.find("Content-Length");
|
||||||
|
if (contentLength != "") {
|
||||||
|
size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10);
|
||||||
|
m_request.content = std::string(content, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_request.headers.add("RemoteHost", m_socket.lowest_layer().remote_endpoint().address().to_string());
|
||||||
|
|
||||||
|
if (result == HTTPLexer::GOOD) {
|
||||||
|
m_requestHandler.handleRequest(m_request, m_reply);
|
||||||
|
}
|
||||||
|
else if (result == HTTPLexer::BAD) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ec != asio::error::operation_aborted) {
|
||||||
|
if (ec) {
|
||||||
|
::LogError(LOG_REST, "SecureClientConnection::read(), %s, code = %u", ec.message().c_str(), ec.value());
|
||||||
|
}
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Perform an synchronous write operation.</summary>
|
||||||
|
void write(HTTPPayload request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_socket.handshake(asio::ssl::stream_base::client);
|
||||||
|
|
||||||
|
auto buffers = request.toBuffers();
|
||||||
|
asio::write(m_socket, buffers);
|
||||||
|
}
|
||||||
|
catch(const asio::system_error& e)
|
||||||
|
{
|
||||||
|
asio::error_code ec = e.code();
|
||||||
|
if (ec) {
|
||||||
|
::LogError(LOG_REST, "SecureClientConnection::write(), %s, code = %u", ec.message().c_str(), ec.value());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// initiate graceful connection closure
|
||||||
|
asio::error_code ignored_ec;
|
||||||
|
m_socket.lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec);
|
||||||
|
}
|
||||||
|
catch(const std::exception& e) {
|
||||||
|
::LogError(LOG_REST, "SecureClientConnection::write(), %s, code = %u", ec.message().c_str(), ec.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asio::ssl::stream<asio::ip::tcp::socket> m_socket;
|
||||||
|
|
||||||
|
RequestHandlerType& m_requestHandler;
|
||||||
|
|
||||||
|
std::array<char, 8192> m_buffer;
|
||||||
|
|
||||||
|
HTTPPayload m_request;
|
||||||
|
HTTPLexer m_lexer;
|
||||||
|
HTTPPayload m_reply;
|
||||||
|
};
|
||||||
|
} // namespace http
|
||||||
|
} // namespace rest
|
||||||
|
} // namespace network
|
||||||
|
|
||||||
|
#endif // ENABLE_TCP_SSL
|
||||||
|
|
||||||
|
#endif // __REST_HTTP__SECURE_CLIENT_CONNECTION_H__
|
||||||
@ -0,0 +1,178 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/**
|
||||||
|
* Digital Voice Modem - Common Library
|
||||||
|
* GPLv2 Open Source. Use is subject to license terms.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* @package DVM / Common Library
|
||||||
|
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#if !defined(__REST_HTTP__SECURE_HTTP_CLIENT_H__)
|
||||||
|
#define __REST_HTTP__SECURE_HTTP_CLIENT_H__
|
||||||
|
|
||||||
|
#if defined(ENABLE_TCP_SSL)
|
||||||
|
|
||||||
|
#include "common/Defines.h"
|
||||||
|
#include "common/network/rest/http/SecureClientConnection.h"
|
||||||
|
#include "common/network/rest/http/HTTPRequestHandler.h"
|
||||||
|
#include "common/Thread.h"
|
||||||
|
|
||||||
|
#include <asio.hpp>
|
||||||
|
#include <asio/ssl.hpp>
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <string>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <utility>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace network
|
||||||
|
{
|
||||||
|
namespace rest
|
||||||
|
{
|
||||||
|
namespace http
|
||||||
|
{
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// This class implements top-level routines of the secure HTTP client.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template<typename RequestHandlerType, template<class> class ConnectionImpl = SecureClientConnection>
|
||||||
|
class SecureHTTPClient : private Thread {
|
||||||
|
public:
|
||||||
|
auto operator=(SecureHTTPClient&) -> SecureHTTPClient& = delete;
|
||||||
|
auto operator=(SecureHTTPClient&&) -> SecureHTTPClient& = delete;
|
||||||
|
SecureHTTPClient(SecureHTTPClient&) = delete;
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the SecureHTTPClient class.</summary>
|
||||||
|
SecureHTTPClient(const std::string& address, uint16_t port) :
|
||||||
|
m_address(address),
|
||||||
|
m_port(port),
|
||||||
|
m_connection(nullptr),
|
||||||
|
m_ioContext(),
|
||||||
|
m_context(asio::ssl::context::tlsv12),
|
||||||
|
m_socket(m_ioContext),
|
||||||
|
m_requestHandler()
|
||||||
|
{
|
||||||
|
/* stub */
|
||||||
|
}
|
||||||
|
/// <summary>Finalizes a instance of the SecureHTTPClient class.</summary>
|
||||||
|
~SecureHTTPClient() override
|
||||||
|
{
|
||||||
|
if (m_connection != nullptr) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Helper to set the HTTP request handlers.</summary>
|
||||||
|
template<typename Handler>
|
||||||
|
void setHandler(Handler&& handler)
|
||||||
|
{
|
||||||
|
m_requestHandler = RequestHandlerType(std::forward<Handler>(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Send HTTP request to HTTP server.</summary>
|
||||||
|
bool request(HTTPPayload& request)
|
||||||
|
{
|
||||||
|
if (m_completed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
asio::post(m_ioContext, [this, request]() {
|
||||||
|
std::lock_guard<std::mutex> guard(m_lock);
|
||||||
|
{
|
||||||
|
if (m_connection != nullptr) {
|
||||||
|
m_connection->send(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Opens connection to the network.</summary>
|
||||||
|
bool open()
|
||||||
|
{
|
||||||
|
if (m_completed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Closes connection to the network.</summary>
|
||||||
|
void close()
|
||||||
|
{
|
||||||
|
if (m_completed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_completed = true;
|
||||||
|
m_ioContext.stop();
|
||||||
|
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// <summary></summary>
|
||||||
|
void entry() override
|
||||||
|
{
|
||||||
|
if (m_completed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
asio::ip::tcp::resolver resolver(m_ioContext);
|
||||||
|
auto endpoints = resolver.resolve(m_address, std::to_string(m_port));
|
||||||
|
|
||||||
|
try {
|
||||||
|
connect(endpoints);
|
||||||
|
|
||||||
|
// the entry() call will block until all asynchronous operations
|
||||||
|
// have finished
|
||||||
|
m_ioContext.run();
|
||||||
|
}
|
||||||
|
catch (std::exception&) { /* stub */ }
|
||||||
|
|
||||||
|
if (m_connection != nullptr) {
|
||||||
|
m_connection->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Perform an asynchronous connect operation.</summary>
|
||||||
|
void connect(asio::ip::basic_resolver_results<asio::ip::tcp>& endpoints)
|
||||||
|
{
|
||||||
|
asio::connect(m_socket, endpoints);
|
||||||
|
|
||||||
|
m_connection = std::make_unique<ConnectionType>(std::move(m_socket), m_context, m_requestHandler);
|
||||||
|
m_connection->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string m_address;
|
||||||
|
uint16_t m_port;
|
||||||
|
|
||||||
|
typedef ConnectionImpl<RequestHandlerType> ConnectionType;
|
||||||
|
|
||||||
|
std::unique_ptr<ConnectionType> m_connection;
|
||||||
|
|
||||||
|
bool m_completed = false;
|
||||||
|
asio::io_context m_ioContext;
|
||||||
|
|
||||||
|
asio::ssl::context m_context;
|
||||||
|
asio::ip::tcp::socket m_socket;
|
||||||
|
|
||||||
|
RequestHandlerType m_requestHandler;
|
||||||
|
|
||||||
|
std::mutex m_lock;
|
||||||
|
};
|
||||||
|
} // namespace http
|
||||||
|
} // namespace rest
|
||||||
|
} // namespace network
|
||||||
|
|
||||||
|
#endif // ENABLE_TCP_SSL
|
||||||
|
|
||||||
|
#endif // __REST_HTTP__SECURE_HTTP_CLIENT_H__
|
||||||
@ -0,0 +1,164 @@
|
|||||||
|
// SPDX-License-Identifier: BSL-1.0
|
||||||
|
/**
|
||||||
|
* Digital Voice Modem - Common Library
|
||||||
|
* BSL-1.0 Open Source. Use is subject to license terms.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* @package DVM / Common Library
|
||||||
|
* @derivedfrom CRUD (https://github.com/venediktov/CRUD)
|
||||||
|
* @license BSL-1.0 License (https://opensource.org/license/bsl1-0-html)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
|
||||||
|
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#if !defined(__REST_HTTP__SECURE_HTTP_SERVER_H__)
|
||||||
|
#define __REST_HTTP__SECURE_HTTP_SERVER_H__
|
||||||
|
|
||||||
|
#if defined(ENABLE_TCP_SSL)
|
||||||
|
|
||||||
|
#include "common/Defines.h"
|
||||||
|
#include "common/network/rest/http/SecureServerConnection.h"
|
||||||
|
#include "common/network/rest/http/ServerConnectionManager.h"
|
||||||
|
#include "common/network/rest/http/HTTPRequestHandler.h"
|
||||||
|
|
||||||
|
#include <asio.hpp>
|
||||||
|
#include <asio/ssl.hpp>
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <string>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <utility>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace network
|
||||||
|
{
|
||||||
|
namespace rest
|
||||||
|
{
|
||||||
|
namespace http
|
||||||
|
{
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// This class implements top-level routines of the secure HTTP server.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template<typename RequestHandlerType, template<class> class ConnectionImpl = SecureServerConnection>
|
||||||
|
class SecureHTTPServer {
|
||||||
|
public:
|
||||||
|
auto operator=(SecureHTTPServer&) -> SecureHTTPServer& = delete;
|
||||||
|
auto operator=(SecureHTTPServer&&) -> SecureHTTPServer& = delete;
|
||||||
|
SecureHTTPServer(SecureHTTPServer&) = delete;
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the SecureHTTPServer class.</summary>
|
||||||
|
explicit SecureHTTPServer(const std::string& address, uint16_t port) :
|
||||||
|
m_ioService(),
|
||||||
|
m_acceptor(m_ioService),
|
||||||
|
m_connectionManager(),
|
||||||
|
m_context(asio::ssl::context::tlsv12),
|
||||||
|
m_socket(m_ioService),
|
||||||
|
m_requestHandler()
|
||||||
|
{
|
||||||
|
asio::ip::address ipAddress = asio::ip::address::from_string(address);
|
||||||
|
m_endpoint = asio::ip::tcp::endpoint(ipAddress, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Helper to set the SSL certificate and private key.</summary>
|
||||||
|
bool setCertAndKey(const std::string& keyFile, const std::string& certFile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_context.use_certificate_chain_file(certFile);
|
||||||
|
m_context.use_private_key_file(keyFile, asio::ssl::context::pem);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch(const std::exception& e) {
|
||||||
|
::LogError(LOG_REST, "%s", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Helper to set the HTTP request handlers.</summary>
|
||||||
|
template<typename Handler>
|
||||||
|
void setHandler(Handler&& handler)
|
||||||
|
{
|
||||||
|
m_requestHandler = RequestHandlerType(std::forward<Handler>(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Open TCP acceptor.</summary>
|
||||||
|
void open()
|
||||||
|
{
|
||||||
|
// open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR)
|
||||||
|
m_acceptor.open(m_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(m_endpoint);
|
||||||
|
m_acceptor.listen();
|
||||||
|
|
||||||
|
accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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_context, 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;
|
||||||
|
|
||||||
|
asio::ip::tcp::endpoint m_endpoint;
|
||||||
|
|
||||||
|
ServerConnectionManager<ConnectionTypePtr> m_connectionManager;
|
||||||
|
|
||||||
|
asio::ssl::context m_context;
|
||||||
|
asio::ip::tcp::socket m_socket;
|
||||||
|
|
||||||
|
std::string m_certFile;
|
||||||
|
std::string m_keyFile;
|
||||||
|
|
||||||
|
RequestHandlerType m_requestHandler;
|
||||||
|
};
|
||||||
|
} // namespace http
|
||||||
|
} // namespace rest
|
||||||
|
} // namespace network
|
||||||
|
|
||||||
|
#endif // ENABLE_TCP_SSL
|
||||||
|
|
||||||
|
#endif // __REST_HTTP__SECURE_HTTP_SERVER_H__
|
||||||
@ -0,0 +1,202 @@
|
|||||||
|
// SPDX-License-Identifier: BSL-1.0
|
||||||
|
/**
|
||||||
|
* Digital Voice Modem - Common Library
|
||||||
|
* BSL-1.0 Open Source. Use is subject to license terms.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* @package DVM / Common Library
|
||||||
|
* @derivedfrom CRUD (https://github.com/venediktov/CRUD)
|
||||||
|
* @license BSL-1.0 License (https://opensource.org/license/bsl1-0-html)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
|
||||||
|
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#if !defined(__REST_HTTP__SECURE_SERVER_CONNECTION_H__)
|
||||||
|
#define __REST_HTTP__SECURE_SERVER_CONNECTION_H__
|
||||||
|
|
||||||
|
#if defined(ENABLE_TCP_SSL)
|
||||||
|
|
||||||
|
#include "common/Defines.h"
|
||||||
|
#include "common/network/rest/http/HTTPLexer.h"
|
||||||
|
#include "common/network/rest/http/HTTPPayload.h"
|
||||||
|
#include "common/Log.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
#include <iterator>
|
||||||
|
#include <asio.hpp>
|
||||||
|
#include <asio/ssl.hpp>
|
||||||
|
|
||||||
|
namespace network
|
||||||
|
{
|
||||||
|
namespace rest
|
||||||
|
{
|
||||||
|
namespace http
|
||||||
|
{
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Prototypes
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template<class> class ServerConnectionManager;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// This class represents a single connection from a client.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template <typename RequestHandlerType>
|
||||||
|
class SecureServerConnection : public std::enable_shared_from_this<SecureServerConnection<RequestHandlerType>> {
|
||||||
|
typedef SecureServerConnection<RequestHandlerType> selfType;
|
||||||
|
typedef std::shared_ptr<selfType> selfTypePtr;
|
||||||
|
typedef ServerConnectionManager<selfTypePtr> ConnectionManagerType;
|
||||||
|
public:
|
||||||
|
auto operator=(SecureServerConnection&) -> SecureServerConnection& = delete;
|
||||||
|
auto operator=(SecureServerConnection&&) -> SecureServerConnection& = delete;
|
||||||
|
SecureServerConnection(SecureServerConnection&) = delete;
|
||||||
|
|
||||||
|
/// <summary>Initializes a new instance of the SecureServerConnection class.</summary>
|
||||||
|
explicit SecureServerConnection(asio::ip::tcp::socket socket, asio::ssl::context& context, ConnectionManagerType& manager, RequestHandlerType& handler,
|
||||||
|
bool persistent = false) :
|
||||||
|
m_socket(std::move(socket), context),
|
||||||
|
m_connectionManager(manager),
|
||||||
|
m_requestHandler(handler),
|
||||||
|
m_lexer(HTTPLexer(false)),
|
||||||
|
m_persistent(persistent)
|
||||||
|
{
|
||||||
|
/* stub */
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Start the first asynchronous operation for the connection.</summary>
|
||||||
|
void start() { handshake(); }
|
||||||
|
/// <summary>Stop all asynchronous operations associated with the connection.</summary>
|
||||||
|
void stop()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (m_socket.lowest_layer().is_open()) {
|
||||||
|
m_socket.lowest_layer().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(const std::exception&) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// <summary>Perform an asynchronous SSL handshake.</summary>
|
||||||
|
void handshake()
|
||||||
|
{
|
||||||
|
if (!m_persistent) {
|
||||||
|
auto self(this->shared_from_this());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_socket.async_handshake(asio::ssl::stream_base::server, [this](asio::error_code ec) {
|
||||||
|
if (!ec) {
|
||||||
|
read();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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) {
|
||||||
|
HTTPLexer::ResultType result;
|
||||||
|
char* content;
|
||||||
|
|
||||||
|
std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred);
|
||||||
|
|
||||||
|
std::string contentLength = m_request.headers.find("Content-Length");
|
||||||
|
if (contentLength != "") {
|
||||||
|
size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10);
|
||||||
|
m_request.content = std::string(content, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_request.headers.add("RemoteHost", m_socket.lowest_layer().remote_endpoint().address().to_string());
|
||||||
|
|
||||||
|
if (result == HTTPLexer::GOOD) {
|
||||||
|
m_requestHandler.handleRequest(m_request, m_reply);
|
||||||
|
write();
|
||||||
|
}
|
||||||
|
else if (result == HTTPLexer::BAD) {
|
||||||
|
m_reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST);
|
||||||
|
write();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ec != asio::error::operation_aborted) {
|
||||||
|
if (ec) {
|
||||||
|
::LogError(LOG_REST, "SecureServerConnection::read(), %s, code = %u", ec.message().c_str(), ec.value());
|
||||||
|
}
|
||||||
|
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.add("Connection", "keep-alive");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto buffers = m_reply.toBuffers();
|
||||||
|
asio::async_write(m_socket, buffers, [=](asio::error_code ec, std::size_t) {
|
||||||
|
if (m_persistent) {
|
||||||
|
m_lexer.reset();
|
||||||
|
m_reply.headers = HTTPHeaders();
|
||||||
|
m_reply.status = HTTPPayload::OK;
|
||||||
|
m_reply.content = "";
|
||||||
|
m_request = HTTPPayload();
|
||||||
|
read();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!ec) {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// initiate graceful connection closure
|
||||||
|
asio::error_code ignored_ec;
|
||||||
|
m_socket.lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec);
|
||||||
|
}
|
||||||
|
catch(const std::exception& e) { ::LogError(LOG_REST, "%s", ec.message().c_str()); }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ec != asio::error::operation_aborted) {
|
||||||
|
if (ec) {
|
||||||
|
::LogError(LOG_REST, "SecureServerConnection::write(), %s, code = %u", ec.message().c_str(), ec.value());
|
||||||
|
}
|
||||||
|
m_connectionManager.stop(this->shared_from_this());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
asio::ssl::stream<asio::ip::tcp::socket> m_socket;
|
||||||
|
|
||||||
|
ConnectionManagerType& m_connectionManager;
|
||||||
|
RequestHandlerType& m_requestHandler;
|
||||||
|
|
||||||
|
std::array<char, 8192> m_buffer;
|
||||||
|
|
||||||
|
HTTPPayload m_request;
|
||||||
|
HTTPLexer m_lexer;
|
||||||
|
HTTPPayload m_reply;
|
||||||
|
|
||||||
|
bool m_persistent;
|
||||||
|
};
|
||||||
|
} // namespace http
|
||||||
|
} // namespace rest
|
||||||
|
} // namespace network
|
||||||
|
|
||||||
|
#endif // ENABLE_TCP_SSL
|
||||||
|
|
||||||
|
#endif // __REST_HTTP__SECURE_SERVER_CONNECTION_H__
|
||||||
Loading…
Reference in new issue