summaryrefslogtreecommitdiffhomepage
path: root/https.cpp
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 /https.cpp
parent3cb78411178f8458f889975799060e0bb866d2cf (diff)
Consolidate HTTP+HTTPS via CRTP
Diffstat (limited to 'https.cpp')
-rw-r--r--https.cpp818
1 files changed, 355 insertions, 463 deletions
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();
+}