forked from boostorg/beast
Refactor detect_ssl_op:
fix #955 The asynchronous SSL detector uses a stackless coroutine.
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
Version 152:
|
Version 152:
|
||||||
|
|
||||||
|
* Refactor detect_ssl_op
|
||||||
|
|
||||||
WebSocket:
|
WebSocket:
|
||||||
|
|
||||||
* Redistribute the read tests in the translation units
|
* Redistribute the read tests in the translation units
|
||||||
|
@@ -22,6 +22,7 @@
|
|||||||
//[example_core_detect_ssl_1
|
//[example_core_detect_ssl_1
|
||||||
|
|
||||||
#include <boost/beast/core.hpp>
|
#include <boost/beast/core.hpp>
|
||||||
|
#include <boost/asio/coroutine.hpp>
|
||||||
#include <boost/logic/tribool.hpp>
|
#include <boost/logic/tribool.hpp>
|
||||||
|
|
||||||
/** Return `true` if a buffer contains a TLS/SSL client handshake.
|
/** Return `true` if a buffer contains a TLS/SSL client handshake.
|
||||||
@@ -302,22 +303,21 @@ async_detect_ssl(
|
|||||||
|
|
||||||
//[example_core_detect_ssl_6
|
//[example_core_detect_ssl_6
|
||||||
|
|
||||||
// Read from a stream to invoke is_tls_handshake asynchronously
|
// Read from a stream to invoke is_tls_handshake asynchronously.
|
||||||
|
// This will be implemented using Asio's "stackless coroutines"
|
||||||
|
// which are based on macros forming a switch statement. The
|
||||||
|
// operation is derived from `coroutine` for this reason.
|
||||||
//
|
//
|
||||||
template<
|
template<
|
||||||
class AsyncReadStream,
|
class AsyncReadStream,
|
||||||
class DynamicBuffer,
|
class DynamicBuffer,
|
||||||
class Handler>
|
class Handler>
|
||||||
class detect_ssl_op
|
class detect_ssl_op : public boost::asio::coroutine
|
||||||
{
|
{
|
||||||
// This composed operation has trivial state,
|
// This composed operation has trivial state,
|
||||||
// so it is just kept inside the class and can
|
// so it is just kept inside the class and can
|
||||||
// be cheaply copied as needed by the implementation.
|
// 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_;
|
AsyncReadStream& stream_;
|
||||||
DynamicBuffer& buffer_;
|
DynamicBuffer& buffer_;
|
||||||
Handler handler_;
|
Handler handler_;
|
||||||
@@ -370,28 +370,6 @@ public:
|
|||||||
return (boost::asio::get_associated_executor)(handler_, stream_.get_executor());
|
return (boost::asio::get_associated_executor)(handler_, stream_.get_executor());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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_));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Our main entry point. This will get called as our
|
// Our main entry point. This will get called as our
|
||||||
// intermediate operations complete. Definition below.
|
// intermediate operations complete. Definition below.
|
||||||
//
|
//
|
||||||
@@ -416,15 +394,14 @@ operator()(boost::beast::error_code ec, std::size_t bytes_transferred)
|
|||||||
{
|
{
|
||||||
namespace beast = boost::beast;
|
namespace beast = boost::beast;
|
||||||
|
|
||||||
// Execute the state machine
|
// This introduces the scope of the stackless coroutine
|
||||||
switch(step_)
|
BOOST_ASIO_CORO_REENTER(*this)
|
||||||
{
|
{
|
||||||
// Initial state
|
// There could already be data in the buffer
|
||||||
case 0:
|
// so we do this first, before reading from the stream.
|
||||||
// See if we can detect the handshake
|
|
||||||
result_ = is_ssl_handshake(buffer_.data());
|
result_ = is_ssl_handshake(buffer_.data());
|
||||||
|
|
||||||
// If there's a result, call the handler
|
// If we got an answer, return it
|
||||||
if(! boost::indeterminate(result_))
|
if(! boost::indeterminate(result_))
|
||||||
{
|
{
|
||||||
// We need to invoke the handler, but the guarantee
|
// We need to invoke the handler, but the guarantee
|
||||||
@@ -434,63 +411,51 @@ operator()(boost::beast::error_code ec, std::size_t bytes_transferred)
|
|||||||
// `bind_handler` lets us bind arguments in a safe way
|
// `bind_handler` lets us bind arguments in a safe way
|
||||||
// that preserves the type customization hooks of the
|
// that preserves the type customization hooks of the
|
||||||
// original handler.
|
// original handler.
|
||||||
step_ = 1;
|
BOOST_ASIO_CORO_YIELD
|
||||||
return boost::asio::post(
|
boost::asio::post(
|
||||||
stream_.get_executor(),
|
stream_.get_executor(),
|
||||||
beast::bind_handler(std::move(*this), ec, 0));
|
beast::bind_handler(std::move(*this), ec, 0));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
// 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(beast::read_size(buffer_, 1536)), 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
|
// Loop until an error occurs or we get a definitive answer
|
||||||
result_ = false;
|
for(;;)
|
||||||
|
{
|
||||||
|
// The algorithm should never need more than 4 bytes
|
||||||
|
BOOST_ASSERT(buffer_.size() < 4);
|
||||||
|
|
||||||
// We don't need bind_handler here because we were invoked
|
BOOST_ASIO_CORO_YIELD
|
||||||
// as a result of an intermediate asynchronous operation.
|
{
|
||||||
break;
|
// Prepare the buffer's output area.
|
||||||
|
auto const mutable_buffer = buffer_.prepare(beast::read_size(buffer_, 1536));
|
||||||
|
|
||||||
|
// Try to fill our buffer by reading from the stream
|
||||||
|
stream_.async_read_some(mutable_buffer, std::move(*this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for an error
|
||||||
|
if(ec)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Commit what we read into the buffer's input area.
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit the bytes that we read
|
// Invoke the final handler.
|
||||||
buffer_.commit(bytes_transferred);
|
handler_(ec, result_);
|
||||||
|
|
||||||
// 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_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//]
|
//]
|
||||||
|
Reference in New Issue
Block a user