summaryrefslogtreecommitdiffhomepage
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test-webserver.cpp200
1 files changed, 193 insertions, 7 deletions
diff --git a/tests/test-webserver.cpp b/tests/test-webserver.cpp
index 10f6dca..602eb77 100644
--- a/tests/test-webserver.cpp
+++ b/tests/test-webserver.cpp
@@ -29,6 +29,8 @@
#include <exception>
#include <filesystem>
#include <iostream>
+#include <memory>
+#include <mutex>
#include <sstream>
#include <stdexcept>
#include <string>
@@ -38,11 +40,14 @@
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/types.h>
#include <libreichwein/file.h>
#include <libreichwein/process.h>
#include "webserver.h"
+#include "response.h"
using namespace std::string_literals;
namespace fs = std::filesystem;
@@ -436,9 +441,19 @@ BOOST_DATA_TEST_CASE_F(Fixture, http_get_file_not_found, data::make({false, true
// Test server
class WebsocketServerProcess
{
+ // shared data between Unix processes
+ struct shared_data_t {
+ std::mutex mutex;
+ char subprotocol[1024]{};
+ char target[1024]{};
+ };
+
public:
WebsocketServerProcess()
{
+ m_shared = std::unique_ptr<shared_data_t, std::function<void(shared_data_t*)>>(
+ (shared_data_t*)mmap(NULL, sizeof(shared_data_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0),
+ [this](shared_data_t*){munmap(m_shared.get(), sizeof(shared_data_t));});
start();
}
@@ -447,7 +462,6 @@ public:
stop();
}
- // Echoes back all received WebSocket messages
void do_session(boost::asio::ip::tcp::socket socket)
{
try
@@ -463,18 +477,27 @@ public:
std::string("Reichwein.IT Test Websocket Server"));
}));
- // Accept the websocket handshake
- ws.accept();
+ boost::beast::http::request_parser<boost::beast::http::string_body> parser;
+ request_type req;
+ boost::beast::flat_buffer buffer;
+
+ boost::beast::http::read(ws.next_layer(), buffer, parser);
+ req = parser.get();
+ {
+ std::lock_guard lock{m_shared->mutex};
+ strncpy(m_shared->subprotocol, std::string{req[http::field::sec_websocket_protocol]}.data(), sizeof(m_shared->subprotocol));
+ strncpy(m_shared->target, std::string{req.target()}.data(), sizeof(m_shared->target));
+ }
+
+ ws.accept(req);
for(;;)
{
- // This buffer will hold the incoming message
boost::beast::flat_buffer buffer;
- // Read a message
ws.read(buffer);
- // Echo the message back
+ // Reply with <request>: <counter>
ws.text(ws.got_text());
std::string data(boost::asio::buffers_begin(buffer.data()), boost::asio::buffers_end(buffer.data()));
data += ": " + std::to_string(m_count++);
@@ -562,9 +585,22 @@ public:
m_pid = 0;
}
+ std::string subprotocol()
+ {
+ std::lock_guard lock{m_shared->mutex};
+ return m_shared->subprotocol;
+ }
+
+ std::string target()
+ {
+ std::lock_guard lock{m_shared->mutex};
+ return m_shared->target;
+ }
+
private:
int m_pid{};
int m_count{};
+ std::unique_ptr<shared_data_t, std::function<void(shared_data_t*)>> m_shared;
}; // class WebsocketServerProcess
BOOST_FIXTURE_TEST_CASE(websocket, Fixture)
@@ -658,7 +694,8 @@ BOOST_FIXTURE_TEST_CASE(websocket, Fixture)
// 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 = "[" + host + "]";
+ if (host == "::1")
+ host = "[" + host + "]";
host += ':' + std::to_string(ep.port());
// Perform the SSL handshake
@@ -707,3 +744,152 @@ BOOST_FIXTURE_TEST_CASE(websocket, Fixture)
BOOST_REQUIRE(websocketProcess.is_running());
}
+BOOST_FIXTURE_TEST_CASE(websocket_subprotocol, Fixture)
+{
+ std::string webserver_config{R"CONFIG(<webserver>
+ <user>www-data</user>
+ <group>www-data</group>
+ <threads>10</threads>
+ <statisticspath>stats.db</statisticspath>
+ <plugin-directory>../plugins</plugin-directory>
+ <sites>
+ <site>
+ <name>localhost</name>
+ <host>ip6-localhost</host>
+ <host>localhost</host>
+ <host>127.0.0.1</host>
+ <host>[::1]</host>
+ <path requested="/">
+ <plugin>websocket</plugin>
+ <target>::1:8765</target>
+ </path>
+ <certpath>testchain.pem</certpath>
+ <keypath>testkey.pem</keypath>
+ </site>
+ </sites>
+ <sockets>
+ <socket>
+ <address>127.0.0.1</address>
+ <port>8080</port>
+ <protocol>http</protocol>
+ <site>localhost</site>
+ </socket>
+ <socket>
+ <address>::1</address>
+ <port>8080</port>
+ <protocol>http</protocol>
+ <site>localhost</site>
+ </socket>
+ <socket>
+ <address>127.0.0.1</address>
+ <port>8081</port>
+ <protocol>https</protocol>
+ <site>localhost</site>
+ </socket>
+ <socket>
+ <address>::1</address>
+ <port>8081</port>
+ <protocol>https</protocol>
+ <site>localhost</site>
+ </socket>
+ </sockets>
+</webserver>
+)CONFIG"};
+ WebserverProcess serverProcess{webserver_config};
+ BOOST_REQUIRE(serverProcess.is_running());
+
+ WebsocketServerProcess websocketProcess;
+ BOOST_REQUIRE(websocketProcess.is_running());
+
+ 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
+ if (host == "::1")
+ 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)
+ {
+ req.set(boost::beast::http::field::user_agent,
+ std::string("Reichwein.IT Test Websocket Client"));
+ }));
+
+ ws.set_option(boost::beast::websocket::stream_base::decorator(
+ [](boost::beast::websocket::request_type& req)
+ {
+ req.set(boost::beast::http::field::sec_websocket_protocol, "protocol1");
+ }));
+
+ // Perform the websocket handshake
+ ws.handshake(host, "/path1/target1");
+
+ // 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");
+
+ 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: 2");
+
+ // Close the WebSocket connection
+ ws.close(boost::beast::websocket::close_code::normal);
+
+ BOOST_CHECK_EQUAL(websocketProcess.subprotocol(), "protocol1");
+ BOOST_CHECK_EQUAL(websocketProcess.target(), "/path1/target1");
+ BOOST_REQUIRE(websocketProcess.is_running());
+ BOOST_REQUIRE(serverProcess.is_running());
+}
+