Add basic_timeout_stream:

This stream wrapper replaces basic_stream_socket and
provides just the timeout functionality.

* basic_stream_socket is removed
This commit is contained in:
Vinnie Falco
2019-01-14 10:15:07 -08:00
parent cfd9b46014
commit 72ea142c5c
18 changed files with 2530 additions and 2631 deletions

View File

@ -1,3 +1,9 @@
Version 204
* Add basic_timeout_stream
--------------------------------------------------------------------------------
Version 203
* Update networking refresher doc

View File

@ -41,6 +41,7 @@
[def __asio_handler_allocate__ [@boost:/doc/html/boost_asio/reference/asio_handler_allocate.html `asio_handler_allocate`]]
[def __asio_handler_invoke__ [@boost:/doc/html/boost_asio/reference/asio_handler_invoke.html `asio_handler_invoke`]]
[def __basic_stream_socket__ [@boost:/doc/html/boost_asio/reference/basic_stream_socket.html `basic_stream_socket`]]
[def __const_buffer__ [@boost:/doc/html/boost_asio/reference/const_buffer.html `const_buffer`]]
[def __deduced__ [@boost:/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.automatic_deduction_of_initiating_function_return_type ['DEDUCED]]]
[def __executor_work_guard__ [@boost:/doc/html/boost_asio/reference/executor_work_guard.html `net::executor_work_guard`]]
@ -64,6 +65,7 @@
[def __CompletionHandler__ [@boost:/doc/html/boost_asio/reference/CompletionHandler.html ['CompletionHandler]]]
[def __CompletionCondition__ [@boost:/doc/html/boost_asio/reference/CompletionCondition.html ['CompletionCondition]]]
[def __ConnectCondition__ [@boost:/doc/html/boost_asio/reference/ConnectCondition.html ['ConnectCondition]]]
[def __ConnectHandler__ [@boost:/doc/html/boost_asio/reference/ConnectHandler.html ['ConnectHandler]]]
[def __ConstBufferSequence__ [@boost:/doc/html/boost_asio/reference/ConstBufferSequence.html ['ConstBufferSequence]]]
[def __EndpointSequence__ [@boost:/doc/html/boost_asio/reference/EndpointSequence.html ['EndpointSequence]]]
[def __Executor__ [@boost:/doc/html/boost_asio/reference/Executor1.html ['Executor]]]

View File

@ -6,7 +6,7 @@
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
-->
@ -180,7 +180,7 @@
<member><link linkend="beast.ref.boost__beast__async_op_base">async_op_base</link></member>
<member><link linkend="beast.ref.boost__beast__basic_flat_buffer">basic_flat_buffer</link></member>
<member><link linkend="beast.ref.boost__beast__basic_multi_buffer">basic_multi_buffer</link></member>
<member><link linkend="beast.ref.boost__beast__basic_stream_socket">basic_stream_socket</link></member>
<member><link linkend="beast.ref.boost__beast__basic_timeout_stream">basic_timeout_stream</link></member>
<member><link linkend="beast.ref.boost__beast__buffered_read_stream">buffered_read_stream</link></member>
<member><link linkend="beast.ref.boost__beast__buffers_adaptor">buffers_adaptor</link></member>
<member><link linkend="beast.ref.boost__beast__buffers_cat_view">buffers_cat_view</link></member>
@ -209,9 +209,9 @@
<member><link linkend="beast.ref.boost__beast__static_buffer_base">static_buffer_base</link></member>
<member><link linkend="beast.ref.boost__beast__static_string">static_string</link></member>
<member><link linkend="beast.ref.boost__beast__stable_async_op_base">stable_async_op_base</link></member>
<member><link linkend="beast.ref.boost__beast__stream_socket">stream_socket</link></member>
<member><link linkend="beast.ref.boost__beast__string_param">string_param</link></member>
<member><link linkend="beast.ref.boost__beast__string_view">string_view</link></member>
<member><link linkend="beast.ref.boost__beast__timeout_stream">timeout_stream</link></member>
</simplelist>
<bridgehead renderas="sect3">Constants</bridgehead>
<simplelist type="vert" columns="1">

View File

@ -27,6 +27,9 @@
<xsl:when test="type = 'class ConnectCondition'">
<xsl:text>class __ConnectCondition__</xsl:text>
</xsl:when>
<xsl:when test="type = 'class ConnectHandler'">
<xsl:text>class __ConnectHandler__</xsl:text>
</xsl:when>
<xsl:when test="declname = 'ConstBufferSequence' or type = 'class ConstBufferSequence'">
<xsl:text>class __ConstBufferSequence__</xsl:text>
</xsl:when>

View File

@ -12,7 +12,7 @@
#include <boost/beast/core/detail/config.hpp>
#include <boost/beast/core/basic_stream_socket.hpp>
#include <boost/beast/core/basic_timeout_stream.hpp>
#include <boost/beast/core/bind_handler.hpp>
#include <boost/beast/core/buffer_traits.hpp>
#include <boost/beast/core/buffered_read_stream.hpp>
@ -38,9 +38,9 @@
#include <boost/beast/core/span.hpp>
#include <boost/beast/core/static_buffer.hpp>
#include <boost/beast/core/static_string.hpp>
#include <boost/beast/core/stream_socket.hpp>
#include <boost/beast/core/string.hpp>
#include <boost/beast/core/string_param.hpp>
#include <boost/beast/core/timeout_stream.hpp>
#include <boost/beast/core/type_traits.hpp>
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,844 @@
//
// Copyright (c) 2018 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_BASIC_TIMEOUT_SOCKET_HPP
#define BOOST_BEAST_CORE_BASIC_TIMEOUT_SOCKET_HPP
#include <boost/beast/core/detail/config.hpp>
#include <boost/beast/core/detail/timeout_stream_base.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/core/type_traits.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/basic_stream_socket.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/executor.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/config/workaround.hpp>
#include <boost/optional.hpp>
#include <chrono>
#include <memory>
namespace boost {
namespace beast {
//------------------------------------------------------------------------------
/** A stream socket with an integrated timeout on reading, writing, and connecting.
This layered stream wrapper manages a contained `net::basic_stream_socket`
object to provide the following additional features:
@li A timeout may be specified for each logical asynchronous
operation that performs reading, writing, and/or connecting.
@li The class template is parameterized on the executor type to be
used for all asynchronous operations. This achieves partial support for
<em>"Networking TS enhancement to enable custom I/O executors"</em>
(<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html">P1322R0</a>).
Objects of this type are used in place of a regular networking socket,
where timeouts on operations are desired. In particular this class
template is designed to be used in place of `net::basic_stream_socket`
or more typically, `net:ip::tcp::socket`. Constructors are provided to
use a particular execution context or executor, subject to temporary
restrictions based on the current implementation of networking. Additional
constructors allow the timeout stream to be constructed from a networking
socket that already exists.
Although the stream supports multiple concurrent outstanding asynchronous
operations, the stream object itself is not thread-safe. The caller is
responsible for ensuring that the stream is accessed from only one thread
at a time. This includes the times when the stream, and its underlying
socket, is accessed by the networking implementation. To meet these
thread safety requirements, all asynchronous operations must be performed
by the stream within the same implicit strand (only one thread calling `run()`
on the corresponding `net::io_context`) or within the same explicit strand
such as an instance of `net::strand`.
When using explicit strands, calls to initiating functions may use
`net::bind_handler` with a suitable executor on the completion handler.
Alternatively, the executor may be specified once by passing it as a stream
class template parameter, and providing an instance of the executor upon
construction (if the executor type is not <em>DefaultConstructible</em>).
Regardless of the method chosen, the executor used with the stream must
provide the following guarantees:
@li <b>Ordering:</b>
Function objects submitted to the executor from the same thread shall
execute in the order they were submitted.
@li <b>Concurrency:</b>
Function objects submitted to the executor shall never run concurrently
with each other.
The executor type `net::strand` meets these requirements. Use of a
strand as the executor in the stream class template offers an additional
notational convenience: the strand does not need to be specified in
each individual initiating function call.
@par Usage
To use this stream declare an instance of the class. Then, before
each logical operation for which a timeout is desired, call
@ref expires_after with a duration, or call @ref expires_at with a
time point. Alternatively, call @ref expires_never to disable the
timeout for subsequent logical operations. A logical operation
is any series of one or more direct or indirect calls to the timeout
stream's read, write, or connect functions.
When a timeout is set and a mixed operation is performed (one that
includes both reads and writes, for example) the timeout applies
to all of the intermediate asynchronous operations used in the
enclosing operation. This allows timeouts to be applied to stream
algorithms which were not written specifically to allow for timeouts,
when those algorithms are passed a timeout stream with a timeout set.
When a timeout occurs the socket will be closed, canceling any
pending I/O operations. The completion handlers for these canceled
operations will be invoked with the error @ref beast::error::timeout.
@par Examples
This function reads an HTTP request with a timeout, then sends the
HTTP response with a different timeout.
@code
void process_http_1 (timeout_stream& stream, net::yield_context yield)
{
flat_buffer buffer;
http::request<http::empty_body> req;
// Read the request, with a 15 second timeout
stream.expires_after(std::chrono::seconds(15));
http::async_read(stream, buffer, req, yield);
// Calculate the response
http::response<http::string_body> res = make_response(req);
// Send the response, with a 30 second timeout.
stream.expires_after (std::chrono::seconds(30));
http::async_write (stream, res, yield);
}
@endcode
The example above could be expressed using a single timeout with a
simple modification. The function that follows first reads an HTTP
request then sends the HTTP response, with a single timeout that
applies to the entire combined operation of reading and writing:
@code
void process_http_2 (timeout_stream& stream, net::yield_context yield)
{
flat_buffer buffer;
http::request<http::empty_body> req;
// Require that the read and write combined take no longer than 30 seconds
stream.expires_after(std::chrono::seconds(30));
http::async_read(stream, buffer, req, yield);
http::response<http::string_body> res = make_response(req);
http::async_write (stream, res, yield);
}
@endcode
Some stream algorithms, such as `ssl::stream::async_handshake` perform
both reads and writes. A timeout set before calling the initiating function
of such composite stream algorithms will apply to the entire composite
operation. For example, a timeout may be set on performing the SSL handshake
thusly:
@code
void do_ssl_handshake (net::ssl::stream<timeout_stream>& stream, net::yield_context yield)
{
// Require that the SSL handshake take no longer than 10 seconds
stream.expires_after(std::chrono::seconds(10));
stream.async_handshake(net::ssl::stream_base::client, yield);
}
@endcode
@tparam Protocol A type meeting the requirements of <em>Protocol</em>
representing the protocol the protocol to use for the basic stream socket.
A common choice is `net::ip::tcp`.
@tparam Executor A type meeting the requirements of <em>Executor</em> to
be used for submitting all completion handlers which do not already have an
associated executor. This type defaults to `net::io_context::executor_type`.
@par Thread Safety
<em>Distinct objects</em>: Safe.@n
<em>Shared objects</em>: Unsafe. The application must also ensure
that all asynchronous operations are performed within the same
implicit or explicit strand.
@see <em>"Networking TS enhancement to enable custom I/O executors"</em>
(<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html">P1322R0</a>).
*/
template<
class Protocol,
class Executor = typename
net::basic_stream_socket<Protocol>::executor_type>
class basic_timeout_stream
#if ! BOOST_BEAST_DOXYGEN
: private detail::timeout_stream_base
#endif
{
using time_point = typename
std::chrono::steady_clock::time_point;
static constexpr time_point never()
{
return (time_point::max)();
}
static std::size_t constexpr no_limit =
(std::numeric_limits<std::size_t>::max)();
struct op_state
{
net::steady_timer timer; // for timing out
bool pending = false; // if op is pending
bool closed = false; // if timed out
explicit
op_state(net::io_context& ioc)
: timer(ioc)
{
}
};
// friend class template declaration in a class template is ignored
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88672
#if BOOST_WORKAROUND(BOOST_GCC, > 0)
public:
#endif
struct impl_type
: std::enable_shared_from_this<impl_type>
{
Executor ex; // must come first
op_state read;
op_state write;
net::basic_stream_socket<Protocol> socket;
template<class... Args>
explicit
impl_type(Executor const&, Args&&...);
impl_type(impl_type&&) = default;
impl_type& operator=(impl_type&&);
void reset(); // set timeouts to never
void close(); // cancel everything
};
#if BOOST_WORKAROUND(BOOST_GCC, > 0)
private:
#endif
// We use shared ownership for the state so it can
// outlive the destruction of the stream_socket object,
// in the case where there is no outstanding read or
// write but the implementation is still waiting on
// the rate timer.
std::shared_ptr<impl_type> impl_;
// Restricted until P1322R0 is incorporated into Boost.Asio.
static_assert(
std::is_convertible<
decltype(std::declval<Executor const&>().context()),
net::io_context&>::value,
"Only net::io_context is currently supported for executor_type::context()");
template<bool, class, class> class async_op;
template<class, class, class>
friend class detail::timeout_stream_connect_op;
template<class, class>
friend class basic_timeout_stream;
struct timeout_handler;
public:
/// The type of the executor associated with the object.
using executor_type = Executor;
/// The type of the next layer.
using next_layer_type = net::basic_stream_socket<Protocol>;
/// The protocol type.
using protocol_type = Protocol;
/// The endpoint type.
using endpoint_type = typename Protocol::endpoint;
/** Destructor
This function destroys the socket, cancelling any outstanding
asynchronous operations associated with the socket as if by
calling cancel.
*/
~basic_timeout_stream();
/** Construct the stream without opening it.
This constructor creates a timeout stream. The underlying socket needs
to be opened and then connected or accepted before data can be sent or
received on it.
@param ctx An object whose type meets the requirements of
<em>ExecutionContext</em>, which the stream will use to dispatch
handlers for any asynchronous operations performed on the socket.
Currently, the only supported type for `ctx` is `net::io_context`.
@note This function does not participate in overload resolution unless:
@li `std::is_convertible<ExecutionContext&, net::execution_context&>::value` is `true`, and
@li `std::is_constructible<executor_type, typename ExecutionContext::executor_type>::value` is `true`.
@see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html
*/
template<
class ExecutionContext
#if ! BOOST_BEAST_DOXYGEN
, class = typename std::enable_if<
std::is_convertible<
ExecutionContext&,
net::execution_context&>::value &&
std::is_constructible<
executor_type,
typename ExecutionContext::executor_type>::value
>::type
#endif
>
explicit
basic_timeout_stream(ExecutionContext& ctx);
/** Construct the stream without opening it.
This constructor creates a timeout stream. The underlying socket needs
to be opened and then connected or accepted before data can be sent or
received on it.
@param ex The executor which stream will use to dispatch handlers for
any asynchronous operations performed on the underlying socket.
Currently, only executors that return a `net::io_context&` from
`ex.context()` are supported.
@see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html
*/
explicit
basic_timeout_stream(executor_type const& ex);
/** Construct the stream with an existing socket.
This constructor creates a timeout stream by taking ownership of an
already existing socket. The executor will be the same as the executor
of the provided socket.
@param socket The socket object to construct with, which becomes the
next layer of the timeout stream. Ownership of this socket is
transferred by move.
@see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html
*/
explicit
basic_timeout_stream(
net::basic_stream_socket<Protocol>&& socket);
/** Construct the stream with an executor and existing socket.
This constructor creates a timeout stream by taking ownership of an
already existing socket.
@param ex The executor which stream will use to dispatch handlers for
any asynchronous operations performed on the underlying socket.
Currently, only executors that return a `net::io_context&` from
`ex.context()` are supported.
@param socket The socket object to construct with, which becomes the
next layer of the timeout stream. Ownership of this socket is
transferred by move.
@see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html
*/
basic_timeout_stream(
executor_type const& ex,
net::basic_stream_socket<Protocol>&& socket);
/** Move-construct a stream from another stream
This constructor moves a stream from one object to another.
The behavior of moving a stream while asynchronous operations
are outstanding is undefined.
@param other The other object from which the move will occur.
@note Following the move, the moved-from object is in a newly
constructed state.
*/
basic_timeout_stream(basic_timeout_stream&& other);
/** Move-assign a stream from another stream
This assignment operator moves a stream socket from one object
to another.
The behavior of move assignment while asynchronous operations
are pending is undefined.
@param other The other basic_timeout_stream object from which the
move will occur.
@note Following the move, the moved-from object is a newly
constructed state.
*/
basic_timeout_stream& operator=(basic_timeout_stream&& other);
//--------------------------------------------------------------------------
/** 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() const noexcept
{
return impl_->ex;
}
/** Get a reference to the underlying socket.
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() noexcept
{
return impl_->socket;
}
/** Get a reference to the underlying socket.
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 noexcept
{
return impl_->socket;
}
/** Set the timeout for the next logical operation.
This sets either the read timer, the write timer, or
both timers to expire after the specified amount of time
has elapsed. If a timer expires when the corresponding
asynchronous operation is outstanding, the stream will be
closed and any outstanding operations will complete with the
error @ref beast::error::timeout. Otherwise, if the timer
expires while no operations are outstanding, and the expiraton
is not set again, the next operation will time out immediately.
The timer applies collectively to any asynchronous reads
or writes initiated after the expiration is set, until the
expiration is set again. A call to @ref beast::async_connect
counts as both a read and a write.
@param expiry_time The amount of time after which a logical
operation should be considered timed out.
*/
void
expires_after(
std::chrono::nanoseconds expiry_time);
/** Set the timeout for the next logical operation.
This sets either the read timer, the write timer, or both
timers to expire at the specified time point. If a timer
expires when the corresponding asynchronous operation is
outstanding, the stream will be closed and any outstanding
operations will complete with the error @ref beast::error::timeout.
Otherwise, if the timer expires while no operations are outstanding,
and the expiraton is not set again, the next operation will time out
immediately.
The timer applies collectively to any asynchronous reads
or writes initiated after the expiration is set, until the
expiration is set again. A call to @ref beast::async_connect
counts as both a read and a write.
@param expiry_time The time point after which a logical
operation should be considered timed out.
*/
void
expires_at(net::steady_timer::time_point expiry_time);
/// Disable the timeout for the next logical operation.
void
expires_never();
/// Cancel all asynchronous operations associated with the socket.
void
cancel();
/** Close the timed stream.
This cancels all timers and pending I/O. The completion handlers
for any pending I/O will see an error code.
*/
void
close();
//--------------------------------------------------------------------------
/** Start an asynchronous connect.
This function is used to asynchronously connect a socket to the
specified remote endpoint. The function call always returns immediately.
The underlying socket is automatically opened if it is not already open.
If the connect fails, and the socket was automatically opened, the socket
is not returned to the closed state.
@param ep The remote endpoint to which the underlying socket will be
connected. Copies will be made of the endpoint object as required.
@param handler The handler to be called when the operation completes.
The implementation will take ownership of the handler by move construction.
The handler must be invocable with this signature:
@code
void handler(
error_code ec // Result of operation
);
@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
`net::post()`.
*/
template<class ConnectHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(ConnectHandler,
void(error_code))
async_connect(
endpoint_type ep,
ConnectHandler&& handler);
/** Start an asynchronous read.
This function is used to asynchronously read data from the stream.
The function call always returns immediately.
@param buffers A range of zero or more buffers to read stream data into.
Although the buffers object may be copied as necessary, ownership of the
underlying memory blocks 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 operation completes.
The implementation will take ownership of the handler by move construction.
The handler must be invocable with this signature:
@code
void handler(
error_code error, // Result of operation.
std::size_t bytes_transferred // Number of bytes read.
);
@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
`net::post()`.
*/
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);
/** Start an asynchronous write.
This function is used to asynchronously write data to the stream.
The function call always returns immediately.
@param buffers A range of zero or more buffers to be written to the stream.
Although the buffers object may be copied as necessary, ownership of the
underlying memory blocks 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 operation completes.
The implementation will take ownership of the handler by move construction.
The handler must be invocable with this signature:
@code
void handler(
error_code error, // Result of operation.
std::size_t bytes_transferred // Number of bytes written.
);
@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
`net::post()`.
*/
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);
};
//------------------------------------------------------------------------------
/** Asynchronously establishes a socket connection by trying each endpoint in a sequence, and terminating if a timeout occurs.
This function attempts to connect a socket to one of a sequence of
endpoints. It does this by repeated calls to the underlying socket's
@c async_connect member function, once for each endpoint in the sequence,
until a connection is successfully established or a timeout occurs.
@param stream The @ref beast::basic_timeout_stream to be connected. If the
underlying socket is already open, it will be closed.
@param endpoints A sequence of endpoints. This this object must meet
the requirements of <em>EndpointSequence</em>.
@param handler The handler to be called when the connect operation
completes. Ownership of the handler may be transferred. The function
signature of the handler must be:
@code
void handler(
// Result of operation. if the sequence is empty, set to
// net::error::not_found. Otherwise, contains the
// error from the last connection attempt.
error_code const& error,
// On success, the successfully connected endpoint.
// Otherwise, a default-constructed endpoint.
typename Protocol::endpoint const& endpoint
);
@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
`net::io_context::post()`.
*/
template<
class Protocol, class Executor,
class EndpointSequence,
class RangeConnectHandler
#if ! BOOST_BEAST_DOXYGEN
,class = typename std::enable_if<
net::is_endpoint_sequence<
EndpointSequence>::value>::type
#endif
>
BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler,
void (error_code, typename Protocol::endpoint))
async_connect(
basic_timeout_stream<Protocol, Executor>& stream,
EndpointSequence const& endpoints,
RangeConnectHandler&& handler);
/** Asynchronously establishes a socket connection by trying each endpoint in a sequence, and terminating if a timeout occurs.
This function attempts to connect a socket to one of a sequence of
endpoints. It does this by repeated calls to the underlying socket's
@c async_connect member function, once for each endpoint in the sequence,
until a connection is successfully established or a timeout occurs.
@param stream The @ref beast::basic_timeout_stream to be connected. If the
underlying socket is already open, it will be closed.
@param endpoints A sequence of endpoints. This this object must meet
the requirements of <em>EndpointSequence</em>.
@param connect_condition A function object that is called prior to each
connection attempt. The signature of the function object must be:
@code
bool connect_condition(
error_code const& ec,
typename Protocol::endpoint const& next);
@endcode
The @c ec parameter contains the result from the most recent connect
operation. Before the first connection attempt, @c ec is always set to
indicate success. The @c next parameter is the next endpoint to be tried.
The function object should return true if the next endpoint should be tried,
and false if it should be skipped.
@param handler The handler to be called when the connect operation
completes. Ownership of the handler may be transferred. The function
signature of the handler must be:
@code
void handler(
// Result of operation. if the sequence is empty, set to
// net::error::not_found. Otherwise, contains the
// error from the last connection attempt.
error_code const& error,
// On success, the successfully connected endpoint.
// Otherwise, a default-constructed endpoint.
typename Protocol::endpoint const& endpoint
);
@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
`net::io_context::post()`.
@par Example
The following connect condition function object can be used to output
information about the individual connection attempts:
@code
struct my_connect_condition
{
bool operator()(
error_code const& ec,
net::ip::tcp::endpoint const& next)
{
if (ec)
std::cout << "Error: " << ec.message() << std::endl;
std::cout << "Trying: " << next << std::endl;
return true;
}
};
@endcode
*/
template<
class Protocol, class Executor,
class EndpointSequence,
class ConnectCondition,
class RangeConnectHandler
#if ! BOOST_BEAST_DOXYGEN
,class = typename std::enable_if<
net::is_endpoint_sequence<
EndpointSequence>::value>::type
#endif
>
BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler,
void (error_code, typename Protocol::endpoint))
async_connect(
basic_timeout_stream<Protocol, Executor>& stream,
EndpointSequence const& endpoints,
ConnectCondition connect_condition,
RangeConnectHandler&& handler);
/** Asynchronously establishes a socket connection by trying each endpoint in a sequence, and terminating if a timeout occurs.
This function attempts to connect a socket to one of a sequence of
endpoints. It does this by repeated calls to the underlying socket's
@c async_connect member function, once for each endpoint in the sequence,
until a connection is successfully established or a timeout occurs.
@param stream The @ref beast::basic_timeout_stream to be connected. If the
underlying socket is already open, it will be closed.
@param begin An iterator pointing to the start of a sequence of endpoints.
@param end An iterator pointing to the end of a sequence of endpoints.
@param handler The handler to be called when the connect operation
completes. Ownership of the handler may be transferred. The function
signature of the handler must be:
@code
void handler(
// Result of operation. if the sequence is empty, set to
// net::error::not_found. Otherwise, contains the
// error from the last connection attempt.
error_code const& error,
// On success, an iterator denoting the successfully
// connected endpoint. Otherwise, the end iterator.
Iterator iterator
);
@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
`net::io_context::post()`.
*/
template<
class Protocol, class Executor,
class Iterator,
class IteratorConnectHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler,
void (error_code, Iterator))
async_connect(
basic_timeout_stream<Protocol, Executor>& stream,
Iterator begin, Iterator end,
IteratorConnectHandler&& handler);
/** Asynchronously establishes a socket connection by trying each endpoint in a sequence, and terminating if a timeout occurs.
This function attempts to connect a socket to one of a sequence of
endpoints. It does this by repeated calls to the underlying socket's
@c async_connect member function, once for each endpoint in the sequence,
until a connection is successfully established or a timeout occurs.
@param stream The @ref beast::basic_timeout_stream to be connected. If the
underlying socket is already open, it will be closed.
@param begin An iterator pointing to the start of a sequence of endpoints.
@param end An iterator pointing to the end of a sequence of endpoints.
@param connect_condition A function object that is called prior to each
connection attempt. The signature of the function object must be:
@code
bool connect_condition(
error_code const& ec,
Iterator next);
@endcode
@param handler The handler to be called when the connect operation
completes. Ownership of the handler may be transferred. The function
signature of the handler must be:
@code
void handler(
// Result of operation. if the sequence is empty, set to
// net::error::not_found. Otherwise, contains the
// error from the last connection attempt.
error_code const& error,
// On success, an iterator denoting the successfully
// connected endpoint. Otherwise, the end iterator.
Iterator iterator
);
@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
`net::io_context::post()`.
*/
template<
class Protocol, class Executor,
class Iterator,
class ConnectCondition,
class IteratorConnectHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler,
void (error_code, Iterator))
async_connect(
basic_timeout_stream<Protocol, Executor>& stream,
Iterator begin, Iterator end,
ConnectCondition connect_condition,
IteratorConnectHandler&& handler);
} // beast
} // boost
#include <boost/beast/core/impl/basic_timeout_stream.hpp>
#endif

View File

@ -7,8 +7,8 @@
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_CORE_DETAIL_STREAM_SOCKET_BASE_HPP
#define BOOST_BEAST_CORE_DETAIL_STREAM_SOCKET_BASE_HPP
#ifndef BOOST_BEAST_CORE_DETAIL_TIMEOUT_STREAM_BASE_HPP
#define BOOST_BEAST_CORE_DETAIL_TIMEOUT_STREAM_BASE_HPP
#include <boost/assert.hpp>
#include <boost/core/exchange.hpp>
@ -18,9 +18,20 @@ namespace beast {
namespace detail {
template<class, class, class>
class stream_socket_connect_op;
class timeout_stream_connect_op;
class stream_socket_base
struct any_endpoint
{
template<class Error, class Endpoint>
bool
operator()(
Error const&, Endpoint const&) const noexcept
{
return true;
}
};
class timeout_stream_base
{
protected:
class pending_guard
@ -43,9 +54,11 @@ protected:
b_ = true;
}
pending_guard(pending_guard&& other) noexcept
pending_guard(
pending_guard&& other) noexcept
: b_(other.b_)
, clear_(boost::exchange(other.clear_, false))
, clear_(boost::exchange(
other.clear_, false))
{
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,699 @@
//
// Copyright (c) 2018 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_BASIC_TIMEOUT_STREAM_HPP
#define BOOST_BEAST_CORE_IMPL_BASIC_TIMEOUT_STREAM_HPP
#include <boost/beast/core/async_op_base.hpp>
#include <boost/beast/core/buffers_prefix.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/assert.hpp>
#include <boost/core/exchange.hpp>
#include <cstdlib>
#include <type_traits>
#include <utility>
namespace boost {
namespace beast {
template<class Protocol, class Executor>
struct basic_timeout_stream<
Protocol, Executor>::timeout_handler
{
op_state& state;
std::shared_ptr<impl_type> impl;
void
operator()(error_code ec)
{
// timer canceled
if(ec == net::error::operation_aborted)
return;
BOOST_ASSERT(! ec);
if(! state.closed)
{
// timeout
impl->close();
state.closed = true;
}
else
{
// late completion
state.closed = false;
}
}
};
//------------------------------------------------------------------------------
/*
The algorithm for implementing the timeout depends
on the executor providing ordered execution guarantee.
`net::strand` automatically provides this, and we assume
that an implicit strand (one thread calling io_context::run)
also provides this.
*/
template<class Protocol, class Executor>
template<bool isRead, class Buffers, class Handler>
class basic_timeout_stream<Protocol, Executor>::async_op
: public async_op_base<Handler, Executor>
, public boost::asio::coroutine
{
std::shared_ptr<impl_type> impl_;
pending_guard pg_;
Buffers b_;
op_state&
state(std::true_type)
{
return impl_->read;
}
op_state&
state(std::false_type)
{
return impl_->write;
}
op_state&
state()
{
return state(
std::integral_constant<bool, isRead>{});
}
void
async_perform(std::true_type)
{
impl_->socket.async_read_some(
b_, std::move(*this));
}
void
async_perform(std::false_type)
{
impl_->socket.async_write_some(
b_, std::move(*this));
}
public:
template<class Handler_>
async_op(
basic_timeout_stream& s,
Buffers const& b,
Handler_&& h)
: async_op_base<Handler, Executor>(
std::forward<Handler_>(h), s.get_executor())
, impl_(s.impl_)
, pg_(state().pending)
, b_(b)
{
(*this)({});
}
void
operator()(
error_code ec,
std::size_t bytes_transferred = 0)
{
BOOST_ASIO_CORO_REENTER(*this)
{
// VFALCO TODO handle buffer size == 0
// must come first
state().timer.async_wait(
net::bind_executor(
this->get_executor(),
timeout_handler{state(),
impl_->shared_from_this()}));
BOOST_ASIO_CORO_YIELD
async_perform(
std::integral_constant<bool, isRead>{});
// try cancelling timer
auto const n =
state().timer.cancel();
if(state().closed)
{
// timeout handler already invoked
BOOST_ASSERT(n == 0);
ec = beast::error::timeout;
state().closed = false;
}
else if(n == 0)
{
// timeout handler already queued
ec = beast::error::timeout;
impl_->close();
state().closed = true;
}
else
{
// timeout was canceled
BOOST_ASSERT(n == 1);
}
pg_.reset();
this->invoke(ec, bytes_transferred);
}
}
};
//------------------------------------------------------------------------------
namespace detail {
template<
class Protocol, class Executor, class Handler>
class timeout_stream_connect_op
: public async_op_base<Handler, Executor>
{
using stream_type =
beast::basic_timeout_stream<Protocol, Executor>;
using timeout_handler =
typename stream_type::timeout_handler;
std::shared_ptr<typename basic_timeout_stream<
Protocol, Executor>::impl_type> impl_;
typename stream_type::pending_guard pg0_;
typename stream_type::pending_guard pg1_;
public:
template<
class Endpoints, class Condition,
class Handler_>
timeout_stream_connect_op(
stream_type& s,
Endpoints const& eps,
Condition cond,
Handler_&& h)
: async_op_base<Handler, Executor>(
std::forward<Handler_>(h), s.get_executor())
, impl_(s.impl_)
, pg0_(impl_->read.pending)
, pg1_(impl_->write.pending)
{
// must come first
// VFALCO TODO what about the handler's allocator?
impl_->write.timer.async_wait(
net::bind_executor(
this->get_executor(),
timeout_handler{impl_->write,
impl_->shared_from_this()}));
net::async_connect(impl_->socket,
eps, cond, std::move(*this));
// *this is now moved-from
}
template<
class Iterator, class Condition,
class Handler_>
timeout_stream_connect_op(
stream_type& s,
Iterator begin, Iterator end,
Condition cond,
Handler_&& h)
: async_op_base<Handler, Executor>(
std::forward<Handler_>(h), s.get_executor())
, impl_(s.impl_)
, pg0_(impl_->read.pending)
, pg1_(impl_->write.pending)
{
// must come first
impl_->write.timer.async_wait(
net::bind_executor(
this->get_executor(),
timeout_handler{impl_->write,
impl_->shared_from_this()}));
net::async_connect(impl_->socket,
begin, end, cond, std::move(*this));
// *this is now moved-from
}
template<class Handler_>
timeout_stream_connect_op(
stream_type& s,
typename stream_type::endpoint_type ep,
Handler_&& h)
: async_op_base<Handler, Executor>(
std::forward<Handler_>(h), s.get_executor())
, impl_(s.impl_)
, pg0_(impl_->read.pending)
, pg1_(impl_->write.pending)
{
// must come first
impl_->write.timer.async_wait(
net::bind_executor(
this->get_executor(),
timeout_handler{impl_->write,
impl_->shared_from_this()}));
impl_->socket.async_connect(
ep, std::move(*this));
// *this is now moved-from
}
template<class... Args>
void
operator()(error_code ec, Args&&... args)
{
// try to cancel the timer
auto const n =
impl_->write.timer.cancel();
if(impl_->write.closed)
{
// timeout handler already invoked
BOOST_ASSERT(n == 0);
ec = beast::error::timeout;
impl_->write.closed = false;
}
else if(n == 0)
{
// timeout handler already queued
ec = beast::error::timeout;
impl_->close();
impl_->write.closed = true;
}
else
{
// timeout was canceled
BOOST_ASSERT(n == 1);
}
pg0_.reset();
pg1_.reset();
this->invoke(ec, std::forward<Args>(args)...);
}
};
} // detail
//------------------------------------------------------------------------------
template<class Protocol, class Executor>
template<class... Args>
basic_timeout_stream<Protocol, Executor>::
impl_type::
impl_type(
Executor const& ex_,
Args&&... args)
: ex(ex_)
, read(ex_.context())
, write(ex_.context())
, socket(std::forward<Args>(args)...)
{
reset();
}
template<class Protocol, class Executor>
auto
basic_timeout_stream<Protocol, Executor>::
impl_type::
operator=(impl_type&& other) -> impl_type&
{
// VFALCO This hack is because legacy io_context::strand
// doesn't support operator=. Don't worry, constructing
// an executor cannot throw.
ex.~Executor();
::new(&ex) Executor(other.ex);
socket = std::move(other.socket);
read = std::move(other.read);
write = std::move(other.write);
return *this;
}
template<class Protocol, class Executor>
void
basic_timeout_stream<Protocol, Executor>::
impl_type::
reset()
{
// If assert goes off, it means that there are
// already read or write (or connect) operations
// outstanding, so there is nothing to apply
// the expiration time to!
//
BOOST_ASSERT(! read.pending || ! write.pending);
if(! read.pending)
BOOST_VERIFY(
read.timer.expires_at(never()) == 0);
if(! write.pending)
BOOST_VERIFY(
write.timer.expires_at(never()) == 0);
}
template<class Protocol, class Executor>
void
basic_timeout_stream<Protocol, Executor>::
impl_type::
close()
{
socket.close();
// have to let the read/write ops cancel the timer,
// otherwise we will get error::timeout on close when
// we actually want net::error::operation_aborted.
//
//read.timer.cancel();
//write.timer.cancel();
}
//------------------------------------------------------------------------------
template<class Protocol, class Executor>
basic_timeout_stream<Protocol, Executor>::
~basic_timeout_stream()
{
// the shared object can outlive *this,
// cancel any operations so the shared
// object is destroyed as soon as possible.
impl_->close();
}
template<class Protocol, class Executor>
template<class ExecutionContext, class>
basic_timeout_stream<Protocol, Executor>::
basic_timeout_stream(ExecutionContext& ctx)
: impl_(std::make_shared<impl_type>(
ctx.get_executor(),
ctx))
{
// Restriction is necessary until Asio fully supports P1322R0
static_assert(
std::is_same<ExecutionContext, net::io_context>::value,
"Only net::io_context is currently supported for ExecutionContext");
}
template<class Protocol, class Executor>
basic_timeout_stream<Protocol, Executor>::
basic_timeout_stream(executor_type const& ex)
: impl_(std::make_shared<impl_type>(
ex, ex.context()))
{
}
template<class Protocol, class Executor>
basic_timeout_stream<Protocol, Executor>::
basic_timeout_stream(
net::basic_stream_socket<Protocol>&& socket)
: impl_(std::make_shared<impl_type>(
socket.get_executor(), std::move(socket)))
{
}
template<class Protocol, class Executor>
basic_timeout_stream<Protocol, Executor>::
basic_timeout_stream(
executor_type const& ex,
net::basic_stream_socket<Protocol>&& socket)
: impl_(std::make_shared<impl_type>(
ex, std::move(socket)))
{
// Restriction is necessary until Asio fully supports P1322R0
if(ex.context().get_executor() != socket.get_executor())
throw std::invalid_argument(
"basic_timeout_stream currently requires ctx.get_executor() == socket.get_executor()");
}
template<class Protocol, class Executor>
basic_timeout_stream<Protocol, Executor>::
basic_timeout_stream(basic_timeout_stream&& other)
: impl_(std::make_shared<impl_type>(
std::move(*other.impl_)))
{
// Can't move while operations are pending!
BOOST_ASSERT(! impl_->read.pending);
BOOST_ASSERT(! impl_->write.pending);
}
template<class Protocol, class Executor>
auto
basic_timeout_stream<Protocol, Executor>::
operator=(basic_timeout_stream&& other) ->
basic_timeout_stream&
{
// Can't move while operations are pending!
BOOST_ASSERT(! impl_->read.pending);
BOOST_ASSERT(! impl_->write.pending);
BOOST_ASSERT(! other.impl_->read.pending);
BOOST_ASSERT(! other.impl_->write.pending);
*impl_ = std::move(*other.impl_);
return *this;
}
//------------------------------------------------------------------------------
template<class Protocol, class Executor>
void
basic_timeout_stream<Protocol, Executor>::
expires_after(std::chrono::nanoseconds expiry_time)
{
// If assert goes off, it means that there are
// already read or write (or connect) operations
// outstanding, so there is nothing to apply
// the expiration time to!
//
BOOST_ASSERT(
! impl_->read.pending ||
! impl_->write.pending);
if(! impl_->read.pending)
BOOST_VERIFY(
impl_->read.timer.expires_after(
expiry_time) == 0);
if(! impl_->write.pending)
BOOST_VERIFY(
impl_->write.timer.expires_after(
expiry_time) == 0);
}
template<class Protocol, class Executor>
void
basic_timeout_stream<Protocol, Executor>::
expires_at(
net::steady_timer::time_point expiry_time)
{
// If assert goes off, it means that there are
// already read or write (or connect) operations
// outstanding, so there is nothing to apply
// the expiration time to!
//
BOOST_ASSERT(
! impl_->read.pending ||
! impl_->write.pending);
if(! impl_->read.pending)
BOOST_VERIFY(
impl_->read.timer.expires_at(
expiry_time) == 0);
if(! impl_->write.pending)
BOOST_VERIFY(
impl_->write.timer.expires_at(
expiry_time) == 0);
}
template<class Protocol, class Executor>
void
basic_timeout_stream<Protocol, Executor>::
expires_never()
{
impl_->reset();
}
template<class Protocol, class Executor>
void
basic_timeout_stream<Protocol, Executor>::
cancel()
{
error_code ec;
impl_->socket.cancel(ec);
}
template<class Protocol, class Executor>
void
basic_timeout_stream<Protocol, Executor>::
close()
{
impl_->close();
}
template<class Protocol, class Executor>
template<class ConnectHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(ConnectHandler,
void(error_code))
basic_timeout_stream<Protocol, Executor>::
async_connect(
endpoint_type ep,
ConnectHandler&& handler)
{
BOOST_BEAST_HANDLER_INIT(
ConnectHandler, void(error_code));
detail::timeout_stream_connect_op<
Protocol, Executor, BOOST_ASIO_HANDLER_TYPE(
ConnectHandler, void(error_code))>(*this,
ep, std::forward<ConnectHandler>(handler));
return init.result.get();
}
template<class Protocol, class Executor>
template<class MutableBufferSequence, class ReadHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler,
void(error_code, std::size_t))
basic_timeout_stream<Protocol, Executor>::
async_read_some(
MutableBufferSequence const& buffers,
ReadHandler&& handler)
{
static_assert(net::is_mutable_buffer_sequence<
MutableBufferSequence>::value,
"MutableBufferSequence requirements not met");
BOOST_BEAST_HANDLER_INIT(
ReadHandler, void(error_code, std::size_t));
async_op<true, MutableBufferSequence, BOOST_ASIO_HANDLER_TYPE(
ReadHandler, void(error_code, std::size_t))>(
*this, buffers, std::forward<ReadHandler>(handler));
return init.result.get();
}
template<class Protocol, class Executor>
template<class ConstBufferSequence, class WriteHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler,
void(error_code, std::size_t))
basic_timeout_stream<Protocol, Executor>::
async_write_some(
ConstBufferSequence const& buffers,
WriteHandler&& handler)
{
static_assert(net::is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
BOOST_BEAST_HANDLER_INIT(
WriteHandler, void(error_code, std::size_t));
async_op<false, ConstBufferSequence, BOOST_ASIO_HANDLER_TYPE(
WriteHandler, void(error_code, std::size_t))>(
*this, buffers, std::forward<WriteHandler>(handler));
return init.result.get();
}
//------------------------------------------------------------------------------
template<
class Protocol, class Executor,
class EndpointSequence,
class RangeConnectHandler,
class>
BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler,
void(error_code, typename Protocol::endpoint))
async_connect(
basic_timeout_stream<Protocol, Executor>& stream,
EndpointSequence const& endpoints,
RangeConnectHandler&& handler)
{
BOOST_BEAST_HANDLER_INIT(RangeConnectHandler,
void(error_code, typename Protocol::endpoint));
detail::timeout_stream_connect_op<Protocol, Executor,
BOOST_ASIO_HANDLER_TYPE(RangeConnectHandler,
void(error_code,
typename Protocol::endpoint))>(
stream, endpoints, detail::any_endpoint{},
std::forward<RangeConnectHandler>(handler));
return init.result.get();
}
template<
class Protocol, class Executor,
class EndpointSequence,
class ConnectCondition,
class RangeConnectHandler,
class>
BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler,
void (error_code, typename Protocol::endpoint))
async_connect(
basic_timeout_stream<Protocol, Executor>& stream,
EndpointSequence const& endpoints,
ConnectCondition connect_condition,
RangeConnectHandler&& handler)
{
BOOST_BEAST_HANDLER_INIT(RangeConnectHandler,
void(error_code, typename Protocol::endpoint));
detail::timeout_stream_connect_op<Protocol, Executor,
BOOST_ASIO_HANDLER_TYPE(RangeConnectHandler,
void(error_code,
typename Protocol::endpoint))>(
stream, endpoints, connect_condition,
std::forward<RangeConnectHandler>(handler));
return init.result.get();
}
template<
class Protocol, class Executor,
class Iterator,
class IteratorConnectHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler,
void (error_code, Iterator))
async_connect(
basic_timeout_stream<Protocol, Executor>& stream,
Iterator begin, Iterator end,
IteratorConnectHandler&& handler)
{
BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler,
void(error_code, Iterator));
detail::timeout_stream_connect_op<Protocol, Executor,
BOOST_ASIO_HANDLER_TYPE(IteratorConnectHandler,
void(error_code, Iterator))>(
stream, begin, end, detail::any_endpoint{},
std::forward<IteratorConnectHandler>(handler));
return init.result.get();
}
template<
class Protocol, class Executor,
class Iterator,
class ConnectCondition,
class IteratorConnectHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler,
void (error_code, Iterator))
async_connect(
basic_timeout_stream<Protocol, Executor>& stream,
Iterator begin, Iterator end,
ConnectCondition connect_condition,
IteratorConnectHandler&& handler)
{
BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler,
void(error_code, Iterator));
detail::timeout_stream_connect_op<Protocol, Executor,
BOOST_ASIO_HANDLER_TYPE(IteratorConnectHandler,
void(error_code, Iterator))>(
stream, begin, end, connect_condition,
std::forward<IteratorConnectHandler>(handler));
return init.result.get();
}
} // beast
} // boost
#endif

View File

@ -7,11 +7,11 @@
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_CORE_STREAM_SOCKET_HPP
#define BOOST_BEAST_CORE_STREAM_SOCKET_HPP
#ifndef BOOST_BEAST_CORE_TIMEOUT_STREAM_HPP
#define BOOST_BEAST_CORE_TIMEOUT_STREAM_HPP
#include <boost/beast/core/detail/config.hpp>
#include <boost/beast/core/basic_stream_socket.hpp>
#include <boost/beast/core/basic_timeout_stream.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/asio/ip/tcp.hpp>
@ -20,7 +20,7 @@ namespace beast {
/** A TCP/IP stream socket which supports timeouts and rate limits
*/
using stream_socket = basic_stream_socket<
using timeout_stream = basic_timeout_stream<
net::ip::tcp,
net::io_context::executor_type>;

View File

@ -28,7 +28,7 @@ add_executable (tests-beast-core
_detail_variant.cpp
_detail_varint.cpp
async_op_base.cpp
basic_stream_socket.cpp
basic_timeout_stream.cpp
bind_handler.cpp
buffer_traits.cpp
buffered_read_stream.cpp
@ -55,9 +55,9 @@ add_executable (tests-beast-core
span.cpp
static_buffer.cpp
static_string.cpp
stream_socket.cpp
string.cpp
string_param.cpp
timeout_stream.cpp
type_traits.cpp
)

View File

@ -17,7 +17,7 @@ local SOURCES =
_detail_variant.cpp
_detail_varint.cpp
async_op_base.cpp
basic_stream_socket.cpp
basic_timeout_stream.cpp
bind_handler.cpp
buffer_traits.cpp
buffered_read_stream.cpp
@ -44,9 +44,9 @@ local SOURCES =
span.cpp
static_buffer.cpp
static_string.cpp
stream_socket.cpp
string.cpp
string_param.cpp
timeout_stream.cpp
type_traits.cpp
;

View File

@ -1,132 +0,0 @@
//
// Copyright (c) 2018 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
//
// Test that header file is self-contained.
#include <boost/beast/core/basic_stream_socket.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/beast/core/stream_socket.hpp>
#include <boost/asio/read_until.hpp>
#include <boost/asio/streambuf.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ip/udp.hpp>
namespace boost {
namespace beast {
class basic_stream_socket_test
: public beast::unit_test::suite
{
public:
struct read_handler
{
template<class... Args>
void operator()(Args&&...)
{
}
};
template <class Protocol, class Executor, class ReadHandler>
void async_read_line (
basic_stream_socket<Protocol, Executor>& stream,
net::streambuf& buffer, ReadHandler&& handler)
{
stream.expires_after(std::chrono::seconds(30));
net::async_read_until(stream, buffer, "\r\n", std::forward<ReadHandler>(handler));
}
void
testJavadocs()
{
BEAST_EXPECT((&basic_stream_socket_test::async_read_line<
net::ip::tcp, net::io_context::executor_type, read_handler>));
}
struct other_t
{
};
void
testMembers()
{
using tcp = net::ip::tcp;
using ep_t = tcp::endpoint;
using ioc_t = net::io_context;
using ex_t = ioc_t::executor_type;
using stream_t = basic_stream_socket<tcp, ex_t>;
net::io_context ioc;
auto ex = ioc.get_executor();
// construction
{
stream_t{ioc};
stream_t{ex};
BOOST_STATIC_ASSERT(! std::is_constructible<
stream_t, other_t>::value);
}
{
stream_t{ioc, tcp::v4()};
stream_t{ex, tcp::v4()};
BOOST_STATIC_ASSERT(! std::is_constructible<
stream_t, other_t, tcp>::value);
}
{
stream_t{ioc, ep_t{}};
stream_t{ex, ep_t{}};
BOOST_STATIC_ASSERT(! std::is_constructible<
stream_t, other_t, ep_t>::value);
}
{
tcp::socket sock(ioc);
stream_t{ioc, std::move(sock)};
stream_t{ex, std::move(sock)};
BOOST_STATIC_ASSERT(! std::is_constructible<
stream_t, other_t, tcp::socket>::value);
}
// move
{
stream_t s1(ioc);
stream_t s2(std::move(s1));
}
{
stream_t s1(ioc);
stream_t s2(ioc);
s2 = std::move(s1);
}
// converting move
{
// We don't have any convertible protocol types
#if 0
basic_stream_socket<net::ip::udp, ex_t> s1(ioc);
stream_t s2(std::move(s1));
stream_t s3 = std::move(s1);
#endif
}
}
void
run()
{
testJavadocs();
testMembers();
}
};
BEAST_DEFINE_TESTSUITE(beast,core,basic_stream_socket);
} // beast
} // boost

View File

@ -0,0 +1,932 @@
//
// Copyright (c) 2018 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
//
// Test that header file is self-contained.
#include <boost/beast/core/basic_timeout_stream.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/beast/core/timeout_stream.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/empty_body.hpp>
#include <boost/beast/http/read.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/beast/http/write.hpp>
#include <boost/beast/websocket/stream.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/strand.hpp>
#include <array>
#include <thread>
namespace boost {
namespace beast {
class basic_timeout_stream_test
: public beast::unit_test::suite
{
public:
//--------------------------------------------------------------------------
struct socket_pair
{
net::io_context ioc1;
net::ip::tcp::socket s1;
net::io_context ioc2;
net::ip::tcp::socket s2;
socket_pair()
: s1(ioc1)
, s2(ioc2)
{
net::io_context ioc;
net::ip::tcp::acceptor a(ioc);
net::ip::tcp::endpoint ep(
net::ip::make_address_v4("127.0.0.1"), 0);
a.open(ep.protocol());
a.set_option(
net::socket_base::reuse_address(true));
a.bind(ep);
a.listen(1);
a.async_accept(s2,
[](error_code ec)
{
#if 0
if(ec == net::error::operation_aborted)
return;
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
#endif
});
s1.async_connect(a.local_endpoint(),
[](error_code ec)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
});
for(;;)
if(
ioc.poll() +
ioc1.poll() +
ioc2.poll() == 0)
break;
BOOST_ASSERT(s1.is_open());
#if 0
BOOST_ASSERT(s2.is_open()); // VFALCO Fails on Travis for some reason
BOOST_ASSERT(
s1.remote_endpoint() ==
s2.local_endpoint());
BOOST_ASSERT(
s2.remote_endpoint() ==
s1.local_endpoint());
#endif
}
};
//--------------------------------------------------------------------------
class server
{
string_view s_;
std::ostream& log_;
net::io_context ioc_;
net::ip::tcp::acceptor acceptor_;
net::ip::tcp::socket socket_;
std::thread t_;
void
fail(error_code ec, string_view what)
{
if(ec != net::error::operation_aborted)
log_ << what << ": " << ec.message() << "\n";
}
public:
server(
string_view s,
net::ip::tcp::endpoint ep,
std::ostream& log)
: s_(s)
, log_(log)
, ioc_(1)
, acceptor_(ioc_)
, socket_(ioc_)
{
boost::system::error_code ec;
acceptor_.open(ep.protocol(), ec);
if(ec)
{
fail(ec, "open");
return;
}
acceptor_.set_option(
net::socket_base::reuse_address(true), ec);
if(ec)
{
fail(ec, "set_option");
return;
}
acceptor_.bind(ep, ec);
if(ec)
{
fail(ec, "bind");
return;
}
acceptor_.listen(
net::socket_base::max_listen_connections, ec);
if(ec)
{
fail(ec, "listen");
return;
}
acceptor_.async_accept(socket_,
[this](error_code ec)
{
this->on_accept(ec);
});
t_ = std::thread(
[this]
{
ioc_.run();
});
}
~server()
{
ioc_.stop();
t_.join();
}
net::ip::tcp::endpoint
local_endpoint() const noexcept
{
return acceptor_.local_endpoint();
}
private:
class session
: public std::enable_shared_from_this<session>
{
string_view s_;
net::ip::tcp::socket socket_;
public:
session(
string_view s,
net::ip::tcp::socket sock,
std::ostream&)
: s_(s)
, socket_(std::move(sock))
{
}
void
run()
{
if(s_.empty())
socket_.async_wait(
net::socket_base::wait_read,
std::bind(
&session::on_read,
shared_from_this(),
std::placeholders::_1));
else
net::async_write(
socket_,
net::const_buffer(s_.data(), s_.size()),
std::bind(
&session::on_write,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
protected:
void
on_read(error_code ec)
{
boost::ignore_unused(ec);
}
void
on_write(error_code, std::size_t)
{
}
};
void
on_accept(error_code ec)
{
if(! acceptor_.is_open())
return;
if(ec)
fail(ec, "accept");
else
std::make_shared<session>(
s_, std::move(socket_), log_)->run();
acceptor_.async_accept(socket_,
[this](error_code ec)
{
this->on_accept(ec);
});
}
};
//--------------------------------------------------------------------------
void
testStrand()
{
{
using strand_type = net::io_context::strand;
net::io_context ioc;
strand_type st(ioc);
basic_timeout_stream<
net::ip::tcp, strand_type> s(st);
BEAST_EXPECT(s.get_executor() == st);
}
#if 0
// VFALCO This is disallowed until Asio implements P1322R0
{
using strand_type = net::strand<
net::io_context::executor_type>;
net::io_context ioc;
strand_type st(ioc.get_executor());
basic_timeout_stream<
net::ip::tcp, strand_type> s(st);
BEAST_EXPECT(s.get_executor() == st);
}
#endif
}
struct other_t
{
};
void
testMembers()
{
using tcp = net::ip::tcp;
using stream_t = basic_timeout_stream<tcp>;
net::io_context ioc;
auto ex = ioc.get_executor();
// construction
BOOST_STATIC_ASSERT(! std::is_constructible<
stream_t, other_t>::value);
BOOST_STATIC_ASSERT(! std::is_constructible<
stream_t, other_t, tcp::socket>::value);
{
stream_t s(ioc);
}
{
stream_t s(ex);
}
{
stream_t s((tcp::socket(ioc)));
}
{
stream_t s(ex, tcp::socket(ioc));
}
{
net::io_context ioc2;
try
{
// mismatched execution contexts
stream_t s(
ioc2.get_executor(),
tcp::socket(ioc));
fail("mismatched execution context", __FILE__, __LINE__);
}
catch(std::invalid_argument const&)
{
pass();
}
}
// move
{
stream_t s1(ioc);
stream_t s2(std::move(s1));
}
// assign
{
stream_t s1(ioc);
stream_t s2(ioc);
s2 = std::move(s1);
}
// get_executor
{
stream_t s(ioc);
BEAST_EXPECT(
s.get_executor() == ioc.get_executor());
}
// layers
{
net::socket_base::keep_alive opt;
tcp::socket sock(ioc);
sock.open(tcp::v4());
sock.get_option(opt);
BEAST_EXPECT(! opt.value());
stream_t s(ioc);
s.next_layer().open(tcp::v4());
s.next_layer().get_option(opt);
BEAST_EXPECT(! opt.value());
opt = true;
sock.set_option(opt);
opt = false;
BEAST_EXPECT(! opt.value());
s = stream_t(std::move(sock));
static_cast<stream_t const&>(s).next_layer().get_option(opt);
BEAST_EXPECT(opt.value());
}
}
//--------------------------------------------------------------------------
struct match
{
suite& suite_;
error_code ec_;
std::size_t n_;
match(suite& s, error_code ec, std::size_t n)
: suite_(s)
, ec_(ec)
, n_(n)
{
}
match(match&& other)
: suite_(other.suite_)
, ec_(other.ec_)
, n_(boost::exchange(other.n_,
(std::numeric_limits<std::size_t>::max)()))
{
}
~match()
{
suite_.BEAST_EXPECT(
n_ == (std::numeric_limits<std::size_t>::max)());
}
void
operator()(error_code ec, std::size_t n)
{
suite_.expect(ec == ec_, ec.message(), __FILE__, __LINE__);
suite_.BEAST_EXPECT(n == n_);
n_ = (std::numeric_limits<std::size_t>::max)();
}
};
void
testRead()
{
using tcp = net::ip::tcp;
using stream_t = basic_timeout_stream<tcp>;
char buf[4];
std::memset(buf, 0, 4);
net::mutable_buffer mb(buf, sizeof(buf));
auto const ep = net::ip::tcp::endpoint(
net::ip::make_address("127.0.0.1"), 0);
// success
{
server srv("*", ep, log);
net::io_context ioc;
stream_t s(ioc);
s.next_layer().connect(srv.local_endpoint());
s.async_read_some(mb, match{*this, {}, 1});
ioc.run_for(std::chrono::seconds(1));
}
// success, with timeout
{
server srv("*", ep, log);
net::io_context ioc;
stream_t s(ioc);
s.next_layer().connect(srv.local_endpoint());
s.expires_after(std::chrono::milliseconds(100));
s.async_read_some(mb, match{*this, {}, 1});
ioc.run_for(std::chrono::seconds(1));
s.expires_never();
ioc.run();
}
// close
{
server srv("", ep, log);
net::io_context ioc;
stream_t s(ioc);
s.next_layer().connect(srv.local_endpoint());
s.async_read_some(mb, match{*this,
net::error::operation_aborted, 0});
{
error_code ec;
s.next_layer().shutdown(
net::socket_base::shutdown_both,
ec);
}
s.close();
ioc.run_for(std::chrono::seconds(1));
}
// cancel
{
server srv("", ep, log);
net::io_context ioc;
stream_t s(ioc);
s.next_layer().connect(srv.local_endpoint());
s.async_read_some(mb, match{*this,
net::error::operation_aborted, 0});
ioc.run_for(std::chrono::milliseconds(100));
s.cancel();
ioc.run_for(std::chrono::seconds(1));
}
// immediate timeout
{
server srv("*", ep, log);
net::io_context ioc;
stream_t s(ioc);
s.next_layer().connect(srv.local_endpoint());
s.expires_after(std::chrono::seconds(-1));
s.async_read_some(mb,
[&](error_code ec, std::size_t n)
{
#if 0
// Unreliable on epoll impls
BEAST_EXPECT(
(ec == error::timeout && n == 0) ||
(! ec && n == 1));
#else
boost::ignore_unused(ec, n);
pass();
#endif
});
ioc.run_for(std::chrono::seconds(1));
}
// fail, with timeout
{
server srv("", ep, log);
net::io_context ioc;
stream_t s(ioc);
s.next_layer().connect(srv.local_endpoint());
s.expires_after(std::chrono::milliseconds(100));
s.async_read_some(mb,
match{*this, error::timeout, 0});
ioc.run_for(std::chrono::seconds(1));
}
// success, with timeout
{
server srv("*", ep, log);
net::io_context ioc;
stream_t s(ioc);
s.next_layer().connect(srv.local_endpoint());
s.expires_at(
std::chrono::steady_clock::now() +
std::chrono::milliseconds(100));
s.async_read_some(mb,
match{*this, {}, 1});
ioc.run_for(std::chrono::seconds(1));
}
// abandoned ops
{
server srv("*", ep, log);
net::io_context ioc;
stream_t s(ioc);
s.next_layer().connect(srv.local_endpoint());
s.async_read_some(mb, [&](error_code, std::size_t){});
}
{
server srv("*", ep, log);
net::io_context ioc;
stream_t s(ioc);
s.next_layer().connect(srv.local_endpoint());
s.expires_after(std::chrono::seconds(1));
s.async_read_some(mb, [&](error_code, std::size_t){});
}
// edge case:
// timer completion becomes queued before
// the I/O completion handler is invoked
// VFALCO Fails on OSX Travis
#if 0
{
socket_pair p;
bool invoked = false;
stream_t s(std::move(p.s1));
s.expires_after(std::chrono::seconds(0));
s.async_read_some(mb,
[&](error_code ec, std::size_t)
{
invoked = true;
BEAST_EXPECTS(ec == error::timeout,
ec.message());
});
p.s2.async_write_some(
net::const_buffer("*", 1),
[&](error_code ec, std::size_t n)
{
boost::ignore_unused(ec, n);
});
p.ioc1.run();
p.ioc1.restart();
p.ioc2.run();
p.ioc2.restart();
p.ioc1.run();
BEAST_EXPECT(invoked);
}
#endif
}
void
testWrite()
{
using tcp = net::ip::tcp;
using stream_t = basic_timeout_stream<tcp>;
char buf[4];
std::memset(buf, 0, 4);
net::mutable_buffer mb(buf, sizeof(buf));
auto const ep = net::ip::tcp::endpoint(
net::ip::make_address("127.0.0.1"), 0);
// write
{
server srv("", ep, log);
net::io_context ioc;
stream_t s(ioc);
s.next_layer().connect(srv.local_endpoint());
s.async_write_some(mb,
match{*this, {}, mb.size()});
{
error_code ec;
s.next_layer().shutdown(
net::socket_base::shutdown_both,
ec);
}
s.close();
ioc.run();
}
// write abandoned
{
server srv("*", ep, log);
net::io_context ioc;
stream_t s(ioc);
s.next_layer().connect(srv.local_endpoint());
s.async_write_some(mb, [&](error_code, std::size_t){});
}
}
void
testConnect()
{
using tcp = net::ip::tcp;
using stream_t = basic_timeout_stream<tcp>;
auto const ep = net::ip::tcp::endpoint(
net::ip::make_address("127.0.0.1"), 0);
// overload 1
{
server srv("", ep, log);
net::io_context ioc;
stream_t s(ioc);
bool invoked = false;
std::array<tcp::endpoint, 1> epa{{
srv.local_endpoint()}};
beast::async_connect(s, epa,
[&](error_code ec, tcp::endpoint)
{
invoked = true;
BEAST_EXPECTS(! ec, ec.message());
});
ioc.run_for(std::chrono::seconds(1));
BEAST_EXPECT(invoked);
}
// overload 2
{
server srv("", ep, log);
net::io_context ioc;
stream_t s(ioc);
bool invoked = false;
std::array<tcp::endpoint, 1> epa{{
srv.local_endpoint()}};
beast::async_connect(s, epa,
[](error_code, tcp::endpoint)
{
return true;
},
[&](error_code ec, tcp::endpoint)
{
invoked = true;
BEAST_EXPECTS(! ec, ec.message());
});
ioc.run_for(std::chrono::seconds(1));
BEAST_EXPECT(invoked);
}
// overload 3
{
server srv("", ep, log);
net::io_context ioc;
stream_t s(ioc);
bool invoked = false;
std::array<tcp::endpoint, 1> epa{{
srv.local_endpoint()}};
using iter_type =
std::array<tcp::endpoint, 1>::const_iterator;
beast::async_connect(s, epa.begin(), epa.end(),
[&](error_code ec, iter_type)
{
invoked = true;
BEAST_EXPECTS(! ec, ec.message());
});
ioc.run_for(std::chrono::seconds(1));
BEAST_EXPECT(invoked);
}
// overload 4
{
server srv("", ep, log);
net::io_context ioc;
stream_t s(ioc);
bool invoked = false;
std::array<tcp::endpoint, 1> epa{{
srv.local_endpoint()}};
using iter_type =
std::array<tcp::endpoint, 1>::const_iterator;
beast::async_connect(s, epa.begin(), epa.end(),
[](error_code, tcp::endpoint)
{
return true;
},
[&](error_code ec, iter_type)
{
invoked = true;
BEAST_EXPECTS(! ec, ec.message());
});
ioc.run_for(std::chrono::seconds(1));
BEAST_EXPECT(invoked);
}
// success
{
server srv("", ep, log);
net::io_context ioc;
stream_t s(ioc);
bool invoked = false;
std::array<tcp::endpoint, 1> epa{{
srv.local_endpoint()}};
beast::async_connect(s, epa,
[&](error_code ec, tcp::endpoint)
{
invoked = true;
BEAST_EXPECTS(! ec, ec.message());
});
ioc.run_for(std::chrono::seconds(1));
BEAST_EXPECT(invoked);
}
// success, with timeout
{
server srv("", ep, log);
net::io_context ioc;
stream_t s(ioc);
bool invoked = false;
std::array<tcp::endpoint, 1> epa{{
srv.local_endpoint()}};
s.expires_after(std::chrono::milliseconds(100));
beast::async_connect(s, epa,
[&](error_code ec, tcp::endpoint)
{
invoked = true;
BEAST_EXPECTS(! ec, ec.message());
});
ioc.run_for(std::chrono::seconds(1));
BEAST_EXPECT(invoked);
}
// immediate timeout
{
net::io_context ioc;
stream_t s(tcp::socket(ioc, tcp::v6()));
bool invoked = false;
std::array<tcp::endpoint, 1> epa{{
net::ip::tcp::endpoint(
net::ip::make_address("192.168.0.254"), 1)}};
s.expires_after(std::chrono::seconds(-1));
beast::async_connect(s, epa,
[&](error_code ec, tcp::endpoint)
{
invoked = true;
BEAST_EXPECTS(
ec == error::timeout, ec.message());
});
ioc.run_for(std::chrono::seconds(1));
BEAST_EXPECT(invoked);
}
// edge case:
// timer completion becomes queued before
// the I/O completion handler is invoked
// VFALCO Seems to hang on OSX Travis
#if 0
{
net::io_context ioc1;
stream_t s1(ioc1);
net::io_context ioc2;
net::ip::tcp::acceptor a(ioc2);
a.open(ep.protocol());
a.set_option(
net::socket_base::reuse_address(true));
a.bind(ep);
a.listen(1);
a.async_accept([](error_code, tcp::socket){});
bool invoked = false;
s1.expires_after(std::chrono::seconds(0));
s1.async_connect(
a.local_endpoint(),
[&](error_code ec)
{
invoked = true;
BEAST_EXPECTS(ec == error::timeout,
ec.message());
});
ioc1.run();
ioc1.restart();
ioc2.run();
ioc2.restart();
ioc1.run();
BEAST_EXPECT(invoked);
}
#endif
/* VFALCO
We need a reliable way of causing a real
timeout, for example a stable IP address
for which connections are never established,
but that also do not cause immediate failure.
*/
#if 0
// timeout (unreachable ipv4 host)
{
net::io_context ioc;
stream_t s(tcp::socket(ioc, tcp::v6()));
bool invoked = false;
std::array<tcp::endpoint, 1> epa{{
net::ip::tcp::endpoint(
net::ip::make_address("192.168.0.254"), 1)}};
s.expires_after(std::chrono::milliseconds(100));
beast::async_connect(s, epa,
[&](error_code ec, tcp::endpoint)
{
invoked = true;
BEAST_EXPECTS(
ec == error::timeout, ec.message());
});
ioc.run_for(std::chrono::seconds(1));
BEAST_EXPECT(invoked);
}
// timeout (ipv6 black hole)
{
net::io_context ioc;
stream_t s(tcp::socket(ioc, tcp::v6()));
bool invoked = false;
std::array<tcp::endpoint, 1> epa{{
tcp::endpoint(
net::ip::address(
net::ip::make_address_v6("100::")),
1)
}};
s.expires_after(std::chrono::milliseconds(100));
beast::async_connect(s, epa,
[&](error_code ec, tcp::endpoint)
{
invoked = true;
BEAST_EXPECTS(
ec == error::timeout, ec.message());
});
ioc.run_for(std::chrono::seconds(1));
BEAST_EXPECT(invoked);
}
#endif
}
//--------------------------------------------------------------------------
http::response<http::string_body>
make_response(http::request<http::empty_body>)
{
return {};
}
void process_http_1 (timeout_stream& stream, net::yield_context yield)
{
flat_buffer buffer;
http::request<http::empty_body> req;
// Read the request, with a 15 second timeout
stream.expires_after(std::chrono::seconds(15));
http::async_read(stream, buffer, req, yield);
// Calculate the response
http::response<http::string_body> res = make_response(req);
// Send the response, with a 30 second timeout.
stream.expires_after (std::chrono::seconds(30));
http::async_write (stream, res, yield);
}
void process_http_2 (timeout_stream& stream, net::yield_context yield)
{
flat_buffer buffer;
http::request<http::empty_body> req;
// Require that the read and write combined take no longer than 30 seconds
stream.expires_after(std::chrono::seconds(30));
http::async_read(stream, buffer, req, yield);
http::response<http::string_body> res = make_response(req);
http::async_write (stream, res, yield);
}
websocket::stream<timeout_stream>
process_websocket(timeout_stream&& stream, net::yield_context yield)
{
websocket::stream<timeout_stream> ws(std::move(stream));
// Require that the entire websocket handshake take no longer than 10 seconds
ws.next_layer().expires_after(std::chrono::seconds(10));
ws.async_accept(yield);
return ws;
}
void
testJavadocs()
{
BEAST_EXPECT(&basic_timeout_stream_test::process_http_1);
BEAST_EXPECT(&basic_timeout_stream_test::process_http_2);
BEAST_EXPECT(&basic_timeout_stream_test::process_websocket);
}
//--------------------------------------------------------------------------
void
run()
{
testStrand();
testMembers();
testRead();
testWrite();
testConnect();
testJavadocs();
}
};
BEAST_DEFINE_TESTSUITE(beast,core,basic_timeout_stream);
} // beast
} // boost

View File

@ -1,279 +0,0 @@
//
// Copyright (c) 2018 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
//
// Test that header file is self-contained.
#include <boost/beast/core/stream_socket.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/beast/test/yield_to.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <array>
#include <thread>
namespace boost {
namespace beast {
class stream_socket_test
: public beast::unit_test::suite
, public test::enable_yield_to
{
public:
class server
{
std::ostream& log_;
net::io_context ioc_;
net::ip::tcp::acceptor acceptor_;
net::ip::tcp::socket socket_;
std::thread t_;
void
fail(error_code ec, string_view what)
{
if(ec != net::error::operation_aborted)
log_ << what << ": " << ec.message() << "\n";
}
public:
server(
net::ip::tcp::endpoint ep,
std::ostream& log)
: log_(log)
, ioc_(1)
, acceptor_(ioc_)
, socket_(ioc_)
{
boost::system::error_code ec;
acceptor_.open(ep.protocol(), ec);
if(ec)
{
fail(ec, "open");
return;
}
acceptor_.set_option(
net::socket_base::reuse_address(true), ec);
if(ec)
{
fail(ec, "set_option");
return;
}
acceptor_.bind(ep, ec);
if(ec)
{
fail(ec, "bind");
return;
}
acceptor_.listen(
net::socket_base::max_listen_connections, ec);
if(ec)
{
fail(ec, "listen");
return;
}
acceptor_.async_accept(socket_,
[this](error_code ec){ this->on_accept(ec); });
t_ = std::thread([this]{ ioc_.run(); });
}
~server()
{
ioc_.stop();
t_.join();
}
private:
class session
: public std::enable_shared_from_this<session>
{
net::ip::tcp::socket socket_;
public:
session(
net::ip::tcp::socket sock,
std::ostream&)
: socket_(std::move(sock))
{
}
void
run()
{
socket_.async_wait(
net::socket_base::wait_read,
std::bind(
&session::on_read,
shared_from_this(),
std::placeholders::_1));
}
protected:
void
on_read(error_code ec)
{
boost::ignore_unused(ec);
}
};
void
on_accept(error_code ec)
{
if(! acceptor_.is_open())
return;
if(ec)
fail(ec, "accept");
else
std::make_shared<session>(
std::move(socket_), log_)->run();
acceptor_.async_accept(socket_,
[this](error_code ec)
{
this->on_accept(ec);
});
}
};
void
testAsync()
{
net::ip::tcp::endpoint ep(
net::ip::make_address("127.0.0.1"), 8080);
server srv(ep, log);
{
net::io_context ioc;
stream_socket s(ioc);
s.next_layer().connect(ep);
char buf[32];
error_code ec;
s.expires_after(std::chrono::seconds(1));
s.async_read_some(net::buffer(buf),
[&ec](error_code ec_, std::size_t)
{
ec = ec_;
});
ioc.run();
BEAST_EXPECTS(
ec == error::timeout, ec.message());
}
}
void
testConnect()
{
using es_t =
std::array<net::ip::tcp::endpoint, 2>;
{
net::io_context ioc;
stream_socket s(ioc);
beast::async_connect(s, es_t{},
[](error_code, es_t::value_type)
{
});
}
{
net::io_context ioc;
stream_socket s(ioc);
beast::async_connect(s, es_t{},
[](error_code, es_t::value_type)
{
return true;
},
[](error_code, es_t::value_type)
{
});
}
{
es_t es;
net::io_context ioc;
stream_socket s(ioc);
beast::async_connect(s, es.begin(), es.end(),
[](error_code, es_t::iterator)
{
});
}
{
es_t es;
net::io_context ioc;
stream_socket s(ioc);
beast::async_connect(s, es.begin(), es.end(),
[](error_code, es_t::value_type)
{
return true;
},
[](error_code, es_t::iterator)
{
});
}
pass();
}
void callConnects()
{
using es_t =
std::array<net::ip::tcp::endpoint, 2>;
{
net::io_context ioc;
stream_socket s(ioc);
async_connect(s, es_t{},
[](error_code, es_t::value_type)
{
});
}
{
net::io_context ioc;
stream_socket s(ioc);
async_connect(s, es_t{},
[](error_code, es_t::value_type)
{
return true;
},
[](error_code, es_t::value_type)
{
});
}
{
es_t es;
net::io_context ioc;
stream_socket s(ioc);
async_connect(s, es.begin(), es.end(),
[](error_code, es_t::iterator)
{
});
}
{
es_t es;
net::io_context ioc;
stream_socket s(ioc);
async_connect(s, es.begin(), es.end(),
[](error_code, es_t::value_type)
{
return true;
},
[](error_code, es_t::iterator)
{
});
}
}
void
run()
{
testAsync();
testConnect();
}
};
BEAST_DEFINE_TESTSUITE(beast,core,stream_socket);
} // beast
} // boost

View File

@ -215,8 +215,7 @@ template<class Executor>
struct associated_executor<
boost::beast::legacy_handler, Executor>
{
using type = typename
boost::beast::simple_executor;
using type = boost::beast::simple_executor;
static type get(
boost::beast::legacy_handler const&,

View File

@ -0,0 +1,11 @@
//
// Copyright (c) 2018 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
//
// Test that header file is self-contained.
#include <boost/beast/core/timeout_stream.hpp>