summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2023-01-12 15:30:07 +0100
committerRoland Reichwein <mail@reichwein.it>2023-01-12 15:30:07 +0100
commit00ed7df1a09cad8862f2c586347f4f55c99681e5 (patch)
treee24ef2699affc7630ea42e728e62df7c6686f714
parent3cb78411178f8458f889975799060e0bb866d2cf (diff)
Consolidate HTTP+HTTPS via CRTP
-rw-r--r--TODO1
-rw-r--r--http.cpp278
-rw-r--r--http.h11
-rw-r--r--https.cpp818
-rw-r--r--https.h27
-rw-r--r--response.h2
-rw-r--r--server.cpp335
-rw-r--r--server.h3
-rw-r--r--websocket.cpp302
-rw-r--r--websocket.h314
10 files changed, 975 insertions, 1116 deletions
diff --git a/TODO b/TODO
index fc4e4a8..ce4267a 100644
--- a/TODO
+++ b/TODO
@@ -5,7 +5,6 @@ test:
- Redirect
Big file bug
- dynamic plugin interface (file buffer, ...)
-CRTP: http+https
FastCGI from command line
stats.png
diff --git a/http.cpp b/http.cpp
index 469f1aa..89f78d7 100644
--- a/http.cpp
+++ b/http.cpp
@@ -30,281 +30,3 @@ namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace websocket = beast::websocket;
-namespace {
-
-//------------------------------------------------------------------------------
-
-// Report a failure
-void fail(beast::error_code ec, char const* what)
-{
- std::cerr << what << ": " << ec.message() << "\n";
-}
-
-// Handles an HTTP server connection
-class session : public std::enable_shared_from_this<session>
-{
- boost::asio::io_context& ioc_;
- beast::tcp_stream stream_;
- beast::flat_buffer buffer_;
- Server& m_server;
- std::optional<http::request_parser<http::string_body>> parser_;
- request_type req_;
- std::shared_ptr<response_type> res_; // std::shared_ptr<void> ?
-
- void handle_request(::Server& server, request_type&& req)
- {
- stream_.expires_after(std::chrono::seconds(300)); // timeout on write by server much longer than read timeout from client
- auto sp = std::make_shared<response_type>(response::generate_response(req, server));
-
- res_ = sp;
-
- // Write the response
- http::async_write(
- stream_,
- *sp,
- beast::bind_front_handler(
- &session::on_write,
- shared_from_this(),
- sp->need_eof()));
- }
-
- void handle_websocket()
- {
- beast::get_lowest_layer(stream_).expires_never();
- make_websocket_session(ioc_, std::move(stream_), response::get_websocket_address(req_, m_server), parser_->release());
- }
-
-public:
- // Take ownership of the stream
- session(
- boost::asio::io_context& ioc,
- tcp::socket&& socket,
- Server& server):
- ioc_(ioc),
- stream_(std::move(socket)),
- m_server(server)
- {
- }
-
- // Start the asynchronous operation
- void run()
- {
- // We need to be executing within a strand to perform async operations
- // on the I/O objects in this session.
- net::dispatch(stream_.get_executor(),
- beast::bind_front_handler(
- &session::do_read,
- shared_from_this()));
- }
-
- void do_read()
- {
- // Make the request empty before reading,
- // otherwise the operation behavior is undefined.
- req_ = {};
-
- // this is the way to reset the parser. it's necessary.
- // https://github.com/boostorg/beast/issues/927
- parser_.emplace();
- parser_->body_limit(1000000000); // 1GB limit
-
- // Set the timeout.
- stream_.expires_after(std::chrono::seconds(30));
-
- // Read a request
- http::async_read(stream_, buffer_, *parser_,
- beast::bind_front_handler(
- &session::on_read,
- shared_from_this()));
- }
-
- void
- on_read(
- beast::error_code ec,
- std::size_t bytes_transferred
- )
- {
- boost::ignore_unused(bytes_transferred);
-
- // This means they closed the connection
- if (ec == http::error::end_of_stream)
- return do_close();
-
- if (ec == http::error::partial_message)
- return; // ignore
-
- if (ec)
- return fail(ec, "read");
-
- req_ = parser_->get();
-
- if (websocket::is_upgrade(req_))
- {
- handle_websocket();
- return;
- }
-
- // Send the response
- handle_request(m_server, std::move(req_));
- }
-
- void
- on_write(
- bool close,
- beast::error_code ec,
- std::size_t bytes_transferred
- )
- {
- boost::ignore_unused(bytes_transferred);
-
- if(ec)
- return fail(ec, "write");
-
- if(close)
- {
- // This means we should close the connection, usually because
- // the response indicated the "Connection: close" semantic.
- return do_close();
- }
-
- // We're done with the response so delete it
- res_ = nullptr;
-
- // Read another request
- do_read();
- }
-
- void
- do_close()
- {
- // Send a TCP shutdown
- beast::error_code ec;
- stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
- // At this point the connection is closed gracefully
- }
-};
-
-//------------------------------------------------------------------------------
-
-// Accepts incoming connections and launches the sessions
-class listener : public std::enable_shared_from_this<listener>
-{
- net::io_context& ioc_;
- tcp::acceptor acceptor_;
- Server& m_server;
-
-public:
- listener(
- net::io_context& ioc,
- tcp::endpoint endpoint,
- Server& server)
- : ioc_(ioc)
- , acceptor_(net::make_strand(ioc))
- , m_server(server)
- {
- beast::error_code ec;
-
- // Open the acceptor
- acceptor_.open(endpoint.protocol(), ec);
- if(ec)
- {
- fail(ec, "open");
- return;
- }
-
- // Allow address reuse
- acceptor_.set_option(net::socket_base::reuse_address(true), ec);
- if(ec)
- {
- fail(ec, "set_option");
- return;
- }
-
- // Bind to the server address
- acceptor_.bind(endpoint, ec);
- if(ec)
- {
- fail(ec, "bind");
- return;
- }
-
- // Start listening for connections
- acceptor_.listen(
- net::socket_base::max_listen_connections, ec);
- if(ec)
- {
- fail(ec, "listen");
- return;
- }
- }
-
- // Start accepting incoming connections
- void
- run()
- {
- do_accept();
- }
-
-private:
- void
- do_accept()
- {
- // The new connection gets its own strand
- acceptor_.async_accept(
- net::make_strand(ioc_),
- beast::bind_front_handler(
- &listener::on_accept,
- shared_from_this()));
- }
-
- void
- on_accept(beast::error_code ec, tcp::socket socket)
- {
- if(ec)
- {
- fail(ec, "accept");
- }
- else
- {
- // Create the session and run it
- std::make_shared<session>(
- ioc_,
- std::move(socket),
- m_server)->run();
- }
-
- // Accept another connection
- do_accept();
- }
-};
-
-} // anonymous namespace
-
-//------------------------------------------------------------------------------
-
-namespace HTTP {
-
- Server::Server(Config& config, boost::asio::io_context& ioc, const Socket& socket, plugins_container_type& plugins, Statistics& statistics)
- : ::Server(config, ioc, socket, plugins, statistics)
- {
- }
-
- Server::~Server()
- {
- }
-
- int Server::start()
- {
- auto const address = net::ip::make_address(m_socket.address);
- auto const port = static_cast<unsigned short>(std::atoi(m_socket.port.data()));
-
- // Create and launch a listening port
- std::make_shared<listener>(
- m_ioc,
- tcp::endpoint{address, port},
- *this)->run();
-
- return EXIT_SUCCESS;
- }
-
-} // namespace HTTP
diff --git a/http.h b/http.h
index 7c3bb9a..7da60b2 100644
--- a/http.h
+++ b/http.h
@@ -6,14 +6,3 @@
#include "config.h"
#include "server.h"
-namespace HTTP {
-
-class Server: public ::Server
-{
-public:
- Server(Config& config, boost::asio::io_context& ioc, const Socket& socket, plugins_container_type& plugins, Statistics& statistics);
- virtual ~Server();
- int start() override;
-};
-
-} // namespace HTTP
diff --git a/https.cpp b/https.cpp
index 6b32195..20fab61 100644
--- a/https.cpp
+++ b/https.cpp
@@ -6,8 +6,6 @@
#include "response.h"
#include "websocket.h"
-#include "libreichwein/file.h"
-
#include <openssl/ssl.h>
#include <openssl/crypto.h>
@@ -46,526 +44,420 @@ using namespace Reichwein;
namespace {
-//------------------------------------------------------------------------------
-
// Handles an HTTP server connection
-class session : public std::enable_shared_from_this<session>
+template<class Derived>
+class session
{
+private:
+ Derived& derived()
+ {
+ return static_cast<Derived&>(*this);
+ }
+
boost::asio::io_context& ioc_;
- beast::ssl_stream<beast::tcp_stream> stream_;
beast::flat_buffer buffer_;
- Server& m_server;
+ Server& server_;
std::optional<http::request_parser<http::string_body>> parser_; // need to reset parser every time, no other mechanism currently
request_type req_;
- std::shared_ptr<response_type> res_; // std::shared_ptr<void>
+ std::shared_ptr<response_type> res_;
void handle_request()
{
- beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(300)); // timeout on write by server much longer than read timeout from client
- auto sp = std::make_shared<response_type>(response::generate_response(req_, m_server));
+ beast::get_lowest_layer(derived().stream()).expires_after(std::chrono::seconds(300)); // timeout on write by server much longer than read timeout from client
+ auto sp = std::make_shared<response_type>(response::generate_response(req_, server_));
res_ = sp;
// Write the response
http::async_write(
- stream_,
+ derived().stream(),
*sp,
beast::bind_front_handler(
&session::on_write,
- shared_from_this(),
+ derived().shared_from_this(),
sp->need_eof()));
}
void handle_websocket()
{
- beast::get_lowest_layer(stream_).expires_never();
- make_websocket_session(ioc_, std::move(stream_), response::get_websocket_address(req_, m_server), parser_->release());
+ beast::get_lowest_layer(derived().stream()).expires_never();
+ make_websocket_session(ioc_, std::move(derived().stream()), response::get_websocket_address(req_, server_), parser_->release());
}
public:
- // Take ownership of the socket
- explicit
- session(
- boost::asio::io_context& ioc,
- tcp::socket&& socket,
- ssl::context& ctx,
- Server& server):
- ioc_(ioc),
- stream_(std::move(socket), ctx),
- m_server(server)
- {
- }
-
- // Start the asynchronous operation
- void
- run()
- {
- // We need to be executing within a strand to perform async operations
- // on the I/O objects in this session.
- net::dispatch(
- stream_.get_executor(),
- beast::bind_front_handler(
- &session::on_run,
- shared_from_this()));
- }
-
- void
- on_run()
- {
- // Set the timeout.
- beast::get_lowest_layer(stream_).expires_after(
- std::chrono::seconds(30));
-
- // Perform the SSL handshake
- stream_.async_handshake(
- ssl::stream_base::server,
- beast::bind_front_handler(
- &session::on_handshake,
- shared_from_this()));
- }
-
- void
- on_handshake(
- beast::error_code ec
- )
- {
- if(ec)
- return fail(ec, "https handshake");
-
- do_read();
- }
-
- void
- do_read()
- {
- // Make the request empty before reading,
- // otherwise the operation behavior is undefined.
- req_ = {};
-
- // this is the way to reset the parser. it's necessary.
- // https://github.com/boostorg/beast/issues/927
- parser_.emplace();
- parser_->body_limit(1000000000); // 1GB limit
-
- // Set the timeout.
- beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
-
- // Read a request
- http::async_read(stream_, buffer_, *parser_,
- beast::bind_front_handler(
- &session::on_read,
- shared_from_this()));
- }
-
- void
- on_read(
- beast::error_code ec,
- std::size_t bytes_transferred)
- {
- boost::ignore_unused(bytes_transferred);
-
- // This means they closed the connection
- if (ec == http::error::end_of_stream)
- return do_close();
-
- if (ec == http::error::partial_message)
- return; // ignore
-
- if (ec)
- return fail(ec, "https read");
-
- req_ = parser_->get();
-
- if (websocket::is_upgrade(req_))
- {
- handle_websocket();
- return;
- }
-
- // Send the response
- handle_request();
- }
-
- void
- on_write(
- bool close,
- beast::error_code ec,
- std::size_t bytes_transferred
- )
- {
- boost::ignore_unused(bytes_transferred);
-
- if(ec)
- return fail(ec, "https write");
-
- if(close)
- {
- // This means we should close the connection, usually because
- // the response indicated the "Connection: close" semantic.
- return do_close();
- }
-
- // We're done with the response so delete it
- res_ = nullptr;
-
- // Read another request
- do_read();
- }
-
- void
- do_close()
- {
- // Set the timeout.
- beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
-
- // Perform the SSL shutdown
- stream_.async_shutdown(
- beast::bind_front_handler(
- &session::on_shutdown,
- shared_from_this()));
- }
-
- void
- on_shutdown(beast::error_code ec)
- {
- if(ec)
- return fail(ec, "https shutdown");
-
- // At this point the connection is closed gracefully
- }
-};
+ explicit
+ session(
+ boost::asio::io_context& ioc,
+ Server& server):
+ ioc_(ioc),
+ server_(server)
+ {
+ }
-//------------------------------------------------------------------------------
+ // Start the asynchronous operation
+ void
+ run()
+ {
+ // We need to be executing within a strand to perform async operations
+ // on the I/O objects in this session.
+ net::dispatch(
+ derived().stream().get_executor(),
+ beast::bind_front_handler(
+ &Derived::on_run,
+ derived().shared_from_this()));
+ }
-// Accepts incoming connections and launches the sessions
-class listener : public std::enable_shared_from_this<listener>
-{
- net::io_context& ioc_;
- ssl::context& ctx_;
- tcp::acceptor acceptor_;
- ::Server& m_server;
+ void
+ do_read()
+ {
+ // Make the request empty before reading,
+ // otherwise the operation behavior is undefined.
+ req_ = {};
+
+ // this is the way to reset the parser. it's necessary.
+ // https://github.com/boostorg/beast/issues/927
+ parser_.emplace();
+ parser_->body_limit(1000000000); // 1GB limit
-public:
- listener(
- net::io_context& ioc,
- ssl::context& ctx,
- tcp::endpoint endpoint,
- Server& server) :
- ioc_(ioc),
- ctx_(ctx),
- acceptor_(ioc),
- m_server(server)
- {
- beast::error_code ec;
-
- // Open the acceptor
- acceptor_.open(endpoint.protocol(), ec);
- if(ec)
- {
- fail(ec, "https open");
- return;
- }
-
- // Allow address reuse
- acceptor_.set_option(net::socket_base::reuse_address(true), ec);
- if(ec)
- {
- fail(ec, "https set_option");
- return;
- }
-
- // Bind to the server address
- acceptor_.bind(endpoint, ec);
- if(ec)
- {
- fail(ec, "https bind");
- return;
- }
-
- // Start listening for connections
- acceptor_.listen(
- net::socket_base::max_listen_connections, ec);
- if(ec)
- {
- fail(ec, "https listen");
- return;
- }
- }
-
- // Start accepting incoming connections
- void
- run()
- {
- do_accept();
- }
+ // Set the timeout.
+ beast::get_lowest_layer(derived().stream()).expires_after(std::chrono::seconds(30));
+
+ // Read a request
+ http::async_read(derived().stream(), buffer_, *parser_,
+ beast::bind_front_handler(
+ &session::on_read,
+ derived().shared_from_this()));
+ }
+
+ void
+ on_read(
+ beast::error_code ec,
+ std::size_t bytes_transferred)
+ {
+ boost::ignore_unused(bytes_transferred);
+
+ // This means they closed the connection
+ if (ec == http::error::end_of_stream)
+ return derived().do_close();
+
+ if (ec == http::error::partial_message)
+ return; // ignore
+
+ if (ec)
+ return fail(ec, "http read");
+
+ req_ = parser_->get();
+
+ if (websocket::is_upgrade(req_))
+ {
+ handle_websocket();
+ return;
+ }
+
+ // Send the response
+ handle_request();
+ }
+
+ void
+ on_write(
+ bool close,
+ beast::error_code ec,
+ std::size_t bytes_transferred
+ )
+ {
+ boost::ignore_unused(bytes_transferred);
+
+ if (ec)
+ return fail(ec, "http write");
+
+ if (close)
+ {
+ // This means we should close the connection, usually because
+ // the response indicated the "Connection: close" semantic.
+ return derived().do_close();
+ }
+
+ // We're done with the response so delete it
+ res_ = nullptr;
+
+ // Read another request
+ do_read();
+ }
-private:
- void
- do_accept()
- {
- // The new connection gets its own strand
- acceptor_.async_accept(
- net::make_strand(ioc_),
- beast::bind_front_handler(
- &listener::on_accept,
- shared_from_this()));
- }
-
- void
- on_accept(beast::error_code ec, tcp::socket socket)
- {
- if(ec)
- {
- fail(ec, "https accept");
- }
- else
- {
- // Create the session and run it
- std::make_shared<session>(
- ioc_,
- std::move(socket),
- ctx_,
- m_server)->run();
- }
-
- // Accept another connection
- do_accept();
- }
};
-/* Load a signed certificate into the ssl context, and configure
- the context for use with a server.
-*/
-void load_server_certificate(boost::asio::ssl::context& ctx, const fs::path& cert_path, const fs::path& key_path)
+class plain_session:
+ public session<plain_session>,
+ public std::enable_shared_from_this<plain_session>
{
- /*
- The certificate was generated from CMD.EXE on Windows 10 using:
-
- winpty openssl dhparam -out dh.pem 2048
- winpty openssl req -newkey rsa:4096 -sha256 -nodes -keyout key.pem -x509 -days 10000 -out cert.pem -subj "//C=DE\ST=BY\L=Munich\O=Reichwein\CN=reichwein.it"
- */
-
- std::string const dh =
- "-----BEGIN DH PARAMETERS-----\n"
- "MIIBCAKCAQEArzQc5mpm0Fs8yahDeySj31JZlwEphUdZ9StM2D8+Fo7TMduGtSi+\n"
- "/HRWVwHcTFAgrxVdm+dl474mOUqqaz4MpzIb6+6OVfWHbQJmXPepZKyu4LgUPvY/\n"
- "4q3/iDMjIS0fLOu/bLuObwU5ccZmDgfhmz1GanRlTQOiYRty3FiOATWZBRh6uv4u\n"
- "tff4A9Bm3V9tLx9S6djq31w31Gl7OQhryodW28kc16t9TvO1BzcV3HjRPwpe701X\n"
- "oEEZdnZWANkkpR/m/pfgdmGPU66S2sXMHgsliViQWpDCYeehrvFRHEdR9NV+XJfC\n"
- "QMUk26jPTIVTLfXmmwU0u8vUkpR7LQKkwwIBAg==\n"
- "-----END DH PARAMETERS-----\n";
-
- ctx.set_options(
- boost::asio::ssl::context::default_workarounds |
- boost::asio::ssl::context::no_sslv2 |
- boost::asio::ssl::context::single_dh_use);
-
- std::string cert;
- if (cert_path.empty()) {
- // use dummy self signed certificate. Will be replaced by real
- // certificate if configured upon respective session
- cert =
- "-----BEGIN CERTIFICATE-----\n"
- "MIIDnTCCAoWgAwIBAgIULkYtO+2Ddeg+qLZ+aDQpmA5b4L0wDQYJKoZIhvcNAQEL\n"
- "BQAwXjELMAkGA1UEBhMCREUxEDAOBgNVBAgMB0JhdmFyaWExDzANBgNVBAcMBk11\n"
- "bmljaDEVMBMGA1UECgwMUmVpY2h3ZWluIElUMRUwEwYDVQQDDAxyZWljaHdlaW4u\n"
- "aXQwHhcNMjAwNDA1MDgwNzIyWhcNNDcwODIyMDgwNzIyWjBeMQswCQYDVQQGEwJE\n"
- "RTEQMA4GA1UECAwHQmF2YXJpYTEPMA0GA1UEBwwGTXVuaWNoMRUwEwYDVQQKDAxS\n"
- "ZWljaHdlaW4gSVQxFTATBgNVBAMMDHJlaWNod2Vpbi5pdDCCASIwDQYJKoZIhvcN\n"
- "AQEBBQADggEPADCCAQoCggEBALJNb0WLbz+xP+YITMMk+eeK/SIOCRFs/9aZIAyK\n"
- "ParGauxa+8d25mlfJTAo6/G0h3sA240JHyNpOzVOogPU+v4dRWyGO0w5vHVD0caB\n"
- "rDb1eEfmLtqfKLLUL9iPDReUh6WAE7qoNDtfoT551uSMIae1cpPUduVTnSkEgw8k\n"
- "NjJSHYT800jSB2R+e7tJG3ErXDM63R3B8RbitZPoWACjpBxDT+Qrj0fBFS4AWw6b\n"
- "z09uitv0RrgI6CW7xRh3UAdRwEBGHiU6HTIthX6LNgez1UL0sfu1iZ22wNmYZP/S\n"
- "sL3b20WtSH9LN2PRJ4q3AGt6RMbmSGr65ljha9xkTFna0Y8CAwEAAaNTMFEwHQYD\n"
- "VR0OBBYEFKd5/MGFZUAUV502vJ/Kcswax8WVMB8GA1UdIwQYMBaAFKd5/MGFZUAU\n"
- "V502vJ/Kcswax8WVMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB\n"
- "AIBS4AfM7wiunQ2UZQQ5A0Un99+BLax9e+h11h/jGeJ+/9maY/E9MK6UG9LXoOv2\n"
- "z32Q7Ta2xKeRu6GC/qupwYJ0Xt3LENOfogsaNCAgxKlAN48LGlRyCTvzWsEMh28j\n"
- "RaelWonh2qQoiryKLVnRwrg8g1Bu4v+V437cIBmeZPxf0spEL9EVqlN+iS8plmel\n"
- "7/F4ULdybKGq39tgicuS7JhnY21ZzOFoq0bWnKBbAeTndmuROdb3pEppxW6pwu0q\n"
- "TFdMrSJE38kiQh2O9IchPQbTZ+Rdj0HE9NxStlrNr5bu6rjikRm50/G3JoXpzYdp\n"
- "AN4ZI2QZ6R6Y+TzDixKecNk=\n"
- "-----END CERTIFICATE-----\n"
- ;
- } else {
- cert = File::getFile(cert_path);
- }
-
- ctx.use_certificate_chain(
- boost::asio::buffer(cert.data(), cert.size()));
-
- std::string key;
- if (key_path == "") {
- // use dummy self signed key. Will be replaced by real
- // certificate if configured upon respective session
- key =
- "-----BEGIN PRIVATE KEY-----\n"
- "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyTW9Fi28/sT/m\n"
- "CEzDJPnniv0iDgkRbP/WmSAMij2qxmrsWvvHduZpXyUwKOvxtId7ANuNCR8jaTs1\n"
- "TqID1Pr+HUVshjtMObx1Q9HGgaw29XhH5i7anyiy1C/Yjw0XlIelgBO6qDQ7X6E+\n"
- "edbkjCGntXKT1HblU50pBIMPJDYyUh2E/NNI0gdkfnu7SRtxK1wzOt0dwfEW4rWT\n"
- "6FgAo6QcQ0/kK49HwRUuAFsOm89Pborb9Ea4COglu8UYd1AHUcBARh4lOh0yLYV+\n"
- "izYHs9VC9LH7tYmdtsDZmGT/0rC929tFrUh/Szdj0SeKtwBrekTG5khq+uZY4Wvc\n"
- "ZExZ2tGPAgMBAAECggEBAK9bJKIa3dCgPB257/TEOtsTgJyrfROcRYkCk9iBZOC9\n"
- "v46wdIrZTwY2wtY4iMPwLoY0c7ijTfJ/nfFxYjmujyK4Gvz+jvcKmWQizP8TrRFo\n"
- "HWFo6o+slFQ8BspO9itIspd7/OtIXgY+qNBO959Sig7sjsEA5eXoc9pRS6vqizq0\n"
- "j4G/UO5Amr/l3ciEJiqMJgZsIVLDKaGlqFTymydSqkB8UHQYWK1kunQxhK4Ldycu\n"
- "hTooQE7tXM0zvoFVV6v1fldV5OFsZk2kPMNtvMO6ZEpOM4rNMlg+vJy8kB1fb3Gs\n"
- "iFE/DCUpZsMSserQMU9/hfrYlndgsFD5Sr1EVGEebhECgYEA1gc9qx+ugdhYTY5j\n"
- "tJDXjOsnw8KY/l/1y+mQ8XNJ9MVdBGy1WB+uWB4teiyJchV49gn2XlKUK2rcCBvZ\n"
- "vC5CwPmFi2t70JezQgnXtDlbR0bARPlRd741i4rBpD7hEiZNCTOd2HFBpUg/CGWN\n"
- "E4n1ksazBm6jvv3Jo6WAa07Z390CgYEA1USrFqmc/fKGQpTCdH0qYZv3hQtrb1TQ\n"
- "9YnrbhtaC0haPpintZKjvhU3tCd1fPuIDXtMAgaaKSyoGiE2aMvLxt1/eV08BkMi\n"
- "kGIss9poYNi5+6ZD9QAHmHJhzZtVGj8U5L8379XmwxAByiBRVVE8CW1X/e6+iJpz\n"
- "+CLgN+zEVlsCgYEAsuOAdtxXJm4meERwL8b0cvNF3Eh1Sf/42MPTAwzCntSrh3w5\n"
- "InvwY/RtPHWnN/ScksEG7BWHhLafTCPDHJdp8hNcvIhNB68UBDln0loyYePP5pag\n"
- "sj4IUSbb7SUlR989elhrMTKQlM5K6QDAJrmjyVdM4S5urL9A3wgAyzAvyP0CgYAO\n"
- "paGuc8WxdzebWQYl4/bGL2UHgSpGwid7xZYiwMQlZDm2dNuHz+NpCaICwHcEN243\n"
- "ptEojnWGAGgnK0LGXcDIDqxTlICr2W6FRgjV7Vkf1aKoUtn1+KOM58YpzdJBdDWm\n"
- "JC/eS+2GVhIZZLDRUDv0VcsmSIBTd3AhiZumm588YwKBgBZfNqfmHAwIP2pM1wml\n"
- "Ck3vaLLvonghj3iQW9CFJ/SqLOnfT4KJkFObR6oGbxY0RtXsCrmSqidIKgDd0Kkq\n"
- "L6QbHp2j3+16GBdmLNUJlfjBTNPJp69IDKztjeCX7/8JZs79p/LAv+I9Sh4lVw4O\n"
- "IrDprlB0yzP5zigcsAZeViYJ\n"
- "-----END PRIVATE KEY-----\n"
- ;
- } else {
- key = File::getFile(key_path);
- }
- ctx.use_private_key(
- boost::asio::buffer(key.data(), key.size()),
- boost::asio::ssl::context::file_format::pem);
-
- ctx.use_tmp_dh(
- boost::asio::buffer(dh.data(), dh.size()));
-}
+ beast::tcp_stream stream_;
-int ServerNameError(SSL *s, HTTPS::Server::ctx_type& ctx_map)
-{
- std::shared_ptr<ssl::context> ctx{ctx_map.at("")};
- SSL_set_SSL_CTX(s, ctx->native_handle());
- return SSL_CLIENT_HELLO_SUCCESS; // OK for now
-}
+public:
+ explicit plain_session(
+ boost::asio::io_context& ioc,
+ tcp::socket&& socket,
+ Server& server):
+ session(ioc, server),
+ stream_(std::move(socket))
+ {
+ }
+
+ void on_run()
+ {
+ // We need to be executing within a strand to perform async operations
+ // on the I/O objects in this session. Skip ssl handshake for plain http.
+ net::dispatch(stream_.get_executor(),
+ beast::bind_front_handler(
+ &session::do_read,
+ shared_from_this()));
+ }
-std::string unbracketed(const std::string& s)
-{
- if (s.size() >= 2 && s.front() == '[' && s.back() == ']') {
- return s.substr(1, s.size() - 2);
- } else {
- return s;
+ void
+ do_close()
+ {
+ // Send a TCP shutdown
+ beast::error_code ec;
+ stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
+ // At this point the connection is closed gracefully
}
-}
-int servername_callback(SSL *s, int *al, void *arg)
-{
- HTTPS::Server::ctx_type& ctx_map = *(HTTPS::Server::ctx_type*)arg;
-
- if (0) { // not really necessary
- int* numbers;
- size_t numbers_size;
- if (SSL_client_hello_get1_extensions_present(s, &numbers, &numbers_size) != 1) {
- std::cout << "Error on SSL_client_hello_get1_extensions_present" << std::endl;
- return ServerNameError(s, ctx_map);
- }
- bool server_name_available {false};
- for (size_t i = 0; i < numbers_size; i++)
- if (numbers[i] == 0)
- server_name_available = true;
+ beast::tcp_stream& stream()
+ {
+ return stream_;
+ }
- OPENSSL_free(numbers);
+}; // class
- if (!server_name_available) {
- std::cout << "Error: No server_name available at SSL_client_hello_get1_extensions_present" << std::endl;
- return ServerNameError(s, ctx_map);
- }
+class ssl_session:
+ public session<ssl_session>,
+ public std::enable_shared_from_this<ssl_session>
+{
+ beast::ssl_stream<beast::tcp_stream> stream_;
+public:
+ explicit ssl_session(
+ boost::asio::io_context& ioc,
+ tcp::socket&& socket,
+ ssl::context& ctx,
+ Server& server):
+ session(ioc, server),
+ stream_(std::move(socket), ctx)
+ {
}
+
+ void on_run()
+ {
+ // Set the timeout
+ beast::get_lowest_layer(stream_).expires_after(
+ std::chrono::seconds(30));
- const unsigned char* data;
- size_t data_size;
- // 0 is server_name
- if (SSL_client_hello_get0_ext(s, 0, &data, &data_size) != 1) {
- std::cout << "Warning: Error on SSL_client_hello_get0_ext: servername not available. Using dummy ctx." << std::endl;
- return ServerNameError(s, ctx_map);
+ // Perform the SSL handshake
+ stream_.async_handshake(
+ ssl::stream_base::server,
+ beast::bind_front_handler(
+ &ssl_session::on_handshake,
+ shared_from_this()));
}
- // SNI Server Name, See https://tools.ietf.org/html/rfc6066 (TODO: why are there 5 bytes header?)
- std::string server_name {std::string((const char*)data, (size_t)data_size)};
- if (server_name.size() >= 5 && server_name[0] == '\0')
- server_name = server_name.substr(5);
+ void
+ on_handshake(beast::error_code ec)
+ {
+ if (ec)
+ return fail(ec, "https handshake");
- server_name = unbracketed(server_name);
+ do_read();
+ }
+
+ void
+ do_close()
+ {
+ // Set the timeout.
+ beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
- auto it {ctx_map.find(server_name)};
- std::shared_ptr<ssl::context> ctx{};
- if (it != ctx_map.end()) {
- ctx = it->second;
- } else {
- std::cout << "Warning: server_name " << server_name << " (" << server_name.size() << ") not found in list of prepared contexts. Using dummy ctx." << std::endl;
- return ServerNameError(s, ctx_map);
+ // Perform the SSL shutdown
+ stream_.async_shutdown(
+ beast::bind_front_handler(
+ &ssl_session::on_shutdown,
+ shared_from_this()));
}
- SSL_set_SSL_CTX(s, ctx->native_handle());
-
- return SSL_CLIENT_HELLO_SUCCESS;
-}
+ void
+ on_shutdown(beast::error_code ec)
+ {
+ if (ec)
+ return fail(ec, "https shutdown");
-} // anonymous namespace
-//------------------------------------------------------------------------------
+ // At this point the connection is closed gracefully
+ }
-namespace HTTPS {
+ beast::ssl_stream<beast::tcp_stream>& stream()
+ {
+ return stream_;
+ }
-Server::Server(Config& config, boost::asio::io_context& ioc, const Socket& socket, plugins_container_type& plugins, Statistics& statistics)
- : ::Server(config, ioc, socket, plugins, statistics)
-{
- load_certificates(); // load initially
-}
+}; // class
-Server::~Server()
+// Accepts incoming connections and launches the sessions
+template<class Derived>
+class listener
{
-}
+private:
+ Derived& derived()
+ {
+ return static_cast<Derived&>(*this);
+ }
+
+protected:
+ net::io_context& ioc_;
+ tcp::acceptor acceptor_;
+ ::Server& server_;
+
+public:
+ explicit listener(
+ net::io_context& ioc,
+ tcp::endpoint endpoint,
+ Server& server):
+ ioc_(ioc),
+ acceptor_(ioc),
+ server_(server)
+ {
+ beast::error_code ec;
+
+ // Open the acceptor
+ acceptor_.open(endpoint.protocol(), ec);
+ if (ec)
+ {
+ fail(ec, "http listener open");
+ return;
+ }
-void Server::load_certificates()
+ // Allow address reuse
+ acceptor_.set_option(net::socket_base::reuse_address(true), ec);
+ if (ec)
+ {
+ fail(ec, "http listener set_option");
+ return;
+ }
+
+ // Bind to the server address
+ acceptor_.bind(endpoint, ec);
+ if (ec)
+ {
+ fail(ec, "http listener bind");
+ return;
+ }
+
+ // Start listening for connections
+ acceptor_.listen(net::socket_base::max_listen_connections, ec);
+ if (ec)
+ {
+ fail(ec, "http listener listen");
+ return;
+ }
+ }
+
+ // Start accepting incoming connections
+ void
+ run()
+ {
+ do_accept();
+ }
+
+protected:
+ void
+ do_accept()
+ {
+ // The new connection gets its own strand
+ acceptor_.async_accept(
+ net::make_strand(ioc_),
+ beast::bind_front_handler(
+ &Derived::on_accept,
+ derived().shared_from_this()));
+ }
+}; // class
+
+class plain_listener:
+ public listener<plain_listener>,
+ public std::enable_shared_from_this<plain_listener>
{
- // initial dummy, before we can add specific ctx w/ certificate
- std::shared_ptr<ssl::context> ctx_dummy{std::make_shared<ssl::context>(tls_method)};
- load_server_certificate(*ctx_dummy, "", "");
- SSL_CTX_set_client_hello_cb(ctx_dummy->native_handle(), servername_callback, &m_ctx);
- m_ctx.emplace("", ctx_dummy);
-
- // import the real certificates
- for (const auto& serve_site: m_socket.serve_sites) {
- for (const auto& site: m_config.Sites()) {
- if (site.first == serve_site) {
- std::shared_ptr<ssl::context> ctx {std::make_shared<ssl::context>(tls_method)};
-
- std::cout << "Creating SSL context/cert for site " << serve_site << " on port " << m_socket.port << std::endl;
-
- load_server_certificate(*ctx, site.second.cert_path, site.second.key_path);
- SSL_CTX_set_client_hello_cb(ctx->native_handle(), servername_callback, &m_ctx);
-
- for (const auto& host: site.second.hosts) {
- std::cout << " Adding Host " << host << std::endl;
- m_ctx.emplace(unbracketed(host), ctx);
- }
- }
+public:
+ explicit plain_listener(
+ net::io_context& ioc,
+ tcp::endpoint endpoint,
+ Server& server):
+ listener(ioc, endpoint, server)
+ {
+ }
+
+ void
+ on_accept(beast::error_code ec, tcp::socket socket)
+ {
+ if (ec) {
+ fail(ec, "plain listener accept");
+ } else {
+ // Create the session and run it
+ std::make_shared<plain_session>(
+ ioc_,
+ std::move(socket),
+ server_)->run();
}
+
+ // Accept another connection
+ do_accept();
}
-}
+}; // class
-int Server::start()
+class ssl_listener:
+ public listener<ssl_listener>,
+ public std::enable_shared_from_this<ssl_listener>
{
- auto const address = net::ip::make_address(m_socket.address);
- auto const port = static_cast<unsigned short>(std::atoi(m_socket.port.data()));
+ ssl::context& ctx_;
+
+public:
+ explicit ssl_listener(
+ net::io_context& ioc,
+ ssl::context& ctx,
+ tcp::endpoint endpoint,
+ Server& server):
+ listener(ioc, endpoint, server),
+ ctx_(ctx)
+ {
+ }
- // Create and launch a listening port
- std::make_shared<listener>(
- m_ioc,
- *m_ctx[""],
- tcp::endpoint{address, port},
- *this)->run();
+ void
+ on_accept(beast::error_code ec, tcp::socket socket)
+ {
+ if (ec) {
+ fail(ec, "ssl listener accept");
+ } else {
+ // Create the session and run it
+ std::make_shared<ssl_session>(
+ ioc_,
+ std::move(socket),
+ ctx_,
+ server_)->run();
+ }
+
+ // Accept another connection
+ do_accept();
+ }
+}; // class
- return EXIT_SUCCESS;
+} // namespace
+
+void make_listener(net::io_context& ioc, net::ip::address address, unsigned short port, Server& server)
+{
+ std::make_shared<plain_listener>(
+ ioc,
+ tcp::endpoint{address, port},
+ server)->run();
}
-} // namespace HTTPS
+void make_listener(net::io_context& ioc, ssl::context& ctx, net::ip::address address, unsigned short port, Server& server)
+{
+ std::make_shared<ssl_listener>(
+ ioc,
+ ctx,
+ tcp::endpoint{address, port},
+ server)->run();
+}
diff --git a/https.h b/https.h
index 2a1caa8..226490e 100644
--- a/https.h
+++ b/https.h
@@ -15,27 +15,8 @@
#include "config.h"
#include "server.h"
-namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
+// plain / http
+void make_listener(boost::asio::io_context& ioc, boost::asio::ip::address address, unsigned short port, Server& server);
-namespace HTTPS {
-
-static const ssl::context_base::method tls_method {ssl::context::tlsv13};
-
-class Server: public ::Server
-{
-public:
- typedef std::unordered_map<std::string, std::shared_ptr<ssl::context>> ctx_type;
-
-private:
- ctx_type m_ctx;
-
-public:
- Server(Config& config, boost::asio::io_context& ioc, const Socket& socket, plugins_container_type& plugins, Statistics& statistics);
- virtual ~Server();
-
- void load_certificates();
-
- int start() override;
-};
-
-}
+// ssl / https
+void make_listener(boost::asio::io_context& ioc, boost::asio::ssl::context& ctx, boost::asio::ip::address address, unsigned short port, Server& server);
diff --git a/response.h b/response.h
index 46de9d9..089c619 100644
--- a/response.h
+++ b/response.h
@@ -17,7 +17,7 @@ namespace response {
response_type generate_response(request_type& req, Server& server);
-// Get host:port e.g. reichwein.it:6543
+// Get host:port/path e.g. reichwein.it:6543/path1
std::string get_websocket_address(request_type& req, Server& server);
} // namespace
diff --git a/server.cpp b/server.cpp
index d22d559..3f6183b 100644
--- a/server.cpp
+++ b/server.cpp
@@ -1,5 +1,6 @@
#include <boost/beast/version.hpp>
+#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
@@ -10,11 +11,14 @@
#include <boost/config.hpp>
#include <exception>
+#include <filesystem>
#include <functional>
#include <iostream>
#include <thread>
#include <vector>
+#include <libreichwein/file.h>
+
#include "server.h"
#include "http.h"
@@ -27,11 +31,23 @@ namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
+namespace fs = std::filesystem;
+using namespace Reichwein;
const std::string Server::VersionString{ "Reichwein.IT Webserver "s + std::string{VERSION} };
namespace {
const int32_t stats_timer_seconds { 24 * 60 * 60 }; // save stats once a day
+
+ std::string unbracketed(const std::string& s)
+ {
+ if (s.size() >= 2 && s.front() == '[' && s.back() == ']') {
+ return s.substr(1, s.size() - 2);
+ } else {
+ return s;
+ }
+ }
+
} // anonymous namespace
Server::Server(Config& config, boost::asio::io_context& ioc, const Socket& socket, plugins_container_type& plugins, Statistics& statistics)
@@ -47,6 +63,298 @@ Server::~Server()
{
}
+Config& Server::GetConfig()
+{
+ return m_config;
+}
+
+const Socket& Server::GetSocket()
+{
+ return m_socket;
+}
+
+plugin_type Server::GetPlugin(const std::string& name)
+{
+ try {
+ return m_plugins.at(name);
+ } catch (const std::out_of_range& ex) {
+ std::cout << "Out of range at Server::GetPlugin(): " << name << std::endl;
+ std::rethrow_exception(std::current_exception());
+ } catch (...) {
+ std::cout << "Unknown exception at Server::GetPlugin(): " << name << std::endl;
+ std::rethrow_exception(std::current_exception());
+ }
+}
+
+Statistics& Server::GetStatistics()
+{
+ return m_statistics;
+}
+
+namespace HTTP {
+
+class Server: public ::Server
+{
+public:
+ Server(Config& config, boost::asio::io_context& ioc, const Socket& socket, plugins_container_type& plugins, Statistics& statistics)
+ : ::Server(config, ioc, socket, plugins, statistics)
+ {
+ }
+
+ ~Server() override
+ {
+ }
+
+ int start() override
+ {
+ auto const address = net::ip::make_address(m_socket.address);
+ auto const port = static_cast<unsigned short>(std::atoi(m_socket.port.data()));
+
+ // Create and launch a listening port
+ make_listener(m_ioc, address, port, *this);
+
+ return EXIT_SUCCESS;
+ }
+};
+
+} // namespace HTTP
+
+namespace {
+
+/* Load a signed certificate into the ssl context, and configure
+ the context for use with a server.
+*/
+void load_server_certificate(boost::asio::ssl::context& ctx, const fs::path& cert_path, const fs::path& key_path)
+{
+ /*
+ The certificate was generated from CMD.EXE on Windows 10 using:
+
+ winpty openssl dhparam -out dh.pem 2048
+ winpty openssl req -newkey rsa:4096 -sha256 -nodes -keyout key.pem -x509 -days 10000 -out cert.pem -subj "//C=DE\ST=BY\L=Munich\O=Reichwein\CN=reichwein.it"
+ */
+
+ std::string const dh =
+ "-----BEGIN DH PARAMETERS-----\n"
+ "MIIBCAKCAQEArzQc5mpm0Fs8yahDeySj31JZlwEphUdZ9StM2D8+Fo7TMduGtSi+\n"
+ "/HRWVwHcTFAgrxVdm+dl474mOUqqaz4MpzIb6+6OVfWHbQJmXPepZKyu4LgUPvY/\n"
+ "4q3/iDMjIS0fLOu/bLuObwU5ccZmDgfhmz1GanRlTQOiYRty3FiOATWZBRh6uv4u\n"
+ "tff4A9Bm3V9tLx9S6djq31w31Gl7OQhryodW28kc16t9TvO1BzcV3HjRPwpe701X\n"
+ "oEEZdnZWANkkpR/m/pfgdmGPU66S2sXMHgsliViQWpDCYeehrvFRHEdR9NV+XJfC\n"
+ "QMUk26jPTIVTLfXmmwU0u8vUkpR7LQKkwwIBAg==\n"
+ "-----END DH PARAMETERS-----\n";
+
+ ctx.set_options(
+ boost::asio::ssl::context::default_workarounds |
+ boost::asio::ssl::context::no_sslv2 |
+ boost::asio::ssl::context::single_dh_use);
+
+ std::string cert;
+ if (cert_path.empty()) {
+ // use dummy self signed certificate. Will be replaced by real
+ // certificate if configured upon respective session
+ cert =
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIDnTCCAoWgAwIBAgIULkYtO+2Ddeg+qLZ+aDQpmA5b4L0wDQYJKoZIhvcNAQEL\n"
+ "BQAwXjELMAkGA1UEBhMCREUxEDAOBgNVBAgMB0JhdmFyaWExDzANBgNVBAcMBk11\n"
+ "bmljaDEVMBMGA1UECgwMUmVpY2h3ZWluIElUMRUwEwYDVQQDDAxyZWljaHdlaW4u\n"
+ "aXQwHhcNMjAwNDA1MDgwNzIyWhcNNDcwODIyMDgwNzIyWjBeMQswCQYDVQQGEwJE\n"
+ "RTEQMA4GA1UECAwHQmF2YXJpYTEPMA0GA1UEBwwGTXVuaWNoMRUwEwYDVQQKDAxS\n"
+ "ZWljaHdlaW4gSVQxFTATBgNVBAMMDHJlaWNod2Vpbi5pdDCCASIwDQYJKoZIhvcN\n"
+ "AQEBBQADggEPADCCAQoCggEBALJNb0WLbz+xP+YITMMk+eeK/SIOCRFs/9aZIAyK\n"
+ "ParGauxa+8d25mlfJTAo6/G0h3sA240JHyNpOzVOogPU+v4dRWyGO0w5vHVD0caB\n"
+ "rDb1eEfmLtqfKLLUL9iPDReUh6WAE7qoNDtfoT551uSMIae1cpPUduVTnSkEgw8k\n"
+ "NjJSHYT800jSB2R+e7tJG3ErXDM63R3B8RbitZPoWACjpBxDT+Qrj0fBFS4AWw6b\n"
+ "z09uitv0RrgI6CW7xRh3UAdRwEBGHiU6HTIthX6LNgez1UL0sfu1iZ22wNmYZP/S\n"
+ "sL3b20WtSH9LN2PRJ4q3AGt6RMbmSGr65ljha9xkTFna0Y8CAwEAAaNTMFEwHQYD\n"
+ "VR0OBBYEFKd5/MGFZUAUV502vJ/Kcswax8WVMB8GA1UdIwQYMBaAFKd5/MGFZUAU\n"
+ "V502vJ/Kcswax8WVMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB\n"
+ "AIBS4AfM7wiunQ2UZQQ5A0Un99+BLax9e+h11h/jGeJ+/9maY/E9MK6UG9LXoOv2\n"
+ "z32Q7Ta2xKeRu6GC/qupwYJ0Xt3LENOfogsaNCAgxKlAN48LGlRyCTvzWsEMh28j\n"
+ "RaelWonh2qQoiryKLVnRwrg8g1Bu4v+V437cIBmeZPxf0spEL9EVqlN+iS8plmel\n"
+ "7/F4ULdybKGq39tgicuS7JhnY21ZzOFoq0bWnKBbAeTndmuROdb3pEppxW6pwu0q\n"
+ "TFdMrSJE38kiQh2O9IchPQbTZ+Rdj0HE9NxStlrNr5bu6rjikRm50/G3JoXpzYdp\n"
+ "AN4ZI2QZ6R6Y+TzDixKecNk=\n"
+ "-----END CERTIFICATE-----\n"
+ ;
+ } else {
+ cert = File::getFile(cert_path);
+ }
+
+ ctx.use_certificate_chain(
+ boost::asio::buffer(cert.data(), cert.size()));
+
+ std::string key;
+ if (key_path == "") {
+ // use dummy self signed key. Will be replaced by real
+ // certificate if configured upon respective session
+ key =
+ "-----BEGIN PRIVATE KEY-----\n"
+ "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyTW9Fi28/sT/m\n"
+ "CEzDJPnniv0iDgkRbP/WmSAMij2qxmrsWvvHduZpXyUwKOvxtId7ANuNCR8jaTs1\n"
+ "TqID1Pr+HUVshjtMObx1Q9HGgaw29XhH5i7anyiy1C/Yjw0XlIelgBO6qDQ7X6E+\n"
+ "edbkjCGntXKT1HblU50pBIMPJDYyUh2E/NNI0gdkfnu7SRtxK1wzOt0dwfEW4rWT\n"
+ "6FgAo6QcQ0/kK49HwRUuAFsOm89Pborb9Ea4COglu8UYd1AHUcBARh4lOh0yLYV+\n"
+ "izYHs9VC9LH7tYmdtsDZmGT/0rC929tFrUh/Szdj0SeKtwBrekTG5khq+uZY4Wvc\n"
+ "ZExZ2tGPAgMBAAECggEBAK9bJKIa3dCgPB257/TEOtsTgJyrfROcRYkCk9iBZOC9\n"
+ "v46wdIrZTwY2wtY4iMPwLoY0c7ijTfJ/nfFxYjmujyK4Gvz+jvcKmWQizP8TrRFo\n"
+ "HWFo6o+slFQ8BspO9itIspd7/OtIXgY+qNBO959Sig7sjsEA5eXoc9pRS6vqizq0\n"
+ "j4G/UO5Amr/l3ciEJiqMJgZsIVLDKaGlqFTymydSqkB8UHQYWK1kunQxhK4Ldycu\n"
+ "hTooQE7tXM0zvoFVV6v1fldV5OFsZk2kPMNtvMO6ZEpOM4rNMlg+vJy8kB1fb3Gs\n"
+ "iFE/DCUpZsMSserQMU9/hfrYlndgsFD5Sr1EVGEebhECgYEA1gc9qx+ugdhYTY5j\n"
+ "tJDXjOsnw8KY/l/1y+mQ8XNJ9MVdBGy1WB+uWB4teiyJchV49gn2XlKUK2rcCBvZ\n"
+ "vC5CwPmFi2t70JezQgnXtDlbR0bARPlRd741i4rBpD7hEiZNCTOd2HFBpUg/CGWN\n"
+ "E4n1ksazBm6jvv3Jo6WAa07Z390CgYEA1USrFqmc/fKGQpTCdH0qYZv3hQtrb1TQ\n"
+ "9YnrbhtaC0haPpintZKjvhU3tCd1fPuIDXtMAgaaKSyoGiE2aMvLxt1/eV08BkMi\n"
+ "kGIss9poYNi5+6ZD9QAHmHJhzZtVGj8U5L8379XmwxAByiBRVVE8CW1X/e6+iJpz\n"
+ "+CLgN+zEVlsCgYEAsuOAdtxXJm4meERwL8b0cvNF3Eh1Sf/42MPTAwzCntSrh3w5\n"
+ "InvwY/RtPHWnN/ScksEG7BWHhLafTCPDHJdp8hNcvIhNB68UBDln0loyYePP5pag\n"
+ "sj4IUSbb7SUlR989elhrMTKQlM5K6QDAJrmjyVdM4S5urL9A3wgAyzAvyP0CgYAO\n"
+ "paGuc8WxdzebWQYl4/bGL2UHgSpGwid7xZYiwMQlZDm2dNuHz+NpCaICwHcEN243\n"
+ "ptEojnWGAGgnK0LGXcDIDqxTlICr2W6FRgjV7Vkf1aKoUtn1+KOM58YpzdJBdDWm\n"
+ "JC/eS+2GVhIZZLDRUDv0VcsmSIBTd3AhiZumm588YwKBgBZfNqfmHAwIP2pM1wml\n"
+ "Ck3vaLLvonghj3iQW9CFJ/SqLOnfT4KJkFObR6oGbxY0RtXsCrmSqidIKgDd0Kkq\n"
+ "L6QbHp2j3+16GBdmLNUJlfjBTNPJp69IDKztjeCX7/8JZs79p/LAv+I9Sh4lVw4O\n"
+ "IrDprlB0yzP5zigcsAZeViYJ\n"
+ "-----END PRIVATE KEY-----\n"
+ ;
+ } else {
+ key = File::getFile(key_path);
+ }
+ ctx.use_private_key(
+ boost::asio::buffer(key.data(), key.size()),
+ boost::asio::ssl::context::file_format::pem);
+
+ ctx.use_tmp_dh(
+ boost::asio::buffer(dh.data(), dh.size()));
+}
+
+} // namespace
+
+namespace HTTPS {
+
+typedef std::unordered_map<std::string, std::shared_ptr<ssl::context>> ctx_type;
+
+static const ssl::context_base::method tls_method {ssl::context::tlsv13};
+
+int ServerNameError(SSL *s, ctx_type& ctx_map)
+{
+ std::shared_ptr<ssl::context> ctx{ctx_map.at("")};
+ SSL_set_SSL_CTX(s, ctx->native_handle());
+ return SSL_CLIENT_HELLO_SUCCESS; // OK for now
+}
+
+int servername_callback(SSL *s, int *al, void *arg)
+{
+ ctx_type& ctx_map = *(ctx_type*)arg;
+
+ if (0) { // not really necessary
+ int* numbers;
+ size_t numbers_size;
+ if (SSL_client_hello_get1_extensions_present(s, &numbers, &numbers_size) != 1) {
+ std::cout << "Error on SSL_client_hello_get1_extensions_present" << std::endl;
+ return ServerNameError(s, ctx_map);
+ }
+ bool server_name_available {false};
+ for (size_t i = 0; i < numbers_size; i++)
+ if (numbers[i] == 0)
+ server_name_available = true;
+
+ OPENSSL_free(numbers);
+
+ if (!server_name_available) {
+ std::cout << "Error: No server_name available at SSL_client_hello_get1_extensions_present" << std::endl;
+ return ServerNameError(s, ctx_map);
+ }
+ }
+
+ const unsigned char* data;
+ size_t data_size;
+ // 0 is server_name
+ if (SSL_client_hello_get0_ext(s, 0, &data, &data_size) != 1) {
+ std::cout << "Warning: Error on SSL_client_hello_get0_ext: servername not available. Using dummy ctx." << std::endl;
+ return ServerNameError(s, ctx_map);
+ }
+
+ // SNI Server Name, See https://tools.ietf.org/html/rfc6066 (TODO: why are there 5 bytes header?)
+ std::string server_name {std::string((const char*)data, (size_t)data_size)};
+ if (server_name.size() >= 5 && server_name[0] == '\0')
+ server_name = server_name.substr(5);
+
+ server_name = unbracketed(server_name);
+
+ auto it {ctx_map.find(server_name)};
+ std::shared_ptr<ssl::context> ctx{};
+ if (it != ctx_map.end()) {
+ ctx = it->second;
+ } else {
+ std::cout << "Warning: server_name " << server_name << " (" << server_name.size() << ") not found in list of prepared contexts. Using dummy ctx." << std::endl;
+ return ServerNameError(s, ctx_map);
+ }
+
+ SSL_set_SSL_CTX(s, ctx->native_handle());
+
+ return SSL_CLIENT_HELLO_SUCCESS;
+}
+
+class Server: public ::Server
+{
+private:
+ ctx_type m_ctx;
+
+public:
+ Server(Config& config, boost::asio::io_context& ioc, const Socket& socket, plugins_container_type& plugins, Statistics& statistics):
+ ::Server(config, ioc, socket, plugins, statistics)
+ {
+ load_certificates(); // load initially
+ }
+
+ ~Server() override
+ {
+ }
+
+ void load_certificates()
+ {
+ // initial dummy, before we can add specific ctx w/ certificate
+ std::shared_ptr<ssl::context> ctx_dummy{std::make_shared<ssl::context>(tls_method)};
+ load_server_certificate(*ctx_dummy, "", "");
+ SSL_CTX_set_client_hello_cb(ctx_dummy->native_handle(), servername_callback, &m_ctx);
+ m_ctx.emplace("", ctx_dummy);
+
+ // import the real certificates
+ for (const auto& serve_site: m_socket.serve_sites) {
+ for (const auto& site: m_config.Sites()) {
+ if (site.first == serve_site) {
+ std::shared_ptr<ssl::context> ctx {std::make_shared<ssl::context>(tls_method)};
+
+ std::cout << "Creating SSL context/cert for site " << serve_site << " on port " << m_socket.port << std::endl;
+
+ load_server_certificate(*ctx, site.second.cert_path, site.second.key_path);
+ SSL_CTX_set_client_hello_cb(ctx->native_handle(), servername_callback, &m_ctx);
+
+ for (const auto& host: site.second.hosts) {
+ std::cout << " Adding Host " << host << std::endl;
+ m_ctx.emplace(unbracketed(host), ctx);
+ }
+ }
+ }
+ }
+ }
+
+ int start() override
+ {
+ auto const address = net::ip::make_address(m_socket.address);
+ auto const port = static_cast<unsigned short>(std::atoi(m_socket.port.data()));
+
+ // Create and launch a listening port
+ make_listener(m_ioc, *m_ctx[""], address, port, *this);
+
+ return EXIT_SUCCESS;
+ }
+
+};
+
+} // namespace HTTPS
+
int run_server(Config& config, plugins_container_type& plugins)
{
Statistics stats(config.statistics_path());
@@ -106,30 +414,3 @@ int run_server(Config& config, plugins_container_type& plugins)
return EXIT_SUCCESS;
}
-Config& Server::GetConfig()
-{
- return m_config;
-}
-
-const Socket& Server::GetSocket()
-{
- return m_socket;
-}
-
-plugin_type Server::GetPlugin(const std::string& name)
-{
- try {
- return m_plugins.at(name);
- } catch (const std::out_of_range& ex) {
- std::cout << "Out of range at Server::GetPlugin(): " << name << std::endl;
- std::rethrow_exception(std::current_exception());
- } catch (...) {
- std::cout << "Unknown exception at Server::GetPlugin(): " << name << std::endl;
- std::rethrow_exception(std::current_exception());
- }
-}
-
-Statistics& Server::GetStatistics()
-{
- return m_statistics;
-}
diff --git a/server.h b/server.h
index 096f7f6..131bae9 100644
--- a/server.h
+++ b/server.h
@@ -1,11 +1,14 @@
#pragma once
#include <boost/asio/io_context.hpp>
+#include <boost/asio/ssl.hpp>
#include "config.h"
#include "plugin.h"
#include "statistics.h"
+namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
+
using namespace std::string_literals;
// Base class for HTTP and HTTPS classes
diff --git a/websocket.cpp b/websocket.cpp
index 37d4b59..68ff194 100644
--- a/websocket.cpp
+++ b/websocket.cpp
@@ -1,5 +1,307 @@
+//
+// Websocket, implemented via CRTP for both plain and ssl websockets
+//
#include "websocket.h"
+namespace beast = boost::beast; // from <boost/beast.hpp>
+namespace http = beast::http; // from <boost/beast/http.hpp>
+namespace net = boost::asio; // from <boost/asio.hpp>
+namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
+namespace websocket = beast::websocket;
+using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
+using namespace std::placeholders;
+
+// Server session, asynchronous, proxying, implemented w/ CRTP for plain+ssl variants
+template<class Derived>
+class websocket_session
+{
+private:
+ Derived& derived()
+ {
+ return static_cast<Derived&>(*this);
+ }
+
+ boost::asio::io_context& ioc_;
+ boost::asio::ip::tcp::resolver resolver_;
+ boost::beast::flat_buffer buffer_in_;
+ boost::beast::websocket::stream<beast::tcp_stream> ws_app_;
+ boost::beast::flat_buffer buffer_out_;
+ std::string host_;
+ std::string port_;
+ std::string subprotocol_;
+ std::string relative_target_;
+
+public:
+ explicit websocket_session(boost::asio::io_context& ioc, std::string&& websocket_address):
+ ioc_(ioc),
+ resolver_(boost::asio::make_strand(ioc_)),
+ ws_app_(boost::asio::make_strand(ioc_)),
+ host_{},
+ port_{},
+ subprotocol_{},
+ relative_target_{}
+ {
+ // Parse websocket address host:port :
+
+ auto colon_pos{websocket_address.find_last_of(':')};
+
+ if (colon_pos == std::string::npos) {
+ std::cerr << "Warning: Bad websocket address (colon missing): " << websocket_address << std::endl;
+ return;
+ }
+
+ auto slash_pos{websocket_address.find('/')};
+ if (slash_pos == std::string::npos) {
+ std::cerr << "Warning: Bad websocket address (slash missing): " << websocket_address << std::endl;
+ return;
+ }
+ if (slash_pos <= colon_pos) {
+ std::cerr << "Warning: Bad websocket address: " << websocket_address << std::endl;
+ return;
+ }
+
+ host_ = websocket_address.substr(0, colon_pos);
+ port_ = websocket_address.substr(colon_pos + 1, slash_pos - (colon_pos + 1));
+ relative_target_ = websocket_address.substr(slash_pos);
+ }
+
+ //
+ // The initial setup path
+ //
+
+ // Start the asynchronous accept operation
+ void do_accept_in(request_type req)
+ {
+ // Set suggested timeout settings for the websocket
+ derived().ws_in().set_option(
+ websocket::stream_base::timeout::suggested(
+ beast::role_type::server));
+
+ // Set a decorator to change the Server of the handshake
+ derived().ws_in().set_option(websocket::stream_base::decorator(
+ [](websocket::response_type& res)
+ {
+ res.set(http::field::server,
+ std::string{"Reichwein.IT Webserver"});
+ }));
+
+ // Forward subprotocol from request to target websocket
+ subprotocol_ = std::string{req[http::field::sec_websocket_protocol]};
+
+ // Accept the websocket handshake
+ derived().ws_in().async_accept(
+ req,
+ beast::bind_front_handler(
+ &websocket_session::on_accept_in,
+ derived().shared_from_this()));
+ }
+
+private:
+ void on_accept_in(beast::error_code ec)
+ {
+ if (ec)
+ return fail(ec, "accept in");
+
+ resolver_.async_resolve(host_, port_,
+ beast::bind_front_handler(&websocket_session::on_resolve_app, derived().shared_from_this()));
+ }
+
+ void on_resolve_app(beast::error_code ec, tcp::resolver::results_type results)
+ {
+ if (ec)
+ return fail(ec, "resolve app");
+
+ beast::get_lowest_layer(ws_app_).async_connect(results,
+ beast::bind_front_handler(&websocket_session::on_connect_app, derived().shared_from_this()));
+ }
+
+ void on_connect_app(beast::error_code ec, tcp::resolver::results_type::endpoint_type endpoint)
+ {
+ if (ec)
+ return fail(ec, "connect app");
+
+ beast::get_lowest_layer(ws_app_).expires_never();
+
+ host_ += ':' + std::to_string(endpoint.port());
+
+ // Set suggested timeout settings for the websocket
+ ws_app_.set_option(
+ websocket::stream_base::timeout::suggested(
+ beast::role_type::client));
+
+ ws_app_.set_option(boost::beast::websocket::stream_base::decorator(
+ [](boost::beast::websocket::request_type& req)
+ {
+ req.set(boost::beast::http::field::user_agent, "Reichwein.IT Webserver Websocket client");
+ }));
+
+ ws_app_.set_option(boost::beast::websocket::stream_base::decorator(
+ [this](boost::beast::websocket::request_type& req)
+ {
+ req.set(boost::beast::http::field::sec_websocket_protocol, subprotocol_);
+ }));
+
+ ws_app_.async_handshake(host_, relative_target_,
+ beast::bind_front_handler(&websocket_session::on_handshake_app, derived().shared_from_this()));
+ }
+
+ void on_handshake_app(beast::error_code ec)
+ {
+ if (ec)
+ return fail(ec, "handshake app");
+
+ // Start reading messages from both sides, asynchronously
+ do_read_in();
+ do_read_app();
+ }
+
+ //
+ // The input path (client,ws_in_ -> app,ws_app_) via
+ //
+
+ void
+ do_read_in()
+ {
+ // Read a message into our buffer
+ derived().ws_in().async_read(
+ buffer_in_,
+ beast::bind_front_handler(
+ &websocket_session::on_read_in,
+ derived().shared_from_this()));
+ }
+
+ void
+ on_read_in(
+ beast::error_code ec,
+ std::size_t bytes_transferred)
+ {
+ boost::ignore_unused(bytes_transferred);
+
+ // This indicates that the websocket_session was closed
+ if (ec == websocket::error::closed)
+ return;
+
+ if (ec)
+ fail(ec, "read in");
+
+ ws_app_.text(derived().ws_in().got_text());
+
+ do_write_app();
+ }
+
+ void do_write_app()
+ {
+ ws_app_.async_write(buffer_in_.data(),
+ beast::bind_front_handler(
+ &websocket_session::on_write_app,
+ derived().shared_from_this()));
+ }
+
+ void on_write_app(beast::error_code ec, std::size_t bytes_transferred)
+ {
+ boost::ignore_unused(bytes_transferred);
+
+ if (ec)
+ fail(ec, "write app");
+
+ buffer_in_.consume(buffer_in_.size());
+
+ // Do another read
+ do_read_in();
+ }
+
+ //
+ // The output path (app,ws_app_ -> client,ws_in_)
+ //
+
+ void do_read_app()
+ {
+ // Read a message into our buffer
+ ws_app_.async_read(
+ buffer_out_,
+ beast::bind_front_handler(
+ &websocket_session::on_read_app,
+ derived().shared_from_this()));
+ }
+
+ void on_read_app(beast::error_code ec, std::size_t bytes_transferred)
+ {
+ boost::ignore_unused(bytes_transferred);
+
+ if (ec == websocket::error::closed)
+ return;
+
+ if (ec)
+ fail(ec, "read app");
+
+ do_write_out();
+ }
+
+ void do_write_out()
+ {
+ derived().ws_in().async_write(buffer_out_.data(),
+ beast::bind_front_handler(
+ &websocket_session::on_write_out,
+ derived().shared_from_this()));
+ }
+
+ void on_write_out(
+ beast::error_code ec,
+ std::size_t bytes_transferred)
+ {
+ boost::ignore_unused(bytes_transferred);
+
+ if(ec)
+ return fail(ec, "write out");
+
+ // Clear the buffer
+ buffer_out_.consume(buffer_out_.size());
+
+ // Do another read
+ do_read_app();
+ }
+
+}; // class
+
+class plain_websocket_session:
+ public websocket_session<plain_websocket_session>,
+ public std::enable_shared_from_this<plain_websocket_session>
+{
+ boost::beast::websocket::stream<beast::tcp_stream> ws_in_;
+
+public:
+
+ explicit plain_websocket_session(boost::asio::io_context& ioc, beast::tcp_stream&& stream, std::string&& websocket_address):
+ websocket_session(ioc, std::move(websocket_address)),
+ ws_in_(std::move(stream))
+ {
+ }
+
+ boost::beast::websocket::stream<beast::tcp_stream>& ws_in()
+ {
+ return ws_in_;
+ }
+}; // class
+
+class ssl_websocket_session:
+ public websocket_session<ssl_websocket_session>,
+ public std::enable_shared_from_this<ssl_websocket_session>
+{
+ boost::beast::websocket::stream<beast::ssl_stream<beast::tcp_stream>> ws_in_;
+
+public:
+
+ explicit ssl_websocket_session(boost::asio::io_context& ioc, beast::ssl_stream<beast::tcp_stream>&& stream, std::string&& websocket_address):
+ websocket_session(ioc, std::move(websocket_address)),
+ ws_in_(std::move(stream))
+ {
+ }
+
+ boost::beast::websocket::stream<beast::ssl_stream<beast::tcp_stream>>& ws_in()
+ {
+ return ws_in_;
+ }
+}; // class
void make_websocket_session(boost::asio::io_context& ioc, beast::tcp_stream&& stream, std::string websocket_address, request_type&& req)
{
diff --git a/websocket.h b/websocket.h
index e3c2b30..87b5a04 100644
--- a/websocket.h
+++ b/websocket.h
@@ -20,318 +20,8 @@
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
-#include <algorithm>
-#include <cstddef>
-#include <cstdlib>
-#include <filesystem>
-#include <functional>
-#include <iostream>
-#include <memory>
-#include <optional>
#include <string>
-#include <thread>
-#include <vector>
-namespace beast = boost::beast; // from <boost/beast.hpp>
-namespace http = beast::http; // from <boost/beast/http.hpp>
-namespace net = boost::asio; // from <boost/asio.hpp>
-namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
-namespace websocket = beast::websocket;
-using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
-using namespace std::placeholders;
-
-// Server session, asynchronous, proxying, implemented w/ CRTP for plain+ssl variants
-template<class Derived>
-class websocket_session
-{
-private:
- Derived& derived()
- {
- return static_cast<Derived&>(*this);
- }
-
- boost::asio::io_context& ioc_;
- boost::asio::ip::tcp::resolver resolver_;
- boost::beast::flat_buffer buffer_in_;
- boost::beast::websocket::stream<beast::tcp_stream> ws_app_;
- boost::beast::flat_buffer buffer_out_;
- std::string host_;
- std::string port_;
- std::string subprotocol_;
- std::string relative_target_;
-
-public:
- explicit websocket_session(boost::asio::io_context& ioc, std::string&& websocket_address):
- ioc_(ioc),
- resolver_(boost::asio::make_strand(ioc_)),
- ws_app_(boost::asio::make_strand(ioc_)),
- host_{},
- port_{},
- subprotocol_{},
- relative_target_{}
- {
- // Parse websocket address host:port :
-
- auto colon_pos{websocket_address.find_last_of(':')};
-
- if (colon_pos == std::string::npos) {
- std::cerr << "Warning: Bad websocket address (colon missing): " << websocket_address << std::endl;
- return;
- }
-
- auto slash_pos{websocket_address.find('/')};
- if (slash_pos == std::string::npos) {
- std::cerr << "Warning: Bad websocket address (slash missing): " << websocket_address << std::endl;
- return;
- }
- if (slash_pos <= colon_pos) {
- std::cerr << "Warning: Bad websocket address: " << websocket_address << std::endl;
- return;
- }
-
- host_ = websocket_address.substr(0, colon_pos);
- port_ = websocket_address.substr(colon_pos + 1, slash_pos - (colon_pos + 1));
- relative_target_ = websocket_address.substr(slash_pos);
- }
-
- //
- // The initial setup path
- //
-
- // Start the asynchronous accept operation
- void do_accept_in(request_type req)
- {
- // Set suggested timeout settings for the websocket
- derived().ws_in().set_option(
- websocket::stream_base::timeout::suggested(
- beast::role_type::server));
-
- // Set a decorator to change the Server of the handshake
- derived().ws_in().set_option(websocket::stream_base::decorator(
- [](websocket::response_type& res)
- {
- res.set(http::field::server,
- std::string{"Reichwein.IT Webserver"});
- }));
-
- // Forward subprotocol from request to target websocket
- subprotocol_ = std::string{req[http::field::sec_websocket_protocol]};
-
- // Accept the websocket handshake
- derived().ws_in().async_accept(
- req,
- beast::bind_front_handler(
- &websocket_session::on_accept_in,
- derived().shared_from_this()));
- }
-
-private:
- void on_accept_in(beast::error_code ec)
- {
- if (ec)
- return fail(ec, "accept in");
-
- resolver_.async_resolve(host_, port_,
- beast::bind_front_handler(&websocket_session::on_resolve_app, derived().shared_from_this()));
- }
-
- void on_resolve_app(beast::error_code ec, tcp::resolver::results_type results)
- {
- if (ec)
- return fail(ec, "resolve app");
-
- beast::get_lowest_layer(ws_app_).async_connect(results,
- beast::bind_front_handler(&websocket_session::on_connect_app, derived().shared_from_this()));
- }
-
- void on_connect_app(beast::error_code ec, tcp::resolver::results_type::endpoint_type endpoint)
- {
- if (ec)
- return fail(ec, "connect app");
-
- beast::get_lowest_layer(ws_app_).expires_never();
-
- host_ += ':' + std::to_string(endpoint.port());
-
- // Set suggested timeout settings for the websocket
- ws_app_.set_option(
- websocket::stream_base::timeout::suggested(
- beast::role_type::client));
-
- ws_app_.set_option(boost::beast::websocket::stream_base::decorator(
- [](boost::beast::websocket::request_type& req)
- {
- req.set(boost::beast::http::field::user_agent, "Reichwein.IT Webserver Websocket client");
- }));
-
- ws_app_.set_option(boost::beast::websocket::stream_base::decorator(
- [this](boost::beast::websocket::request_type& req)
- {
- req.set(boost::beast::http::field::sec_websocket_protocol, subprotocol_);
- }));
-
- ws_app_.async_handshake(host_, relative_target_,
- beast::bind_front_handler(&websocket_session::on_handshake_app, derived().shared_from_this()));
- }
-
- void on_handshake_app(beast::error_code ec)
- {
- if (ec)
- return fail(ec, "handshake app");
-
- // Start reading messages from both sides, asynchronously
- do_read_in();
- do_read_app();
- }
-
- //
- // The input path (client,ws_in_ -> app,ws_app_) via
- //
-
- void
- do_read_in()
- {
- // Read a message into our buffer
- derived().ws_in().async_read(
- buffer_in_,
- beast::bind_front_handler(
- &websocket_session::on_read_in,
- derived().shared_from_this()));
- }
-
- void
- on_read_in(
- beast::error_code ec,
- std::size_t bytes_transferred)
- {
- boost::ignore_unused(bytes_transferred);
-
- // This indicates that the websocket_session was closed
- if (ec == websocket::error::closed)
- return;
-
- if (ec)
- fail(ec, "read in");
-
- ws_app_.text(derived().ws_in().got_text());
-
- do_write_app();
- }
-
- void do_write_app()
- {
- ws_app_.async_write(buffer_in_.data(),
- beast::bind_front_handler(
- &websocket_session::on_write_app,
- derived().shared_from_this()));
- }
-
- void on_write_app(beast::error_code ec, std::size_t bytes_transferred)
- {
- boost::ignore_unused(bytes_transferred);
-
- if (ec)
- fail(ec, "write app");
-
- buffer_in_.consume(buffer_in_.size());
-
- // Do another read
- do_read_in();
- }
-
- //
- // The output path (app,ws_app_ -> client,ws_in_)
- //
-
- void do_read_app()
- {
- // Read a message into our buffer
- ws_app_.async_read(
- buffer_out_,
- beast::bind_front_handler(
- &websocket_session::on_read_app,
- derived().shared_from_this()));
- }
-
- void on_read_app(beast::error_code ec, std::size_t bytes_transferred)
- {
- boost::ignore_unused(bytes_transferred);
-
- if (ec == websocket::error::closed)
- return;
-
- if (ec)
- fail(ec, "read app");
-
- do_write_out();
- }
-
- void do_write_out()
- {
- derived().ws_in().async_write(buffer_out_.data(),
- beast::bind_front_handler(
- &websocket_session::on_write_out,
- derived().shared_from_this()));
- }
-
- void on_write_out(
- beast::error_code ec,
- std::size_t bytes_transferred)
- {
- boost::ignore_unused(bytes_transferred);
-
- if(ec)
- return fail(ec, "write out");
-
- // Clear the buffer
- buffer_out_.consume(buffer_out_.size());
-
- // Do another read
- do_read_app();
- }
-
-}; // class
-
-class plain_websocket_session:
- public websocket_session<plain_websocket_session>,
- public std::enable_shared_from_this<plain_websocket_session>
-{
- boost::beast::websocket::stream<beast::tcp_stream> ws_in_;
-
-public:
-
- explicit plain_websocket_session(boost::asio::io_context& ioc, beast::tcp_stream&& stream, std::string&& websocket_address):
- websocket_session(ioc, std::move(websocket_address)),
- ws_in_(std::move(stream))
- {
- }
-
- boost::beast::websocket::stream<beast::tcp_stream>& ws_in()
- {
- return ws_in_;
- }
-}; // class
-
-class ssl_websocket_session:
- public websocket_session<ssl_websocket_session>,
- public std::enable_shared_from_this<ssl_websocket_session>
-{
- boost::beast::websocket::stream<beast::ssl_stream<beast::tcp_stream>> ws_in_;
-
-public:
-
- explicit ssl_websocket_session(boost::asio::io_context& ioc, beast::ssl_stream<beast::tcp_stream>&& stream, std::string&& websocket_address):
- websocket_session(ioc, std::move(websocket_address)),
- ws_in_(std::move(stream))
- {
- }
-
- boost::beast::websocket::stream<beast::ssl_stream<beast::tcp_stream>>& ws_in()
- {
- return ws_in_;
- }
-}; // class
-
-void make_websocket_session(boost::asio::io_context& ioc, beast::tcp_stream&& stream, std::string websocket_address, request_type&& req);
-void make_websocket_session(boost::asio::io_context& ioc, beast::ssl_stream<beast::tcp_stream>&& stream, std::string websocket_address, request_type&& req);
+void make_websocket_session(boost::asio::io_context& ioc, boost::beast::tcp_stream&& stream, std::string websocket_address, request_type&& req);
+void make_websocket_session(boost::asio::io_context& ioc, boost::beast::ssl_stream<beast::tcp_stream>&& stream, std::string websocket_address, request_type&& req);