Add multi_port to server-framework

This commit is contained in:
Vinnie Falco
2017-07-20 08:15:27 -07:00
parent f6e0e5ef47
commit a4a4121444
22 changed files with 937 additions and 301 deletions
+1 -1
View File
@@ -62,7 +62,7 @@ ports created in the example program, and the HTTP services contained by
the HTTP ports:
<img width="880" height = "344" alt = "ServerFramework"
src="https://raw.githubusercontent.com/vinniefalco/Beast/server/doc/images/server.png">
src="https://raw.githubusercontent.com/vinniefalco/Beast/master/doc/images/server.png">
## PortHandler Requirements
```C++
+481
View File
@@ -0,0 +1,481 @@
//
// 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_DETECT_SSL_HPP
#define BEAST_EXAMPLE_SERVER_DETECT_SSL_HPP
#include <boost/assert.hpp>
#include <boost/config.hpp>
//------------------------------------------------------------------------------
//
// Example: Detect TLS/SSL
//
//------------------------------------------------------------------------------
//[example_core_detect_ssl_1
#include <beast.hpp>
#include <boost/logic/tribool.hpp>
/** Return `true` if a buffer contains a TLS/SSL client handshake.
This function returns `true` if the beginning of the buffer
indicates that a TLS handshake is being negotiated, and that
there are at least four octets in the buffer.
If the content of the buffer cannot possibly be a TLS handshake
request, the function returns `false`. Otherwise, if additional
octets are required, `boost::indeterminate` is returned.
@param buffer The input buffer to inspect. This type must meet
the requirements of @b ConstBufferSequence.
@return `boost::tribool` indicating whether the buffer contains
a TLS client handshake, does not contain a handshake, or needs
additional octets.
@see
http://www.ietf.org/rfc/rfc2246.txt
7.4. Handshake protocol
*/
template<class ConstBufferSequence>
boost::tribool
is_ssl_handshake(ConstBufferSequence const& buffers);
//]
using namespace beast;
//[example_core_detect_ssl_2
template<
class ConstBufferSequence>
boost::tribool
is_ssl_handshake(
ConstBufferSequence const& buffers)
{
// Make sure buffers meets the requirements
static_assert(is_const_buffer_sequence<ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
// We need at least one byte to really do anything
if(boost::asio::buffer_size(buffers) < 1)
return boost::indeterminate;
// Extract the first byte, which holds the
// "message" type for the Handshake protocol.
unsigned char v;
boost::asio::buffer_copy(boost::asio::buffer(&v, 1), buffers);
// Check that the message type is "SSL Handshake" (rfc2246)
if(v != 0x16)
{
// This is definitely not a handshake
return false;
}
// At least four bytes are needed for the handshake
// so make sure that we get them before returning `true`
if(boost::asio::buffer_size(buffers) < 4)
return boost::indeterminate;
// This can only be a TLS/SSL handshake
return true;
}
//]
//[example_core_detect_ssl_3
/** Detect a TLS/SSL handshake on a stream.
This function reads from a stream to determine if a TLS/SSL
handshake is being received. The function call will block
until one of the following conditions is true:
@li The disposition of the handshake is determined
@li An error occurs
Octets read from the stream will be stored in the passed dynamic
buffer, which may be used to perform the TLS handshake if the
detector returns true, or otherwise consumed by the caller based
on the expected protocol.
@param stream The stream to read from. This type must meet the
requirements of @b SyncReadStream.
@param buffer The dynamic buffer to use. This type must meet the
requirements of @b DynamicBuffer.
@param ec Set to the error if any occurred.
@return `boost::tribool` indicating whether the buffer contains
a TLS client handshake, does not contain a handshake, or needs
additional octets. If an error occurs, the return value is
undefined.
*/
template<
class SyncReadStream,
class DynamicBuffer>
boost::tribool
detect_ssl(
SyncReadStream& stream,
DynamicBuffer& buffer,
error_code& ec)
{
// Make sure arguments meet the requirements
static_assert(is_sync_read_stream<SyncReadStream>::value,
"SyncReadStream requirements not met");
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
"DynamicBuffer requirements not met");
// Loop until an error occurs or we get a definitive answer
for(;;)
{
// There could already be data in the buffer
// so we do this first, before reading from the stream.
auto const result = is_ssl_handshake(buffer.data());
// If we got an answer, return it
if(! boost::indeterminate(result))
{
ec = {}; // indicate no error
return result;
}
// The algorithm should never need more than 4 bytes
BOOST_ASSERT(buffer.size() < 4);
// Create up to 4 bytes of space in the buffer's output area.
auto const mutable_buffer = buffer.prepare(4 - buffer.size());
// Try to fill our buffer by reading from the stream
std::size_t const bytes_transferred = stream.read_some(mutable_buffer, ec);
// Check for an error
if(ec)
break;
// Commit what we read into the buffer's input area.
buffer.commit(bytes_transferred);
}
// error
return false;
}
//]
//[example_core_detect_ssl_4
/** Detect a TLS/SSL handshake asynchronously on a stream.
This function is used to asynchronously determine if a TLS/SSL
handshake is being received.
The function call always returns immediately. The asynchronous
operation will continue until one of the following conditions
is true:
@li The disposition of the handshake is determined
@li An error occurs
This operation is implemented in terms of zero or more calls to
the next layer's `async_read_some` function, and is known as a
<em>composed operation</em>. The program must ensure that the
stream performs no other operations until this operation completes.
Octets read from the stream will be stored in the passed dynamic
buffer, which may be used to perform the TLS handshake if the
detector returns true, or otherwise consumed by the caller based
on the expected protocol.
@param stream The stream to read from. This type must meet the
requirements of @b AsyncReadStream.
@param buffer The dynamic buffer to use. This type must meet the
requirements of @b DynamicBuffer.
@param handler The handler to be called when the request
completes. Copies will be made of the handler as required.
The equivalent function signature of the handler must be:
@code
void handler(
error_code const& error, // Set to the error, if any
boost::tribool result // The result of the detector
);
@endcode
Regardless of whether the asynchronous operation completes
immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a
manner equivalent to using `boost::asio::io_service::post`.
*/
template<
class AsyncReadStream,
class DynamicBuffer,
class CompletionToken>
async_return_type< /*< The [link beast.ref.beast__async_return_type `async_return_type`] customizes the return value based on the completion token >*/
CompletionToken,
void(error_code, boost::tribool)> /*< This is the signature for the completion handler >*/
async_detect_ssl(
AsyncReadStream& stream,
DynamicBuffer& buffer,
CompletionToken&& token);
//]
//[example_core_detect_ssl_5
// This is the composed operation.
template<
class AsyncReadStream,
class DynamicBuffer,
class Handler>
class detect_ssl_op;
// Here is the implementation of the asynchronous initation function
template<
class AsyncReadStream,
class DynamicBuffer,
class CompletionToken>
async_return_type<
CompletionToken,
void(error_code, boost::tribool)>
async_detect_ssl(
AsyncReadStream& stream,
DynamicBuffer& buffer,
CompletionToken&& token)
{
// Make sure arguments meet the requirements
static_assert(is_async_read_stream<AsyncReadStream>::value,
"SyncReadStream requirements not met");
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
"DynamicBuffer requirements not met");
// This helper manages some of the handler's lifetime and
// uses the result and handler specializations associated with
// the completion token to help customize the return value.
//
beast::async_completion<
CompletionToken, void(beast::error_code, boost::tribool)> init{token};
// Create the composed operation and launch it. This is a constructor
// call followed by invocation of operator(). We use handler_type
// to convert the completion token into the correct handler type,
// allowing user defined specializations of the async result template
// to take effect.
//
detect_ssl_op<AsyncReadStream, DynamicBuffer, handler_type<
CompletionToken, void(error_code, boost::tribool)>>{
stream, buffer, init.completion_handler}(
beast::error_code{}, 0);
// This hook lets the caller see a return value when appropriate.
// For example this might return std::future<error_code, boost::tribool> if
// CompletionToken is boost::asio::use_future.
//
// If a coroutine is used for the token, the return value from
// this function will be the `boost::tribool` representing the result.
//
return init.result.get();
}
//]
//[example_core_detect_ssl_6
// Read from a stream to invoke is_tls_handshake asynchronously
//
template<
class AsyncReadStream,
class DynamicBuffer,
class Handler>
class detect_ssl_op
{
// This composed operation has trivial state,
// so it is just kept inside the class and can
// be cheaply copied as needed by the implementation.
// Indicates what step in the operation's state
// machine to perform next, starting from zero.
int step_ = 0;
AsyncReadStream& stream_;
DynamicBuffer& buffer_;
Handler handler_;
boost::tribool result_ = false;
public:
// Boost.Asio requires that handlers are CopyConstructible.
// The state for this operation is cheap to copy.
detect_ssl_op(detect_ssl_op const&) = default;
// The constructor just keeps references the callers varaibles.
//
template<class DeducedHandler>
detect_ssl_op(AsyncReadStream& stream,
DynamicBuffer& buffer, DeducedHandler&& handler)
: stream_(stream)
, buffer_(buffer)
, handler_(std::forward<DeducedHandler>(handler))
{
}
// Determines if the next asynchronous operation represents a
// continuation of the asynchronous flow of control associated
// with the final handler. If we are past step two, it means
// we have performed an asynchronous operation therefore any
// subsequent operation would represent a continuation.
// Otherwise, we propagate the handler's associated value of
// is_continuation. Getting this right means the implementation
// may schedule the invokation of the invoked functions more
// efficiently.
//
friend bool asio_handler_is_continuation(detect_ssl_op* op)
{
// This next call is structured to permit argument
// dependent lookup to take effect.
using boost::asio::asio_handler_is_continuation;
// Always use std::addressof to pass the pointer to the handler,
// otherwise an unwanted overload of operator& may be called instead.
return op->step_ > 2 ||
asio_handler_is_continuation(std::addressof(op->handler_));
}
// Handler hook forwarding. These free functions invoke the hooks
// associated with the final completion handler. In effect, they
// make the Asio implementation treat our composed operation the
// same way it would treat the final completion handler for the
// purpose of memory allocation and invocation.
//
// Our implementation just passes through the call to the hook
// associated with the final handler.
friend void* asio_handler_allocate(std::size_t size, detect_ssl_op* op)
{
using boost::asio::asio_handler_allocate;
return asio_handler_allocate(size, std::addressof(op->handler_));
}
friend void asio_handler_deallocate(void* p, std::size_t size, detect_ssl_op* op)
{
using boost::asio::asio_handler_deallocate;
return asio_handler_deallocate(p, size, std::addressof(op->handler_));
}
template<class Function>
friend void asio_handler_invoke(Function&& f, detect_ssl_op* op)
{
using boost::asio::asio_handler_invoke;
return asio_handler_invoke(f, std::addressof(op->handler_));
}
// Our main entry point. This will get called as our
// intermediate operations complete. Definition below.
//
void operator()(beast::error_code ec, std::size_t bytes_transferred);
};
//]
//[example_core_detect_ssl_7
// detect_ssl_op is callable with the signature
// void(error_code, bytes_transferred),
// allowing `*this` to be used as a ReadHandler
//
template<
class AsyncStream,
class DynamicBuffer,
class Handler>
void
detect_ssl_op<AsyncStream, DynamicBuffer, Handler>::
operator()(beast::error_code ec, std::size_t bytes_transferred)
{
// Execute the state machine
switch(step_)
{
// Initial state
case 0:
// See if we can detect the handshake
result_ = is_ssl_handshake(buffer_.data());
// If there's a result, call the handler
if(! boost::indeterminate(result_))
{
// We need to invoke the handler, but the guarantee
// is that the handler will not be called before the
// call to async_detect_ssl returns, so we must post
// the operation to the io_service. The helper function
// `bind_handler` lets us bind arguments in a safe way
// that preserves the type customization hooks of the
// original handler.
step_ = 1;
return stream_.get_io_service().post(
bind_handler(std::move(*this), ec, 0));
}
// The algorithm should never need more than 4 bytes
BOOST_ASSERT(buffer_.size() < 4);
step_ = 2;
do_read:
// We need more bytes, but no more than four total.
return stream_.async_read_some(buffer_.prepare(4 - buffer_.size()), std::move(*this));
case 1:
// Call the handler
break;
case 2:
// Set this so that asio_handler_is_continuation knows that
// the next asynchronous operation represents a continuation
// of the initial asynchronous operation.
step_ = 3;
BOOST_FALLTHROUGH;
case 3:
if(ec)
{
// Deliver the error to the handler
result_ = false;
// We don't need bind_handler here because we were invoked
// as a result of an intermediate asynchronous operation.
break;
}
// Commit the bytes that we read
buffer_.commit(bytes_transferred);
// See if we can detect the handshake
result_ = is_ssl_handshake(buffer_.data());
// If it is detected, call the handler
if(! boost::indeterminate(result_))
{
// We don't need bind_handler here because we were invoked
// as a result of an intermediate asynchronous operation.
break;
}
// Read some more
goto do_read;
}
// Invoke the final handler.
handler_(ec, result_);
}
//]
#endif
+38 -10
View File
@@ -15,7 +15,9 @@
#include <beast/http/empty_body.hpp>
#include <beast/http/message.hpp>
#include <beast/http/string_body.hpp>
#include <boost/filesystem/path.hpp>
#include <string>
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<class Body, class Fields>
void
send(response<Body, Fields>&&);
@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<Body, Fields>&& 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<class Body, class Fields>
beast::http::response<beast::http::string_body>
not_found(beast::http::request<Body, Fields> const& req,
not_found(
beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& rel_path) const
{
beast::http::response<beast::http::string_body> res;
@@ -207,7 +233,8 @@ private:
//
template<class Body, class Fields>
beast::http::response<file_body>
get(beast::http::request<Body, Fields> const& req,
get(
beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& full_path) const
{
beast::http::response<file_body> res;
@@ -224,7 +251,8 @@ private:
//
template<class Body, class Fields>
beast::http::response<beast::http::empty_body>
head(beast::http::request<Body, Fields> const& req,
head(
beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& full_path) const
{
beast::http::response<beast::http::empty_body> res;
+57 -16
View File
@@ -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<isRequest, Body, Fields> 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 Derived, class... Services>
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<class ConstBufferSequence>
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... Services>
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<async_http_con<Services...>>
// 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<socket_type>
// Declare this base last now that everything else got set up first.
// Constructs last, destroys first
//
, public async_http_con_base<async_http_con<Services...>, Services...>
{
public:
// Construct the plain connection.
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
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();
}
};
+17 -10
View File
@@ -328,22 +328,28 @@ private:
template<class... Services>
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<sync_http_con<Services...>>
// 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<socket_type>
// Declare this base last now that everything else got set up first.
// Constructs last, destroys first
//
, public sync_http_con_base<sync_http_con<Services...>, Services...>
{
public:
// Construct the plain connection.
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
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();
}
};
+90 -33
View File
@@ -24,25 +24,28 @@ namespace framework {
template<class... Services>
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<sync_https_con<Services...>>
// 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<ssl_stream<socket_type>>
// Declare this base last now that everything else got set up first.
// Constructs last, destroys first
//
, public sync_http_con_base<sync_https_con<Services...>, Services...>
{
public:
// Construct the plain connection.
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
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<socket_type>&
stream()
@@ -99,25 +105,28 @@ private:
template<class... Services>
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<async_https_con<Services...>>
// 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<ssl_stream<socket_type>>
// Declare this base last now that everything else got set up first.
// Constructs last, destroys first
//
, public async_http_con_base<async_https_con<Services...>, Services...>
{
public:
// Construct the plain connection.
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
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<socket_type>&
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<class ConstBufferSequence>
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<async_https_con<Services...>, 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<async_https_con<Services...>>(
@@ -358,8 +416,7 @@ public:
log_,
services_,
instance_.next_id(),
ep
)->run();
ep)->run();
}
};
+172 -121
View File
@@ -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<ws_sync_port>(
ec,
endpoint_type{addr,static_cast<unsigned short>(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<http_sync_port<
ws_upgrade_service<ws_sync_port>,
file_service
>>(
ec,
endpoint_type{addr,static_cast<unsigned short>(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<ws_async_port>(
ec,
endpoint_type{addr, port},
endpoint_type{addr,
static_cast<unsigned short>(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<http_async_port<
ws_upgrade_service<ws_async_port>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(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<ws_sync_port>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(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<http_sync_port<
ws_upgrade_service<ws_sync_port>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(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<wss_async_port>(
auto wsp = instance.make_port<wss_sync_port>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(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<https_async_port<
ws_upgrade_service<wss_async_port>,
auto sp = instance.make_port<https_sync_port<
ws_upgrade_service<wss_sync_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<wss_async_port>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(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<https_async_port<
ws_upgrade_service<wss_async_port>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(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<wss_sync_port>(
auto sp = instance.make_port<multi_port<
ws_upgrade_service<multi_port_base>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 6)},
static_cast<unsigned short>(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<https_sync_port<
ws_upgrade_service<wss_sync_port>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(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
+398
View File
@@ -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 <beast/core.hpp>
#include <boost/function.hpp>
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... Services>
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<multi_con<Services...>>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<socket_type>
// Constructs last, destroys first
//
, public async_http_con_base<multi_con<Services...>, 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<class... Args>
multi_con(
socket_type&& sock,
boost::asio::ssl::context& ctx,
Args&&... args)
: base_from_member<socket_type>(std::move(sock))
, async_http_con_base<multi_con<Services...>, Services...>(std::forward<Args>(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<multi_con<Services...>, 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<async_https_con<Services...>>(
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<void(beast::websocket::stream<socket_type>&)>;
using on_new_stream_cb2 = boost::function<void(beast::websocket::stream<ssl_stream<socket_type>>&)>;
// 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<class NextLayer>
void callback(beast::websocket::stream<NextLayer>&);
@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<class Callback>
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<class Body, class Fields>
void
on_upgrade(
socket_type&& sock,
endpoint_type ep,
beast::http::request<Body, Fields>&& req)
{
// Create the connection and call the version of
// run that takes the request since we have it already
//
std::make_shared<async_ws_con>(
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<class Body, class Fields>
void
on_upgrade(
ssl_stream<socket_type>&& stream,
endpoint_type ep,
beast::http::request<Body, Fields>&& req)
{
std::make_shared<async_wss_con>(
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... Services>
class multi_port : public multi_port_base
{
// The list of services connections created from this port will support
service_list<Services...> services_;
public:
/** Constructor
All arguments are forwarded to the multi_port_base constructor.
*/
template<class... Args>
multi_port(Args&&... args)
: multi_port_base(std::forward<Args>(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<std::size_t Index, class... Args>
void
init(error_code& ec, Args&&... args)
{
services_.template init<Index>(
ec,
std::forward<Args>(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<multi_con<Services...>>(
std::move(sock),
ctx_,
"multi_port",
log_,
services_,
instance_.next_id(),
ep
)->detect();
}
};
} // framework
#endif
+1 -1
View File
@@ -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
+24 -16
View File
@@ -12,7 +12,7 @@
#include <beast/core/multi_buffer.hpp>
#include <beast/websocket/stream.hpp>
#include <functional>
#include <boost/function.hpp>
#include <memory>
#include <ostream>
@@ -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<async_ws_con>
// 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<beast::websocket::stream<socket_type>>
// Declare this base last now that everything else got set up first.
// Constructs last, destroys first
//
, public async_ws_con_base<async_ws_con>
{
public:
// Construct the plain connection.
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
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<socket_type>&
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<socket_type>&)>;
// The type of the on_new_stream callback
//
using on_new_stream_cb =
boost::function<void(beast::websocket::stream<socket_type>&)>;
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<class Body, class Fields>
void
accept(
on_upgrade(
socket_type&& sock,
endpoint_type ep,
beast::http::request<Body, Fields>&& req)
@@ -356,8 +365,7 @@ public:
log_,
instance_.next_id(),
ep,
cb_
)->run(std::move(req));
cb_)->run(std::move(req));
}
};
+27 -18
View File
@@ -12,7 +12,7 @@
#include <beast/core/multi_buffer.hpp>
#include <beast/websocket.hpp>
#include <functional>
#include <boost/function.hpp>
#include <memory>
#include <ostream>
#include <thread>
@@ -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<class Callback>
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<sync_ws_con>
// 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<beast::websocket::stream<socket_type>>
// Declare this base last now that everything else got set up first.
// Constructs last, destroys first
//
, public sync_ws_con_base<sync_ws_con>
{
@@ -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<socket_type>&
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<socket_type>&)>;
// The type of the on_new_stream callback
//
using on_new_stream_cb =
boost::function<void(beast::websocket::stream<socket_type>&)>;
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<class Body, class Fields>
void
accept(
on_upgrade(
socket_type&& sock,
endpoint_type ep,
beast::http::request<Body, Fields>&& req)
{
// Create the connection object and run it,
// transferring ownershop of the ugprade request.
// transferring ownership of the ugprade request.
//
std::make_shared<sync_ws_con>(
std::move(sock),
@@ -413,8 +423,7 @@ public:
log_,
instance_.next_id(),
ep,
cb_
)->run(std::move(req));
cb_)->run(std::move(req));
}
};
@@ -25,7 +25,7 @@ namespace framework {
template<class PortHandler>
class ws_upgrade_service
{
std::shared_ptr<PortHandler> handler_;
PortHandler& handler_;
public:
/** Constructor
@@ -34,9 +34,8 @@ public:
handle WebSocket upgrade requests.
*/
explicit
ws_upgrade_service(
std::shared_ptr<PortHandler> 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));
+70 -47
View File
@@ -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<sync_wss_con>
// 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<beast::websocket::stream<ssl_stream<socket_type>>>
// Declare this base last now that everything else got set up first.
// Constructs last, destroys first
//
, public sync_ws_con_base<sync_wss_con>
{
public:
// Construct the plain connection.
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
explicit
@@ -51,7 +57,7 @@ public:
{
}
// Construct the connection from an existing, handshaked SSL stream
// Construct from an existing, handshaked SSL stream
//
template<class... Args>
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<ssl_stream<socket_type>>&
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<async_wss_con>
// 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<beast::websocket::stream<ssl_stream<socket_type>>>
// Declare this base last now that everything else got set up first.
// Constructs last, destroys first
//
, public async_ws_con_base<async_wss_con>
{
public:
// Construct the connection.
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
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<class... Args>
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<ssl_stream<socket_type>>&
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<socket_type>&)>;
using on_new_stream_cb2 = boost::function<
void(beast::websocket::stream<ssl_stream<socket_type>>&)>;
// The types of the on_new_stream callbacks
//
using on_new_stream_cb1 =
boost::function<void(beast::websocket::stream<socket_type>&)>;
using on_new_stream_cb2 =
boost::function<void(beast::websocket::stream<ssl_stream<socket_type>>&)>;
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<class Body, class Fields>
void
accept(
on_upgrade(
ssl_stream<socket_type>&& stream,
endpoint_type ep,
beast::http::request<Body, Fields>&& req)
{
// Create the connection object and run it,
// transferring ownershop of the ugprade request.
// transferring ownership of the ugprade request.
//
std::make_shared<sync_wss_con>(
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<socket_type>&)>;
using on_new_stream_cb2 = boost::function<
void(beast::websocket::stream<ssl_stream<socket_type>>&)>;
// The types of the on_new_stream callbacks
//
using on_new_stream_cb1 =
boost::function<void(beast::websocket::stream<socket_type>&)>;
using on_new_stream_cb2 =
boost::function<void(beast::websocket::stream<ssl_stream<socket_type>>&)>;
// 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<class Body, class Fields>
void
accept(
on_upgrade(
ssl_stream<socket_type>&& stream,
endpoint_type ep,
beast::http::request<Body, Fields>&& req)
@@ -404,8 +428,7 @@ public:
log_,
instance_.next_id(),
ep,
cb2_
)->run(std::move(req));
cb2_)->run(std::move(req));
}
};