Add flat_stream to experimental:

This adds a new stream wrapper class template designed to address
a performance shortcoming of boost::asio::ssl::stream.
This commit is contained in:
Vinnie Falco
2018-04-29 15:01:22 -07:00
parent 46e658a91b
commit 6108cf3eb7
17 changed files with 801 additions and 850 deletions

View File

@@ -1,3 +1,9 @@
Version 170:
* Add flat_stream to experimental
--------------------------------------------------------------------------------
Version 169:
* Use buffers_to_string in tests

View File

@@ -216,16 +216,6 @@ listed here along with a description of their use:
in the documentation. It is used by the "flex" servers which
support both plain and SSL sessions on the same port.
]
][
[[source_file example/common/flat_stream.hpp]]
[
This wrapper flattens buffer sequences having length greater than 1
and total size below a predefined amount, using a dynamic memory
allocation. It is primarily designed to overcome a performance
limitation of the current Boost.Asio version of `ssl::stream`, which
does not use OpenSSL's scatter/gather interface for its primitive
read and write operations.
]
][
[[source_file example/common/root_certificates.hpp]]
[

View File

@@ -284,6 +284,26 @@
</row>
</tbody>
</tgroup>
<tgroup cols="1">
<colspec colname="a"/>
<thead>
<row>
<entry valign="center" namest="a" nameend="c">
<bridgehead renderas="sect2">Experimental</bridgehead>
</entry>
</row>
</thead>
<tbody>
<row>
<entry valign="top">
<bridgehead renderas="sect3">Classes</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="beast.ref.boost__beast__flat_stream">flat_stream</link></member>
</simplelist>
</entry>
</row>
</tbody>
</tgroup>
<!--
<tgroup cols="1">
<colspec colname="a"/>

View File

@@ -105,6 +105,7 @@ WARN_LOGFILE =
INPUT = \
$(LIB_DIR)/include/boost/beast/ \
$(LIB_DIR)/include/boost/beast/core \
$(LIB_DIR)/include/boost/beast/experimental/core \
$(LIB_DIR)/include/boost/beast/http \
$(LIB_DIR)/include/boost/beast/websocket \
$(LIB_DIR)/include/boost/beast/zlib

View File

@@ -15,7 +15,6 @@ if (OPENSSL_FOUND)
add_executable (advanced-server-flex
${BOOST_BEAST_FILES}
${PROJECT_SOURCE_DIR}/example/common/detect_ssl.hpp
${PROJECT_SOURCE_DIR}/example/common/flat_stream.hpp
${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp
${PROJECT_SOURCE_DIR}/example/common/ssl_stream.hpp
Jamfile

View File

@@ -1,506 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_COMMON_FLAT_STREAM_HPP
#define BOOST_BEAST_EXAMPLE_COMMON_FLAT_STREAM_HPP
#include <boost/beast/core/buffers_prefix.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/core/handler_ptr.hpp>
#include <boost/beast/core/type_traits.hpp>
#include <boost/beast/websocket/teardown.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <boost/asio/handler_continuation_hook.hpp>
#include <boost/asio/handler_invoke_hook.hpp>
#include <boost/throw_exception.hpp>
#include <iterator>
#include <memory>
#include <utility>
namespace detail {
class flat_stream_base
{
public:
static std::size_t constexpr coalesce_limit = 64 * 1024;
// calculates the coalesce settings for a buffer sequence
template<class BufferSequence>
static
std::pair<std::size_t, bool>
coalesce(BufferSequence const& buffers, std::size_t limit)
{
std::pair<std::size_t, bool> result{0, false};
auto first = boost::asio::buffer_sequence_begin(buffers);
auto last = boost::asio::buffer_sequence_end(buffers);
if(first != last)
{
result.first = boost::asio::buffer_size(*first);
if(result.first < limit)
{
auto it = first;
auto prev = first;
while(++it != last)
{
auto const n =
boost::asio::buffer_size(*it);
if(result.first + n > limit)
break;
result.first += n;
prev = it;
}
result.second = prev != first;
}
}
return result;
}
};
} // detail
/** Flat stream wrapper.
This wrapper flattens buffer sequences having length greater than 1
and total size below a predefined amount, using a dynamic memory
allocation. It is primarily designed to overcome a performance
limitation of the current Boost.Asio version of ssl::stream, which
does not use OpenSSL's scatter/gather interface for its primitive
read and write operations.
See:
https://github.com/boostorg/beast/issues/1108
https://github.com/boostorg/asio/issues/100
https://stackoverflow.com/questions/38198638/openssl-ssl-write-from-multiple-buffers-ssl-writev
https://stackoverflow.com/questions/50026167/performance-drop-on-port-from-beast-1-0-0-b66-to-boost-1-67-0-beast
*/
template<class NextLayer>
class flat_stream : private detail::flat_stream_base
{
// Largest buffer size we will flatten
static std::size_t constexpr max_size = 1024 * 1024;
template<class, class> class write_op;
NextLayer stream_;
public:
/// The type of the next layer.
using next_layer_type =
typename std::remove_reference<NextLayer>::type;
/// The type of the lowest layer.
using lowest_layer_type = boost::beast::get_lowest_layer<next_layer_type>;
/// The type of the executor associated with the object.
using executor_type = typename next_layer_type::executor_type;
~flat_stream() = default;
flat_stream(flat_stream&&) = default;
flat_stream(flat_stream const&) = delete;
flat_stream& operator=(flat_stream&&) = default;
flat_stream& operator=(flat_stream const&) = delete;
/** Constructor
Arguments, if any, are forwarded to the next layer's constructor.
*/
template<class... Args>
explicit
flat_stream(Args&&... args);
//--------------------------------------------------------------------------
/** Get the executor associated with the object.
This function may be used to obtain the executor object that the
stream uses to dispatch handlers for asynchronous operations.
@return A copy of the executor that stream will use to dispatch handlers.
*/
executor_type
get_executor() noexcept
{
return stream_.get_executor();
}
/** Get a reference to the next layer
This function returns a reference to the next layer
in a stack of stream layers.
@return A reference to the next layer in the stack of
stream layers.
*/
next_layer_type&
next_layer()
{
return stream_;
}
/** Get a reference to the next layer
This function returns a reference to the next layer in a
stack of stream layers.
@return A reference to the next layer in the stack of
stream layers.
*/
next_layer_type const&
next_layer() const
{
return stream_;
}
/** Get a reference to the lowest layer
This function returns a reference to the lowest layer
in a stack of stream layers.
@return A reference to the lowest layer in the stack of
stream layers.
*/
lowest_layer_type&
lowest_layer()
{
return stream_.lowest_layer();
}
/** Get a reference to the lowest layer
This function returns a reference to the lowest layer
in a stack of stream layers.
@return A reference to the lowest layer in the stack of
stream layers. Ownership is not transferred to the caller.
*/
lowest_layer_type const&
lowest_layer() const
{
return stream_.lowest_layer();
}
//--------------------------------------------------------------------------
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers);
template<class MutableBufferSequence>
std::size_t
read_some(
MutableBufferSequence const& buffers,
boost::system::error_code& ec);
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers);
template<class ConstBufferSequence>
std::size_t
write_some(
ConstBufferSequence const& buffers,
boost::system::error_code& ec);
template<
class MutableBufferSequence,
class ReadHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(
ReadHandler, void(boost::system::error_code, std::size_t))
async_read_some(
MutableBufferSequence const& buffers,
ReadHandler&& handler);
template<
class ConstBufferSequence,
class WriteHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(
WriteHandler, void(boost::system::error_code, std::size_t))
async_write_some(
ConstBufferSequence const& buffers,
WriteHandler&& handler);
template<class T>
friend
void
teardown(boost::beast::websocket::role_type,
flat_stream<T>& s, boost::system::error_code& ec);
template<class T, class TeardownHandler>
friend
void
async_teardown(boost::beast::websocket::role_type role,
flat_stream<T>& s, TeardownHandler&& handler);
};
//------------------------------------------------------------------------------
template<class NextLayer>
template<class ConstBufferSequence, class Handler>
class flat_stream<NextLayer>::write_op
: public boost::asio::coroutine
{
using alloc_type = typename
boost::asio::associated_allocator_t<Handler>::template
rebind<char>::other;
struct deleter
{
alloc_type alloc;
std::size_t size = 0;
explicit
deleter(alloc_type const& alloc_)
: alloc(alloc_)
{
}
void
operator()(char* p)
{
alloc.deallocate(p, size);
}
};
flat_stream<NextLayer>& s_;
ConstBufferSequence b_;
std::unique_ptr<char, deleter> p_;
Handler h_;
public:
template<class DeducedHandler>
write_op(
flat_stream<NextLayer>& s,
ConstBufferSequence const& b,
DeducedHandler&& h)
: s_(s)
, b_(b)
, p_(nullptr, deleter{
(boost::asio::get_associated_allocator)(h)})
, h_(std::forward<DeducedHandler>(h))
{
}
using allocator_type =
boost::asio::associated_allocator_t<Handler>;
allocator_type
get_allocator() const noexcept
{
return (boost::asio::get_associated_allocator)(h_);
}
using executor_type = boost::asio::associated_executor_t<
Handler, decltype(std::declval<NextLayer&>().get_executor())>;
executor_type
get_executor() const noexcept
{
return (boost::asio::get_associated_executor)(
h_, s_.get_executor());
}
void
operator()(
boost::system::error_code ec,
std::size_t bytes_transferred)
{
BOOST_ASIO_CORO_REENTER(*this)
{
BOOST_ASIO_CORO_YIELD
{
auto const result = coalesce(b_, coalesce_limit);
if(result.second)
{
p_.get_deleter().size = result.first;
p_.reset(p_.get_deleter().alloc.allocate(
p_.get_deleter().size));
boost::asio::buffer_copy(
boost::asio::buffer(
p_.get(), p_.get_deleter().size),
b_, result.first);
s_.stream_.async_write_some(
boost::asio::buffer(
p_.get(), p_.get_deleter().size),
std::move(*this));
}
else
{
s_.stream_.async_write_some(
boost::beast::buffers_prefix(result.first, b_),
std::move(*this));
}
}
p_.reset();
h_(ec, bytes_transferred);
}
}
friend
bool asio_handler_is_continuation(write_op* op)
{
using boost::asio::asio_handler_is_continuation;
return asio_handler_is_continuation(
std::addressof(op->h_));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, write_op* op)
{
using boost::asio::asio_handler_invoke;
asio_handler_invoke(f, std::addressof(op->h_));
}
};
//------------------------------------------------------------------------------
template<class NextLayer>
template<class... Args>
flat_stream<NextLayer>::
flat_stream(Args&&... args)
: stream_(std::forward<Args>(args)...)
{
}
template<class NextLayer>
template<class MutableBufferSequence>
std::size_t
flat_stream<NextLayer>::
read_some(MutableBufferSequence const& buffers)
{
static_assert(boost::beast::is_sync_read_stream<next_layer_type>::value,
"SyncReadStream requirements not met");
static_assert(boost::asio::is_mutable_buffer_sequence<
MutableBufferSequence>::value,
"MutableBufferSequence requirements not met");
boost::system::error_code ec;
auto n = read_some(buffers, ec);
if(ec)
BOOST_THROW_EXCEPTION(boost::system::system_error{ec});
return n;
}
template<class NextLayer>
template<class MutableBufferSequence>
std::size_t
flat_stream<NextLayer>::
read_some(MutableBufferSequence const& buffers, boost::system::error_code& ec)
{
return stream_.read_some(buffers, ec);
}
template<class NextLayer>
template<class ConstBufferSequence>
std::size_t
flat_stream<NextLayer>::
write_some(ConstBufferSequence const& buffers)
{
static_assert(boost::beast::is_sync_write_stream<next_layer_type>::value,
"SyncWriteStream requirements not met");
static_assert(boost::asio::is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
boost::system::error_code ec;
auto n = write_some(buffers, ec);
if(ec)
BOOST_THROW_EXCEPTION(boost::system::system_error{ec});
return n;
}
template<class NextLayer>
template<class ConstBufferSequence>
std::size_t
flat_stream<NextLayer>::
write_some(
ConstBufferSequence const& buffers,
boost::system::error_code& ec)
{
auto const result = coalesce(buffers, coalesce_limit);
if(result.second)
{
std::unique_ptr<char[]> p{new char[result.first]};
auto const b = boost::asio::buffer(p.get(), result.first);
boost::asio::buffer_copy(b, buffers);
return stream_.write_some(b, ec);
}
return stream_.write_some(
boost::beast::buffers_prefix(result.first, buffers), ec);
}
template<class NextLayer>
template<
class MutableBufferSequence,
class ReadHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(
ReadHandler, void(boost::system::error_code, std::size_t))
flat_stream<NextLayer>::
async_read_some(
MutableBufferSequence const& buffers,
ReadHandler&& handler)
{
static_assert(boost::beast::is_async_read_stream<next_layer_type>::value,
"AsyncReadStream requirements not met");
static_assert(boost::asio::is_mutable_buffer_sequence<
MutableBufferSequence >::value,
"MutableBufferSequence requirements not met");
return stream_.async_read_some(
buffers, std::forward<ReadHandler>(handler));
}
template<class NextLayer>
template<
class ConstBufferSequence,
class WriteHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(
WriteHandler, void(boost::system::error_code, std::size_t))
flat_stream<NextLayer>::
async_write_some(
ConstBufferSequence const& buffers,
WriteHandler&& handler)
{
static_assert(boost::beast::is_async_write_stream<next_layer_type>::value,
"AsyncWriteStream requirements not met");
static_assert(boost::asio::is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
BOOST_BEAST_HANDLER_INIT(
WriteHandler, void(boost::system::error_code, std::size_t));
write_op<ConstBufferSequence, BOOST_ASIO_HANDLER_TYPE(
WriteHandler, void(boost::system::error_code, std::size_t))>{
*this, buffers, std::move(init.completion_handler)}({}, 0);
return init.result.get();
}
template<class NextLayer>
void
teardown(
boost::beast::websocket::role_type role,
flat_stream<NextLayer>& s,
boost::system::error_code& ec)
{
using boost::beast::websocket::teardown;
teardown(role, s.stream_, ec);
}
template<class NextLayer, class TeardownHandler>
void
async_teardown(
boost::beast::websocket::role_type role,
flat_stream<NextLayer>& s,
TeardownHandler&& handler)
{
using boost::beast::websocket::async_teardown;
async_teardown(role, s.stream_, std::move(handler));
}
#endif

View File

@@ -1,329 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_COMMON_SSL_STREAM_HPP
#define BOOST_BEAST_EXAMPLE_COMMON_SSL_STREAM_HPP
// This include is necessary to work with `ssl::stream` and `boost::beast::websocket::stream`
#include <boost/beast/websocket/ssl.hpp>
#include "flat_stream.hpp"
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
/** C++11 enabled SSL socket wrapper
This wrapper provides an interface identical to `boost::asio::ssl::stream`,
with the following additional properties:
@li Satisfies @b MoveConstructible
@li Satisfies @b MoveAssignable
@li Constructible from a moved socket.
*/
template<class NextLayer>
class ssl_stream
: public boost::asio::ssl::stream_base
{
using ssl_stream_type = boost::asio::ssl::stream<NextLayer>;
using stream_type = flat_stream<ssl_stream_type>;
std::unique_ptr<stream_type> p_;
boost::asio::ssl::context* ctx_;
public:
/// The native handle type of the SSL stream.
using native_handle_type =
typename ssl_stream_type::native_handle_type;
/// Structure for use with deprecated impl_type.
using impl_struct = typename ssl_stream_type::impl_struct;
/// The type of the next layer.
using next_layer_type = typename ssl_stream_type::next_layer_type;
/// The type of the lowest layer.
using lowest_layer_type = typename ssl_stream_type::lowest_layer_type;
/// The type of the executor associated with the object.
using executor_type = typename stream_type::executor_type;
template<class Arg>
ssl_stream(
Arg&& arg,
boost::asio::ssl::context& ctx)
: p_(new stream_type{
std::forward<Arg>(arg), ctx})
, ctx_(&ctx)
{
}
ssl_stream(ssl_stream&& other)
: p_(std::move(other.p_))
, ctx_(other.ctx_)
{
}
ssl_stream& operator=(ssl_stream&& other)
{
p_ = std::move(other.p_);
ctx_ = other.ctx_;
return *this;
}
executor_type
get_executor() noexcept
{
return p_->get_executor();
}
native_handle_type
native_handle()
{
return p_->next_layer().native_handle();
}
next_layer_type const&
next_layer() const
{
return p_->next_layer().next_layer();
}
next_layer_type&
next_layer()
{
return p_->next_layer().next_layer();
}
lowest_layer_type&
lowest_layer()
{
return p_->lowest_layer();
}
lowest_layer_type const&
lowest_layer() const
{
return p_->lowest_layer();
}
void
set_verify_mode(boost::asio::ssl::verify_mode v)
{
p_->next_layer().set_verify_mode(v);
}
boost::system::error_code
set_verify_mode(boost::asio::ssl::verify_mode v,
boost::system::error_code& ec)
{
return p_->next_layer().set_verify_mode(v, ec);
}
void
set_verify_depth(int depth)
{
p_->next_layer().set_verify_depth(depth);
}
boost::system::error_code
set_verify_depth(
int depth, boost::system::error_code& ec)
{
return p_->next_layer().set_verify_depth(depth, ec);
}
template<class VerifyCallback>
void
set_verify_callback(VerifyCallback callback)
{
p_->next_layer().set_verify_callback(callback);
}
template<class VerifyCallback>
boost::system::error_code
set_verify_callback(VerifyCallback callback,
boost::system::error_code& ec)
{
return p_->next_layer().set_verify_callback(callback, ec);
}
void
handshake(handshake_type type)
{
p_->next_layer().handshake(type);
}
boost::system::error_code
handshake(handshake_type type,
boost::system::error_code& ec)
{
return p_->next_layer().handshake(type, ec);
}
template<class ConstBufferSequence>
void
handshake(
handshake_type type, ConstBufferSequence const& buffers)
{
p_->next_layer().handshake(type, buffers);
}
template<class ConstBufferSequence>
boost::system::error_code
handshake(handshake_type type,
ConstBufferSequence const& buffers,
boost::system::error_code& ec)
{
return p_->next_layer().handshake(type, buffers, ec);
}
template<class HandshakeHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler,
void(boost::system::error_code))
async_handshake(handshake_type type,
BOOST_ASIO_MOVE_ARG(HandshakeHandler) handler)
{
return p_->next_layer().async_handshake(type,
BOOST_ASIO_MOVE_CAST(HandshakeHandler)(handler));
}
template<class ConstBufferSequence, class BufferedHandshakeHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler,
void (boost::system::error_code, std::size_t))
async_handshake(handshake_type type, ConstBufferSequence const& buffers,
BOOST_ASIO_MOVE_ARG(BufferedHandshakeHandler) handler)
{
return p_->next_layer().async_handshake(type, buffers,
BOOST_ASIO_MOVE_CAST(BufferedHandshakeHandler)(handler));
}
void
shutdown()
{
p_->next_layer().shutdown();
}
boost::system::error_code
shutdown(boost::system::error_code& ec)
{
return p_->next_layer().shutdown(ec);
}
template<class ShutdownHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(ShutdownHandler,
void (boost::system::error_code))
async_shutdown(BOOST_ASIO_MOVE_ARG(ShutdownHandler) handler)
{
return p_->next_layer().async_shutdown(
BOOST_ASIO_MOVE_CAST(ShutdownHandler)(handler));
}
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers)
{
return p_->write_some(buffers);
}
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers,
boost::system::error_code& ec)
{
return p_->write_some(buffers, ec);
}
template<class ConstBufferSequence, class WriteHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler,
void (boost::system::error_code, std::size_t))
async_write_some(ConstBufferSequence const& buffers,
BOOST_ASIO_MOVE_ARG(WriteHandler) handler)
{
return p_->async_write_some(buffers,
BOOST_ASIO_MOVE_CAST(WriteHandler)(handler));
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers)
{
return p_->read_some(buffers);
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers,
boost::system::error_code& ec)
{
return p_->read_some(buffers, ec);
}
template<class MutableBufferSequence, class ReadHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler,
void(boost::system::error_code, std::size_t))
async_read_some(MutableBufferSequence const& buffers,
BOOST_ASIO_MOVE_ARG(ReadHandler) handler)
{
return p_->async_read_some(buffers,
BOOST_ASIO_MOVE_CAST(ReadHandler)(handler));
}
template<class SyncStream>
friend
void
teardown(boost::beast::websocket::role_type,
ssl_stream<SyncStream>& stream,
boost::system::error_code& ec);
template<class AsyncStream, class TeardownHandler>
friend
void
async_teardown(boost::beast::websocket::role_type,
ssl_stream<AsyncStream>& stream, TeardownHandler&& handler);
};
// These hooks are used to inform boost::beast::websocket::stream on
// how to tear down the connection as part of the WebSocket
// protocol specifications
template<class SyncStream>
inline
void
teardown(
boost::beast::websocket::role_type role,
ssl_stream<SyncStream>& stream,
boost::system::error_code& ec)
{
// Just forward it to the wrapped stream
using boost::beast::websocket::teardown;
teardown(role, *stream.p_, ec);
}
template<class AsyncStream, class TeardownHandler>
inline
void
async_teardown(
boost::beast::websocket::role_type role,
ssl_stream<AsyncStream>& stream,
TeardownHandler&& handler)
{
// Just forward it to the wrapped stream
using boost::beast::websocket::async_teardown;
async_teardown(role,
*stream.p_, std::forward<TeardownHandler>(handler));
}
#endif

View File

@@ -0,0 +1,64 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_CORE_DETAIL_FLAT_STREAM_HPP
#define BOOST_BEAST_CORE_DETAIL_FLAT_STREAM_HPP
#include <boost/asio/buffer.hpp>
#include <cstdlib>
#include <iterator>
namespace boost {
namespace beast {
namespace detail {
class flat_stream_base
{
public:
// Largest buffer size we will flatten.
// 16KB is the upper limit on reasonably sized HTTP messages.
static std::size_t constexpr coalesce_limit = 16 * 1024;
// calculates the coalesce settings for a buffer sequence
template<class BufferSequence>
static
std::pair<std::size_t, bool>
coalesce(BufferSequence const& buffers, std::size_t limit)
{
std::pair<std::size_t, bool> result{0, false};
auto first = boost::asio::buffer_sequence_begin(buffers);
auto last = boost::asio::buffer_sequence_end(buffers);
if(first != last)
{
result.first = boost::asio::buffer_size(*first);
if(result.first < limit)
{
auto it = first;
auto prev = first;
while(++it != last)
{
auto const n =
boost::asio::buffer_size(*it);
if(result.first + n > limit)
break;
result.first += n;
prev = it;
}
result.second = prev != first;
}
}
return result;
}
};
} // detail
} // beast
} // boost
#endif

View File

@@ -0,0 +1,353 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_CORE_FLAT_STREAM_HPP
#define BOOST_BEAST_CORE_FLAT_STREAM_HPP
#include <boost/beast/core/error.hpp>
#include <boost/beast/core/type_traits.hpp>
#include <boost/beast/experimental/core/detail/flat_stream.hpp>
#include <boost/asio/async_result.hpp>
#include <cstdlib>
#include <utility>
namespace boost {
namespace beast {
/** Stream wrapper to improve ssl::stream write performance.
This wrapper flattens writes for buffer sequences having length
greater than 1 and total size below a predefined amount, using
a dynamic memory allocation. It is primarily designed to overcome
a performance limitation of the current version of `boost::asio::ssl::stream`,
which does not use OpenSSL's scatter/gather interface for its
low-level read some and write some operations.
@par Example
To use the @ref flat_stream template with SSL streams, declare
a variable of the correct type. Parameters passed to the constructor
will be forwarded to the next layer's constructor:
@code
flat_stream<ssl::stream<ip::tcp::socket>> fs{ioc, ctx};
@endcode
Alternatively you can write
@code
ssl::stream<ip::tcp::socket> ss{ioc, ctx};
flat_stream<ssl::stream<ip::tcp::socket>&> fs{ss};
@endcode
The resulting stream may be passed to any stream algorithms which
operate on synchronous or asynchronous read or write streams,
examples include:
@li `boost::asio::read`, `boost::asio::async_read`
@li `boost::asio::write`, `boost::asio::async_write`
@li `boost::asio::read_until`, `boost::asio::async_read_until`
The stream may also be used as a template parameter in other
stream wrappers, such as for websocket:
@code
websocket::stream<flat_stream<ssl::stream<ip::tcp::socket>>> ws{ioc, ctx};
@endcode
@tparam NextLayer The type representing the next layer, to which
data will be read and written during operations. For synchronous
operations, the type must support the @b SyncStream concept. For
asynchronous operations, the type must support the @b AsyncStream
concept. This type will usually be some variation of
`boost::asio::ssl::stream`.
@par Concepts
@b AsyncStream
@b SyncStream
@see
@li https://github.com/boostorg/beast/issues/1108
@li https://github.com/boostorg/asio/issues/100
@li https://stackoverflow.com/questions/38198638/openssl-ssl-write-from-multiple-buffers-ssl-writev
@li https://stackoverflow.com/questions/50026167/performance-drop-on-port-from-beast-1-0-0-b66-to-boost-1-67-0-beast
*/
template<class NextLayer>
class flat_stream
#ifndef BOOST_BEAST_DOXYGEN
: private detail::flat_stream_base
#endif
{
// Largest buffer size we will flatten.
// 16KB is the upper limit on reasonably sized HTTP messages.
static std::size_t constexpr max_size = 16 * 1024;
template<class, class> class write_op;
NextLayer stream_;
public:
/// The type of the next layer.
using next_layer_type =
typename std::remove_reference<NextLayer>::type;
/// The type of the lowest layer.
using lowest_layer_type = boost::beast::get_lowest_layer<next_layer_type>;
/// The type of the executor associated with the object.
using executor_type = typename next_layer_type::executor_type;
flat_stream(flat_stream&&) = default;
flat_stream(flat_stream const&) = default;
flat_stream& operator=(flat_stream&&) = default;
flat_stream& operator=(flat_stream const&) = default;
/** Destructor
The treatment of pending operations will be the same as that
of the next layer.
*/
~flat_stream() = default;
/** Constructor
Arguments, if any, are forwarded to the next layer's constructor.
*/
template<class... Args>
explicit
flat_stream(Args&&... args);
//--------------------------------------------------------------------------
/** Get the executor associated with the object.
This function may be used to obtain the executor object that the
stream uses to dispatch handlers for asynchronous operations.
@return A copy of the executor that stream will use to dispatch handlers.
*/
executor_type
get_executor() noexcept
{
return stream_.get_executor();
}
/** Get a reference to the next layer
This function returns a reference to the next layer
in a stack of stream layers.
@return A reference to the next layer in the stack of
stream layers.
*/
next_layer_type&
next_layer()
{
return stream_;
}
/** Get a reference to the next layer
This function returns a reference to the next layer in a
stack of stream layers.
@return A reference to the next layer in the stack of
stream layers.
*/
next_layer_type const&
next_layer() const
{
return stream_;
}
/** Get a reference to the lowest layer
This function returns a reference to the lowest layer
in a stack of stream layers.
@return A reference to the lowest layer in the stack of
stream layers.
*/
lowest_layer_type&
lowest_layer()
{
return stream_.lowest_layer();
}
/** Get a reference to the lowest layer
This function returns a reference to the lowest layer
in a stack of stream layers.
@return A reference to the lowest layer in the stack of
stream layers. Ownership is not transferred to the caller.
*/
lowest_layer_type const&
lowest_layer() const
{
return stream_.lowest_layer();
}
//--------------------------------------------------------------------------
/** Read some data from the stream.
This function is used to read data from the stream. The function call will
block until one or more bytes of data has been read successfully, or until
an error occurs.
@param buffers The buffers into which the data will be read.
@returns The number of bytes read.
@throws boost::system::system_error Thrown on failure.
@note The `read_some` operation may not read all of the requested number of
bytes. Consider using the function `boost::asio::read` if you need to ensure
that the requested amount of data is read before the blocking operation
completes.
*/
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers);
/** Read some data from the stream.
This function is used to read data from the stream. The function call will
block until one or more bytes of data has been read successfully, or until
an error occurs.
@param buffers The buffers into which the data will be read.
@param ec Set to indicate what error occurred, if any.
@returns The number of bytes read.
@note The `read_some` operation may not read all of the requested number of
bytes. Consider using the function `boost::asio::read` if you need to ensure
that the requested amount of data is read before the blocking operation
completes.
*/
template<class MutableBufferSequence>
std::size_t
read_some(
MutableBufferSequence const& buffers,
error_code& ec);
/** Start an asynchronous read.
This function is used to asynchronously read one or more bytes of data from
the stream. The function call always returns immediately.
@param buffers The buffers into which the data will be read. Although the
buffers object may be copied as necessary, ownership of the underlying
buffers is retained by the caller, which must guarantee that they remain
valid until the handler is called.
@param handler The handler to be called when the read operation completes.
Copies will be made of the handler as required. The equivalent function
signature of the handler must be:
@code void handler(
const boost::system::error_code& error, // Result of operation.
std::size_t bytes_transferred // Number of bytes read.
); @endcode
@note The `read_some` operation may not read all of the requested number of
bytes. Consider using the function `boost::asio::async_read` if you need
to ensure that the requested amount of data is read before the asynchronous
operation completes.
*/
template<
class MutableBufferSequence,
class ReadHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(
ReadHandler, void(error_code, std::size_t))
async_read_some(
MutableBufferSequence const& buffers,
ReadHandler&& handler);
/** Write some data to the stream.
This function is used to write data on the stream. The function call will
block until one or more bytes of data has been written successfully, or
until an error occurs.
@param buffers The data to be written.
@returns The number of bytes written.
@throws boost::system::system_error Thrown on failure.
@note The `write_some` operation may not transmit all of the data to the
peer. Consider using the function `boost::asio::write` if you need to
ensure that all data is written before the blocking operation completes.
*/
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers);
/** Write some data to the stream.
This function is used to write data on the stream. The function call will
block until one or more bytes of data has been written successfully, or
until an error occurs.
@param buffers The data to be written.
@param ec Set to indicate what error occurred, if any.
@returns The number of bytes written.
@note The `write_some` operation may not transmit all of the data to the
peer. Consider using the function `boost::asio::write` if you need to
ensure that all data is written before the blocking operation completes.
*/
template<class ConstBufferSequence>
std::size_t
write_some(
ConstBufferSequence const& buffers,
error_code& ec);
/** Start an asynchronous write.
This function is used to asynchronously write one or more bytes of data to
the stream. The function call always returns immediately.
@param buffers The data to be written to the stream. Although the buffers
object may be copied as necessary, ownership of the underlying buffers is
retained by the caller, which must guarantee that they remain valid until
the handler is called.
@param handler The handler to be called when the write operation completes.
Copies will be made of the handler as required. The equivalent function
signature of the handler must be:
@code void handler(
const boost::system::error_code& error, // Result of operation.
std::size_t bytes_transferred // Number of bytes written.
); @endcode
@note The `async_write_some` operation may not transmit all of the data to
the peer. Consider using the function `boost::asio::async_write` if you need
to ensure that all data is written before the asynchronous operation completes.
*/
template<
class ConstBufferSequence,
class WriteHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(
WriteHandler, void(error_code, std::size_t))
async_write_some(
ConstBufferSequence const& buffers,
WriteHandler&& handler);
};
} // beast
} // boost
#include <boost/beast/experimental/core/impl/flat_stream.ipp>
#endif

View File

@@ -0,0 +1,304 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_CORE_IMPL_FLAT_STREAM_IPP
#define BOOST_BEAST_CORE_IMPL_FLAT_STREAM_IPP
#include <boost/beast/core/buffers_prefix.hpp>
#include <boost/beast/websocket/teardown.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/handler_continuation_hook.hpp>
#include <boost/asio/handler_invoke_hook.hpp>
namespace boost {
namespace beast {
template<class NextLayer>
template<class ConstBufferSequence, class Handler>
class flat_stream<NextLayer>::write_op
: public boost::asio::coroutine
{
using alloc_type = typename
boost::asio::associated_allocator_t<Handler>::template
rebind<char>::other;
struct deleter
{
alloc_type alloc;
std::size_t size = 0;
explicit
deleter(alloc_type const& alloc_)
: alloc(alloc_)
{
}
void
operator()(char* p)
{
alloc.deallocate(p, size);
}
};
flat_stream<NextLayer>& s_;
ConstBufferSequence b_;
std::unique_ptr<char, deleter> p_;
Handler h_;
public:
template<class DeducedHandler>
write_op(
flat_stream<NextLayer>& s,
ConstBufferSequence const& b,
DeducedHandler&& h)
: s_(s)
, b_(b)
, p_(nullptr, deleter{
(boost::asio::get_associated_allocator)(h)})
, h_(std::forward<DeducedHandler>(h))
{
}
using allocator_type =
boost::asio::associated_allocator_t<Handler>;
allocator_type
get_allocator() const noexcept
{
return (boost::asio::get_associated_allocator)(h_);
}
using executor_type = boost::asio::associated_executor_t<
Handler, decltype(std::declval<NextLayer&>().get_executor())>;
executor_type
get_executor() const noexcept
{
return (boost::asio::get_associated_executor)(
h_, s_.get_executor());
}
void
operator()(
boost::system::error_code ec,
std::size_t bytes_transferred);
friend
bool asio_handler_is_continuation(write_op* op)
{
using boost::asio::asio_handler_is_continuation;
return asio_handler_is_continuation(
std::addressof(op->h_));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, write_op* op)
{
using boost::asio::asio_handler_invoke;
asio_handler_invoke(f, std::addressof(op->h_));
}
};
template<class NextLayer>
template<class ConstBufferSequence, class Handler>
void
flat_stream<NextLayer>::
write_op<ConstBufferSequence, Handler>::
operator()(
error_code ec,
std::size_t bytes_transferred)
{
BOOST_ASIO_CORO_REENTER(*this)
{
BOOST_ASIO_CORO_YIELD
{
auto const result = coalesce(b_, coalesce_limit);
if(result.second)
{
p_.get_deleter().size = result.first;
p_.reset(p_.get_deleter().alloc.allocate(
p_.get_deleter().size));
boost::asio::buffer_copy(
boost::asio::buffer(
p_.get(), p_.get_deleter().size),
b_, result.first);
s_.stream_.async_write_some(
boost::asio::buffer(
p_.get(), p_.get_deleter().size),
std::move(*this));
}
else
{
s_.stream_.async_write_some(
boost::beast::buffers_prefix(result.first, b_),
std::move(*this));
}
}
p_.reset();
h_(ec, bytes_transferred);
}
}
//------------------------------------------------------------------------------
template<class NextLayer>
template<class... Args>
flat_stream<NextLayer>::
flat_stream(Args&&... args)
: stream_(std::forward<Args>(args)...)
{
}
template<class NextLayer>
template<class MutableBufferSequence>
std::size_t
flat_stream<NextLayer>::
read_some(MutableBufferSequence const& buffers)
{
static_assert(boost::beast::is_sync_read_stream<next_layer_type>::value,
"SyncReadStream requirements not met");
static_assert(boost::asio::is_mutable_buffer_sequence<
MutableBufferSequence>::value,
"MutableBufferSequence requirements not met");
error_code ec;
auto n = read_some(buffers, ec);
if(ec)
BOOST_THROW_EXCEPTION(boost::system::system_error{ec});
return n;
}
template<class NextLayer>
template<class MutableBufferSequence>
std::size_t
flat_stream<NextLayer>::
read_some(MutableBufferSequence const& buffers, error_code& ec)
{
return stream_.read_some(buffers, ec);
}
template<class NextLayer>
template<
class MutableBufferSequence,
class ReadHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(
ReadHandler, void(error_code, std::size_t))
flat_stream<NextLayer>::
async_read_some(
MutableBufferSequence const& buffers,
ReadHandler&& handler)
{
static_assert(boost::beast::is_async_read_stream<next_layer_type>::value,
"AsyncReadStream requirements not met");
static_assert(boost::asio::is_mutable_buffer_sequence<
MutableBufferSequence >::value,
"MutableBufferSequence requirements not met");
return stream_.async_read_some(
buffers, std::forward<ReadHandler>(handler));
}
template<class NextLayer>
template<class ConstBufferSequence>
std::size_t
flat_stream<NextLayer>::
write_some(ConstBufferSequence const& buffers)
{
static_assert(boost::beast::is_sync_write_stream<next_layer_type>::value,
"SyncWriteStream requirements not met");
static_assert(boost::asio::is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
auto const result = coalesce(buffers, coalesce_limit);
if(result.second)
{
std::unique_ptr<char[]> p{new char[result.first]};
auto const b = boost::asio::buffer(p.get(), result.first);
boost::asio::buffer_copy(b, buffers);
return stream_.write_some(b);
}
return stream_.write_some(
boost::beast::buffers_prefix(result.first, buffers));
}
template<class NextLayer>
template<class ConstBufferSequence>
std::size_t
flat_stream<NextLayer>::
write_some(ConstBufferSequence const& buffers, error_code& ec)
{
static_assert(boost::beast::is_sync_write_stream<next_layer_type>::value,
"SyncWriteStream requirements not met");
static_assert(boost::asio::is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
auto const result = coalesce(buffers, coalesce_limit);
if(result.second)
{
std::unique_ptr<char[]> p{new char[result.first]};
auto const b = boost::asio::buffer(p.get(), result.first);
boost::asio::buffer_copy(b, buffers);
return stream_.write_some(b, ec);
}
return stream_.write_some(
boost::beast::buffers_prefix(result.first, buffers), ec);
}
template<class NextLayer>
template<
class ConstBufferSequence,
class WriteHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(
WriteHandler, void(error_code, std::size_t))
flat_stream<NextLayer>::
async_write_some(
ConstBufferSequence const& buffers,
WriteHandler&& handler)
{
static_assert(boost::beast::is_async_write_stream<next_layer_type>::value,
"AsyncWriteStream requirements not met");
static_assert(boost::asio::is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
BOOST_BEAST_HANDLER_INIT(
WriteHandler, void(error_code, std::size_t));
write_op<ConstBufferSequence, BOOST_ASIO_HANDLER_TYPE(
WriteHandler, void(error_code, std::size_t))>{
*this, buffers, std::move(init.completion_handler)}({}, 0);
return init.result.get();
}
template<class NextLayer>
void
teardown(
boost::beast::websocket::role_type role,
flat_stream<NextLayer>& s,
error_code& ec)
{
using boost::beast::websocket::teardown;
teardown(role, s.next_layer(), ec);
}
template<class NextLayer, class TeardownHandler>
void
async_teardown(
boost::beast::websocket::role_type role,
flat_stream<NextLayer>& s,
TeardownHandler&& handler)
{
using boost::beast::websocket::async_teardown;
async_teardown(role, s.next_layer(), std::move(handler));
}
} // beast
} // boost
#endif

View File

@@ -27,6 +27,7 @@ add_executable (tests-beast
)
add_subdirectory (core)
add_subdirectory (experimental)
add_subdirectory (http)
add_subdirectory (websocket)
add_subdirectory (zlib)

View File

@@ -14,6 +14,7 @@ alias run-tests :
[ compile websocket.cpp ]
[ compile zlib.cpp ]
core//run-tests
experimental//run-tests
http//run-tests
websocket//run-tests
zlib//run-tests
@@ -21,6 +22,7 @@ alias run-tests :
alias fat-tests :
core//fat-tests
experimental//fat-tests
http//fat-tests
websocket//fat-tests
zlib//fat-tests

View File

@@ -0,0 +1,23 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(test/extras/include/boost/beast extras)
GroupSources(subtree/unit_test/include/boost/beast extras)
GroupSources(include/boost/beast beast)
GroupSources(test/beast/experimental "/")
add_executable (tests-beast-experimental
${BOOST_BEAST_FILES}
${EXTRAS_FILES}
${TEST_MAIN}
Jamfile
flat_stream.cpp
)
set_property(TARGET tests-beast-experimental PROPERTY FOLDER "tests")

View File

@@ -0,0 +1,25 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
local SOURCES =
flat_stream.cpp
;
local RUN_TESTS ;
for local f in $(SOURCES)
{
RUN_TESTS += [ run $(f) $(TEST_MAIN) ] ;
}
alias run-tests : $(RUN_TESTS) ;
exe fat-tests : $(TEST_MAIN) $(SOURCES) ;
explicit fat-tests ;

View File

@@ -8,7 +8,7 @@
//
// Test that header file is self-contained.
#include <example/common/flat_stream.hpp>
#include <boost/beast/experimental/core/flat_stream.hpp>
#include <boost/beast/test/websocket.hpp>
#include <boost/beast/test/yield_to.hpp>
@@ -39,7 +39,7 @@ public:
for(auto const n : v0)
v.emplace_back("", n);
auto const result =
::detail::flat_stream_base::coalesce(v, limit);
boost::beast::detail::flat_stream_base::coalesce(v, limit);
BEAST_EXPECT(result.first == count);
BEAST_EXPECT(result.second == copy);
return result;

View File

@@ -20,7 +20,6 @@ add_executable (tests-example-common
${TEST_MAIN}
Jamfile
detect_ssl.cpp
flat_stream.cpp
root_certificates.cpp
server_certificate.cpp
session_alloc.cpp

View File

@@ -9,7 +9,6 @@
local SOURCES =
detect_ssl.cpp
flat_stream.cpp
root_certificates.cpp
server_certificate.cpp
session_alloc.cpp