]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/boost/libs/beast/example/advanced/server-flex/advanced_server_flex.cpp
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / boost / libs / beast / example / advanced / server-flex / advanced_server_flex.cpp
index 30d6f462ca187e21cf0f49add43ade24dd97f189..b0c889bf9aa793a797f4cc2b00c5acf7d176bac9 100644 (file)
@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
+// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
 //
 // Distributed under the Boost Software License, Version 1.0. (See accompanying
 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 //
 //------------------------------------------------------------------------------
 
-#include "example/common/detect_ssl.hpp"
 #include "example/common/server_certificate.hpp"
-#include "example/common/ssl_stream.hpp"
 
 #include <boost/beast/core.hpp>
 #include <boost/beast/http.hpp>
+#include <boost/beast/ssl.hpp>
 #include <boost/beast/websocket.hpp>
 #include <boost/beast/version.hpp>
 #include <boost/asio/bind_executor.hpp>
-#include <boost/asio/ip/tcp.hpp>
-#include <boost/asio/ssl/stream.hpp>
-#include <boost/asio/strand.hpp>
+#include <boost/asio/dispatch.hpp>
+#include <boost/asio/signal_set.hpp>
 #include <boost/asio/steady_timer.hpp>
-#include <boost/config.hpp>
+#include <boost/asio/strand.hpp>
+#include <boost/make_unique.hpp>
+#include <boost/optional.hpp>
 #include <algorithm>
 #include <cstdlib>
 #include <functional>
 #include <thread>
 #include <vector>
 
-using tcp = boost::asio::ip::tcp;               // from <boost/asio/ip/tcp.hpp>
+namespace beast = boost::beast;                 // from <boost/beast.hpp>
+namespace http = beast::http;                   // from <boost/beast/http.hpp>
+namespace websocket = beast::websocket;         // from <boost/beast/websocket.hpp>
+namespace net = boost::asio;                    // from <boost/asio.hpp>
 namespace ssl = boost::asio::ssl;               // from <boost/asio/ssl.hpp>
-namespace http = boost::beast::http;            // from <boost/beast/http.hpp>
-namespace websocket = boost::beast::websocket;  // from <boost/beast/websocket.hpp>
+using tcp = boost::asio::ip::tcp;               // from <boost/asio/ip/tcp.hpp>
 
 // Return a reasonable mime type based on the extension of a file.
-boost::beast::string_view
-mime_type(boost::beast::string_view path)
+beast::string_view
+mime_type(beast::string_view path)
 {
-    using boost::beast::iequals;
+    using beast::iequals;
     auto const ext = [&path]
     {
         auto const pos = path.rfind(".");
-        if(pos == boost::beast::string_view::npos)
-            return boost::beast::string_view{};
+        if(pos == beast::string_view::npos)
+            return beast::string_view{};
         return path.substr(pos);
     }();
     if(iequals(ext, ".htm"))  return "text/html";
@@ -81,13 +83,13 @@ mime_type(boost::beast::string_view path)
 // The returned path is normalized for the platform.
 std::string
 path_cat(
-    boost::beast::string_view base,
-    boost::beast::string_view path)
+    beast::string_view base,
+    beast::string_view path)
 {
     if(base.empty())
-        return path.to_string();
-    std::string result = base.to_string();
-#if BOOST_MSVC
+        return std::string(path);
+    std::string result(base);
+#ifdef BOOST_MSVC
     char constexpr path_separator = '\\';
     if(result.back() == path_separator)
         result.resize(result.size() - 1);
@@ -113,45 +115,45 @@ template<
     class Send>
 void
 handle_request(
-    boost::beast::string_view doc_root,
+    beast::string_view doc_root,
     http::request<Body, http::basic_fields<Allocator>>&& req,
     Send&& send)
 {
     // Returns a bad request response
     auto const bad_request =
-    [&req](boost::beast::string_view why)
+    [&req](beast::string_view why)
     {
         http::response<http::string_body> res{http::status::bad_request, req.version()};
         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
         res.set(http::field::content_type, "text/html");
         res.keep_alive(req.keep_alive());
-        res.body() = why.to_string();
+        res.body() = std::string(why);
         res.prepare_payload();
         return res;
     };
 
     // Returns a not found response
     auto const not_found =
-    [&req](boost::beast::string_view target)
+    [&req](beast::string_view target)
     {
         http::response<http::string_body> res{http::status::not_found, req.version()};
         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
         res.set(http::field::content_type, "text/html");
         res.keep_alive(req.keep_alive());
-        res.body() = "The resource '" + target.to_string() + "' was not found.";
+        res.body() = "The resource '" + std::string(target) + "' was not found.";
         res.prepare_payload();
         return res;
     };
 
     // Returns a server error response
     auto const server_error =
-    [&req](boost::beast::string_view what)
+    [&req](beast::string_view what)
     {
         http::response<http::string_body> res{http::status::internal_server_error, req.version()};
         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
         res.set(http::field::content_type, "text/html");
         res.keep_alive(req.keep_alive());
-        res.body() = "An error occurred: '" + what.to_string() + "'";
+        res.body() = "An error occurred: '" + std::string(what) + "'";
         res.prepare_payload();
         return res;
     };
@@ -164,7 +166,7 @@ handle_request(
     // Request path must be absolute and not contain "..".
     if( req.target().empty() ||
         req.target()[0] != '/' ||
-        req.target().find("..") != boost::beast::string_view::npos)
+        req.target().find("..") != beast::string_view::npos)
         return send(bad_request("Illegal request-target"));
 
     // Build the path to the requested file
@@ -173,25 +175,28 @@ handle_request(
         path.append("index.html");
 
     // Attempt to open the file
-    boost::beast::error_code ec;
+    beast::error_code ec;
     http::file_body::value_type body;
-    body.open(path.c_str(), boost::beast::file_mode::scan, ec);
+    body.open(path.c_str(), beast::file_mode::scan, ec);
 
     // Handle the case where the file doesn't exist
-    if(ec == boost::system::errc::no_such_file_or_directory)
+    if(ec == beast::errc::no_such_file_or_directory)
         return send(not_found(req.target()));
 
     // Handle an unknown error
     if(ec)
         return send(server_error(ec.message()));
 
+    // Cache the size since we need it after the move
+    auto const size = body.size();
+
     // Respond to HEAD request
     if(req.method() == http::verb::head)
     {
         http::response<http::empty_body> res{http::status::ok, req.version()};
         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
         res.set(http::field::content_type, mime_type(path));
-        res.content_length(body.size());
+        res.content_length(size);
         res.keep_alive(req.keep_alive());
         return send(std::move(res));
     }
@@ -203,7 +208,7 @@ handle_request(
         std::make_tuple(http::status::ok, req.version())};
     res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
     res.set(http::field::content_type, mime_type(path));
-    res.content_length(body.size());
+    res.content_length(size);
     res.keep_alive(req.keep_alive());
     return send(std::move(res));
 }
@@ -212,8 +217,28 @@ handle_request(
 
 // Report a failure
 void
-fail(boost::system::error_code ec, char const* what)
+fail(beast::error_code ec, char const* what)
 {
+    // ssl::error::stream_truncated, also known as an SSL "short read",
+    // indicates the peer closed the connection without performing the
+    // required closing handshake (for example, Google does this to
+    // improve performance). Generally this can be a security issue,
+    // but if your communication protocol is self-terminated (as
+    // it is with both HTTP and WebSocket) then you may simply
+    // ignore the lack of close_notify.
+    //
+    // https://github.com/boostorg/beast/issues/38
+    //
+    // https://security.stackexchange.com/questions/91435/how-to-handle-a-malicious-ssl-tls-shutdown
+    //
+    // When a short read would cut off the end of an HTTP message,
+    // Beast returns the error beast::http::error::partial_message.
+    // Therefore, if we see a short read here, it has occurred
+    // after the message has been completed, so it is safe to ignore it.
+
+    if(ec == net::ssl::error::stream_truncated)
+        return;
+
     std::cerr << what << ": " << ec.message() << "\n";
 }
 
@@ -233,70 +258,39 @@ class websocket_session
         return static_cast<Derived&>(*this);
     }
 
-    boost::beast::multi_buffer buffer_;
-
-protected:
-    boost::asio::strand<
-        boost::asio::io_context::executor_type> strand_;
-    boost::asio::steady_timer timer_;
-
-public:
-    // Construct the session
-    explicit
-    websocket_session(boost::asio::io_context& ioc)
-        : strand_(ioc.get_executor())
-        , timer_(ioc,
-            (std::chrono::steady_clock::time_point::max)())
-    {
-    }
+    beast::flat_buffer buffer_;
 
     // Start the asynchronous operation
     template<class Body, class Allocator>
     void
     do_accept(http::request<Body, http::basic_fields<Allocator>> req)
     {
-        // Set the timer
-        timer_.expires_after(std::chrono::seconds(15));
+        // Set suggested timeout settings for the websocket
+        derived().ws().set_option(
+            websocket::stream_base::timeout::suggested(
+                beast::role_type::server));
+
+        // Set a decorator to change the Server of the handshake
+        derived().ws().set_option(
+            websocket::stream_base::decorator(
+            [](websocket::response_type& res)
+            {
+                res.set(http::field::server,
+                    std::string(BOOST_BEAST_VERSION_STRING) +
+                        " advanced-server-flex");
+            }));
 
         // Accept the websocket handshake
         derived().ws().async_accept(
             req,
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
-                    &websocket_session::on_accept,
-                    derived().shared_from_this(),
-                    std::placeholders::_1)));
-    }
-
-    // Called when the timer expires.
-    void
-    on_timer(boost::system::error_code ec)
-    {
-        if(ec && ec != boost::asio::error::operation_aborted)
-            return fail(ec, "timer");
-
-        // Verify that the timer really expired since the deadline may have moved.
-        if(timer_.expiry() <= std::chrono::steady_clock::now())
-            derived().do_timeout();
-
-        // Wait on the timer
-        timer_.async_wait(
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
-                    &websocket_session::on_timer,
-                    derived().shared_from_this(),
-                    std::placeholders::_1)));
+            beast::bind_front_handler(
+                &websocket_session::on_accept,
+                derived().shared_from_this()));
     }
 
     void
-    on_accept(boost::system::error_code ec)
+    on_accept(beast::error_code ec)
     {
-        // Happens when the timer closes the socket
-        if(ec == boost::asio::error::operation_aborted)
-            return;
-
         if(ec)
             return fail(ec, "accept");
 
@@ -307,32 +301,21 @@ public:
     void
     do_read()
     {
-        // Set the timer
-        timer_.expires_after(std::chrono::seconds(15));
-
         // Read a message into our buffer
         derived().ws().async_read(
             buffer_,
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
-                    &websocket_session::on_read,
-                    derived().shared_from_this(),
-                    std::placeholders::_1,
-                    std::placeholders::_2)));
+            beast::bind_front_handler(
+                &websocket_session::on_read,
+                derived().shared_from_this()));
     }
 
     void
     on_read(
-        boost::system::error_code ec,
+        beast::error_code ec,
         std::size_t bytes_transferred)
     {
         boost::ignore_unused(bytes_transferred);
 
-        // Happens when the timer closes the socket
-        if(ec == boost::asio::error::operation_aborted)
-            return;
-
         // This indicates that the websocket_session was closed
         if(ec == websocket::error::closed)
             return;
@@ -344,26 +327,18 @@ public:
         derived().ws().text(derived().ws().got_text());
         derived().ws().async_write(
             buffer_.data(),
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
-                    &websocket_session::on_write,
-                    derived().shared_from_this(),
-                    std::placeholders::_1,
-                    std::placeholders::_2)));
+            beast::bind_front_handler(
+                &websocket_session::on_write,
+                derived().shared_from_this()));
     }
 
     void
     on_write(
-        boost::system::error_code ec,
+        beast::error_code ec,
         std::size_t bytes_transferred)
     {
         boost::ignore_unused(bytes_transferred);
 
-        // Happens when the timer closes the socket
-        if(ec == boost::asio::error::operation_aborted)
-            return;
-
         if(ec)
             return fail(ec, "write");
 
@@ -373,183 +348,88 @@ public:
         // Do another read
         do_read();
     }
+
+public:
+    // Start the asynchronous operation
+    template<class Body, class Allocator>
+    void
+    run(http::request<Body, http::basic_fields<Allocator>> req)
+    {
+        // Accept the WebSocket upgrade request
+        do_accept(std::move(req));
+    }
 };
 
+//------------------------------------------------------------------------------
+
 // Handles a plain WebSocket connection
 class plain_websocket_session
     : public websocket_session<plain_websocket_session>
     , public std::enable_shared_from_this<plain_websocket_session>
 {
-    websocket::stream<tcp::socket> ws_;
-    bool close_ = false;
+    websocket::stream<beast::tcp_stream> ws_;
 
 public:
     // Create the session
     explicit
-    plain_websocket_session(tcp::socket socket)
-        : websocket_session<plain_websocket_session>(
-            socket.get_executor().context())
-        , ws_(std::move(socket))
+    plain_websocket_session(
+        beast::tcp_stream&& stream)
+        : ws_(std::move(stream))
     {
     }
 
     // Called by the base class
-    websocket::stream<tcp::socket>&
+    websocket::stream<beast::tcp_stream>&
     ws()
     {
         return ws_;
     }
-
-    // Start the asynchronous operation
-    template<class Body, class Allocator>
-    void
-    run(http::request<Body, http::basic_fields<Allocator>> req)
-    {
-        // Run the timer. The timer is operated
-        // continuously, this simplifies the code.
-        on_timer({});
-
-        // Accept the WebSocket upgrade request
-        do_accept(std::move(req));
-    }
-
-    void
-    do_timeout()
-    {
-        // This is so the close can have a timeout
-        if(close_)
-            return;
-        close_ = true;
-
-        // Set the timer
-        timer_.expires_after(std::chrono::seconds(15));
-
-        // Close the WebSocket Connection
-        ws_.async_close(
-            websocket::close_code::normal,
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
-                    &plain_websocket_session::on_close,
-                    shared_from_this(),
-                    std::placeholders::_1)));
-    }
-
-    void
-    on_close(boost::system::error_code ec)
-    {
-        // Happens when close times out
-        if(ec == boost::asio::error::operation_aborted)
-            return;
-
-        if(ec)
-            return fail(ec, "close");
-
-        // At this point the connection is gracefully closed
-    }
 };
 
+//------------------------------------------------------------------------------
+
 // Handles an SSL WebSocket connection
 class ssl_websocket_session
     : public websocket_session<ssl_websocket_session>
     , public std::enable_shared_from_this<ssl_websocket_session>
 {
-    websocket::stream<ssl_stream<tcp::socket>> ws_;
-    boost::asio::strand<
-        boost::asio::io_context::executor_type> strand_;
-    bool eof_ = false;
+    websocket::stream<
+        beast::ssl_stream<beast::tcp_stream>> ws_;
 
 public:
-    // Create the http_session
+    // Create the ssl_websocket_session
     explicit
-    ssl_websocket_session(ssl_stream<tcp::socket> stream)
-        : websocket_session<ssl_websocket_session>(
-            stream.get_executor().context())
-        , ws_(std::move(stream))
-        , strand_(ws_.get_executor())
+    ssl_websocket_session(
+        beast::ssl_stream<beast::tcp_stream>&& stream)
+        : ws_(std::move(stream))
     {
     }
 
     // Called by the base class
-    websocket::stream<ssl_stream<tcp::socket>>&
+    websocket::stream<
+        beast::ssl_stream<beast::tcp_stream>>&
     ws()
     {
         return ws_;
     }
-
-    // Start the asynchronous operation
-    template<class Body, class Allocator>
-    void
-    run(http::request<Body, http::basic_fields<Allocator>> req)
-    {
-        // Run the timer. The timer is operated
-        // continuously, this simplifies the code.
-        on_timer({});
-
-        // Accept the WebSocket upgrade request
-        do_accept(std::move(req));
-    }
-
-    void
-    do_eof()
-    {
-        eof_ = true;
-
-        // Set the timer
-        timer_.expires_after(std::chrono::seconds(15));
-
-        // Perform the SSL shutdown
-        ws_.next_layer().async_shutdown(
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
-                    &ssl_websocket_session::on_shutdown,
-                    shared_from_this(),
-                    std::placeholders::_1)));
-    }
-
-    void
-    on_shutdown(boost::system::error_code ec)
-    {
-        // Happens when the shutdown times out
-        if(ec == boost::asio::error::operation_aborted)
-            return;
-
-        if(ec)
-            return fail(ec, "shutdown");
-
-        // At this point the connection is closed gracefully
-    }
-
-    void
-    do_timeout()
-    {
-        // If this is true it means we timed out performing the shutdown
-        if(eof_)
-            return;
-
-        // Start the timer again
-        timer_.expires_at(
-            (std::chrono::steady_clock::time_point::max)());
-        on_timer({});
-        do_eof();
-    }
 };
 
+//------------------------------------------------------------------------------
+
 template<class Body, class Allocator>
 void
 make_websocket_session(
-    tcp::socket socket,
+    beast::tcp_stream stream,
     http::request<Body, http::basic_fields<Allocator>> req)
 {
     std::make_shared<plain_websocket_session>(
-        std::move(socket))->run(std::move(req));
+        std::move(stream))->run(std::move(req));
 }
 
 template<class Body, class Allocator>
 void
 make_websocket_session(
-    ssl_stream<tcp::socket> stream,
+    beast::ssl_stream<beast::tcp_stream> stream,
     http::request<Body, http::basic_fields<Allocator>> req)
 {
     std::make_shared<ssl_websocket_session>(
@@ -645,18 +525,16 @@ class http_session
                     http::async_write(
                         self_.derived().stream(),
                         msg_,
-                        boost::asio::bind_executor(
-                            self_.strand_,
-                            std::bind(
-                                &http_session::on_write,
-                                self_.derived().shared_from_this(),
-                                std::placeholders::_1,
-                                msg_.need_eof())));
+                        beast::bind_front_handler(
+                            &http_session::on_write,
+                            self_.derived().shared_from_this(),
+                            msg_.need_eof()));
                 }
             };
 
             // Allocate and store the work
-            items_.emplace_back(new work_impl(self_, std::move(msg)));
+            items_.push_back(
+                boost::make_unique<work_impl>(self_, std::move(msg)));
 
             // If there was no previous work, start this one
             if(items_.size() == 1)
@@ -664,27 +542,23 @@ class http_session
         }
     };
 
-    std::string const& doc_root_;
-    http::request<http::string_body> req_;
+    std::shared_ptr<std::string const> doc_root_;
     queue queue_;
 
+    // The parser is stored in an optional container so we can
+    // construct it from scratch it at the beginning of each new message.
+    boost::optional<http::request_parser<http::string_body>> parser_;
+
 protected:
-    boost::asio::steady_timer timer_;
-    boost::asio::strand<
-        boost::asio::io_context::executor_type> strand_;
-    boost::beast::flat_buffer buffer_;
+    beast::flat_buffer buffer_;
 
 public:
     // Construct the session
     http_session(
-        boost::asio::io_context& ioc,
-        boost::beast::flat_buffer buffer,
-        std::string const& doc_root)
+        beast::flat_buffer buffer,
+        std::shared_ptr<std::string const> const& doc_root)
         : doc_root_(doc_root)
         , queue_(*this)
-        , timer_(ioc,
-            (std::chrono::steady_clock::time_point::max)())
-        , strand_(ioc.get_executor())
         , buffer_(std::move(buffer))
     {
     }
@@ -692,49 +566,31 @@ public:
     void
     do_read()
     {
-        // Set the timer
-        timer_.expires_after(std::chrono::seconds(15));
+        // Construct a new parser for each message
+        parser_.emplace();
+
+        // Apply a reasonable limit to the allowed size
+        // of the body in bytes to prevent abuse.
+        parser_->body_limit(10000);
+
+        // Set the timeout.
+        beast::get_lowest_layer(
+            derived().stream()).expires_after(std::chrono::seconds(30));
 
-        // Read a request
+        // Read a request using the parser-oriented interface
         http::async_read(
             derived().stream(),
             buffer_,
-            req_,
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
-                    &http_session::on_read,
-                    derived().shared_from_this(),
-                    std::placeholders::_1)));
+            *parser_,
+            beast::bind_front_handler(
+                &http_session::on_read,
+                derived().shared_from_this()));
     }
 
-    // Called when the timer expires.
     void
-    on_timer(boost::system::error_code ec)
+    on_read(beast::error_code ec, std::size_t bytes_transferred)
     {
-        if(ec && ec != boost::asio::error::operation_aborted)
-            return fail(ec, "timer");
-
-        // Verify that the timer really expired since the deadline may have moved.
-        if(timer_.expiry() <= std::chrono::steady_clock::now())
-            return derived().do_timeout();
-
-        // Wait on the timer
-        timer_.async_wait(
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
-                    &http_session::on_timer,
-                    derived().shared_from_this(),
-                    std::placeholders::_1)));
-    }
-
-    void
-    on_read(boost::system::error_code ec)
-    {
-        // Happens when the timer closes the socket
-        if(ec == boost::asio::error::operation_aborted)
-            return;
+        boost::ignore_unused(bytes_transferred);
 
         // This means they closed the connection
         if(ec == http::error::end_of_stream)
@@ -744,16 +600,21 @@ public:
             return fail(ec, "read");
 
         // See if it is a WebSocket Upgrade
-        if(websocket::is_upgrade(req_))
+        if(websocket::is_upgrade(parser_->get()))
         {
-            // Transfer the stream to a new WebSocket session
+            // Disable the timeout.
+            // The websocket::stream uses its own timeout settings.
+            beast::get_lowest_layer(derived().stream()).expires_never();
+
+            // Create a websocket session, transferring ownership
+            // of both the socket and the HTTP request.
             return make_websocket_session(
                 derived().release_stream(),
-                std::move(req_));
+                parser_->release());
         }
 
         // Send the response
-        handle_request(doc_root_, std::move(req_), queue_);
+        handle_request(*doc_root_, parser_->release(), queue_);
 
         // If we aren't at the queue limit, try to pipeline another request
         if(! queue_.is_full())
@@ -761,11 +622,9 @@ public:
     }
 
     void
-    on_write(boost::system::error_code ec, bool close)
+    on_write(bool close, beast::error_code ec, std::size_t bytes_transferred)
     {
-        // Happens when the timer closes the socket
-        if(ec == boost::asio::error::operation_aborted)
-            return;
+        boost::ignore_unused(bytes_transferred);
 
         if(ec)
             return fail(ec, "write");
@@ -786,149 +645,135 @@ public:
     }
 };
 
+//------------------------------------------------------------------------------
+
 // Handles a plain HTTP connection
 class plain_http_session
     : public http_session<plain_http_session>
     , public std::enable_shared_from_this<plain_http_session>
 {
-    tcp::socket socket_;
-    boost::asio::strand<
-        boost::asio::io_context::executor_type> strand_;
+    beast::tcp_stream stream_;
 
 public:
-    // Create the http_session
+    // Create the session
     plain_http_session(
-        tcp::socket socket,
-        boost::beast::flat_buffer buffer,
-        std::string const& doc_root)
+        beast::tcp_stream&& stream,
+        beast::flat_buffer&& buffer,
+        std::shared_ptr<std::string const> const& doc_root)
         : http_session<plain_http_session>(
-            socket.get_executor().context(),
             std::move(buffer),
             doc_root)
-        , socket_(std::move(socket))
-        , strand_(socket_.get_executor())
+        , stream_(std::move(stream))
     {
     }
 
-    // Called by the base class
-    tcp::socket&
-    stream()
+    // Start the session
+    void
+    run()
     {
-        return socket_;
+        this->do_read();
     }
 
     // Called by the base class
-    tcp::socket
-    release_stream()
+    beast::tcp_stream&
+    stream()
     {
-        return std::move(socket_);
+        return stream_;
     }
 
-    // Start the asynchronous operation
-    void
-    run()
+    // Called by the base class
+    beast::tcp_stream
+    release_stream()
     {
-        // Run the timer. The timer is operated
-        // continuously, this simplifies the code.
-        on_timer({});
-
-        do_read();
+        return std::move(stream_);
     }
 
+    // Called by the base class
     void
     do_eof()
     {
         // Send a TCP shutdown
-        boost::system::error_code ec;
-        socket_.shutdown(tcp::socket::shutdown_send, ec);
+        beast::error_code ec;
+        stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
 
         // At this point the connection is closed gracefully
     }
-
-    void
-    do_timeout()
-    {
-        // Closing the socket cancels all outstanding operations. They
-        // will complete with boost::asio::error::operation_aborted
-        boost::system::error_code ec;
-        socket_.shutdown(tcp::socket::shutdown_both, ec);
-        socket_.close(ec);
-    }
 };
 
+//------------------------------------------------------------------------------
+
 // Handles an SSL HTTP connection
 class ssl_http_session
     : public http_session<ssl_http_session>
     , public std::enable_shared_from_this<ssl_http_session>
 {
-    ssl_stream<tcp::socket> stream_;
-    boost::asio::strand<
-        boost::asio::io_context::executor_type> strand_;
-    bool eof_ = false;
+    beast::ssl_stream<beast::tcp_stream> stream_;
 
 public:
     // Create the http_session
     ssl_http_session(
-        tcp::socket socket,
+        beast::tcp_stream&& stream,
         ssl::context& ctx,
-        boost::beast::flat_buffer buffer,
-        std::string const& doc_root)
+        beast::flat_buffer&& buffer,
+        std::shared_ptr<std::string const> const& doc_root)
         : http_session<ssl_http_session>(
-            socket.get_executor().context(),
             std::move(buffer),
             doc_root)
-        , stream_(std::move(socket), ctx)
-        , strand_(stream_.get_executor())
+        , stream_(std::move(stream), ctx)
+    {
+    }
+
+    // Start the session
+    void
+    run()
     {
+        // Set the timeout.
+        beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
+
+        // Perform the SSL handshake
+        // Note, this is the buffered version of the handshake.
+        stream_.async_handshake(
+            ssl::stream_base::server,
+            buffer_.data(),
+            beast::bind_front_handler(
+                &ssl_http_session::on_handshake,
+                shared_from_this()));
     }
 
     // Called by the base class
-    ssl_stream<tcp::socket>&
+    beast::ssl_stream<beast::tcp_stream>&
     stream()
     {
         return stream_;
     }
 
     // Called by the base class
-    ssl_stream<tcp::socket>
+    beast::ssl_stream<beast::tcp_stream>
     release_stream()
     {
         return std::move(stream_);
     }
 
-    // Start the asynchronous operation
+    // Called by the base class
     void
-    run()
+    do_eof()
     {
-        // Run the timer. The timer is operated
-        // continuously, this simplifies the code.
-        on_timer({});
+        // Set the timeout.
+        beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
 
-        // Set the timer
-        timer_.expires_after(std::chrono::seconds(15));
-
-        // Perform the SSL handshake
-        // Note, this is the buffered version of the handshake.
-        stream_.async_handshake(
-            ssl::stream_base::server,
-            buffer_.data(),
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
-                    &ssl_http_session::on_handshake,
-                    shared_from_this(),
-                    std::placeholders::_1,
-                    std::placeholders::_2)));
+        // Perform the SSL shutdown
+        stream_.async_shutdown(
+            beast::bind_front_handler(
+                &ssl_http_session::on_shutdown,
+                shared_from_this()));
     }
+
+private:
     void
     on_handshake(
-        boost::system::error_code ec,
+        beast::error_code ec,
         std::size_t bytes_used)
     {
-        // Happens when the handshake times out
-        if(ec == boost::asio::error::operation_aborted)
-            return;
-
         if(ec)
             return fail(ec, "handshake");
 
@@ -939,49 +784,13 @@ public:
     }
 
     void
-    do_eof()
-    {
-        eof_ = true;
-
-        // Set the timer
-        timer_.expires_after(std::chrono::seconds(15));
-
-        // Perform the SSL shutdown
-        stream_.async_shutdown(
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
-                    &ssl_http_session::on_shutdown,
-                    shared_from_this(),
-                    std::placeholders::_1)));
-    }
-
-    void
-    on_shutdown(boost::system::error_code ec)
+    on_shutdown(beast::error_code ec)
     {
-        // Happens when the shutdown times out
-        if(ec == boost::asio::error::operation_aborted)
-            return;
-
         if(ec)
             return fail(ec, "shutdown");
 
         // At this point the connection is closed gracefully
     }
-
-    void
-    do_timeout()
-    {
-        // If this is true it means we timed out performing the shutdown
-        if(eof_)
-            return;
-
-        // Start the timer again
-        timer_.expires_at(
-            (std::chrono::steady_clock::time_point::max)());
-        on_timer({});
-        do_eof();
-    }
 };
 
 //------------------------------------------------------------------------------
@@ -989,22 +798,19 @@ public:
 // Detects SSL handshakes
 class detect_session : public std::enable_shared_from_this<detect_session>
 {
-    tcp::socket socket_;
+    beast::tcp_stream stream_;
     ssl::context& ctx_;
-    boost::asio::strand<
-        boost::asio::io_context::executor_type> strand_;
-    std::string const& doc_root_;
-    boost::beast::flat_buffer buffer_;
+    std::shared_ptr<std::string const> doc_root_;
+    beast::flat_buffer buffer_;
 
 public:
     explicit
     detect_session(
-        tcp::socket socket,
+        tcp::socket&& socket,
         ssl::context& ctx,
-        std::string const& doc_root)
-        : socket_(std::move(socket))
+        std::shared_ptr<std::string const> const& doc_root)
+        : stream_(std::move(socket))
         , ctx_(ctx)
-        , strand_(socket_.get_executor())
         , doc_root_(doc_root)
     {
     }
@@ -1013,21 +819,33 @@ public:
     void
     run()
     {
-        async_detect_ssl(
-            socket_,
-            buffer_,
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
-                    &detect_session::on_detect,
-                    shared_from_this(),
-                    std::placeholders::_1,
-                    std::placeholders::_2)));
+        // We need to be executing within a strand to perform async operations
+        // on the I/O objects in this session. Although not strictly necessary
+        // for single-threaded contexts, this example code is written to be
+        // thread-safe by default.
+        net::dispatch(
+            stream_.get_executor(),
+            beast::bind_front_handler(
+                &detect_session::on_run,
+                this->shared_from_this()));
+    }
+
+    void
+    on_run()
+    {
+        // Set the timeout.
+        stream_.expires_after(std::chrono::seconds(30));
 
+        beast::async_detect_ssl(
+            stream_,
+            buffer_,
+            beast::bind_front_handler(
+                &detect_session::on_detect,
+                this->shared_from_this()));
     }
 
     void
-    on_detect(boost::system::error_code ec, boost::tribool result)
+    on_detect(beast::error_code ec, bool result)
     {
         if(ec)
             return fail(ec, "detect");
@@ -1036,7 +854,7 @@ public:
         {
             // Launch SSL session
             std::make_shared<ssl_http_session>(
-                std::move(socket_),
+                std::move(stream_),
                 ctx_,
                 std::move(buffer_),
                 doc_root_)->run();
@@ -1045,7 +863,7 @@ public:
 
         // Launch plain session
         std::make_shared<plain_http_session>(
-            std::move(socket_),
+            std::move(stream_),
             std::move(buffer_),
             doc_root_)->run();
     }
@@ -1054,23 +872,23 @@ public:
 // 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_;
-    tcp::socket socket_;
-    std::string const& doc_root_;
+    std::shared_ptr<std::string const> doc_root_;
 
 public:
     listener(
-        boost::asio::io_context& ioc,
+        net::io_context& ioc,
         ssl::context& ctx,
         tcp::endpoint endpoint,
-        std::string const& doc_root)
-        : ctx_(ctx)
-        , acceptor_(ioc)
-        , socket_(ioc)
+        std::shared_ptr<std::string const> const& doc_root)
+        : ioc_(ioc)
+        , ctx_(ctx)
+        , acceptor_(net::make_strand(ioc))
         , doc_root_(doc_root)
     {
-        boost::system::error_code ec;
+        beast::error_code ec;
 
         // Open the acceptor
         acceptor_.open(endpoint.protocol(), ec);
@@ -1080,6 +898,14 @@ public:
             return;
         }
 
+        // Allow address reuse
+        acceptor_.set_option(net::socket_base::reuse_address(true), ec);
+        if(ec)
+        {
+            fail(ec, "set_option");
+            return;
+        }
+
         // Bind to the server address
         acceptor_.bind(endpoint, ec);
         if(ec)
@@ -1090,7 +916,7 @@ public:
 
         // Start listening for connections
         acceptor_.listen(
-            boost::asio::socket_base::max_listen_connections, ec);
+            net::socket_base::max_listen_connections, ec);
         if(ec)
         {
             fail(ec, "listen");
@@ -1102,24 +928,23 @@ public:
     void
     run()
     {
-        if(! acceptor_.is_open())
-            return;
         do_accept();
     }
 
+private:
     void
     do_accept()
     {
+        // The new connection gets its own strand
         acceptor_.async_accept(
-            socket_,
-            std::bind(
+            net::make_strand(ioc_),
+            beast::bind_front_handler(
                 &listener::on_accept,
-                shared_from_this(),
-                std::placeholders::_1));
+                shared_from_this()));
     }
 
     void
-    on_accept(boost::system::error_code ec)
+    on_accept(beast::error_code ec, tcp::socket socket)
     {
         if(ec)
         {
@@ -1129,7 +954,7 @@ public:
         {
             // Create the detector http_session and run it
             std::make_shared<detect_session>(
-                std::move(socket_),
+                std::move(socket),
                 ctx_,
                 doc_root_)->run();
         }
@@ -1152,16 +977,16 @@ int main(int argc, char* argv[])
             "    advanced-server-flex 0.0.0.0 8080 . 1\n";
         return EXIT_FAILURE;
     }
-    auto const address = boost::asio::ip::make_address(argv[1]);
+    auto const address = net::ip::make_address(argv[1]);
     auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
-    std::string const doc_root = argv[3];
+    auto const doc_root = std::make_shared<std::string>(argv[3]);
     auto const threads = std::max<int>(1, std::atoi(argv[4]));
 
     // The io_context is required for all I/O
-    boost::asio::io_context ioc{threads};
+    net::io_context ioc{threads};
 
     // The SSL context is required, and holds certificates
-    ssl::context ctx{ssl::context::sslv23};
+    ssl::context ctx{ssl::context::tlsv12};
 
     // This holds the self-signed certificate used by the server
     load_server_certificate(ctx);
@@ -1173,6 +998,17 @@ int main(int argc, char* argv[])
         tcp::endpoint{address, port},
         doc_root)->run();
 
+    // Capture SIGINT and SIGTERM to perform a clean shutdown
+    net::signal_set signals(ioc, SIGINT, SIGTERM);
+    signals.async_wait(
+        [&](beast::error_code const&, int)
+        {
+            // Stop the `io_context`. This will cause `run()`
+            // to return immediately, eventually destroying the
+            // `io_context` and all of the sockets in it.
+            ioc.stop();
+        });
+
     // Run the I/O service on the requested number of threads
     std::vector<std::thread> v;
     v.reserve(threads - 1);
@@ -1184,5 +1020,11 @@ int main(int argc, char* argv[])
         });
     ioc.run();
 
+    // (If we get here, it means we got a SIGINT or SIGTERM)
+
+    // Block until all the threads exit
+    for(auto& t : v)
+        t.join();
+
     return EXIT_SUCCESS;
 }