diff options
| -rw-r--r-- | TODO | 4 | ||||
| -rw-r--r-- | http.cpp | 24 | ||||
| -rw-r--r-- | https.cpp | 2 | ||||
| -rw-r--r-- | tests/test-webserver.cpp | 29 | ||||
| -rw-r--r-- | websocket.cpp | 10 | ||||
| -rw-r--r-- | websocket.h | 94 | 
6 files changed, 113 insertions, 50 deletions
@@ -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 @@ -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();          } @@ -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); +  | 
