summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2023-01-11 18:36:43 +0100
committerRoland Reichwein <mail@reichwein.it>2023-01-11 18:36:43 +0100
commitdeb28a9ce73ed7e38aaa53659027b61976fdca6b (patch)
tree7458809ca11ee8e59b32eb4aed4cd482894035dc
parent64493507905412e36848b9bd97c26f3d7a578ab5 (diff)
Websocket for both http and https
-rw-r--r--TODO4
-rw-r--r--http.cpp24
-rw-r--r--https.cpp2
-rw-r--r--tests/test-webserver.cpp29
-rw-r--r--websocket.cpp10
-rw-r--r--websocket.h94
6 files changed, 113 insertions, 50 deletions
diff --git a/TODO b/TODO
index f1ff320..fc4e4a8 100644
--- a/TODO
+++ b/TODO
@@ -5,9 +5,7 @@ test:
- Redirect
Big file bug
- dynamic plugin interface (file buffer, ...)
-Websockets
-- http+https
-http+https=CRTP
+CRTP: http+https
FastCGI from command line
stats.png
diff --git a/http.cpp b/http.cpp
index 4738ad8..469f1aa 100644
--- a/http.cpp
+++ b/http.cpp
@@ -4,6 +4,7 @@
#include "server.h"
#include "response.h"
+#include "websocket.h"
#include <boost/beast/version.hpp>
#include <boost/beast/core.hpp>
@@ -27,6 +28,7 @@ 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>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
+namespace websocket = beast::websocket;
namespace {
@@ -41,6 +43,7 @@ void fail(beast::error_code ec, char const* what)
// 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;
@@ -65,13 +68,21 @@ class session : public std::enable_shared_from_this<session>
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)
- : stream_(std::move(socket))
- , m_server(server)
+ Server& server):
+ ioc_(ioc),
+ stream_(std::move(socket)),
+ m_server(server)
{
}
@@ -127,6 +138,12 @@ public:
req_ = parser_->get();
+ if (websocket::is_upgrade(req_))
+ {
+ handle_websocket();
+ return;
+ }
+
// Send the response
handle_request(m_server, std::move(req_));
}
@@ -251,6 +268,7 @@ private:
{
// Create the session and run it
std::make_shared<session>(
+ ioc_,
std::move(socket),
m_server)->run();
}
diff --git a/https.cpp b/https.cpp
index 5d19d5b..6b32195 100644
--- a/https.cpp
+++ b/https.cpp
@@ -79,7 +79,7 @@ class session : public std::enable_shared_from_this<session>
void handle_websocket()
{
beast::get_lowest_layer(stream_).expires_never();
- std::make_shared<websocket_session>(ioc_, std::move(stream_), response::get_websocket_address(req_, m_server))->do_accept_in(parser_->release());
+ make_websocket_session(ioc_, std::move(stream_), response::get_websocket_address(req_, m_server), parser_->release());
}
public:
diff --git a/tests/test-webserver.cpp b/tests/test-webserver.cpp
index e9e6df5..077c27e 100644
--- a/tests/test-webserver.cpp
+++ b/tests/test-webserver.cpp
@@ -202,7 +202,7 @@ public:
}
// wait for server to start up
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
void stop()
@@ -611,7 +611,7 @@ private:
std::unique_ptr<shared_data_t, std::function<void(shared_data_t*)>> m_shared;
}; // class WebsocketServerProcess
-BOOST_FIXTURE_TEST_CASE(websocket, Fixture)
+BOOST_FIXTURE_TEST_CASE(websocket_ssl, Fixture)
{
std::string webserver_config{R"CONFIG(<webserver>
<user>www-data</user>
@@ -752,7 +752,7 @@ BOOST_FIXTURE_TEST_CASE(websocket, Fixture)
BOOST_REQUIRE(websocketProcess.is_running());
}
-BOOST_FIXTURE_TEST_CASE(websocket_subprotocol, Fixture)
+BOOST_FIXTURE_TEST_CASE(websocket_plain_subprotocol, Fixture)
{
std::string webserver_config{R"CONFIG(<webserver>
<user>www-data</user>
@@ -810,35 +810,21 @@ BOOST_FIXTURE_TEST_CASE(websocket_subprotocol, Fixture)
BOOST_REQUIRE(websocketProcess.is_running());
std::string host = "::1";
- auto const port = "8081" ;
+ auto const port = "8080" ;
auto const text = "request1";
// The io_context is required for all I/O
boost::asio::io_context ioc;
- // The SSL context is required, and holds certificates
- boost::asio::ssl::context ctx{boost::asio::ssl::context::tlsv13_client};
-
- // This holds the root certificate used for verification
- load_root_certificates(ctx);
-
// These objects perform our I/O
boost::asio::ip::tcp::resolver resolver{ioc};
- boost::beast::websocket::stream<boost::beast::ssl_stream<boost::asio::ip::tcp::socket>> ws{ioc, ctx};
+ boost::beast::websocket::stream<boost::asio::ip::tcp::socket> ws{ioc};
// Look up the domain name
auto const results = resolver.resolve(host, port);
// Make the connection on the IP address we get from a lookup
- auto ep = boost::asio::connect(get_lowest_layer(ws), results);
-
- // Set SNI Hostname (many hosts need this to handshake successfully)
- if(! SSL_set_tlsext_host_name(ws.next_layer().native_handle(), host.c_str()))
- throw boost::beast::system_error(
- boost::beast::error_code(
- static_cast<int>(::ERR_get_error()),
- boost::asio::error::get_ssl_category()),
- "Failed to set SNI Hostname");
+ auto ep = boost::asio::connect(boost::beast::get_lowest_layer(ws), results);
// Update the host_ string. This will provide the value of the
// Host HTTP header during the WebSocket handshake.
@@ -847,9 +833,6 @@ BOOST_FIXTURE_TEST_CASE(websocket_subprotocol, Fixture)
host = "[" + host + "]";
host += ':' + std::to_string(ep.port());
- // Perform the SSL handshake
- ws.next_layer().handshake(boost::asio::ssl::stream_base::client);
-
// Set a decorator to change the User-Agent of the handshake
ws.set_option(boost::beast::websocket::stream_base::decorator(
[](boost::beast::websocket::request_type& req)
diff --git a/websocket.cpp b/websocket.cpp
index a4c8dfb..37d4b59 100644
--- a/websocket.cpp
+++ b/websocket.cpp
@@ -1,2 +1,12 @@
#include "websocket.h"
+
+void make_websocket_session(boost::asio::io_context& ioc, beast::tcp_stream&& stream, std::string websocket_address, request_type&& req)
+{
+ std::make_shared<plain_websocket_session>(ioc, std::move(stream), std::move(websocket_address))->do_accept_in(std::move(req));
+}
+
+void make_websocket_session(boost::asio::io_context& ioc, beast::ssl_stream<beast::tcp_stream>&& stream, std::string websocket_address, request_type&& req)
+{
+ std::make_shared<ssl_websocket_session>(ioc, std::move(stream), std::move(websocket_address))->do_accept_in(std::move(req));
+}
diff --git a/websocket.h b/websocket.h
index 951155e..b941433 100644
--- a/websocket.h
+++ b/websocket.h
@@ -1,6 +1,10 @@
+//
+// Websocket, implemented via CRTP for both plain and ssl websockets
+//
#pragma once
#include "error.h"
+#include "response.h"
#include <boost/asio/buffer.hpp>
#include <boost/beast/core.hpp>
@@ -36,12 +40,18 @@ namespace websocket = beast::websocket;
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
using namespace std::placeholders;
-// Server session, asynchronous, proxying
-class websocket_session: public std::enable_shared_from_this<websocket_session>
+// 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::websocket::stream<beast::ssl_stream<beast::tcp_stream>> ws_in_;
boost::beast::flat_buffer buffer_in_;
boost::beast::websocket::stream<beast::tcp_stream> ws_app_;
boost::beast::flat_buffer buffer_out_;
@@ -50,11 +60,10 @@ class websocket_session: public std::enable_shared_from_this<websocket_session>
std::string subprotocol_;
std::string relative_target_;
-public:
- explicit websocket_session(boost::asio::io_context& ioc, beast::ssl_stream<beast::tcp_stream>&& stream, const std::string& websocket_address):
+public:
+ explicit websocket_session(boost::asio::io_context& ioc, std::string&& websocket_address):
ioc_(ioc),
resolver_(boost::asio::make_strand(ioc_)),
- ws_in_(std::move(stream)),
ws_app_(boost::asio::make_strand(ioc_)),
host_{},
port_{},
@@ -90,16 +99,17 @@ public:
//
// Start the asynchronous accept operation
+ // TODO: why template here?
template<class Body, class Allocator>
void do_accept_in(http::request<Body, http::basic_fields<Allocator>> req)
{
// Set suggested timeout settings for the websocket
- ws_in_.set_option(
+ derived().ws_in().set_option(
websocket::stream_base::timeout::suggested(
beast::role_type::server));
// Set a decorator to change the Server of the handshake
- ws_in_.set_option(websocket::stream_base::decorator(
+ derived().ws_in().set_option(websocket::stream_base::decorator(
[](websocket::response_type& res)
{
res.set(http::field::server,
@@ -110,11 +120,11 @@ public:
subprotocol_ = std::string{req[http::field::sec_websocket_protocol]};
// Accept the websocket handshake
- ws_in_.async_accept(
+ derived().ws_in().async_accept(
req,
beast::bind_front_handler(
&websocket_session::on_accept_in,
- shared_from_this()));
+ derived().shared_from_this()));
}
private:
@@ -124,7 +134,7 @@ private:
return fail(ec, "accept in");
resolver_.async_resolve(host_, port_,
- beast::bind_front_handler(&websocket_session::on_resolve_app, shared_from_this()));
+ 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)
@@ -133,7 +143,7 @@ private:
return fail(ec, "resolve app");
beast::get_lowest_layer(ws_app_).async_connect(results,
- beast::bind_front_handler(&websocket_session::on_connect_app, shared_from_this()));
+ 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)
@@ -163,7 +173,7 @@ private:
}));
ws_app_.async_handshake(host_, relative_target_,
- beast::bind_front_handler(&websocket_session::on_handshake_app, shared_from_this()));
+ beast::bind_front_handler(&websocket_session::on_handshake_app, derived().shared_from_this()));
}
void on_handshake_app(beast::error_code ec)
@@ -184,11 +194,11 @@ private:
do_read_in()
{
// Read a message into our buffer
- ws_in_.async_read(
+ derived().ws_in().async_read(
buffer_in_,
beast::bind_front_handler(
&websocket_session::on_read_in,
- shared_from_this()));
+ derived().shared_from_this()));
}
void
@@ -205,7 +215,7 @@ private:
if (ec)
fail(ec, "read in");
- ws_app_.text(ws_in_.got_text());
+ ws_app_.text(derived().ws_in().got_text());
do_write_app();
}
@@ -215,7 +225,7 @@ private:
ws_app_.async_write(buffer_in_.data(),
beast::bind_front_handler(
&websocket_session::on_write_app,
- shared_from_this()));
+ derived().shared_from_this()));
}
void on_write_app(beast::error_code ec, std::size_t bytes_transferred)
@@ -242,7 +252,7 @@ private:
buffer_out_,
beast::bind_front_handler(
&websocket_session::on_read_app,
- shared_from_this()));
+ derived().shared_from_this()));
}
void on_read_app(beast::error_code ec, std::size_t bytes_transferred)
@@ -260,10 +270,10 @@ private:
void do_write_out()
{
- ws_in_.async_write(buffer_out_.data(),
+ derived().ws_in().async_write(buffer_out_.data(),
beast::bind_front_handler(
&websocket_session::on_write_out,
- shared_from_this()));
+ derived().shared_from_this()));
}
void on_write_out(
@@ -283,3 +293,47 @@ private:
}
}; // 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);
+