From a4a412144414dc404554812ae6f2730106c76a35 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 18 Jun 2017 06:51:51 -0700 Subject: [PATCH] Add multi_port to server-framework --- CHANGELOG.md | 1 + doc/0_main.qbk | 2 +- doc/2_examples.qbk | 2 + doc/3_5_detect_ssl.qbk | 17 +- doc/images/server.png | Bin 18586 -> 19380 bytes example/server-framework/README.md | 2 +- .../detect_ssl.hpp} | 25 +- example/server-framework/file_service.hpp | 48 ++- example/server-framework/http_async_port.hpp | 73 +++- example/server-framework/http_sync_port.hpp | 27 +- example/server-framework/https_ports.hpp | 123 ++++-- example/server-framework/main.cpp | 293 +++++++------ example/server-framework/multi_port.hpp | 398 ++++++++++++++++++ example/server-framework/service_list.hpp | 2 +- example/server-framework/ws_async_port.hpp | 40 +- example/server-framework/ws_sync_port.hpp | 45 +- .../server-framework/ws_upgrade_service.hpp | 9 +- example/server-framework/wss_ports.hpp | 117 ++--- test/core/doc_examples.cpp | 2 +- test/server/CMakeLists.txt | 1 + test/server/Jamfile | 1 + test/server/multi_port.cpp | 10 + 22 files changed, 937 insertions(+), 301 deletions(-) rename example/{doc/core_examples.hpp => server-framework/detect_ssl.hpp} (96%) create mode 100644 example/server-framework/multi_port.hpp create mode 100644 test/server/multi_port.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b56dc4f..92ff87aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Version 61: * Add server-framework SSL HTTP and WebSocket ports * Fix shadowing warnings * Tidy up http-crawl example +* Add multi_port to server-framework API Changes: diff --git a/doc/0_main.qbk b/doc/0_main.qbk index f291cce9..89f49917 100644 --- a/doc/0_main.qbk +++ b/doc/0_main.qbk @@ -75,10 +75,10 @@ [def __static_buffer__ [link beast.ref.beast__static_buffer `static_buffer`]] [def __static_buffer_n__ [link beast.ref.beast__static_buffer_n `static_buffer_n`]] -[import ../example/doc/core_examples.hpp] [import ../example/doc/http_examples.hpp] [import ../example/echo-op/echo_op.cpp] [import ../example/http-client/http_client.cpp] +[import ../example/server-framework/detect_ssl.hpp] [import ../example/server-framework/file_body.hpp] [import ../example/websocket-client/websocket_client.cpp] diff --git a/doc/2_examples.qbk b/doc/2_examples.qbk index 5a3bfbae..7e8c71e9 100644 --- a/doc/2_examples.qbk +++ b/doc/2_examples.qbk @@ -107,6 +107,7 @@ This is a complete program and framework of classes implementing a general purpose server that users may copy to use as the basis for writing their own servers. It serves both HTTP and WebSocket. +* [repo_file example/server-framework/detect_ssl.hpp] * [repo_file example/server-framework/file_body.hpp] * [repo_file example/server-framework/file_service.hpp] * [repo_file example/server-framework/framework.hpp] @@ -115,6 +116,7 @@ for writing their own servers. It serves both HTTP and WebSocket. * [repo_file example/server-framework/http_sync_port.hpp] * [repo_file example/server-framework/https_ports.hpp] * [repo_file example/server-framework/main.cpp] +* [repo_file example/server-framework/multi_port.hpp] * [repo_file example/server-framework/rfc7231.hpp] * [repo_file example/server-framework/server.hpp] * [repo_file example/server-framework/service_list.hpp] diff --git a/doc/3_5_detect_ssl.qbk b/doc/3_5_detect_ssl.qbk index 087e973c..a803e40f 100644 --- a/doc/3_5_detect_ssl.qbk +++ b/doc/3_5_detect_ssl.qbk @@ -18,23 +18,23 @@ Here is the declaration for a function to detect the SSL client handshake. The input to the function is simply a buffer sequence, no stream. This allows the detection algorithm to be used elsewhere. -[example_core_detect_tls_1] +[example_core_detect_ssl_1] The implementation checks the buffer for the presence of the SSL Handshake message octet sequence and returns an apporopriate value: -[example_core_detect_tls_2] +[example_core_detect_ssl_2] Now we define a stream operation. We start with the simple, synchronous version which takes the stream and buffer as input: -[example_core_detect_tls_3] +[example_core_detect_ssl_3] The synchronous algorithm is the model for building the asynchronous operation which has more boilerplate. First, we declare the asynchronous initiation function: -[example_core_detect_tls_4] +[example_core_detect_ssl_4] The implementation of the initiation function is straightforward and contains mostly boilerplate. It is to construct the return @@ -43,13 +43,13 @@ then create the composed operation and launch it. The actual code for interacting with the stream is in the composed operation, which is written as a separate class. -[example_core_detect_tls_5] +[example_core_detect_ssl_5] Now we will declare our composed operation. There is a considerable amount of necessary boilerplate to get this right, but the result is worth the effort. -[example_core_detect_tls_6] +[example_core_detect_ssl_6] The boilerplate is all done, and now we need to implemnt the function call operator that turns this composed operation a completion handler @@ -59,6 +59,9 @@ is a transformation of the synchronous version of `detect_ssl` above, but with the inversion of flow that characterizes code written in the callback style: -[example_core_detect_tls_7] +[example_core_detect_ssl_7] + +This SSL detector is used by the server framework in the example +directory. [endsect] diff --git a/doc/images/server.png b/doc/images/server.png index 52f0c2e228ed2ba4e829b0ccfa5ca3efaee13f19..fc6098005625175ee59126bfb85e649cebf0bfeb 100644 GIT binary patch literal 19380 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 + src="https://raw.githubusercontent.com/vinniefalco/Beast/master/doc/images/server.png"> ## PortHandler Requirements ```C++ diff --git a/example/doc/core_examples.hpp b/example/server-framework/detect_ssl.hpp similarity index 96% rename from example/doc/core_examples.hpp rename to example/server-framework/detect_ssl.hpp index 268f7292..89876872 100644 --- a/example/doc/core_examples.hpp +++ b/example/server-framework/detect_ssl.hpp @@ -5,22 +5,19 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#ifndef BEAST_EXAMPLE_SERVER_DETECT_SSL_HPP +#define BEAST_EXAMPLE_SERVER_DETECT_SSL_HPP + #include #include -/* This file contains the functions and classes found in the documentation - - They are compiled and run as part of the unit tests, so you can copy - the code and use it in your own projects as a starting point for - building a network application. -*/ //------------------------------------------------------------------------------ // // Example: Detect TLS/SSL // //------------------------------------------------------------------------------ -//[example_core_detect_tls_1 +//[example_core_detect_ssl_1 #include #include @@ -55,7 +52,7 @@ is_ssl_handshake(ConstBufferSequence const& buffers); using namespace beast; -//[example_core_detect_tls_2 +//[example_core_detect_ssl_2 template< class ConstBufferSequence> @@ -94,7 +91,7 @@ is_ssl_handshake( //] -//[example_core_detect_tls_3 +//[example_core_detect_ssl_3 /** Detect a TLS/SSL handshake on a stream. @@ -176,7 +173,7 @@ detect_ssl( //] -//[example_core_detect_tls_4 +//[example_core_detect_ssl_4 /** Detect a TLS/SSL handshake asynchronously on a stream. @@ -234,7 +231,7 @@ async_detect_ssl( //] -//[example_core_detect_tls_5 +//[example_core_detect_ssl_5 // This is the composed operation. template< @@ -292,7 +289,7 @@ async_detect_ssl( //] -//[example_core_detect_tls_6 +//[example_core_detect_ssl_6 // Read from a stream to invoke is_tls_handshake asynchronously // @@ -389,7 +386,7 @@ public: //] -//[example_core_detect_tls_7 +//[example_core_detect_ssl_7 // detect_ssl_op is callable with the signature // void(error_code, bytes_transferred), @@ -481,4 +478,4 @@ operator()(beast::error_code ec, std::size_t bytes_transferred) //] -//------------------------------------------------------------------------------ +#endif diff --git a/example/server-framework/file_service.hpp b/example/server-framework/file_service.hpp index c13bb103..1b29f8eb 100644 --- a/example/server-framework/file_service.hpp +++ b/example/server-framework/file_service.hpp @@ -15,7 +15,9 @@ #include #include #include + #include + #include namespace framework { @@ -47,8 +49,8 @@ public: */ explicit file_service( - boost::filesystem::path const& root, - beast::string_view server) + boost::filesystem::path const& root, + beast::string_view server) : root_(root) , server_(server) { @@ -70,10 +72,33 @@ public: ec = {}; } - /** Process a request. - - - @note This is needed for to meet the requirements for @b Service + /** Try to handle a file request. + + @param stream The stream belonging to the connection. + Ownership is not transferred. + + @param ep The remote endpoint of the connection + corresponding to the stream. + + @param req The request message to attempt handling. + Ownership is not transferred. + + @param send The function to invoke with the response. + The function will have this equivalent signature: + + @code + + template + void + send(response&&); + + @endcode + + In C++14 this can be expressed using a generic lambda. In + C++11 it will require a template member function of an invocable + object. + + @return `true` if the request was handled by the service. */ template< class Stream, @@ -86,7 +111,7 @@ public: beast::http::request&& req, Send const& send) const { - // Check the method and take action + // Determine our action based on the method switch(req.method()) { case beast::http::verb::get: @@ -190,7 +215,8 @@ private: // template beast::http::response - not_found(beast::http::request const& req, + not_found( + beast::http::request const& req, boost::filesystem::path const& rel_path) const { beast::http::response res; @@ -207,7 +233,8 @@ private: // template beast::http::response - get(beast::http::request const& req, + get( + beast::http::request const& req, boost::filesystem::path const& full_path) const { beast::http::response res; @@ -224,7 +251,8 @@ private: // template beast::http::response - head(beast::http::request const& req, + head( + beast::http::request const& req, boost::filesystem::path const& full_path) const { beast::http::response res; diff --git a/example/server-framework/http_async_port.hpp b/example/server-framework/http_async_port.hpp index f766bcdd..12d224c5 100644 --- a/example/server-framework/http_async_port.hpp +++ b/example/server-framework/http_async_port.hpp @@ -31,6 +31,9 @@ namespace framework { // struct queued_http_write { + // Destructor must be virtual since we delete a + // derived class through a pointer to the base! + // virtual ~queued_http_write() = default; // When invoked, performs the write operation. @@ -49,8 +52,13 @@ template< class Handler> class queued_http_write_impl : public queued_http_write { + // The stream to write to Stream& stream_; + + // The message to send, which we acquire by move or copy beast::http::message msg_; + + // The handler to invoke when the send completes. Handler handler_; public: @@ -69,7 +77,13 @@ public: { } - // Writes the stored message + // Writes the stored message. + // + // The caller must make sure this invocation represents + // a continuation of an asynchronous operation which is + // already in the right context. For example, already + // running on the associated strand. + // void invoke() override { @@ -120,6 +134,7 @@ make_queued_http_write( template class async_http_con_base : public http_base { +protected: // This function lets us access members of the derived class Derived& impl() @@ -153,7 +168,6 @@ class async_http_con_base : public http_base // Indicates if we have a write active. bool writing_ = false; -protected: // The strand makes sure that our data is // accessed from only one thread at a time. // @@ -172,17 +186,40 @@ public: , services_(services) , id_(id) , ep_(ep) + // The buffer has a limit of 8192, otherwise // the server is vulnerable to a buffer attack. // , buffer_(8192) + , strand_(impl().stream().get_io_service()) { } + // Called to start the object after the listener accepts + // an incoming connection, when no bytes have been read yet. + // void run() { + // Just call run with an empty buffer + run(boost::asio::null_buffers{}); + } + + // Called to start the object after the + // listener accepts an incoming connection. + // + template + void + run(ConstBufferSequence const& buffers) + { + // Copy the data into the buffer for performing + // HTTP reads, so that the bytes get used. + // + buffer_.commit(boost::asio::buffer_copy( + buffer_.prepare(boost::asio::buffer_size(buffers)), + buffers)); + // Give the derived class a chance to do stuff // impl().do_handshake(); @@ -210,7 +247,6 @@ protected: } } -private: // Perform an asynchronous read for the next request header // void @@ -457,25 +493,28 @@ private: template class async_http_con - // Note that we give this object the enable_shared_from_this, and have - // the base class call impl().shared_from_this(). The reason we do that - // is so that the shared_ptr has the correct type. This lets the - // derived class (this class) use its members in calls to std::bind, - // without an ugly call to `dynamic_downcast` or other nonsense. + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. // : public std::enable_shared_from_this> - // We want the socket to be created before the base class so we use - // the "base from member" idiom which Boost provides as a class. + // The stream should be created before the base class so + // use the "base from member" idiom. // , public base_from_member - // Declare this base last now that everything else got set up first. + // Constructs last, destroys first // , public async_http_con_base, Services...> { public: - // Construct the plain connection. + // Constructor + // + // Additional arguments are forwarded to the base class // template async_http_con( @@ -488,8 +527,11 @@ public: } // Returns the stream. - // The base class calls this to obtain the object to - // use for reading and writing HTTP messages. + // + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` // socket_type& stream() @@ -597,8 +639,7 @@ public: log_, services_, instance_.next_id(), - ep - )->run(); + ep)->run(); } }; diff --git a/example/server-framework/http_sync_port.hpp b/example/server-framework/http_sync_port.hpp index b1adfbb2..c80e5775 100644 --- a/example/server-framework/http_sync_port.hpp +++ b/example/server-framework/http_sync_port.hpp @@ -328,22 +328,28 @@ private: template class sync_http_con - // Note that we give this object the `enable_shared_from_this`, and have - // the base class call `impl().shared_from_this()` when needed. + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. // : public std::enable_shared_from_this> - // We want the socket to be created before the base class so we use - // the "base from member" idiom which Boost provides as a class. + // The stream should be created before the base class so + // use the "base from member" idiom. // , public base_from_member - // Declare this base last now that everything else got set up first. + // Constructs last, destroys first // , public sync_http_con_base, Services...> { public: - // Construct the plain connection. + // Constructor + // + // Additional arguments are forwarded to the base class // template sync_http_con( @@ -357,8 +363,10 @@ public: // Returns the stream. // - // The base class calls this to obtain the object to - // use for reading and writing HTTP messages. + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` // socket_type& stream() @@ -458,8 +466,7 @@ public: log_, services_, instance_.next_id(), - ep - )->run(); + ep)->run(); } }; diff --git a/example/server-framework/https_ports.hpp b/example/server-framework/https_ports.hpp index a1e06792..c00b6851 100644 --- a/example/server-framework/https_ports.hpp +++ b/example/server-framework/https_ports.hpp @@ -24,25 +24,28 @@ namespace framework { template class sync_https_con - // Note that we give this object the enable_shared_from_this, and have - // the base class call impl().shared_from_this(). The reason we do that - // is so that the shared_ptr has the correct type. This lets the - // derived class (this class) use its members in calls to std::bind, - // without an ugly call to `dynamic_downcast` or other nonsense. + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. // : public std::enable_shared_from_this> - // We want the socket to be created before the base class so we use - // the "base from member" idiom which Boost provides as a class. + // The stream should be created before the base class so + // use the "base from member" idiom. // , public base_from_member> - // Declare this base last now that everything else got set up first. + // Constructs last, destroys first // , public sync_http_con_base, Services...> { public: - // Construct the plain connection. + // Constructor + // + // Additional arguments are forwarded to the base class // template sync_https_con( @@ -56,8 +59,11 @@ public: } // Returns the stream. - // The base class calls this to obtain the object to - // use for reading and writing HTTP messages. + // + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` // ssl_stream& stream() @@ -99,25 +105,28 @@ private: template class async_https_con - // Note that we give this object the enable_shared_from_this, and have - // the base class call impl().shared_from_this(). The reason we do that - // is so that the shared_ptr has the correct type. This lets the - // derived class (this class) use its members in calls to std::bind, - // without an ugly call to `dynamic_downcast` or other nonsense. + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. // : public std::enable_shared_from_this> - // We want the socket to be created before the base class so we use - // the "base from member" idiom which Boost provides as a class. + // The stream should be created before the base class so + // use the "base from member" idiom. // , public base_from_member> - // Declare this base last now that everything else got set up first. + // Constructs last, destroys first // , public async_http_con_base, Services...> { public: - // Construct the plain connection. + // Constructor + // + // Additional arguments are forwarded to the base class // template async_https_con( @@ -131,8 +140,11 @@ public: } // Returns the stream. - // The base class calls this to obtain the object to - // use for reading and writing HTTP messages. + // + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` // ssl_stream& stream() @@ -140,6 +152,37 @@ public: return this->member; } + // Called by the multi-port after reading some + // bytes from the stream and detecting SSL. + // + template + void + handshake(ConstBufferSequence const& buffers) + { + // Copy the caller's bytes into the buffer we + // use for reading HTTP messages, otherwise + // the memory pointed to by buffers will go out + // of scope. + // + this->buffer_.commit( + boost::asio::buffer_copy( + this->buffer_.prepare(boost::asio::buffer_size(buffers)), + buffers)); + + // Perform SSL handshake. We use the "buffered" + // overload which lets us pass those extra bytes. + // + stream().async_handshake( + boost::asio::ssl::stream_base::server, + buffers, + this->strand_.wrap( + std::bind( + &async_https_con::on_buffered_handshake, + this->shared_from_this(), + std::placeholders::_1, + std::placeholders::_2))); + } + private: friend class async_http_con_base, Services...>; @@ -152,10 +195,11 @@ private: // stream().async_handshake( boost::asio::ssl::stream_base::server, - this->strand_.wrap(std::bind( + this->strand_.wrap( + std::bind( &async_https_con::on_handshake, - this->shared_from_this(), - std::placeholders::_1))); + this->shared_from_this(), + std::placeholders::_1))); } // Called when the SSL handshake completes @@ -169,6 +213,20 @@ private: this->do_run(); } + // Called when the buffered SSL handshake completes + void + on_buffered_handshake(error_code ec, std::size_t bytes_transferred) + { + if(ec) + return this->fail("on_handshake", ec); + + // Consume what was read but leave the rest + this->buffer_.consume(bytes_transferred); + + // No error so run the main loop + this->do_run(); + } + // Called when the end of stream is reached void do_shutdown() @@ -176,10 +234,11 @@ private: // This is an SSL shutdown // stream().async_shutdown( - this->strand_.wrap(std::bind( - &async_https_con::on_shutdown, + this->strand_.wrap( + std::bind( + &async_https_con::on_shutdown, this->shared_from_this(), - std::placeholders::_1))); + std::placeholders::_1))); } // Called when the SSL shutdown completes @@ -273,8 +332,7 @@ public: log_, services_, instance_.next_id(), - ep - )->run(); + ep)->run(); } }; @@ -348,7 +406,7 @@ public: void on_accept(socket_type&& sock, endpoint_type ep) { - // Create an HTTPS connection object + // Create an SSL connection object // and transfer ownership of the socket. // std::make_shared>( @@ -358,8 +416,7 @@ public: log_, services_, instance_.next_id(), - ep - )->run(); + ep)->run(); } }; diff --git a/example/server-framework/main.cpp b/example/server-framework/main.cpp index ff14ac7d..fcaffa8c 100644 --- a/example/server-framework/main.cpp +++ b/example/server-framework/main.cpp @@ -14,6 +14,7 @@ #if BEAST_USE_OPENSSL #include "https_ports.hpp" +#include "multi_port.hpp" #include "wss_ports.hpp" #include "ssl_certificate.hpp" #endif @@ -79,11 +80,11 @@ main( int ac, char const* av[]) { - // Helper for reporting failures - // using namespace framework; using namespace beast::http; + // Helper for reporting failures + // auto const fail = [&]( std::string const& what, @@ -141,17 +142,75 @@ main( //-------------------------------------------------------------------------- // - // Asynchronous WebSocket HTTP + // Synchronous WebSocket HTTP // - // port port + 1 + // port + 0 port + 1 // //-------------------------------------------------------------------------- { - // Install an asynchronous WebSocket echo port handler + // Create a WebSocket port + // + auto wsp = instance.make_port( + ec, + endpoint_type{addr,static_cast(port + 0)}, + instance, + std::cout, + set_ws_options{pmd}); + + if(ec) + return fail("ws_sync_port", ec); + + // Create an HTTP port + // + auto sp = instance.make_port, + file_service + >>( + ec, + endpoint_type{addr,static_cast(port + 1)}, + instance, + std::cout); + + if(ec) + return fail("http_sync_port", ec); + + // Init the ws_upgrade_service to + // forward upgrades to the WebSocket port. + // + sp->template init<0>( + ec, + *wsp // The WebSocket port handler + ); + + if(ec) + return fail("http_sync_port/ws_upgrade_service", ec); + + // Init the file_service to point to the root path. + // + sp->template init<1>( + ec, + root, // The root path + "http_sync_port" // The value for the Server field + ); + + if(ec) + return fail("http_sync_port/file_service", ec); + } + + //-------------------------------------------------------------------------- + // + // Asynchronous WebSocket HTTP + // + // port + 2 port + 3 + // + //-------------------------------------------------------------------------- + { + // Create a WebSocket port // auto wsp = instance.make_port( ec, - endpoint_type{addr, port}, + endpoint_type{addr, + static_cast(port + 2)}, instance, std::cout, set_ws_options{pmd} @@ -160,106 +219,47 @@ main( if(ec) return fail("ws_async_port", ec); - // Install an asynchronous HTTP port handler + // Create an HTTP port // auto sp = instance.make_port, file_service >>( ec, - endpoint_type{addr, - static_cast(port + 1)}, - instance, - std::cout); - - if(ec) - return fail("http_async_port", ec); - - // Set up the ws_upgrade_service. We will route upgrade - // requests to the websocket port handler created earlier. - // - sp->template init<0>( - ec, - wsp // The websocket port handler - ); - - if(ec) - return fail("http_async_port/ws_upgrade_service", ec); - - // Set up the file_service to point to the root path. - // - sp->template init<1>( - ec, - root, // The root path - "http_async_port" // The value for the Server field - ); - - if(ec) - return fail("http_async_port/file_service", ec); - } - - //-------------------------------------------------------------------------- - // - // Synchronous WebSocket HTTP - // - // port + 2 port + 3 - // - //-------------------------------------------------------------------------- - { - // Install a synchronous WebSocket echo port handler - // - auto wsp = instance.make_port( - ec, - endpoint_type{addr, - static_cast(port + 2)}, - instance, - std::cout, - set_ws_options{pmd}); - - if(ec) - return fail("ws_sync_port", ec); - - - // Install a synchronous HTTP port handler - // - auto sp = instance.make_port, - file_service - >>( - ec, endpoint_type{addr, static_cast(port + 3)}, instance, std::cout); if(ec) - return fail("http_sync_port", ec); + return fail("http_async_port", ec); - // Set up the ws_upgrade_service. We will route upgrade - // requests to the websocket port handler created earlier. + // Init the ws_upgrade_service to + // forward upgrades to the WebSocket port. // sp->template init<0>( ec, - wsp + *wsp // The websocket port handler ); if(ec) - return fail("http_sync_port/ws_upgrade_service", ec); + return fail("http_async_port/ws_upgrade_service", ec); - // Set up the file_service to point to the root path. + // Init the file_service to point to the root path. // sp->template init<1>( ec, - root, - "http_sync_port" + root, // The root path + "http_async_port" // The value for the Server field ); if(ec) - return fail("http_sync_port/file_service", ec); + return fail("http_async_port/file_service", ec); } // - // If OpenSSL is available then install some SSL-enabled ports + // The next section supports encrypted connections and requires + // an installed and configured OpenSSL as part of the build. // #if BEAST_USE_OPENSSL @@ -268,31 +268,30 @@ main( //-------------------------------------------------------------------------- // - // Asynchronous Secure WebSocket HTTPS + // Synchronous Secure WebSocket HTTPS // // port + 4 port + 5 // //-------------------------------------------------------------------------- { - // Install an asynchronous Secure WebSocket echo port handler + // Create a WebSocket port // - auto wsp = instance.make_port( + auto wsp = instance.make_port( ec, endpoint_type{addr, static_cast(port + 4)}, instance, std::cout, cert.get(), - set_ws_options{pmd} - ); + set_ws_options{pmd}); if(ec) - return fail("ws_async_port", ec); + return fail("wss_sync_port", ec); - // Install an asynchronous HTTPS port handler + // Create an HTTP port // - auto sp = instance.make_port, + auto sp = instance.make_port, file_service >>( ec, @@ -303,20 +302,82 @@ main( cert.get()); if(ec) - return fail("https_async_port", ec); + return fail("https_sync_port", ec); - // Set up the ws_upgrade_service. We will route upgrade - // requests to the websocket port handler created earlier. + // Init the ws_upgrade_service to + // forward upgrades to the WebSocket port. // sp->template init<0>( ec, - wsp // The websocket port handler + *wsp // The websocket port handler + ); + + if(ec) + return fail("http_sync_port/ws_upgrade_service", ec); + + // Init the file_service to point to the root path. + // + sp->template init<1>( + ec, + root, // The root path + "http_sync_port" // The value for the Server field + ); + + if(ec) + return fail("https_sync_port/file_service", ec); + } + + //-------------------------------------------------------------------------- + // + // Asynchronous Secure WebSocket HTTPS + // + // port + 6 port + 7 + // + //-------------------------------------------------------------------------- + { + // Create a WebSocket port + // + auto wsp = instance.make_port( + ec, + endpoint_type{addr, + static_cast(port + 6)}, + instance, + std::cout, + cert.get(), + set_ws_options{pmd} + ); + + if(ec) + return fail("ws_async_port", ec); + + // Create an HTTP port + // + auto sp = instance.make_port, + file_service + >>( + ec, + endpoint_type{addr, + static_cast(port + 7)}, + instance, + std::cout, + cert.get()); + + if(ec) + return fail("https_async_port", ec); + + // Init the ws_upgrade_service to + // forward upgrades to the WebSocket port. + // + sp->template init<0>( + ec, + *wsp // The websocket port handler ); if(ec) return fail("https_async_port/ws_upgrade_service", ec); - // Set up the file_service to point to the root path. + // Init the file_service to point to the root path. // sp->template init<1>( ec, @@ -330,63 +391,53 @@ main( //-------------------------------------------------------------------------- // - // Synchronous Secure WebSocket HTTPS + // Multi-Port HTTP, WebSockets, + // HTTPS Secure WebSockets // - // port + 6 port + 7 + // Asynchronous, all on the same port! + // + // port + 8 // //-------------------------------------------------------------------------- { - // Install a synchronous Secure WebSocket echo port handler + // Create a multi_port // - auto wsp = instance.make_port( + auto sp = instance.make_port, + file_service + >>( ec, endpoint_type{addr, - static_cast(port + 6)}, + static_cast(port + 8)}, instance, std::cout, cert.get(), set_ws_options{pmd}); if(ec) - return fail("wss_sync_port", ec); + return fail("multi_port", ec); - // Install a synchronous HTTPS port handler - // - auto sp = instance.make_port, - file_service - >>( - ec, - endpoint_type{addr, - static_cast(port + 7)}, - instance, - std::cout, - cert.get()); - - if(ec) - return fail("https_sync_port", ec); - - // Set up the ws_upgrade_service. We will route upgrade - // requests to the websocket port handler created earlier. + // Init the ws_upgrade_service to forward requests to the multi_port. // sp->template init<0>( ec, - wsp // The websocket port handler + *sp // The websocket port handler ); if(ec) - return fail("http_sync_port/ws_upgrade_service", ec); + return fail("multi_port/ws_upgrade_service", ec); - // Set up the file_service to point to the root path. + // Init the ws_upgrade_service to + // forward upgrades to the Multi port. // sp->template init<1>( ec, - root, - "https_sync_port" + root, // The root path + "multi_port" // The value for the Server field ); if(ec) - return fail("https_sync_port/file_service", ec); + return fail("multi_port/file_service", ec); } #endif diff --git a/example/server-framework/multi_port.hpp b/example/server-framework/multi_port.hpp new file mode 100644 index 00000000..934d1abe --- /dev/null +++ b/example/server-framework/multi_port.hpp @@ -0,0 +1,398 @@ +// +// Copyright (c) 2013-2017 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) +// + +#ifndef BEAST_EXAMPLE_SERVER_MULTI_PORT_HPP +#define BEAST_EXAMPLE_SERVER_MULTI_PORT_HPP + +#include "detect_ssl.hpp" +#include "ws_async_port.hpp" +#include "http_async_port.hpp" +#include "https_ports.hpp" +#include "wss_ports.hpp" + +#include + +#include + +namespace framework { + +// A connection that detects an opening SSL handshake +// +// If the SSL handshake is detected, then an HTTPS connection object +// is move constructed from this object. Otherwise, this object continues +// as a normal unencrypted HTTP connection. If the underlying port has +// the ws_upgrade_service configured, the connection may be optionally +// be upgraded to WebSocket by the client. +// +template +class multi_con + + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. + // + : public std::enable_shared_from_this> + + // The stream should be created before the base class so + // use the "base from member" idiom. + // + , public base_from_member + + // Constructs last, destroys first + // + , public async_http_con_base, Services...> +{ + // Context to use if we get an SSL handshake + boost::asio::ssl::context& ctx_; + + // Holds the data we read during ssl detection + beast::static_buffer_n<6> buffer_; + +public: + // Constructor + // + // Additional arguments are simply forwarded to the base class + // + template + multi_con( + socket_type&& sock, + boost::asio::ssl::context& ctx, + Args&&... args) + : base_from_member(std::move(sock)) + , async_http_con_base, Services...>(std::forward(args)...) + , ctx_(ctx) + { + } + + // Returns the stream. + // + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` + // + socket_type& + stream() + { + return this->member; + } + + // Called by the port to launch the connection in detect mode + void + detect() + { + // The detect function operates asynchronously by reading + // in some data from the stream to figure out if its an SSL + // handshake. When it completes, it informs us of the result + // and also stores the bytes it read in the buffer. + // + async_detect_ssl( + stream(), + buffer_, + this->strand_.wrap( + std::bind( + &multi_con::on_detect, + this->shared_from_this(), + std::placeholders::_1, + std::placeholders::_2))); + } + +private: + // Base class needs to be a friend to call our private members + friend class async_http_con_base, Services...>; + + // Called when the handshake detection is complete + // + void + on_detect( + error_code ec, + boost::tribool result) + { + // Report failures if any + if(ec) + return this->fail("on_detect", ec); + + // Was an SSL handshake detected? + if(result) + { + // Yes, get the remote endpoint since it is + // needed to construct the new connection. + // + endpoint_type ep = stream().remote_endpoint(ec); + if(ec) + return this->fail("remote_endpoint", ec); + + // Now launch our new connection object + // + std::make_shared>( + std::move(stream()), + ctx_, + "multi_port", + this->log_, + this->services_, + this->id_, + ep + )->handshake(buffer_.data()); + + // When we return the last shared pointer to this + // object will go away and `*this` will be destroyed. + // + return; + } + + // No SSL handshake, so start the HTTP connection normally. + // + // Since we read some bytes from the connection that might + // contain an HTTP request, we pass the buffer holding those + // bytes to the base class so it can use them. + // + this->run(buffer_.data()); + } + + // This is called by the base before running the main loop. + // + void + do_handshake() + { + // Just run the main loop right away. + // + this->do_run(); + } + + // This is called when the other end closes the connection gracefully. + // + void + do_shutdown() + { + // Attempt a clean TCP/IP shutdown + // + error_code ec; + stream().shutdown( + socket_type::shutdown_both, + ec); + + // Report failure if any + // + if(ec) + return this->fail("shutdown", ec); + } +}; + +//------------------------------------------------------------------------------ + +/* An asynchronous HTTP and WebSocket port handler, plain or SSL + + This type meets the requirements of @b PortHandler. It supports a + variable list of HTTP services in its template parameter list, + and provides a synchronous connection implementation to service. + + The port will automatically detect OpenSSL handshakes and establish + encrypted connections, otherwise will use a plain unencrypted + connection. This all happens through the same port. + + In addition this port can process WebSocket upgrade requests by + launching them as a new asynchronous WebSocket connection using + either plain or OpenSSL transport. + + This class is split up into two parts, the multi_port_base, + and the multi_port, to avoid a recursive type reference when + we name the type of the ws_upgrade_service. +*/ +class multi_port_base +{ +protected: + // VFALCO We use boost::function to work around a compiler + // crash with gcc and clang using libstdc++ + + // The types of the on_stream callback + using on_new_stream_cb1 = boost::function&)>; + using on_new_stream_cb2 = boost::function>&)>; + + // Reference to the server instance that made us + server& instance_; + + // The stream to log to + std::ostream& log_; + + // The context holds the SSL certificates the server uses + boost::asio::ssl::context& ctx_; + + // Called for each new websocket stream + on_new_stream_cb1 cb1_; + on_new_stream_cb2 cb2_; + +public: + /** Constructor + + @param instance The server instance which owns this port + + @param log The stream to use for logging + + @param ctx The SSL context holding the SSL certificates to use + + @param cb A callback which will be invoked for every new + WebSocket connection. This provides an opportunity to change + the settings on the stream before it is used. The callback + should have this equivalent signature: + @code + template + void callback(beast::websocket::stream&); + @endcode + In C++14 this can be accomplished with a generic lambda. In + C++11 it will be necessary to write out a lambda manually, + with a templated operator(). + */ + template + multi_port_base( + server& instance, + std::ostream& log, + boost::asio::ssl::context& ctx, + Callback const& cb) + : instance_(instance) + , log_(log) + , ctx_(ctx) + , cb1_(cb) + , cb2_(cb) + { + } + + /** Accept a WebSocket upgrade request. + + This is used to accept a connection that has already + delivered the handshake. + + @param stream The stream corresponding to the connection. + + @param ep The remote endpoint. + + @param req The upgrade request. + */ + template + void + on_upgrade( + socket_type&& sock, + endpoint_type ep, + beast::http::request&& req) + { + // Create the connection and call the version of + // run that takes the request since we have it already + // + std::make_shared( + std::move(sock), + "multi_port", + log_, + instance_.next_id(), + ep, + cb1_ + )->run(std::move(req)); + } + + /** Accept a WebSocket upgrade request. + + This is used to accept a connection that has already + delivered the handshake. + + @param stream The stream corresponding to the connection. + + @param ep The remote endpoint. + + @param req The upgrade request. + */ + template + void + on_upgrade( + ssl_stream&& stream, + endpoint_type ep, + beast::http::request&& req) + { + std::make_shared( + std::move(stream), + "multi_port", + log_, + instance_.next_id(), + ep, + cb2_ + )->run(std::move(req)); + } +}; + +/* An asynchronous HTTP and WebSocket port handler, plain or SSL + + This class is the other half of multi_port_base. It gets the + Services... variadic type list and owns the service list. +*/ +template +class multi_port : public multi_port_base +{ + // The list of services connections created from this port will support + service_list services_; + +public: + /** Constructor + + All arguments are forwarded to the multi_port_base constructor. + */ + template + multi_port(Args&&... args) + : multi_port_base(std::forward(args)...) + { + } + + /** Initialize a service + + Every service in the list must be initialized exactly once. + + @param args Optional arguments forwarded to the service + constructor. + + @tparam Index The 0-based index of the service to initialize. + + @return A reference to the service list. This permits + calls to be chained in a single expression. + */ + template + void + init(error_code& ec, Args&&... args) + { + services_.template init( + ec, + std::forward(args)...); + } + + /** Called by the server to provide ownership of the socket for a new connection + + @param sock The socket whose ownership is to be transferred + + @ep The remote endpoint + */ + void + on_accept( + socket_type&& sock, + endpoint_type ep) + { + // Create a plain http connection object by transferring + // ownership of the socket, then launch it to perform + // the SSL handshake detection. + // + std::make_shared>( + std::move(sock), + ctx_, + "multi_port", + log_, + services_, + instance_.next_id(), + ep + )->detect(); + } +}; + +} // framework + +#endif diff --git a/example/server-framework/service_list.hpp b/example/server-framework/service_list.hpp index 872b49a3..bd82b5f7 100644 --- a/example/server-framework/service_list.hpp +++ b/example/server-framework/service_list.hpp @@ -90,7 +90,7 @@ public: to the stream. @param req The request message to attempt handling. A service - which handles the request may optionally take ownershop of the + which handles the request may optionally take ownership of the message. @param send The function to invoke with the response. The function diff --git a/example/server-framework/ws_async_port.hpp b/example/server-framework/ws_async_port.hpp index 667de190..da44defd 100644 --- a/example/server-framework/ws_async_port.hpp +++ b/example/server-framework/ws_async_port.hpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -212,22 +212,28 @@ private: // class async_ws_con - // Note that we give this object the `enable_shared_from_this`, and have - // the base class call `impl().shared_from_this()` when needed. + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. // : public std::enable_shared_from_this - // We want the socket to be created before the base class so we use - // the "base from member" idiom which Boost provides as a class. + // The stream should be created before the base class so + // use the "base from member" idiom. // , public base_from_member> - // Declare this base last now that everything else got set up first. + // Constructs last, destroys first // , public async_ws_con_base { public: - // Construct the plain connection. + // Constructor + // + // Additional arguments are forwarded to the base class // template explicit @@ -241,7 +247,10 @@ public: // Returns the stream. // - // The base class calls this to obtain the websocket stream object. + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` // beast::websocket::stream& stream() @@ -270,9 +279,10 @@ private: */ class ws_async_port { - // The type of the on_stream callback - using on_new_stream_cb = std::function< - void(beast::websocket::stream&)>; + // The type of the on_new_stream callback + // + using on_new_stream_cb = + boost::function&)>; server& instance_; std::ostream& log_; @@ -328,8 +338,7 @@ public: log_, instance_.next_id(), ep, - cb_ - )->run(); + cb_)->run(); } /** Accept a WebSocket upgrade request. @@ -345,7 +354,7 @@ public: */ template void - accept( + on_upgrade( socket_type&& sock, endpoint_type ep, beast::http::request&& req) @@ -356,8 +365,7 @@ public: log_, instance_.next_id(), ep, - cb_ - )->run(std::move(req)); + cb_)->run(std::move(req)); } }; diff --git a/example/server-framework/ws_sync_port.hpp b/example/server-framework/ws_sync_port.hpp index b4102972..ebfc1291 100644 --- a/example/server-framework/ws_sync_port.hpp +++ b/example/server-framework/ws_sync_port.hpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include #include @@ -21,8 +21,8 @@ namespace framework { /** A synchronous WebSocket connection. - This base class implements a WebSocket connection object - using synchronous calls. + This base class implements a WebSocket connection object using + synchronous calls over an unencrypted connection. It uses the Curiously Recurring Template pattern (CRTP) where we refer to the derived class in order to access the stream object @@ -55,6 +55,9 @@ class sync_ws_con_base public: // Constructor + // + // Additional arguments are forwarded to the base class + // template sync_ws_con_base( beast::string_view server_name, @@ -261,17 +264,21 @@ private: // class sync_ws_con - // Note that we give this object the `enable_shared_from_this`, and have - // the base class call `impl().shared_from_this()` when needed. + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. // : public std::enable_shared_from_this - // We want the socket to be created before the base class so we use - // the "base from member" idiom which Boost provides as a class. + // The stream should be created before the base class so + // use the "base from member" idiom. // , public base_from_member> - // Declare this base last now that everything else got set up first. + // Constructs last, destroys first // , public sync_ws_con_base { @@ -290,7 +297,10 @@ public: // Returns the stream. // - // The base class calls this to obtain the websocket stream object. + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` // beast::websocket::stream& stream() @@ -324,9 +334,10 @@ private: */ class ws_sync_port { - // The type of the on_stream callback - using on_new_stream_cb = std::function< - void(beast::websocket::stream&)>; + // The type of the on_new_stream callback + // + using on_new_stream_cb = + boost::function&)>; server& instance_; std::ostream& log_; @@ -382,8 +393,7 @@ public: log_, instance_.next_id(), ep, - cb_ - )->run(); + cb_)->run(); } /** Accept a WebSocket upgrade request. @@ -399,13 +409,13 @@ public: */ template void - accept( + on_upgrade( socket_type&& sock, endpoint_type ep, beast::http::request&& req) { // Create the connection object and run it, - // transferring ownershop of the ugprade request. + // transferring ownership of the ugprade request. // std::make_shared( std::move(sock), @@ -413,8 +423,7 @@ public: log_, instance_.next_id(), ep, - cb_ - )->run(std::move(req)); + cb_)->run(std::move(req)); } }; diff --git a/example/server-framework/ws_upgrade_service.hpp b/example/server-framework/ws_upgrade_service.hpp index 8e80dc35..282f2e00 100644 --- a/example/server-framework/ws_upgrade_service.hpp +++ b/example/server-framework/ws_upgrade_service.hpp @@ -25,7 +25,7 @@ namespace framework { template class ws_upgrade_service { - std::shared_ptr handler_; + PortHandler& handler_; public: /** Constructor @@ -34,9 +34,8 @@ public: handle WebSocket upgrade requests. */ explicit - ws_upgrade_service( - std::shared_ptr handler) - : handler_(std::move(handler)) + ws_upgrade_service(PortHandler& handler) + : handler_(handler) { } @@ -86,7 +85,7 @@ public: // Its an ugprade request, so transfer ownership // of the stream and request to the port handler. // - handler_->accept( + handler_.on_upgrade( std::move(stream), ep, std::move(req)); diff --git a/example/server-framework/wss_ports.hpp b/example/server-framework/wss_ports.hpp index 77e8ce2f..eb86adfe 100644 --- a/example/server-framework/wss_ports.hpp +++ b/example/server-framework/wss_ports.hpp @@ -19,26 +19,32 @@ namespace framework { //------------------------------------------------------------------------------ -// This class represents a synchronous Secure WebSocket connection +// A synchronous WebSocket connection over an SSL connection // class sync_wss_con - // Note that we give this object the `enable_shared_from_this`, and have - // the base class call `impl().shared_from_this()` when needed. + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. // : public std::enable_shared_from_this - // We want the socket to be created before the base class so we use - // the "base from member" idiom which Boost provides as a class. + // The stream should be created before the base class so + // use the "base from member" idiom. // , public base_from_member>> - // Declare this base last now that everything else got set up first. + // Constructs last, destroys first // , public sync_ws_con_base { public: - // Construct the plain connection. + // Constructor + // + // Additional arguments are forwarded to the base class // template explicit @@ -51,7 +57,7 @@ public: { } - // Construct the connection from an existing, handshaked SSL stream + // Construct from an existing, handshaked SSL stream // template sync_wss_con( @@ -64,7 +70,10 @@ public: // Returns the stream. // - // The base class calls this to obtain the websocket stream object. + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` // beast::websocket::stream>& stream() @@ -90,30 +99,32 @@ private: //------------------------------------------------------------------------------ -// This class represents an asynchronous Secure WebSocket -// connection which uses an OpenSSL socket as the stream. +// An asynchronous WebSocket connection over an SSL connection // class async_wss_con - // Note that we give this object the enable_shared_from_this, and have - // the base class call impl().shared_from_this(). The reason we do that - // is so that the shared_ptr has the correct type. This lets the - // derived class (this class) use its members in calls to std::bind, - // without an ugly call to `dynamic_downcast` or other nonsense. + // Give this object the enable_shared_from_this, and have + // the base class call impl().shared_from_this(). The reason + // is so that the shared_ptr has the correct type. This lets + // the derived class (this class) use its members in calls to + // `std::bind`, without an ugly call to `dynamic_downcast` or + // other nonsense. // : public std::enable_shared_from_this - // We want the socket to be created before the base class so we use - // the "base from member" idiom which Boost provides as a class. + // The stream should be created before the base class so + // use the "base from member" idiom. // , public base_from_member>> - // Declare this base last now that everything else got set up first. + // Constructs last, destroys first // , public async_ws_con_base { public: - // Construct the connection. + // Constructor + // + // Additional arguments are forwarded to the base class // template async_wss_con( @@ -125,7 +136,7 @@ public: { } - // Construct the connection from an existing, handshaked SSL stream + // Construct from an existing, handshaked SSL stream // template async_wss_con( @@ -137,8 +148,11 @@ public: } // Returns the stream. - // The base class calls this to obtain the object to - // use for reading and writing HTTP messages. + // + // The base class calls this to obtain the object to use for + // reading and writing HTTP messages. This allows the same base + // class to work with different return types for `stream()` such + // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` // beast::websocket::stream>& stream() @@ -159,13 +173,15 @@ private: // stream().next_layer().async_handshake( boost::asio::ssl::stream_base::server, - this->strand_.wrap(std::bind( + this->strand_.wrap( + std::bind( &async_wss_con::on_handshake, - this->shared_from_this(), - std::placeholders::_1))); + this->shared_from_this(), + std::placeholders::_1))); } // Called when the SSL handshake completes + // void on_handshake(error_code ec) { @@ -191,11 +207,13 @@ class wss_sync_port // VFALCO We use boost::function to work around a compiler // crash with gcc and clang using libstdc++ - // The types of the on_stream callback - using on_new_stream_cb1 = boost::function< - void(beast::websocket::stream&)>; - using on_new_stream_cb2 = boost::function< - void(beast::websocket::stream>&)>; + // The types of the on_new_stream callbacks + // + using on_new_stream_cb1 = + boost::function&)>; + + using on_new_stream_cb2 = + boost::function>&)>; server& instance_; std::ostream& log_; @@ -259,8 +277,7 @@ public: log_, instance_.next_id(), ep, - cb2_ - )->run(); + cb2_)->run(); } /** Accept a WebSocket upgrade request. @@ -276,13 +293,13 @@ public: */ template void - accept( + on_upgrade( ssl_stream&& stream, endpoint_type ep, beast::http::request&& req) { // Create the connection object and run it, - // transferring ownershop of the ugprade request. + // transferring ownership of the ugprade request. // std::make_shared( std::move(stream), @@ -290,8 +307,7 @@ public: log_, instance_.next_id(), ep, - cb2_ - )->run(std::move(req)); + cb2_)->run(std::move(req)); } }; @@ -308,15 +324,24 @@ class wss_async_port // VFALCO We use boost::function to work around a compiler // crash with gcc and clang using libstdc++ - // The types of the on_stream callback - using on_new_stream_cb1 = boost::function< - void(beast::websocket::stream&)>; - using on_new_stream_cb2 = boost::function< - void(beast::websocket::stream>&)>; + // The types of the on_new_stream callbacks + // + using on_new_stream_cb1 = + boost::function&)>; + using on_new_stream_cb2 = + boost::function>&)>; + + // Reference to the server instance that made us server& instance_; + + // The stream to log to std::ostream& log_; + + // The context holds the SSL certificates the server uses boost::asio::ssl::context& ctx_; + + // Called for each new websocket stream on_new_stream_cb1 cb1_; on_new_stream_cb2 cb2_; @@ -376,8 +401,7 @@ public: log_, instance_.next_id(), ep, - cb2_ - )->run(); + cb2_)->run(); } /** Accept a WebSocket upgrade request. @@ -393,7 +417,7 @@ public: */ template void - accept( + on_upgrade( ssl_stream&& stream, endpoint_type ep, beast::http::request&& req) @@ -404,8 +428,7 @@ public: log_, instance_.next_id(), ep, - cb2_ - )->run(std::move(req)); + cb2_)->run(std::move(req)); } }; diff --git a/test/core/doc_examples.cpp b/test/core/doc_examples.cpp index df2f1ddb..33caa7f9 100644 --- a/test/core/doc_examples.cpp +++ b/test/core/doc_examples.cpp @@ -5,7 +5,7 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include "example/doc/core_examples.hpp" +#include "example/server-framework/detect_ssl.hpp" #include #include diff --git a/test/server/CMakeLists.txt b/test/server/CMakeLists.txt index e1a49780..8a948b76 100644 --- a/test/server/CMakeLists.txt +++ b/test/server/CMakeLists.txt @@ -26,6 +26,7 @@ add_executable (server-test http_sync_port.cpp https_ports.cpp main.cpp + multi_port.cpp rfc7231.cpp server.cpp service_list.cpp diff --git a/test/server/Jamfile b/test/server/Jamfile index 92b50ecc..4086abe2 100644 --- a/test/server/Jamfile +++ b/test/server/Jamfile @@ -21,6 +21,7 @@ exe server-test : ws_sync_port.cpp ws_upgrade_service.cpp #https_ports.cpp + #multi_port.cpp #ssl_certificate.cpp #ssl_stream.cpp ; diff --git a/test/server/multi_port.cpp b/test/server/multi_port.cpp new file mode 100644 index 00000000..6b108b49 --- /dev/null +++ b/test/server/multi_port.cpp @@ -0,0 +1,10 @@ +// +// Copyright (c) 2013-2017 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) +// + +// Test that header file is self-contained. +#include "../../example/server-framework/multi_port.hpp" +