summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2023-01-09 10:38:29 +0100
committerRoland Reichwein <mail@reichwein.it>2023-01-09 10:38:29 +0100
commitd747193e76baf689211d9f1e42335360288d43c0 (patch)
tree91d3911b94f5e9c5091d9ab978bf4f9b4046d626
parent7b3fe2cc608928df3b885168a0676a771f7bc7be (diff)
First websockets test via https
-rw-r--r--TODO1
-rw-r--r--debian/changelog6
-rw-r--r--https.cpp116
-rw-r--r--plugin_interface.h1
-rw-r--r--tests/test-webserver.cpp85
5 files changed, 208 insertions, 1 deletions
diff --git a/TODO b/TODO
index b5106c3..53c7c5b 100644
--- a/TODO
+++ b/TODO
@@ -1,6 +1,7 @@
Big file bug
Websockets
+FastCGI from command line
stats.png
cgi unhandled headers
git via smart http / cgi
diff --git a/debian/changelog b/debian/changelog
index 9fb309e..fdaa32c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+webserver (1.18) UNRELEASED; urgency=medium
+
+ *
+
+ -- Roland Reichwein <mail@reichwein.it> Sun, 08 Jan 2023 15:26:48 +0100
+
webserver (1.17) unstable; urgency=medium
* Automated date handling (year)
diff --git a/https.cpp b/https.cpp
index 523acb5..ccf14d7 100644
--- a/https.cpp
+++ b/https.cpp
@@ -12,6 +12,9 @@
#include <boost/asio/buffer.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
+#include <boost/beast/websocket.hpp>
+#include <boost/beast/websocket/ssl.hpp>
+#include <boost/asio/buffers_iterator.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/ssl/context.hpp>
#ifdef BOOST_LATEST
@@ -41,6 +44,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>
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 Reichwein;
@@ -82,6 +86,111 @@ void fail(
std::cerr << what << ": " << ec.message() << "\n";
}
+class websocket_session: public std::enable_shared_from_this<websocket_session>
+{
+ websocket::stream<beast::ssl_stream<beast::tcp_stream>> ws_;
+ beast::flat_buffer buffer_;
+
+public:
+ explicit websocket_session(beast::ssl_stream<beast::tcp_stream>&& stream) :
+ ws_(std::move(stream))
+ {
+ }
+
+ // Start the asynchronous accept operation
+ template<class Body, class Allocator>
+ void
+ do_accept(http::request<Body, http::basic_fields<Allocator>> req)
+ {
+ // Set suggested timeout settings for the websocket
+ ws_.set_option(
+ websocket::stream_base::timeout::suggested(
+ beast::role_type::server));
+
+ // Set a decorator to change the Server of the handshake
+ ws_.set_option(websocket::stream_base::decorator(
+ [](websocket::response_type& res)
+ {
+ res.set(http::field::server,
+ std::string{"Reichwein.IT Webserver"});
+ }));
+
+ // Accept the websocket handshake
+ ws_.async_accept(
+ req,
+ beast::bind_front_handler(
+ &websocket_session::on_accept,
+ shared_from_this()));
+ }
+
+private:
+ void
+ on_accept(beast::error_code ec)
+ {
+ if(ec)
+ return fail(ec, "accept");
+
+ // Read a message
+ do_read();
+ }
+
+ void
+ do_read()
+ {
+ // Read a message into our buffer
+ ws_.async_read(
+ buffer_,
+ beast::bind_front_handler(
+ &websocket_session::on_read,
+ shared_from_this()));
+ }
+
+ void
+ on_read(
+ 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");
+
+ // Echo the message
+ ws_.text(ws_.got_text());
+ std::string data(boost::asio::buffers_begin(buffer_.data()), boost::asio::buffers_end(buffer_.data()));
+ static int count{};
+ data += ": " + std::to_string(count++);
+ buffer_.consume(buffer_.size());
+ boost::beast::ostream(buffer_) << data;
+ ws_.async_write(
+ buffer_.data(),
+ beast::bind_front_handler(
+ &websocket_session::on_write,
+ shared_from_this()));
+ }
+
+ void
+ on_write(
+ beast::error_code ec,
+ std::size_t bytes_transferred)
+ {
+ boost::ignore_unused(bytes_transferred);
+
+ if(ec)
+ return fail(ec, "write");
+
+ // Clear the buffer
+ buffer_.consume(buffer_.size());
+
+ // Do another read
+ do_read();
+ }
+};
+
// Handles an HTTP server connection
class session : public std::enable_shared_from_this<session>
{
@@ -265,6 +374,13 @@ public:
return fail(ec, "https read");
req_ = parser_->get();
+
+ if (websocket::is_upgrade(req_))
+ {
+ beast::get_lowest_layer(stream_).expires_never();
+ std::make_shared<websocket_session>(std::move(stream_))->do_accept(parser_->release());
+ return;
+ }
// Send the response
handle_request(m_server, std::move(req_));
diff --git a/plugin_interface.h b/plugin_interface.h
index 830c44c..13e5f53 100644
--- a/plugin_interface.h
+++ b/plugin_interface.h
@@ -16,7 +16,6 @@ public:
//
// The Interface to be implemented by plugins
//
- //
virtual std::string name() = 0;
diff --git a/tests/test-webserver.cpp b/tests/test-webserver.cpp
index ef3b15f..7059bc6 100644
--- a/tests/test-webserver.cpp
+++ b/tests/test-webserver.cpp
@@ -15,10 +15,14 @@
#include <boost/algorithm/string.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
+#include <boost/beast/websocket.hpp>
+#include <boost/beast/websocket/ssl.hpp>
#ifdef BOOST_LATEST
#include <boost/beast/ssl.hpp>
#endif
#include <boost/beast/version.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/buffers_iterator.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/error.hpp>
@@ -441,3 +445,84 @@ BOOST_DATA_TEST_CASE_F(Fixture, http_get_file_not_found, data::make({false, true
BOOST_REQUIRE_EQUAL(response.second, method == boost::beast::http::verb::head ? "" : "404 Not found: /webserver.confSUFFIX");
}
+BOOST_FIXTURE_TEST_CASE(websocket, Fixture)
+{
+ WebserverProcess serverProcess;
+ BOOST_REQUIRE(serverProcess.isRunning());
+
+
+ std::string host = "::1";
+ auto const port = "8081" ;
+ 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};
+
+ // 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");
+
+ // Update the host_ string. This will provide the value of the
+ // Host HTTP header during the WebSocket handshake.
+ // See https://tools.ietf.org/html/rfc7230#section-5.4
+ 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)
+ {
+ req.set(boost::beast::http::field::user_agent,
+ std::string(BOOST_BEAST_VERSION_STRING) +
+ " websocket-client-coro");
+ }));
+
+ // Perform the websocket handshake
+ ws.handshake(host, "/");
+
+ // Send the message
+ ws.write(boost::asio::buffer(std::string(text)));
+
+ // This buffer will hold the incoming message
+ boost::beast::flat_buffer buffer;
+
+ // Read a message into our buffer
+ ws.read(buffer);
+ std::string data(boost::asio::buffers_begin(buffer.data()), boost::asio::buffers_end(buffer.data()));
+ BOOST_CHECK_EQUAL(data, "request1: 0");
+
+ buffer.consume(buffer.size());
+
+ ws.write(boost::asio::buffer(std::string(text)));
+ ws.read(buffer);
+ data = std::string(boost::asio::buffers_begin(buffer.data()), boost::asio::buffers_end(buffer.data()));
+ BOOST_CHECK_EQUAL(data, "request1: 1");
+
+ // Close the WebSocket connection
+ ws.close(boost::beast::websocket::close_code::normal);
+
+ BOOST_REQUIRE(serverProcess.isRunning());
+}
+