diff options
| -rw-r--r-- | Makefile | 7 | ||||
| -rw-r--r-- | TODO | 7 | ||||
| -rw-r--r-- | server_certificate.h | 124 | ||||
| -rw-r--r-- | webserver.cpp | 547 | 
4 files changed, 683 insertions, 2 deletions
@@ -10,6 +10,7 @@ CXXFLAGS=-O0 -g -D_DEBUG  CXXFLAGS+= -Wall -I. +CXXFLAGS+= -pthread  ifeq ($(CXX),clang++-10)  CXXFLAGS+=-std=c++20 -stdlib=libc++  else @@ -26,7 +27,8 @@ LIBS=\  -lboost_thread \  -lboost_filesystem \  -lboost_regex \ --lpthread +-lpthread \ +-lssl -lcrypto  ifeq ($(CXX),clang++-10)  LIBS+= \ @@ -60,6 +62,9 @@ all: test-$(PROJECTNAME) $(PROJECTNAME)  test-$(PROJECTNAME): $(TESTSRC:.cpp=.o)  	$(CXX) $(CXXFLAGS) $^ $(LIBS) -o $@ +$(PROJECTNAME): $(SRC:.cpp=.o) +	$(CXX) $(CXXFLAGS) $^ $(LIBS) -o $@ +  dep: $(TESTSRC:.cpp=.d)  %.d: %.cpp @@ -0,0 +1,7 @@ +Debian package +Plugin: https://www.boost.org/doc/libs/1_72_0/doc/html/boost_dll/tutorial.html#boost_dll.tutorial.symbol_shadowing_problem__linux_ +HTTP+HTTPS: https://www.boost.org/doc/libs/1_72_0/libs/beast/doc/html/beast/examples.html#beast.examples.servers +Config: https://www.boost.org/doc/libs/1_72_0/doc/html/property_tree/tutorial.html +Certbot: https://certbot.eff.org/lets-encrypt/debianbuster-other + +  diff --git a/server_certificate.h b/server_certificate.h new file mode 100644 index 0000000..a20110e --- /dev/null +++ b/server_certificate.h @@ -0,0 +1,124 @@ +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_EXAMPLE_COMMON_SERVER_CERTIFICATE_HPP +#define BOOST_BEAST_EXAMPLE_COMMON_SERVER_CERTIFICATE_HPP + +#include <boost/asio/buffer.hpp> +#include <boost/asio/ssl/context.hpp> +#include <cstddef> +#include <memory> + +/*  Load a signed certificate into the ssl context, and configure +    the context for use with a server. + +    For this to work with the browser or operating system, it is +    necessary to import the "Beast Test CA" certificate into +    the local certificate store, browser, or operating system +    depending on your environment Please see the documentation +    accompanying the Beast certificate for more details. +*/ +inline +void +load_server_certificate(boost::asio::ssl::context& ctx) +{ +    /* +        The certificate was generated from CMD.EXE on Windows 10 using: + +        winpty openssl dhparam -out dh.pem 2048 +        winpty openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 10000 -out cert.pem -subj "//C=US\ST=CA\L=Los Angeles\O=Beast\CN=www.example.com" +    */ + +    std::string const cert = +        "-----BEGIN CERTIFICATE-----\n" +        "MIIDaDCCAlCgAwIBAgIJAO8vBu8i8exWMA0GCSqGSIb3DQEBCwUAMEkxCzAJBgNV\n" +        "BAYTAlVTMQswCQYDVQQIDAJDQTEtMCsGA1UEBwwkTG9zIEFuZ2VsZXNPPUJlYXN0\n" +        "Q049d3d3LmV4YW1wbGUuY29tMB4XDTE3MDUwMzE4MzkxMloXDTQ0MDkxODE4Mzkx\n" +        "MlowSTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMS0wKwYDVQQHDCRMb3MgQW5n\n" +        "ZWxlc089QmVhc3RDTj13d3cuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA\n" +        "A4IBDwAwggEKAoIBAQDJ7BRKFO8fqmsEXw8v9YOVXyrQVsVbjSSGEs4Vzs4cJgcF\n" +        "xqGitbnLIrOgiJpRAPLy5MNcAXE1strVGfdEf7xMYSZ/4wOrxUyVw/Ltgsft8m7b\n" +        "Fu8TsCzO6XrxpnVtWk506YZ7ToTa5UjHfBi2+pWTxbpN12UhiZNUcrRsqTFW+6fO\n" +        "9d7xm5wlaZG8cMdg0cO1bhkz45JSl3wWKIES7t3EfKePZbNlQ5hPy7Pd5JTmdGBp\n" +        "yY8anC8u4LPbmgW0/U31PH0rRVfGcBbZsAoQw5Tc5dnb6N2GEIbq3ehSfdDHGnrv\n" +        "enu2tOK9Qx6GEzXh3sekZkxcgh+NlIxCNxu//Dk9AgMBAAGjUzBRMB0GA1UdDgQW\n" +        "BBTZh0N9Ne1OD7GBGJYz4PNESHuXezAfBgNVHSMEGDAWgBTZh0N9Ne1OD7GBGJYz\n" +        "4PNESHuXezAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCmTJVT\n" +        "LH5Cru1vXtzb3N9dyolcVH82xFVwPewArchgq+CEkajOU9bnzCqvhM4CryBb4cUs\n" +        "gqXWp85hAh55uBOqXb2yyESEleMCJEiVTwm/m26FdONvEGptsiCmF5Gxi0YRtn8N\n" +        "V+KhrQaAyLrLdPYI7TrwAOisq2I1cD0mt+xgwuv/654Rl3IhOMx+fKWKJ9qLAiaE\n" +        "fQyshjlPP9mYVxWOxqctUdQ8UnsUKKGEUcVrA08i1OAnVKlPFjKBvk+r7jpsTPcr\n" +        "9pWXTO9JrYMML7d+XRSZA1n3856OqZDX4403+9FnXCvfcLZLLKTBvwwFgEFGpzjK\n" +        "UEVbkhd5qstF6qWK\n" +        "-----END CERTIFICATE-----\n"; + +    std::string const key = +        "-----BEGIN PRIVATE KEY-----\n" +        "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJ7BRKFO8fqmsE\n" +        "Xw8v9YOVXyrQVsVbjSSGEs4Vzs4cJgcFxqGitbnLIrOgiJpRAPLy5MNcAXE1strV\n" +        "GfdEf7xMYSZ/4wOrxUyVw/Ltgsft8m7bFu8TsCzO6XrxpnVtWk506YZ7ToTa5UjH\n" +        "fBi2+pWTxbpN12UhiZNUcrRsqTFW+6fO9d7xm5wlaZG8cMdg0cO1bhkz45JSl3wW\n" +        "KIES7t3EfKePZbNlQ5hPy7Pd5JTmdGBpyY8anC8u4LPbmgW0/U31PH0rRVfGcBbZ\n" +        "sAoQw5Tc5dnb6N2GEIbq3ehSfdDHGnrvenu2tOK9Qx6GEzXh3sekZkxcgh+NlIxC\n" +        "Nxu//Dk9AgMBAAECggEBAK1gV8uETg4SdfE67f9v/5uyK0DYQH1ro4C7hNiUycTB\n" +        "oiYDd6YOA4m4MiQVJuuGtRR5+IR3eI1zFRMFSJs4UqYChNwqQGys7CVsKpplQOW+\n" +        "1BCqkH2HN/Ix5662Dv3mHJemLCKUON77IJKoq0/xuZ04mc9csykox6grFWB3pjXY\n" +        "OEn9U8pt5KNldWfpfAZ7xu9WfyvthGXlhfwKEetOuHfAQv7FF6s25UIEU6Hmnwp9\n" +        "VmYp2twfMGdztz/gfFjKOGxf92RG+FMSkyAPq/vhyB7oQWxa+vdBn6BSdsfn27Qs\n" +        "bTvXrGe4FYcbuw4WkAKTljZX7TUegkXiwFoSps0jegECgYEA7o5AcRTZVUmmSs8W\n" +        "PUHn89UEuDAMFVk7grG1bg8exLQSpugCykcqXt1WNrqB7x6nB+dbVANWNhSmhgCg\n" +        "VrV941vbx8ketqZ9YInSbGPWIU/tss3r8Yx2Ct3mQpvpGC6iGHzEc/NHJP8Efvh/\n" +        "CcUWmLjLGJYYeP5oNu5cncC3fXUCgYEA2LANATm0A6sFVGe3sSLO9un1brA4zlZE\n" +        "Hjd3KOZnMPt73B426qUOcw5B2wIS8GJsUES0P94pKg83oyzmoUV9vJpJLjHA4qmL\n" +        "CDAd6CjAmE5ea4dFdZwDDS8F9FntJMdPQJA9vq+JaeS+k7ds3+7oiNe+RUIHR1Sz\n" +        "VEAKh3Xw66kCgYB7KO/2Mchesu5qku2tZJhHF4QfP5cNcos511uO3bmJ3ln+16uR\n" +        "GRqz7Vu0V6f7dvzPJM/O2QYqV5D9f9dHzN2YgvU9+QSlUeFK9PyxPv3vJt/WP1//\n" +        "zf+nbpaRbwLxnCnNsKSQJFpnrE166/pSZfFbmZQpNlyeIuJU8czZGQTifQKBgHXe\n" +        "/pQGEZhVNab+bHwdFTxXdDzr+1qyrodJYLaM7uFES9InVXQ6qSuJO+WosSi2QXlA\n" +        "hlSfwwCwGnHXAPYFWSp5Owm34tbpp0mi8wHQ+UNgjhgsE2qwnTBUvgZ3zHpPORtD\n" +        "23KZBkTmO40bIEyIJ1IZGdWO32q79nkEBTY+v/lRAoGBAI1rbouFYPBrTYQ9kcjt\n" +        "1yfu4JF5MvO9JrHQ9tOwkqDmNCWx9xWXbgydsn/eFtuUMULWsG3lNjfst/Esb8ch\n" +        "k5cZd6pdJZa4/vhEwrYYSuEjMCnRb0lUsm7TsHxQrUd6Fi/mUuFU/haC0o0chLq7\n" +        "pVOUFq5mW8p0zbtfHbjkgxyF\n" +        "-----END PRIVATE KEY-----\n"; + +    std::string const dh = +        "-----BEGIN DH PARAMETERS-----\n" +        "MIIBCAKCAQEArzQc5mpm0Fs8yahDeySj31JZlwEphUdZ9StM2D8+Fo7TMduGtSi+\n" +        "/HRWVwHcTFAgrxVdm+dl474mOUqqaz4MpzIb6+6OVfWHbQJmXPepZKyu4LgUPvY/\n" +        "4q3/iDMjIS0fLOu/bLuObwU5ccZmDgfhmz1GanRlTQOiYRty3FiOATWZBRh6uv4u\n" +        "tff4A9Bm3V9tLx9S6djq31w31Gl7OQhryodW28kc16t9TvO1BzcV3HjRPwpe701X\n" +        "oEEZdnZWANkkpR/m/pfgdmGPU66S2sXMHgsliViQWpDCYeehrvFRHEdR9NV+XJfC\n" +        "QMUk26jPTIVTLfXmmwU0u8vUkpR7LQKkwwIBAg==\n" +        "-----END DH PARAMETERS-----\n"; +     +    ctx.set_password_callback( +        [](std::size_t, +            boost::asio::ssl::context_base::password_purpose) +        { +            return "test"; +        }); + +    ctx.set_options( +        boost::asio::ssl::context::default_workarounds | +        boost::asio::ssl::context::no_sslv2 | +        boost::asio::ssl::context::single_dh_use); + +    ctx.use_certificate_chain( +        boost::asio::buffer(cert.data(), cert.size())); + +    ctx.use_private_key( +        boost::asio::buffer(key.data(), key.size()), +        boost::asio::ssl::context::file_format::pem); + +    ctx.use_tmp_dh( +        boost::asio::buffer(dh.data(), dh.size())); +} + +#endif diff --git a/webserver.cpp b/webserver.cpp index 8927fe4..d2d1bee 100644 --- a/webserver.cpp +++ b/webserver.cpp @@ -1,4 +1,549 @@ +#include "server_certificate.h" + +#include <boost/beast/core.hpp> +#include <boost/beast/http.hpp> +#include <boost/beast/ssl.hpp> +#include <boost/beast/version.hpp> +#include <boost/asio/dispatch.hpp> +#include <boost/asio/strand.hpp> +#include <boost/config.hpp> +#include <algorithm> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <memory> +#include <string> +#include <thread> +#include <vector> + +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> +using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp> + +// Return a reasonable mime type based on the extension of a file. +beast::string_view +mime_type(beast::string_view path) +{ +    using beast::iequals; +    auto const ext = [&path] +    { +        auto const pos = path.rfind("."); +        if(pos == beast::string_view::npos) +            return beast::string_view{}; +        return path.substr(pos); +    }(); +    if(iequals(ext, ".htm"))  return "text/html"; +    if(iequals(ext, ".html")) return "text/html"; +    if(iequals(ext, ".php"))  return "text/html"; +    if(iequals(ext, ".css"))  return "text/css"; +    if(iequals(ext, ".txt"))  return "text/plain"; +    if(iequals(ext, ".js"))   return "application/javascript"; +    if(iequals(ext, ".json")) return "application/json"; +    if(iequals(ext, ".xml"))  return "application/xml"; +    if(iequals(ext, ".swf"))  return "application/x-shockwave-flash"; +    if(iequals(ext, ".flv"))  return "video/x-flv"; +    if(iequals(ext, ".png"))  return "image/png"; +    if(iequals(ext, ".jpe"))  return "image/jpeg"; +    if(iequals(ext, ".jpeg")) return "image/jpeg"; +    if(iequals(ext, ".jpg"))  return "image/jpeg"; +    if(iequals(ext, ".gif"))  return "image/gif"; +    if(iequals(ext, ".bmp"))  return "image/bmp"; +    if(iequals(ext, ".ico"))  return "image/vnd.microsoft.icon"; +    if(iequals(ext, ".tiff")) return "image/tiff"; +    if(iequals(ext, ".tif"))  return "image/tiff"; +    if(iequals(ext, ".svg"))  return "image/svg+xml"; +    if(iequals(ext, ".svgz")) return "image/svg+xml"; +    return "application/text"; +} + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( +    beast::string_view base, +    beast::string_view path) +{ +    if(base.empty()) +        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); +    result.append(path.data(), path.size()); +    for(auto& c : result) +        if(c == '/') +            c = path_separator; +#else +    char constexpr path_separator = '/'; +    if(result.back() == path_separator) +        result.resize(result.size() - 1); +    result.append(path.data(), path.size()); +#endif +    return result; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< +    class Body, class Allocator, +    class Send> +void +handle_request( +    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](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() = std::string(why); +        res.prepare_payload(); +        return res; +    }; + +    // Returns a not found response +    auto const not_found = +    [&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 '" + std::string(target) + "' was not found."; +        res.prepare_payload(); +        return res; +    }; + +    // Returns a server error response +    auto const server_error = +    [&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: '" + std::string(what) + "'"; +        res.prepare_payload(); +        return res; +    }; + +    // Make sure we can handle the method +    if( req.method() != http::verb::get && +        req.method() != http::verb::head) +        return send(bad_request("Unknown HTTP-method")); + +    // Request path must be absolute and not contain "..". +    if( req.target().empty() || +        req.target()[0] != '/' || +        req.target().find("..") != beast::string_view::npos) +        return send(bad_request("Illegal request-target")); + +    // Build the path to the requested file +    std::string path = path_cat(doc_root, req.target()); +    if(req.target().back() == '/') +        path.append("index.html"); + +    // Attempt to open the file +    beast::error_code ec; +    http::file_body::value_type body; +    body.open(path.c_str(), beast::file_mode::scan, ec); + +    // Handle the case where the file doesn't exist +    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(size); +        res.keep_alive(req.keep_alive()); +        return send(std::move(res)); +    } + +    // Respond to GET request +    http::response<http::file_body> res{ +        std::piecewise_construct, +        std::make_tuple(std::move(body)), +        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(size); +    res.keep_alive(req.keep_alive()); +    return send(std::move(res)); +} + +//------------------------------------------------------------------------------ + +// Report a failure +void +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"; +} + +// Handles an HTTP server connection +class session : public std::enable_shared_from_this<session> +{ +    // This is the C++11 equivalent of a generic lambda. +    // The function object is used to send an HTTP message. +    struct send_lambda +    { +        session& self_; + +        explicit +        send_lambda(session& self) +            : self_(self) +        { +        } + +        template<bool isRequest, class Body, class Fields> +        void +        operator()(http::message<isRequest, Body, Fields>&& msg) const +        { +            // The lifetime of the message has to extend +            // for the duration of the async operation so +            // we use a shared_ptr to manage it. +            auto sp = std::make_shared< +                http::message<isRequest, Body, Fields>>(std::move(msg)); + +            // Store a type-erased version of the shared +            // pointer in the class to keep it alive. +            self_.res_ = sp; + +            // Write the response +            http::async_write( +                self_.stream_, +                *sp, +                beast::bind_front_handler( +                    &session::on_write, +                    self_.shared_from_this(), +                    sp->need_eof())); +        } +    }; + +    beast::ssl_stream<beast::tcp_stream> stream_; +    beast::flat_buffer buffer_; +    std::shared_ptr<std::string const> doc_root_; +    http::request<http::string_body> req_; +    std::shared_ptr<void> res_; +    send_lambda lambda_; + +public: +    // Take ownership of the socket +    explicit +    session( +        tcp::socket&& socket, +        ssl::context& ctx, +        std::shared_ptr<std::string const> const& doc_root) +        : stream_(std::move(socket), ctx) +        , doc_root_(doc_root) +        , lambda_(*this) +    { +    } + +    // Start the asynchronous operation +    void +    run() +    { +        // 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( +                &session::on_run, +                shared_from_this())); +    } + +    void +    on_run() +    { +        // Set the timeout. +        beast::get_lowest_layer(stream_).expires_after( +            std::chrono::seconds(30)); + +        // Perform the SSL handshake +        stream_.async_handshake( +            ssl::stream_base::server, +            beast::bind_front_handler( +                &session::on_handshake, +                shared_from_this())); +    } + +    void +    on_handshake(beast::error_code ec) +    { +        if(ec) +            return fail(ec, "handshake"); + +        do_read(); +    } + +    void +    do_read() +    { +        // Make the request empty before reading, +        // otherwise the operation behavior is undefined. +        req_ = {}; + +        // Set the timeout. +        beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); + +        // Read a request +        http::async_read(stream_, buffer_, req_, +            beast::bind_front_handler( +                &session::on_read, +                shared_from_this())); +    } + +    void +    on_read( +        beast::error_code ec, +        std::size_t bytes_transferred) +    { +        boost::ignore_unused(bytes_transferred); + +        // This means they closed the connection +        if(ec == http::error::end_of_stream) +            return do_close(); + +        if(ec) +            return fail(ec, "read"); + +        // Send the response +        handle_request(*doc_root_, std::move(req_), lambda_); +    } + +    void +    on_write( +        bool close, +        beast::error_code ec, +        std::size_t bytes_transferred) +    { +        boost::ignore_unused(bytes_transferred); + +        if(ec) +            return fail(ec, "write"); + +        if(close) +        { +            // This means we should close the connection, usually because +            // the response indicated the "Connection: close" semantic. +            return do_close(); +        } + +        // We're done with the response so delete it +        res_ = nullptr; + +        // Read another request +        do_read(); +    } + +    void +    do_close() +    { +        // Set the timeout. +        beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); + +        // Perform the SSL shutdown +        stream_.async_shutdown( +            beast::bind_front_handler( +                &session::on_shutdown, +                shared_from_this())); +    } + +    void +    on_shutdown(beast::error_code ec) +    { +        if(ec) +            return fail(ec, "shutdown"); + +        // At this point the connection is closed gracefully +    } +}; + +//------------------------------------------------------------------------------ + +// 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_; +    std::shared_ptr<std::string const> doc_root_; + +public: +    listener( +        net::io_context& ioc, +        ssl::context& ctx, +        tcp::endpoint endpoint, +        std::shared_ptr<std::string const> const& doc_root) +        : ioc_(ioc) +        , ctx_(ctx) +        , acceptor_(ioc) +        , doc_root_(doc_root) +    { +        beast::error_code ec; + +        // Open the acceptor +        acceptor_.open(endpoint.protocol(), ec); +        if(ec) +        { +            fail(ec, "open"); +            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) +        { +            fail(ec, "bind"); +            return; +        } + +        // Start listening for connections +        acceptor_.listen( +            net::socket_base::max_listen_connections, ec); +        if(ec) +        { +            fail(ec, "listen"); +            return; +        } +    } + +    // Start accepting incoming connections +    void +    run() +    { +        do_accept(); +    } + +private: +    void +    do_accept() +    { +        // The new connection gets its own strand +        acceptor_.async_accept( +            net::make_strand(ioc_), +            beast::bind_front_handler( +                &listener::on_accept, +                shared_from_this())); +    } + +    void +    on_accept(beast::error_code ec, tcp::socket socket) +    { +        if(ec) +        { +            fail(ec, "accept"); +        } +        else +        { +            // Create the session and run it +            std::make_shared<session>( +                std::move(socket), +                ctx_, +                doc_root_)->run(); +        } + +        // Accept another connection +        do_accept(); +    } +}; + +//------------------------------------------------------------------------------ +  int main(int argc, char* argv[])  { - return 0; +    // Check command line arguments. +    if (argc != 5) +    { +        std::cerr << +            "Usage: http-server-async-ssl <address> <port> <doc_root> <threads>\n" << +            "Example:\n" << +            "    http-server-async-ssl 0.0.0.0 8080 . 1\n"; +        return EXIT_FAILURE; +    } +    auto const address = net::ip::make_address(argv[1]); +    auto const port = static_cast<unsigned short>(std::atoi(argv[2])); +    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 +    net::io_context ioc{threads}; + +    // The SSL context is required, and holds certificates +    ssl::context ctx{ssl::context::tlsv12}; + +    // This holds the self-signed certificate used by the server +    load_server_certificate(ctx); + +    // Create and launch a listening port +    std::make_shared<listener>( +        ioc, +        ctx, +        tcp::endpoint{address, port}, +        doc_root)->run(); + +    // Run the I/O service on the requested number of threads +    std::vector<std::thread> v; +    v.reserve(threads - 1); +    for(auto i = threads - 1; i > 0; --i) +        v.emplace_back( +        [&ioc] +        { +            ioc.run(); +        }); +    ioc.run(); + +    return EXIT_SUCCESS;  }  | 
