From 9b14774adae01b4bd114e01ef76fcdfd45224ea3 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 9 Feb 2019 05:15:48 -0800 Subject: [PATCH] basic_stream subsumes stranded_stream: * All functionality of stranded_stream is folded into basic_stream * tcp_stream is an alias for basic_stream with tcp * The tests are expanded to produce full coverage * Timeout implementation is simplified --- CHANGELOG.md | 1 + doc/qbk/03_core/3_layers.qbk | 23 +- doc/qbk/quickref.xml | 5 +- doc/qbk/release_notes.qbk | 15 +- include/boost/beast/core.hpp | 5 +- include/boost/beast/core/basic_stream.hpp | 1464 +++++++++++++++++ .../boost/beast/core/basic_timeout_stream.hpp | 847 ---------- include/boost/beast/core/buffer_size.hpp | 19 + .../beast/core/detail/stranded_socket.hpp | 42 - ...imeout_stream_base.hpp => stream_base.hpp} | 39 +- ...ic_timeout_stream.hpp => basic_stream.hpp} | 570 ++++--- include/boost/beast/core/stranded_socket.hpp | 1102 ------------- .../{timeout_stream.hpp => tcp_stream.hpp} | 19 +- include/boost/beast/websocket/stream.hpp | 86 +- test/beast/core/CMakeLists.txt | 5 +- test/beast/core/Jamfile | 5 +- test/beast/core/basic_stream.cpp | 1245 ++++++++++++++ test/beast/core/basic_timeout_stream.cpp | 1058 ------------ test/beast/core/stranded_socket.cpp | 519 ------ .../{timeout_stream.cpp => tcp_stream.cpp} | 2 +- 20 files changed, 3151 insertions(+), 3920 deletions(-) create mode 100644 include/boost/beast/core/basic_stream.hpp delete mode 100644 include/boost/beast/core/basic_timeout_stream.hpp delete mode 100644 include/boost/beast/core/detail/stranded_socket.hpp rename include/boost/beast/core/detail/{timeout_stream_base.hpp => stream_base.hpp} (59%) rename include/boost/beast/core/impl/{basic_timeout_stream.hpp => basic_stream.hpp} (68%) delete mode 100644 include/boost/beast/core/stranded_socket.hpp rename include/boost/beast/core/{timeout_stream.hpp => tcp_stream.hpp} (50%) create mode 100644 test/beast/core/basic_stream.cpp delete mode 100644 test/beast/core/basic_timeout_stream.cpp delete mode 100644 test/beast/core/stranded_socket.cpp rename test/beast/core/{timeout_stream.cpp => tcp_stream.cpp} (87%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03e9184b..0f029729 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ Version 213: * Fix posix_file::close handling of EINTR +* basic_stream subsumes stranded_stream: -------------------------------------------------------------------------------- diff --git a/doc/qbk/03_core/3_layers.qbk b/doc/qbk/03_core/3_layers.qbk index 7bac24f8..67922c01 100644 --- a/doc/qbk/03_core/3_layers.qbk +++ b/doc/qbk/03_core/3_layers.qbk @@ -46,12 +46,15 @@ facilities for authoring and working with layered streams: [table Layered Stream Algorithms and Types [[Name][Description]] [[ - [link beast.ref.boost__beast__timeout_stream `timeout_stream`] - [link beast.ref.boost__beast__basic_timeout_stream `basic_timeout_stream`] + [link beast.ref.boost__beast__basic_stream `basic_stream`] + [link beast.ref.boost__beast__tcp_stream `tcp_stream`] ][ - A timeout stream meets the requirements for synchronous and asynchronous - read and write streams, and additionally provides configurable timeouts - for logical operations that include reading, writing, and/or connecting. + This stream can be used for synchronous and asynchronous reading + and writing. It allows timeouts to be set on logical operations, + and can have an executor associated with the stream which is + used to invoke completion handlers. This lets you set a strand + on the stream once, which is then used for all asynchronous + operations automatically. ]] [[ [link beast.ref.boost__beast__buffered_read_stream `buffered_read_stream`] @@ -109,16 +112,6 @@ facilities for authoring and working with layered streams: allows for move-construction and move-assignment, and also implements a work-around for a performance limitation in the original SSL stream. ]] -[[ - [link beast.ref.boost__beast__stranded_socket `stranded_socket`] -][ - A timeout stream meets the requirements for synchronous and asynchronous - read and write streams by passing I/O through to an underlying - `net::basic_stream_socket`, and additionally supports - [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html [P1322R0] "Networking TS enhancement to enable custom I/O executors"], - allowing a custom executor (such a strand) to be used for all - asynchronous operations. -]] ] [heading Example] diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index 88d63bf4..4b963b3e 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -24,7 +24,7 @@ Classes (1 of 2) async_op_base 🞲 - basic_timeout_stream 🞲 + basic_stream 🞲 file file_mode file_posix @@ -46,10 +46,9 @@ span static_string stable_async_op_base 🞲 - stranded_socket 🞲 string_param string_view - timeout_stream 🞲 + tcp_stream 🞲 Constants diff --git a/doc/qbk/release_notes.qbk b/doc/qbk/release_notes.qbk index 4bdcbbad..1a9910d0 100644 --- a/doc/qbk/release_notes.qbk +++ b/doc/qbk/release_notes.qbk @@ -14,14 +14,19 @@ [heading Boost 1.70] [/ includes up to version 209] [/ +* Use `beast::tcp_socket` instead of `boost::asio::ip::tcp::socket`! +* subscribe to this GitHub issue to be informed! +* we need testers! +* star the repo! + Exposition: Enlarged scope -New quality of life features: - timed stream +- `net` is a namespace alias for `boost::asio` +- New quality of life features: + tcp_socket async_op_base, stable_async_op_base -New Networking refresher -New websocket-chat-multi example -* `net` is a namespace alias for `boost::asio` +- New Networking refresher +- New websocket-chat-multi example ] [tip diff --git a/include/boost/beast/core.hpp b/include/boost/beast/core.hpp index f24800cb..e286690a 100644 --- a/include/boost/beast/core.hpp +++ b/include/boost/beast/core.hpp @@ -12,7 +12,7 @@ #include -#include +#include #include #include #include @@ -41,10 +41,9 @@ #include #include #include -#include #include #include #include -#include +#include #endif diff --git a/include/boost/beast/core/basic_stream.hpp b/include/boost/beast/core/basic_stream.hpp new file mode 100644 index 00000000..03019be9 --- /dev/null +++ b/include/boost/beast/core/basic_stream.hpp @@ -0,0 +1,1464 @@ +// +// 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_STREAM_HPP +#define BOOST_BEAST_CORE_BASIC_STREAM_HPP + +#include +#include +#include +#include // VFALCO This is unfortunate +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace asio { +namespace ssl { +template class stream; +} // ssl +} // asio +} // boost + +namespace boost { +namespace beast { + +//------------------------------------------------------------------------------ + +/** A stream socket wrapper with timeouts and associated executor. + + This stream wraps a `net::basic_stream_socket` to provide + the following additional features: + + @li Optional timeouts may be specified for each logical asynchronous + operation performing any reading, writing, or connecting. + + @li An Executor may be associated with the stream, which will + be used to invoke any completion handlers which do not already have + an associated executor. This achieves partial support for + [P1322R0] Networking TS enhancement to enable custom I/O executors. + + Although the stream supports multiple concurrent outstanding asynchronous + operations, the stream object 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, are + accessed by the networking implementation. To meet this thread safety + requirement, all asynchronous operations must be performed by the stream + within the same implicit strand (only one thread `net::io_context::run`) + or within the same explicit strand, such as an instance of `net::strand`. + + Completion handlers with explicit associated executors (such as those + arising from use of `net::bind_executor`) will be invoked by the stream + using the associated executor. Otherwise, the completion handler will + be invoked by the executor associated with the stream upon construction. + The type of executor used with this stream must meet the following + requirements: + + @li 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. + + Unlike other stream wrappers, the underlying socket is accessed + through the @ref socket member function instead of `next_layer`. + This causes the @ref basic_stream to be returned in calls + to @ref get_lowest_layer. + + @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 asynchronous read, asynchronous write, or asynchronous 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 (tcp_stream& stream, net::yield_context yield) + { + flat_buffer buffer; + http::request 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 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 (tcp_stream& stream, net::yield_context yield) + { + flat_buffer buffer; + http::request 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 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>& 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 + + @par Blocking I/O + + Synchronous functions behave identically as that of the wrapped + `net::basic_stream_socket`. Timeouts are not available when performing + blocking calls. + + @tparam Protocol A type meeting the requirements of Protocol + 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 Executor to + be used for submitting all completion handlers which do not already have an + associated executor. + + @par Thread Safety + Distinct objects: Safe.@n + Shared objects: Unsafe. The application must also ensure + that all asynchronous operations are performed within the same + implicit or explicit strand. + + @see + + @li @ref beast::connect, @ref beast::async_connect + + @li [P1322R0] Networking TS enhancement to enable custom I/O executors. +*/ +template +class basic_stream +#if ! BOOST_BEAST_DOXYGEN + : private detail::stream_base +#endif +{ +// 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 + , boost::empty_value + { + op_state read; + op_state write; + + net::basic_stream_socket socket; + + impl_type(impl_type&&) = default; + + template + explicit + impl_type(Executor const&, Args&&...); + + template + impl_type(net::basic_stream_socket&& socket_, + std::true_type); + + template + impl_type(net::basic_stream_socket&& socket_, + std::false_type); + + impl_type& operator=(impl_type&&) = delete; + + Executor const& + ex() const noexcept + { + return this->get(); + } + + 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 a timer. + std::shared_ptr impl_; + + // Restricted until P1322R0 is incorporated into Boost.Asio. + static_assert( + std::is_convertible< + decltype(std::declval().context()), + net::io_context&>::value, + "Only net::io_context is currently supported for executor_type::context()"); + + template class async_op; + + template + friend class detail::basic_stream_connect_op; + + template + friend class basic_stream; + + struct timeout_handler; + +#if ! BOOST_BEAST_DOXYGEN + // boost::asio::ssl::stream needs these + // DEPRECATED + template + friend class boost::asio::ssl::stream; + // DEPRECATED + using lowest_layer_type = net::basic_stream_socket; + // DEPRECATED + net::basic_stream_socket& + lowest_layer() noexcept + { + return impl_->socket; + } + // DEPRECATED + net::basic_stream_socket const& + lowest_layer() const noexcept + { + return impl_->socket; + } +#endif + +public: + /** The type of the executor associated with the stream. + + This will be the type of executor used to invoke completion + handlers which do not have an explicit associated executor. + */ + using executor_type = Executor; + + /// The type of the underlying socket. + using socket_type = net::basic_stream_socket; + + /// The protocol type. + using protocol_type = Protocol; + + /// The endpoint type. + using endpoint_type = typename Protocol::endpoint; + + /** Destructor + + This function destroys the stream, cancelling any outstanding + asynchronous operations associated with the socket as if by + calling cancel. + */ + ~basic_stream(); + + /** Construct the stream from an execution context. + + This constructor creates the stream from an execution context. + The underlying socket needs to be open and connected or accepted + before data can be sent or received on it. + + @param ctx An object whose type meets the requirements of + ExecutionContext, which the stream will use to dispatch + handlers that do not have an explicit associated executor. + Currently, the only supported type for `ctx` is `net::io_context`. + + @param args A list of parameters forwarded to the constructor of + the underlying socket. + + @note This function does not participate in overload resolution unless: + + @li `std::is_convertible::value` is `true`, and + + @li `std::is_constructible::value` is `true`. + + @see [P1322R0] Networking TS enhancement to enable custom I/O executors + */ + template< + class ExecutionContext, + class... Args + #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_stream(ExecutionContext& ctx, Args&&... args); + + /** Construct the stream from an executor. + + This constructor creates the stream from an executor. + The underlying socket needs to be open and connected or accepted + before data can be sent or received on it. + + @param ex The executor to use when dispatching handlers that do + not have an explicit associated executor. + Currently, only executors that return a `net::io_context&` from + `ex.context()` are supported. + + @param args A list of parameters forwarded to the constructor of + the underlying socket. + + @see [P1322R0] Networking TS enhancement to enable custom I/O executors + */ + template + explicit + basic_stream( + executor_type const& ex, Args&&... args); + + /** Construct the stream from an existing socket. + + This constructor creates the stream from an existing socket. + The underlying socket needs to be open and connected or accepted + before data can be sent or received on it. + + @param socket The socket to construct from. Ownership of the + socket will be transferred by move-construction. + + @param args A list of parameters forwarded to the constructor of + the underlying socket. + + @note This function does not participate in overload resolution unless: + + @li `OtherProtocol` is convertible to `Protocol`, and one of: + + @li `executor_type` of the stream is constructible from the type of + context returned by calling `socket.get_executor().context()`, or + + @li `executor_type` of the stream is constructible from the type of + executor returned by calling `socket.get_executor()`. + */ + template< + class OtherProtocol +#if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + std::is_convertible::value && ( + std::is_constructible&>().get_executor().context())>::value || + std::is_constructible&>().get_executor())>::value) + >::type +#endif + > + explicit + basic_stream(net::basic_stream_socket&& socket); + + /** Move constructor. + + @param other The other object from which the move will occur. + + @note Following the move, the moved-from object is in the + same state as if newly constructed. + */ + basic_stream(basic_stream&& other); + + /// Move assignment (deleted). + basic_stream& operator=(basic_stream&&) = delete; + + /// Return a reference to the underlying socket + socket_type& + socket() noexcept + { + return impl_->socket; + } + + /// Return a reference to the underlying socket + socket_type const& + socket() const noexcept + { + return impl_->socket; + } + + /** Release ownership of the underlying socket. + + This function causes all outstanding asynchronous connect, + read, and write operations to be canceled as if by a call + to @ref cancel. Ownership of the underlying socket is then + transferred to the caller. + */ + socket_type + release_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. + + This function causes all outstanding asynchronous connect, + read, and write operations to finish immediately. Completion + handlers for cancelled operations will receive the error + `net::error::operation_aborted`. Completion handlers not + yet invoked whose operations have completed, will receive + the error corresponding to the result of the operation (which + may indicate success). + */ + void + cancel(); + + /** Close the timed stream. + + This cancels all of the outstanding asynchronous operations + as if by calling @ref cancel, and closes the file + */ + void + close(); + + //-------------------------------------------------------------------------- + + /** Get the executor associated with the object. + + This function may be used to obtain the executor object that the + stream uses to dispatch completion handlers without an assocaited + executor. + + @return A copy of the executor that stream will use to dispatch handlers. + */ + executor_type + get_executor() const noexcept + { + return impl_->ex(); + } + + /** Connect the stream to the specified endpoint. + + This function is used to connect the underlying socket to the + specified remote endpoint. The function call will block until + the connection is successfully made or an error occurs. + + The underlying socket is automatically opened if needed. + An automatically opened socket is not returned to the + closed state upon failure. + + @param ep The remote endpoint to connect to. + + @throws system_error Thrown on failure. + + @see @ref connect + */ + void + connect(endpoint_type const& ep) + { + impl_->socket.connect(ep); + } + + /** Connect the stream to the specified endpoint. + + This function is used to connect the underlying socket to the + specified remote endpoint. The function call will block until + the connection is successfully made or an error occurs. + + The underlying socket is automatically opened if needed. + An automatically opened socket is not returned to the + closed state upon failure. + + @param ep The remote endpoint to connect to. + + @param ec Set to indicate what error occurred, if any. + + @see @ref connect + */ + void + connect(endpoint_type const& ep, error_code& ec) + { + impl_->socket.connect(ep, ec); + } + + /** Connect the stream to the specified endpoint asynchronously. + + This function is used to asynchronously connect the underlying + socket to the specified remote endpoint. The function call always + returns immediately. + + The underlying socket is automatically opened if needed. + An automatically opened socket is not returned to the + closed state upon failure. + + If the timeout timer expires while the operation is outstanding, + the operation will be canceled and the completion handler will be + invoked with the error @ref error::timeout. + + @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 Invoked when the operation completes. Ownership + of the handler will be transferred by move-construction as needed. + The equivalent function signature of the handler must be: + @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`. + + @see @ref async_connect + */ + template + BOOST_ASIO_INITFN_RESULT_TYPE(ConnectHandler, + void(error_code)) + async_connect( + endpoint_type const& ep, + ConnectHandler&& handler); + + //-------------------------------------------------------------------------- + + /** Read some data. + + This function is used to read some data from the stream. + + The call blocks until one of the following is true: + + @li One or more bytes are read from the stream. + + @li An error occurs. + + @param buffers The buffers into which the data will be read. If the + size of the buffers is zero bytes, the call always returns + immediately with no error. + + @returns The number of bytes read. + + @throws system_error Thrown on failure. + + @note The `read_some` operation may not receive all of the requested + number of bytes. Consider using the function `net::read` if you need + to ensure that the requested amount of data is read before the + blocking operation completes. + */ + template + std::size_t + read_some(MutableBufferSequence const& buffers) + { + return impl_->socket.read_some(buffers); + } + + /** Read some data. + + This function is used to read some data from the underlying socket. + + The call blocks until one of the following is true: + + @li One or more bytes are read from the stream. + + @li An error occurs. + + @param buffers The buffers into which the data will be read. If the + size of the buffers is zero bytes, the call always returns + immediately with no error. + + @param ec Set to indicate what error occurred, if any. + + @returns The number of bytes read. + + @note The `read_some` operation may not receive all of the requested + number of bytes. Consider using the function `net::read` if you need + to ensure that the requested amount of data is read before the + blocking operation completes. + */ + template + std::size_t + read_some( + MutableBufferSequence const& buffers, + error_code& ec) + { + return impl_->socket.read_some(buffers, ec); + } + + /** Read some data asynchronously. + + This function is used to asynchronously read data from the stream. + + This call always returns immediately. The asynchronous operation + will continue until one of the following conditions is true: + + @li One or more bytes are read from the stream. + + @li An error occurs. + + The algorithm, known as a composed asynchronous operation, + is implemented in terms of calls to the next layer's `async_read_some` + function. The program must ensure that no other calls to @ref read_some + or @ref async_read_some are performed until this operation completes. + + If the timeout timer expires while the operation is outstanding, + the operation will be canceled and the completion handler will be + invoked with the error @ref error::timeout. + + @param buffers The buffers into which the data will be read. If the size + of the buffers is zero bytes, the operation always completes immediately + with no error. + 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`. + + @note The `async_read_some` operation may not receive all of the requested + number of bytes. Consider using the function `net::async_read` if you need + to ensure that the requested amount of data is read before the asynchronous + operation completes. + */ + template + BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, + void(error_code, std::size_t)) + async_read_some( + MutableBufferSequence const& buffers, + ReadHandler&& handler); + + /** Write some data. + + This function is used to write some data to the stream. + + The call blocks until one of the following is true: + + @li One or more bytes are written to the stream. + + @li An error occurs. + + @param buffers The buffers from which the data will be written. If the + size of the buffers is zero bytes, the call always returns immediately + with no error. + + @returns The number of bytes written. + + @throws system_error Thrown on failure. + + @note The `write_some` operation may not transmit all of the requested + number of bytes. Consider using the function `net::write` if you need + to ensure that the requested amount of data is written before the + blocking operation completes. + */ + template + std::size_t + write_some(ConstBufferSequence const& buffers) + { + return impl_->socket.write_some(buffers); + } + + /** Write some data. + + This function is used to write some data to the stream. + + The call blocks until one of the following is true: + + @li One or more bytes are written to the stream. + + @li An error occurs. + + @param buffers The buffers from which the data will be written. If the + size of the buffers is zero bytes, the call always returns immediately + with no error. + + @param ec Set to indicate what error occurred, if any. + + @returns The number of bytes written. + + @throws system_error Thrown on failure. + + @note The `write_some` operation may not transmit all of the requested + number of bytes. Consider using the function `net::write` if you need + to ensure that the requested amount of data is written before the + blocking operation completes. + */ + template + std::size_t + write_some( + ConstBufferSequence const& buffers, + error_code& ec) + { + return impl_->socket.write_some(buffers, ec); + } + + /** Write some data asynchronously. + + This function is used to asynchronously write data to the underlying socket. + + This call always returns immediately. The asynchronous operation + will continue until one of the following conditions is true: + + @li One or more bytes are written to the stream. + + @li An error occurs. + + The algorithm, known as a composed asynchronous operation, + is implemented in terms of calls to the next layer's `async_write_some` + function. The program must ensure that no other calls to @ref async_write_some + are performed until this operation completes. + + If the timeout timer expires while the operation is outstanding, + the operation will be canceled and the completion handler will be + invoked with the error @ref error::timeout. + + @param buffers The buffers from which the data will be written. If the + size of the buffers is zero bytes, the operation always completes + immediately with no error. + 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`. + + @note The `async_write_some` operation may not transmit all of the requested + number of bytes. Consider using the function `net::async_write` if you need + to ensure that the requested amount of data is sent before the asynchronous + operation completes. + */ + template + BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, + void(error_code, std::size_t)) + async_write_some( + ConstBufferSequence const& buffers, + WriteHandler&& handler); +}; + +//------------------------------------------------------------------------------ + +/** Establishes a connection by trying each endpoint in a sequence. + + This function attempts to connect the stream to one of a sequence of + endpoints by trying each endpoint until a connection is successfully + established. + + The algorithm, known as a composed operation, is implemented + in terms of calls to the underlying socket's `connect` function. + + @param stream The stream to be connected. + If the underlying socket is already open, it will be closed. + + @param endpoints A sequence of endpoints. + + @returns The successfully connected endpoint. + + @throws system_error Thrown on failure. If the sequence is + empty, the associated error code is `net::error::not_found`. + Otherwise, contains the error from the last connection attempt. +*/ +template< + class Protocol, class Executor, + class EndpointSequence +#if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + net::is_endpoint_sequence< + EndpointSequence>::value>::type +#endif +> +typename Protocol::endpoint +connect( + basic_stream& stream, + EndpointSequence const& endpoints +) +{ + return net::connect(stream.socket(), endpoints); +} + +/** Establishes a connection by trying each endpoint in a sequence. + + This function attempts to connect the stream to one of a sequence of + endpoints by trying each endpoint until a connection is successfully + established. + + The algorithm, known as a composed operation, is implemented + in terms of calls to the underlying socket's `connect` function. + + @param stream The stream to be connected. + If the underlying socket is already open, it will be closed. + + @param endpoints A sequence of endpoints. + + @param ec Set to indicate what error occurred, if any. If the sequence is + empty, set to `net::error::not_found`. Otherwise, contains the error + from the last connection attempt. + + @returns On success, the successfully connected endpoint. Otherwise, a + default-constructed endpoint. +*/ +template< + class Protocol, class Executor, + class EndpointSequence +#if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + net::is_endpoint_sequence< + EndpointSequence>::value>::type +#endif +> +typename Protocol::endpoint +connect( + basic_stream& stream, + EndpointSequence const& endpoints, + error_code& ec +) +{ + return net::connect(stream.socket(), endpoints, ec); +} + +/** Establishes a connection by trying each endpoint in a sequence. + + This function attempts to connect the stream to one of a sequence of + endpoints by trying each endpoint until a connection is successfully + established. + + The algorithm, known as a composed operation, is implemented + in terms of calls to the underlying socket's `connect` function. + + @param stream The 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. + + @returns An iterator denoting the successfully connected endpoint. + + @throws system_error Thrown on failure. If the sequence is + empty, the associated error code is `net::error::not_found`. + Otherwise, contains the error from the last connection attempt. +*/ +template< + class Protocol, class Executor, + class Iterator> +Iterator +connect( + basic_stream& stream, + Iterator begin, Iterator end) +{ + return net::connect(stream.socket(), begin, end); +} + +/** Establishes a connection by trying each endpoint in a sequence. + + This function attempts to connect the stream to one of a sequence of + endpoints by trying each endpoint until a connection is successfully + established. + + The algorithm, known as a composed operation, is implemented + in terms of calls to the underlying socket's `connect` function. + + @param stream The 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 ec Set to indicate what error occurred, if any. If the sequence is + empty, set to boost::asio::error::not_found. Otherwise, contains the error + from the last connection attempt. + + @returns On success, an iterator denoting the successfully connected + endpoint. Otherwise, the end iterator. +*/ +template< + class Protocol, class Executor, + class Iterator> +Iterator +connect( + basic_stream& stream, + Iterator begin, Iterator end, + error_code& ec) +{ + return net::connect(stream.socket(), begin, end, ec); +} + +/** Establishes a connection by trying each endpoint in a sequence. + + This function attempts to connect the stream to one of a sequence of + endpoints by trying each endpoint until a connection is successfully + established. + + The algorithm, known as a composed operation, is implemented + in terms of calls to the underlying socket's `connect` function. + + @param stream The stream to be connected. + If the underlying socket is already open, it will be closed. + + @param endpoints 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, + 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. + + @returns The successfully connected endpoint. + + @throws boost::system::system_error Thrown on failure. If the sequence is + empty, the associated error code is `net::error::not_found`. + Otherwise, contains the error from the last connection attempt. +*/ +template< + class Protocol, class Executor, + class EndpointSequence, class ConnectCondition +#if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + net::is_endpoint_sequence< + EndpointSequence>::value>::type +#endif +> +typename Protocol::endpoint +connect( + basic_stream& stream, + EndpointSequence const& endpoints, + ConnectCondition connect_condition +) +{ + return net::connect(stream.socket(), endpoints, connect_condition); +} + +/** Establishes a connection by trying each endpoint in a sequence. + + This function attempts to connect the stream to one of a sequence of + endpoints by trying each endpoint until a connection is successfully + established. + + The algorithm, known as a composed operation, is implemented + in terms of calls to the underlying socket's `connect` function. + + @param stream The stream to be connected. + If the underlying socket is already open, it will be closed. + + @param endpoints 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, + 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 ec Set to indicate what error occurred, if any. If the sequence is + empty, set to `net::error::not_found`. Otherwise, contains the error + from the last connection attempt. + + @returns On success, the successfully connected endpoint. Otherwise, a + default-constructed endpoint. +*/ +template< + class Protocol, class Executor, + class EndpointSequence, class ConnectCondition +#if ! BOOST_BEAST_DOXYGEN + ,class = typename std::enable_if< + net::is_endpoint_sequence< + EndpointSequence>::value>::type +#endif +> +typename Protocol::endpoint +connect( + basic_stream& stream, + EndpointSequence const& endpoints, + ConnectCondition connect_condition, + error_code& ec) +{ + return net::connect(stream.socket(), endpoints, connect_condition, ec); +} + +/** Establishes a connection by trying each endpoint in a sequence. + + This function attempts to connect the stream to one of a sequence of + endpoints by trying each endpoint until a connection is successfully + established. + + The algorithm, known as a composed operation, is implemented + in terms of calls to the underlying socket's `connect` function. + + @param stream The 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, + 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. + + @returns An iterator denoting the successfully connected endpoint. + + @throws boost::system::system_error Thrown on failure. If the sequence is + empty, the associated @c error_code is `net::error::not_found`. + Otherwise, contains the error from the last connection attempt. +*/ +template< + class Protocol, class Executor, + class Iterator, class ConnectCondition> +Iterator +connect( + basic_stream& stream, + Iterator begin, Iterator end, + ConnectCondition connect_condition) +{ + return net::connect(stream.socket(), begin, end, connect_condition); +} + +/** Establishes a connection by trying each endpoint in a sequence. + + This function attempts to connect the stream to one of a sequence of + endpoints by trying each endpoint until a connection is successfully + established. + + The algorithm, known as a composed operation, is implemented + in terms of calls to the underlying socket's `connect` function. + + @param stream The 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, + 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 ec Set to indicate what error occurred, if any. If the sequence is + empty, set to `net::error::not_found`. Otherwise, contains the error + from the last connection attempt. + + @returns On success, an iterator denoting the successfully connected + endpoint. Otherwise, the end iterator. +*/ +template< + class Protocol, class Executor, + class Iterator, class ConnectCondition> +Iterator +connect( + basic_stream& stream, + Iterator begin, Iterator end, + ConnectCondition connect_condition, + error_code& ec) +{ + return net::connect(stream.socket(), begin, end, connect_condition, ec); +} + +/** Establishes a connection by trying each endpoint in a sequence asynchronously. + + This function attempts to connect the stream to one of a sequence of + endpoints by trying each endpoint until a connection is successfully + established. + + The algorithm, known as a composed asynchronous operation, is + implemented in terms of calls to the underlying socket's `async_connect` + function. + + If the timeout timer expires while the operation is outstanding, + the current connection attempt will be canceled and the completion + handler will be invoked with the error @ref error::timeout. + + @param stream The 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 EndpointSequence. + + @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::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_stream& stream, + EndpointSequence const& endpoints, + RangeConnectHandler&& handler); + +/** Establishes a connection by trying each endpoint in a sequence asynchronously. + + This function attempts to connect the stream to one of a sequence of + endpoints by trying each endpoint until a connection is successfully + established. + + The algorithm, known as a composed asynchronous operation, is + implemented in terms of calls to the underlying socket's `async_connect` + function. + + If the timeout timer expires while the operation is outstanding, + the current connection attempt will be canceled and the completion + handler will be invoked with the error @ref error::timeout. + + @param stream The 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 EndpointSequence. + + @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::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_stream& stream, + EndpointSequence const& endpoints, + ConnectCondition connect_condition, + RangeConnectHandler&& handler); + +/** Establishes a connection by trying each endpoint in a sequence asynchronously. + + This function attempts to connect the stream to one of a sequence of + endpoints by trying each endpoint until a connection is successfully + established. + + The algorithm, known as a composed asynchronous operation, is + implemented in terms of calls to the underlying socket's `async_connect` + function. + + If the timeout timer expires while the operation is outstanding, + the current connection attempt will be canceled and the completion + handler will be invoked with the error @ref error::timeout. + + @param stream The 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::post`. +*/ +template< + class Protocol, class Executor, + class Iterator, + class IteratorConnectHandler> +BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, + void (error_code, Iterator)) +async_connect( + basic_stream& stream, + Iterator begin, Iterator end, + IteratorConnectHandler&& handler); + +/** Establishes a connection by trying each endpoint in a sequence asynchronously. + + This function attempts to connect the stream to one of a sequence of + endpoints by trying each endpoint until a connection is successfully + established. + + The algorithm, known as a composed asynchronous operation, is + implemented in terms of calls to the underlying socket's `async_connect` + function. + + If the timeout timer expires while the operation is outstanding, + the current connection attempt will be canceled and the completion + handler will be invoked with the error @ref error::timeout. + + @param stream The 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::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_stream& stream, + Iterator begin, Iterator end, + ConnectCondition connect_condition, + IteratorConnectHandler&& handler); + +} // beast +} // boost + +#include + +#endif diff --git a/include/boost/beast/core/basic_timeout_stream.hpp b/include/boost/beast/core/basic_timeout_stream.hpp deleted file mode 100644 index ab6d8e91..00000000 --- a/include/boost/beast/core/basic_timeout_stream.hpp +++ /dev/null @@ -1,847 +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 -// - -#ifndef BOOST_BEAST_CORE_BASIC_TIMEOUT_SOCKET_HPP -#define BOOST_BEAST_CORE_BASIC_TIMEOUT_SOCKET_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 - "Networking TS enhancement to enable custom I/O executors" - (P1322R0). - - 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 DefaultConstructible). - Regardless of the method chosen, the executor used with the stream must - provide the following guarantees: - - @li Ordering: - Function objects submitted to the executor from the same thread shall - execute in the order they were submitted. - - @li Concurrency: - 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 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 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 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 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& 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 Protocol - 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 Executor 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 - Distinct objects: Safe.@n - Shared objects: Unsafe. The application must also ensure - that all asynchronous operations are performed within the same - implicit or explicit strand. - - @see "Networking TS enhancement to enable custom I/O executors" - (P1322R0). -*/ -template< - class Protocol, - class Executor = typename - net::basic_stream_socket::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::max)(); - - using tick_type = std::uint64_t; - - struct op_state - { - net::steady_timer timer; // for timing out - tick_type tick = 0; // counts waits - bool pending = false; // if op is pending - bool timeout = 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 - { - Executor ex; // must come first - op_state read; - op_state write; - - net::basic_stream_socket socket; - - template - 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_; - - // Restricted until P1322R0 is incorporated into Boost.Asio. - static_assert( - std::is_convertible< - decltype(std::declval().context()), - net::io_context&>::value, - "Only net::io_context is currently supported for executor_type::context()"); - - template class async_op; - - template - friend class detail::timeout_stream_connect_op; - - template - 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; - - /// 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 - ExecutionContext, 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::value` is `true`, and - @li `std::is_constructible::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&& 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&& 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 - 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 - 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 - 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 EndpointSequence. - - @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& 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 EndpointSequence. - - @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& 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& 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& stream, - Iterator begin, Iterator end, - ConnectCondition connect_condition, - IteratorConnectHandler&& handler); - -} // beast -} // boost - -#include - -#endif diff --git a/include/boost/beast/core/buffer_size.hpp b/include/boost/beast/core/buffer_size.hpp index ffaa6910..34525a89 100644 --- a/include/boost/beast/core/buffer_size.hpp +++ b/include/boost/beast/core/buffer_size.hpp @@ -78,6 +78,25 @@ struct buffer_size_impl } }; +/** Return `true` if a buffer sequence is empty + + This is sometimes faster than using @ref buffer_size +*/ +template +bool +buffers_empty(ConstBufferSequence const& buffers) +{ + auto it = net::buffer_sequence_begin(buffers); + auto end = net::buffer_sequence_end(buffers); + while(it != end) + { + if(net::const_buffer(*it).size() > 0) + return false; + ++it; + } + return true; +} + } // detail /** Return the total number of bytes in a buffer or buffer sequence diff --git a/include/boost/beast/core/detail/stranded_socket.hpp b/include/boost/beast/core/detail/stranded_socket.hpp deleted file mode 100644 index ce2d401a..00000000 --- a/include/boost/beast/core/detail/stranded_socket.hpp +++ /dev/null @@ -1,42 +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 -// - -#ifndef BOOST_BEAST_CORE_DETAIL_STRANDED_SOCKET_HPP -#define BOOST_BEAST_CORE_DETAIL_STRANDED_SOCKET_HPP - -#include -#include -#include - -namespace boost { -namespace beast { -namespace detail { - -template -class stranded_socket_base -{ -protected: - net::basic_stream_socket socket_; - - template - explicit - stranded_socket_base(Args&&... args) - : socket_(std::forward(args)...) - { - } - - stranded_socket_base(stranded_socket_base&&) = default; - stranded_socket_base& operator=(stranded_socket_base&&) = delete; -}; - -} // detail -} // beast -} // boost - -#endif diff --git a/include/boost/beast/core/detail/timeout_stream_base.hpp b/include/boost/beast/core/detail/stream_base.hpp similarity index 59% rename from include/boost/beast/core/detail/timeout_stream_base.hpp rename to include/boost/beast/core/detail/stream_base.hpp index 9e2b22d2..140a0d16 100644 --- a/include/boost/beast/core/detail/timeout_stream_base.hpp +++ b/include/boost/beast/core/detail/stream_base.hpp @@ -7,18 +7,21 @@ // Official repository: https://github.com/boostorg/beast // -#ifndef BOOST_BEAST_CORE_DETAIL_TIMEOUT_STREAM_BASE_HPP -#define BOOST_BEAST_CORE_DETAIL_TIMEOUT_STREAM_BASE_HPP +#ifndef BOOST_BEAST_CORE_DETAIL_STREAM_BASE_HPP +#define BOOST_BEAST_CORE_DETAIL_STREAM_BASE_HPP +#include #include #include +#include +#include namespace boost { namespace beast { namespace detail { template -class timeout_stream_connect_op; +class basic_stream_connect_op; struct any_endpoint { @@ -31,9 +34,27 @@ struct any_endpoint } }; -class timeout_stream_base +struct stream_base { -protected: + using clock_type = std::chrono::steady_clock; + using time_point = typename + std::chrono::steady_clock::time_point; + using tick_type = std::uint64_t; + + struct op_state + { + net::steady_timer timer; // for timing out + tick_type tick = 0; // counts waits + bool pending = false; // if op is pending + bool timeout = false; // if timed out + + explicit + op_state(net::io_context& ioc) + : timer(ioc) + { + } + }; + class pending_guard { bool& b_; @@ -70,6 +91,14 @@ protected: clear_ = false; } }; + + static constexpr time_point never() + { + return (time_point::max)(); + } + + static std::size_t constexpr no_limit = + (std::numeric_limits::max)(); }; } // detail diff --git a/include/boost/beast/core/impl/basic_timeout_stream.hpp b/include/boost/beast/core/impl/basic_stream.hpp similarity index 68% rename from include/boost/beast/core/impl/basic_timeout_stream.hpp rename to include/boost/beast/core/impl/basic_stream.hpp index 53b4b4e6..2958e2e5 100644 --- a/include/boost/beast/core/impl/basic_timeout_stream.hpp +++ b/include/boost/beast/core/impl/basic_stream.hpp @@ -7,11 +7,13 @@ // 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 +#ifndef BOOST_BEAST_CORE_IMPL_BASIC_STREAM_HPP +#define BOOST_BEAST_CORE_IMPL_BASIC_STREAM_HPP #include -#include +#include +#include +#include #include #include #include @@ -23,8 +25,96 @@ namespace boost { namespace beast { +//------------------------------------------------------------------------------ + template -struct basic_timeout_stream< +template +basic_stream:: +impl_type:: +impl_type( + Executor const& ex_, + Args&&... args) + : boost::empty_value( + boost::empty_init_t{}, ex_) + , read(ex().context()) + , write(ex().context()) + , socket(std::forward(args)...) +{ + reset(); +} + +template +template +basic_stream:: +impl_type:: +impl_type(net::basic_stream_socket&& socket_, + std::true_type) + : boost::empty_value( + boost::empty_init_t{}, socket_.get_executor()) + , read(ex().context()) + , write(ex().context()) + , socket(std::move(socket_)) +{ + reset(); +} + +template +template +basic_stream:: +impl_type:: +impl_type(net::basic_stream_socket&& socket_, + std::false_type) + : boost::empty_value(boost::empty_init_t{}, + socket_.get_executor().context()) + , read(ex().context()) + , write(ex().context()) + , socket(std::move(socket_)) +{ + reset(); +} + +template +void +basic_stream:: +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 +void +basic_stream:: +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 +struct basic_stream< Protocol, Executor>::timeout_handler { op_state& state; @@ -50,14 +140,8 @@ struct basic_timeout_stream< return; BOOST_ASSERT(tick == state.tick); - // late completion - if(state.timeout) - { - state.timeout = false; - return; - } - // timeout + BOOST_ASSERT(! state.timeout); sp->close(); state.timeout = true; } @@ -75,7 +159,7 @@ struct basic_timeout_stream< template template -class basic_timeout_stream::async_op +class basic_stream::async_op : public async_op_base , public boost::asio::coroutine { @@ -119,7 +203,7 @@ class basic_timeout_stream::async_op public: template async_op( - basic_timeout_stream& s, + basic_stream& s, Buffers const& b, Handler_&& h) : async_op_base( @@ -138,44 +222,55 @@ public: { BOOST_ASIO_CORO_REENTER(*this) { - // VFALCO TODO handle buffer size == 0 + if(detail::buffers_empty(b_)) + { + BOOST_ASIO_CORO_YIELD + async_perform( + std::integral_constant{}); + if(state().timer.expiry() <= clock_type::now()) + ec = beast::error::timeout; + goto upcall; + } - state().timer.async_wait( - net::bind_executor( - this->get_executor(), - timeout_handler{ - state(), - impl_, - state().tick - })); + if(state().timer.expiry() != never()) + state().timer.async_wait( + net::bind_executor( + this->get_executor(), + timeout_handler{ + state(), + impl_, + state().tick + })); BOOST_ASIO_CORO_YIELD async_perform( std::integral_constant{}); - ++state().tick; - // try cancelling timer - auto const n = - state().timer.cancel(); - if(n == 0) + if(state().timer.expiry() != never()) { - if(state().timeout) + ++state().tick; + + // try cancelling timer + auto const n = + state().timer.cancel(); + if(n == 0) { - // timeout handler invoked - ec = beast::error::timeout; - state().timeout = false; + // timeout handler invoked? + if(state().timeout) + { + // yes, socket already closed + ec = beast::error::timeout; + state().timeout = false; + } } else { - // timeout handler queued, stale + BOOST_ASSERT(n == 1); + BOOST_ASSERT(! state().timeout); } } - else - { - BOOST_ASSERT(n == 1); - BOOST_ASSERT(! state().timeout); - } + upcall: pg_.reset(); this->invoke(ec, bytes_transferred); } @@ -188,17 +283,17 @@ namespace detail { template< class Protocol, class Executor, class Handler> -class timeout_stream_connect_op +class basic_stream_connect_op : public async_op_base { using stream_type = - beast::basic_timeout_stream; + beast::basic_stream; using timeout_handler = typename stream_type::timeout_handler; - std::shared_ptr::impl_type> impl_; + std::shared_ptr impl_; typename stream_type::pending_guard pg0_; typename stream_type::pending_guard pg1_; @@ -209,64 +304,8 @@ class timeout_stream_connect_op } 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( - std::forward(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{ - state(), - impl_, - state().tick})); - - 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( - std::forward(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{ - state(), - impl_, - state().tick})); - - net::async_connect(impl_->socket, - begin, end, cond, std::move(*this)); - // *this is now moved-from - } - template - timeout_stream_connect_op( + basic_stream_connect_op( stream_type& s, typename stream_type::endpoint_type ep, Handler_&& h) @@ -276,48 +315,104 @@ public: , pg0_(impl_->read.pending) , pg1_(impl_->write.pending) { - // must come first - impl_->write.timer.async_wait( - net::bind_executor( - this->get_executor(), - timeout_handler{ - state(), - impl_, - state().tick})); + if(state().timer.expiry() != stream_base::never()) + impl_->write.timer.async_wait( + net::bind_executor( + this->get_executor(), + timeout_handler{ + state(), + impl_, + state().tick})); impl_->socket.async_connect( ep, std::move(*this)); // *this is now moved-from } + template< + class Endpoints, class Condition, + class Handler_> + basic_stream_connect_op( + stream_type& s, + Endpoints const& eps, + Condition const& cond, + Handler_&& h) + : async_op_base( + std::forward(h), s.get_executor()) + , impl_(s.impl_) + , pg0_(impl_->read.pending) + , pg1_(impl_->write.pending) + { + if(state().timer.expiry() != stream_base::never()) + impl_->write.timer.async_wait( + net::bind_executor( + this->get_executor(), + timeout_handler{ + state(), + impl_, + state().tick})); + + net::async_connect(impl_->socket, + eps, cond, std::move(*this)); + // *this is now moved-from + } + + template< + class Iterator, class Condition, + class Handler_> + basic_stream_connect_op( + stream_type& s, + Iterator begin, Iterator end, + Condition const& cond, + Handler_&& h) + : async_op_base( + std::forward(h), s.get_executor()) + , impl_(s.impl_) + , pg0_(impl_->read.pending) + , pg1_(impl_->write.pending) + { + if(state().timer.expiry() != stream_base::never()) + impl_->write.timer.async_wait( + net::bind_executor( + this->get_executor(), + timeout_handler{ + state(), + impl_, + state().tick})); + + net::async_connect(impl_->socket, + begin, end, cond, std::move(*this)); + // *this is now moved-from + } + template void operator()(error_code ec, Args&&... args) { - ++state().tick; - - // try cancelling timer - auto const n = - impl_->write.timer.cancel(); - if(n == 0) + if(state().timer.expiry() != stream_base::never()) { - if(state().timeout) + ++state().tick; + + // try cancelling timer + auto const n = + impl_->write.timer.cancel(); + if(n == 0) { - // timeout handler invoked - ec = beast::error::timeout; - state().timeout = false; + // timeout handler invoked? + if(state().timeout) + { + // yes, socket already closed + ec = beast::error::timeout; + state().timeout = false; + } } else { - // timeout handler queued, stale + BOOST_ASSERT(n == 1); + BOOST_ASSERT(! state().timeout); } } - else - { - BOOST_ASSERT(n == 1); - BOOST_ASSERT(! state().timeout); - } - + pg0_.reset(); pg1_.reset(); this->invoke(ec, std::forward(args)...); @@ -329,82 +424,8 @@ public: //------------------------------------------------------------------------------ template -template -basic_timeout_stream:: -impl_type:: -impl_type( - Executor const& ex_, - Args&&... args) - : ex(ex_) - , read(ex_.context()) - , write(ex_.context()) - , socket(std::forward(args)...) -{ - reset(); -} - -template -auto -basic_timeout_stream:: -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 -void -basic_timeout_stream:: -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 -void -basic_timeout_stream:: -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 -basic_timeout_stream:: -~basic_timeout_stream() +basic_stream:: +~basic_stream() { // the shared object can outlive *this, // cancel any operations so the shared @@ -413,12 +434,12 @@ basic_timeout_stream:: } template -template -basic_timeout_stream:: -basic_timeout_stream(ExecutionContext& ctx) +template +basic_stream:: +basic_stream(ExecutionContext& ctx, Args&&... args) : impl_(std::make_shared( ctx.get_executor(), - ctx)) + ctx, std::forward(args)...)) { // Restriction is necessary until Asio fully supports P1322R0 static_assert( @@ -427,67 +448,52 @@ basic_timeout_stream(ExecutionContext& ctx) } template -basic_timeout_stream:: -basic_timeout_stream(executor_type const& ex) +template +basic_stream:: +basic_stream( + executor_type const& ex, Args&&... args) : impl_(std::make_shared( - ex, ex.context())) + ex, + ex.context(), std::forward(args)...)) { } template -basic_timeout_stream:: -basic_timeout_stream( - net::basic_stream_socket&& socket) +template +basic_stream:: +basic_stream(net::basic_stream_socket&& socket) : impl_(std::make_shared( - socket.get_executor(), std::move(socket))) + std::move(socket), + std::is_constructible&>().get_executor())>{})) { } template -basic_timeout_stream:: -basic_timeout_stream( - executor_type const& ex, - net::basic_stream_socket&& socket) - : impl_(std::make_shared( - 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 -basic_timeout_stream:: -basic_timeout_stream(basic_timeout_stream&& other) +basic_stream:: +basic_stream(basic_stream&& other) : impl_(std::make_shared( std::move(*other.impl_))) { - // Can't move while operations are pending! - BOOST_ASSERT(! impl_->read.pending); - BOOST_ASSERT(! impl_->write.pending); -} - -template -auto -basic_timeout_stream:: -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; + // VFALCO I'm not sure this implementation is correct... } //------------------------------------------------------------------------------ +template +auto +basic_stream:: +release_socket() -> + socket_type +{ + this->cancel(); + return std::move(impl_->socket); +} + template void -basic_timeout_stream:: +basic_stream:: expires_after(std::chrono::nanoseconds expiry_time) { // If assert goes off, it means that there are @@ -512,7 +518,7 @@ expires_after(std::chrono::nanoseconds expiry_time) template void -basic_timeout_stream:: +basic_stream:: expires_at( net::steady_timer::time_point expiry_time) { @@ -538,7 +544,7 @@ expires_at( template void -basic_timeout_stream:: +basic_stream:: expires_never() { impl_->reset(); @@ -546,7 +552,7 @@ expires_never() template void -basic_timeout_stream:: +basic_stream:: cancel() { error_code ec; @@ -555,7 +561,7 @@ cancel() template void -basic_timeout_stream:: +basic_stream:: close() { impl_->close(); @@ -565,14 +571,14 @@ template template BOOST_ASIO_INITFN_RESULT_TYPE(ConnectHandler, void(error_code)) -basic_timeout_stream:: +basic_stream:: async_connect( - endpoint_type ep, + endpoint_type const& ep, ConnectHandler&& handler) { BOOST_BEAST_HANDLER_INIT( ConnectHandler, void(error_code)); - detail::timeout_stream_connect_op< + detail::basic_stream_connect_op< Protocol, Executor, BOOST_ASIO_HANDLER_TYPE( ConnectHandler, void(error_code))>(*this, ep, std::forward(handler)); @@ -583,7 +589,7 @@ template template BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, void(error_code, std::size_t)) -basic_timeout_stream:: +basic_stream:: async_read_some( MutableBufferSequence const& buffers, ReadHandler&& handler) @@ -603,7 +609,7 @@ template template BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, void(error_code, std::size_t)) -basic_timeout_stream:: +basic_stream:: async_write_some( ConstBufferSequence const& buffers, WriteHandler&& handler) @@ -629,13 +635,13 @@ template< BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, void(error_code, typename Protocol::endpoint)) async_connect( - basic_timeout_stream& stream, + basic_stream& stream, EndpointSequence const& endpoints, RangeConnectHandler&& handler) { BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, void(error_code, typename Protocol::endpoint)); - detail::timeout_stream_connect_op( stream, endpoints, detail::any_endpoint{}, @@ -652,14 +658,14 @@ template< BOOST_ASIO_INITFN_RESULT_TYPE(RangeConnectHandler, void (error_code, typename Protocol::endpoint)) async_connect( - basic_timeout_stream& stream, + basic_stream& 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( stream, endpoints, connect_condition, @@ -674,13 +680,13 @@ template< BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, void (error_code, Iterator)) async_connect( - basic_timeout_stream& stream, + basic_stream& stream, Iterator begin, Iterator end, IteratorConnectHandler&& handler) { BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, void(error_code, Iterator)); - detail::timeout_stream_connect_op( stream, begin, end, detail::any_endpoint{}, @@ -696,14 +702,14 @@ template< BOOST_ASIO_INITFN_RESULT_TYPE(IteratorConnectHandler, void (error_code, Iterator)) async_connect( - basic_timeout_stream& stream, + basic_stream& stream, Iterator begin, Iterator end, ConnectCondition connect_condition, IteratorConnectHandler&& handler) { BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, void(error_code, Iterator)); - detail::timeout_stream_connect_op( stream, begin, end, connect_condition, @@ -711,6 +717,46 @@ async_connect( return init.result.get(); } +//------------------------------------------------------------------------------ + +#if ! BOOST_BEAST_DOXYGEN + +template +void +beast_close_socket( + basic_stream& stream) +{ + error_code ec; + stream.socket().close(ec); +} + +template +void +teardown( + websocket::role_type role, + basic_stream& stream, + error_code& ec) +{ + using beast::websocket::teardown; + teardown(role, stream.socket(), ec); +} + +template< + class Protocol, class Executor, + class TeardownHandler> +void +async_teardown( + websocket::role_type role, + basic_stream& stream, + TeardownHandler&& handler) +{ + using beast::websocket::async_teardown; + async_teardown(role, stream.socket(), + std::forward(handler)); +} + +#endif + } // beast } // boost diff --git a/include/boost/beast/core/stranded_socket.hpp b/include/boost/beast/core/stranded_socket.hpp deleted file mode 100644 index 09c5c6d4..00000000 --- a/include/boost/beast/core/stranded_socket.hpp +++ /dev/null @@ -1,1102 +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 -// - -#ifndef BOOST_BEAST_CORE_STRANDED_SOCKET_HPP -#define BOOST_BEAST_CORE_STRANDED_SOCKET_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace beast { - -//------------------------------------------------------------------------------ - -/** A stream-oriented socket using a custom executor, defaulting to a strand - - This class template provides asynchronous and blocking stream-oriented - socket functionality. It is designed as a replacement for - `net::basic_stream_socket`. - - This class template is parameterized on the executor type to be - used for all asynchronous operations. This achieves partial support for - [P1322]. The default template parameter uses a strand for the next - layer's executor. - - Unlike other stream wrappers, the underlying socket is accessed - through the @ref socket member function instead of `next_layer`. - This causes @ref stranded_socket to be returned in calls to - @ref get_lowest_layer. - - @tparam Protocol The protocol to use. - - @tparam Executor The executor to use. - - @par Thread Safety - @e Distinct @e objects: Safe.@n - @e Shared @e objects: Unsafe. - - @par Concepts: - @li SyncReadStream, SyncWriteStream - @li AsyncReadStream, AsyncWriteStream, - @li Protocol - @li Executor - - @see [P1322R0] - Networking TS enhancement to enable custom I/O executors -*/ -template< - class Protocol, - class Executor = net::io_context::strand -> -class stranded_socket -#ifndef BOOST_BEAST_DOXYGEN - : private detail::stranded_socket_base - , private boost::empty_value -#endif -{ - // Restricted until P1322R0 is incorporated into Boost.Asio. - static_assert( - std::is_convertible().context()), - net::io_context&>::value, - "Only net::io_context is currently supported for executor_type::context()"); - -public: - /// The type of the executor associated with the object. - using executor_type = Executor; - - /// The type of the underlying socket. - using socket_type = net::basic_stream_socket; - - /// The protocol type. - using protocol_type = Protocol; - - /// The endpoint type. - using endpoint_type = typename Protocol::endpoint; - - /** Construct the stream without opening it. - - This constructor creates a 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 - ExecutionContext, 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`. - - @param args A list of parameters forwarded to the constructor of - the underlying socket. - - @note This function does not participate in overload resolution unless: - - @li `std::is_convertible::value` is `true`, and - - @li `std::is_constructible::value` is `true`. - - @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html - */ - template< - class ExecutionContext, - class... Args - #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 - stranded_socket(ExecutionContext& ctx, Args&&... args) - : detail::stranded_socket_base( - ctx, std::forward(args)...) - , boost::empty_value( - boost::empty_init_t{}, ctx.get_executor()) - { - // Restriction is necessary until Asio fully supports P1322R0 - static_assert( - std::is_same::value, - "Only net::io_context is currently supported for ExecutionContext"); - } - - /** Construct the stream without opening it. - - This constructor creates a 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. - - @param args A list of parameters forwarded to the constructor of - the underlying socket. - - @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html - */ - template - explicit - stranded_socket(executor_type const& ex, Args&&... args) - : detail::stranded_socket_base( - ex.context(), std::forward(args)...) - , boost::empty_value( - boost::empty_init_t{}, ex) - { - } - - /** 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. - */ - stranded_socket(stranded_socket&& other) = default; - - /// Move assignment (deleted) - stranded_socket& - operator=(stranded_socket&& other) = delete; - - /** Get a reference to the underlying socket. - - This function returns a reference to the underlying - socket used by the stream. - */ - socket_type& - socket() noexcept - { - return this->socket_; - } - - /** Get a reference to the underlying socket. - - This function returns a reference to the underlying - socket used by the stream. - */ - socket_type const& - socket() const noexcept - { - return this->socket_; - } - - //-------------------------------------------------------------------------- - - /** Return the executor associated with the object. - - @return A copy of the executor that stream will use to dispatch handlers. - */ - executor_type - get_executor() const noexcept - { - return this->get(); - } - - /** Connect the socket to the specified endpoint. - - This function is used to connect a socket to the specified remote endpoint. - The function call will block until the connection is successfully made or - an error occurs. - - The 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 socket will be - connected. - - @throws system_error Thrown on failure. - */ - void - connect(endpoint_type const& ep) - { - this->socket_.connect(ep); - } - - /** Connect the socket to the specified endpoint. - - This function is used to connect a socket to the specified remote endpoint. - The function call will block until the connection is successfully made or - an error occurs. - - The 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 socket will be - connected. - - @param ec Set to indicate what error occurred, if any. - */ - void - connect(endpoint_type const& ep, error_code& ec) - { - this->socket_.connect(ep, ec); - } - - /** 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 - BOOST_ASIO_INITFN_RESULT_TYPE(ConnectHandler, - void(error_code)) - async_connect( - endpoint_type const& ep, - ConnectHandler&& handler) - { - BOOST_BEAST_HANDLER_INIT(ConnectHandler, - void(error_code)); - this->socket_.async_connect( - ep, - detail::bind_default_executor( - this->get(), - std::move(init.completion_handler))); - return init.result.get(); - } - - /** 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 `net::read` if you need to ensure - that the requested amount of data is read before the blocking operation - completes. - */ - template - std::size_t - read_some(MutableBufferSequence const& buffers) - { - return this->socket_.read_some(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 `net::read` if you need to ensure - that the requested amount of data is read before the blocking operation - completes. - */ - template - std::size_t - read_some( - MutableBufferSequence const& buffers, - error_code& ec) - { - return this->socket_.read_some(buffers, ec); - } - - /** 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 - BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, - void(error_code, std::size_t)) - async_read_some( - MutableBufferSequence const& buffers, - ReadHandler&& handler) - { - BOOST_BEAST_HANDLER_INIT(ReadHandler, - void(error_code, std::size_t)); - this->socket_.async_read_some( - buffers, - detail::bind_default_executor( - this->get(), - std::move(init.completion_handler))); - return init.result.get(); - } - - /** 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 `net::write` if you need to - ensure that all data is written before the blocking operation completes. - */ - template - std::size_t - write_some(ConstBufferSequence const& buffers) - { - return this->socket_.write_some(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 `net::write` if you need to - ensure that all data is written before the blocking operation completes. - */ - template - std::size_t - write_some( - ConstBufferSequence const& buffers, - error_code& ec) - { - return this->socket_.write_some(buffers, ec); - } - - /** 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 - BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, - void(error_code, std::size_t)) - async_write_some( - ConstBufferSequence const& buffers, - WriteHandler&& handler) - { - BOOST_BEAST_HANDLER_INIT(WriteHandler, - void(error_code, std::size_t)); - this->socket_.async_write_some( - buffers, - detail::bind_default_executor( - this->get(), - std::move(init.completion_handler))); - return init.result.get(); - } -}; - -//------------------------------------------------------------------------------ - -/** Establishes a socket connection by trying each endpoint in a sequence. - - This function attempts to connect a socket to one of a sequence of - endpoints. It does this by repeated calls to the socket's @c connect member - function, once for each endpoint in the sequence, until a connection is - successfully established. - - @param socket The socket to be connected. - If the underlying socket is already open, it will be closed. - - @param endpoints A sequence of endpoints. - - @returns The successfully connected endpoint. - - @throws system_error Thrown on failure. If the sequence is - empty, the associated error code is `net::error::not_found`. - Otherwise, contains the error from the last connection attempt. -*/ -template< - class Protocol, class Executor, - class EndpointSequence -#if ! BOOST_BEAST_DOXYGEN - ,class = typename std::enable_if< - net::is_endpoint_sequence< - EndpointSequence>::value>::type -#endif -> -typename Protocol::endpoint -connect( - stranded_socket& socket, - EndpointSequence const & endpoints -) -{ - return net::connect(socket.socket(), endpoints); -} - -/** Establishes a socket connection by trying each endpoint in a sequence. - - This function attempts to connect a socket to one of a sequence of - endpoints. It does this by repeated calls to the socket's @c connect member - function, once for each endpoint in the sequence, until a connection is - successfully established. - - @param socket The socket to be connected. - If the underlying socket is already open, it will be closed. - - @param endpoints A sequence of endpoints. - - @param ec Set to indicate what error occurred, if any. If the sequence is - empty, set to `net::error::not_found`. Otherwise, contains the error - from the last connection attempt. - - @returns On success, the successfully connected endpoint. Otherwise, a - default-constructed endpoint. -*/ -template< - class Protocol, class Executor, - class EndpointSequence -#if ! BOOST_BEAST_DOXYGEN - ,class = typename std::enable_if< - net::is_endpoint_sequence< - EndpointSequence>::value>::type -#endif -> -typename Protocol::endpoint -connect( - stranded_socket& socket, - EndpointSequence const& endpoints, - error_code& ec -) -{ - return net::connect(socket.socket(), endpoints, ec); -} - -/** Establishes a socket connection by trying each endpoint in a sequence. - - This function attempts to connect a socket to one of a sequence of - endpoints. It does this by repeated calls to the socket's @c connect member - function, once for each endpoint in the sequence, until a connection is - successfully established. - - @param socket The socket 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. - - @returns An iterator denoting the successfully connected endpoint. - - @throws system_error Thrown on failure. If the sequence is - empty, the associated error code is `net::error::not_found`. - Otherwise, contains the error from the last connection attempt. -*/ -template< - class Protocol, class Executor, - class Iterator> -Iterator -connect( - stranded_socket& socket, - Iterator begin, Iterator end) -{ - return net::connect(socket.socket(), begin, end); -} - -/** Establishes a socket connection by trying each endpoint in a sequence. - - This function attempts to connect a socket to one of a sequence of - endpoints. It does this by repeated calls to the socket's @c connect member - function, once for each endpoint in the sequence, until a connection is - successfully established. - - @param socket The socket 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 ec Set to indicate what error occurred, if any. If the sequence is - empty, set to boost::asio::error::not_found. Otherwise, contains the error - from the last connection attempt. - - @returns On success, an iterator denoting the successfully connected - endpoint. Otherwise, the end iterator. -*/ -template< - class Protocol, class Executor, - class Iterator> -Iterator -connect( - stranded_socket& socket, - Iterator begin, Iterator end, - error_code& ec) -{ - return net::connect(socket.socket(), begin, end, ec); -} - -/** Establishes a socket connection by trying each endpoint in a sequence. - - This function attempts to connect a socket to one of a sequence of - endpoints. It does this by repeated calls to the socket's @c connect member - function, once for each endpoint in the sequence, until a connection is - successfully established. - - @param socket The socket to be connected. - If the underlying socket is already open, it will be closed. - - @param endpoints 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, - 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. - - @returns The successfully connected endpoint. - - @throws boost::system::system_error Thrown on failure. If the sequence is - empty, the associated error code is `net::error::not_found`. - Otherwise, contains the error from the last connection attempt. -*/ -template< - class Protocol, class Executor, - class EndpointSequence, class ConnectCondition -#if ! BOOST_BEAST_DOXYGEN - ,class = typename std::enable_if< - net::is_endpoint_sequence< - EndpointSequence>::value>::type -#endif -> -typename Protocol::endpoint -connect( - stranded_socket& socket, - EndpointSequence const& endpoints, - ConnectCondition connect_condition -) -{ - return net::connect(socket.socket(), endpoints, connect_condition); -} - -/** Establishes a socket connection by trying each endpoint in a sequence. - - This function attempts to connect a socket to one of a sequence of - endpoints. It does this by repeated calls to the socket's @c connect member - function, once for each endpoint in the sequence, until a connection is - successfully established. - - @param socket The socket to be connected. - If the underlying socket is already open, it will be closed. - - @param endpoints 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, - 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 ec Set to indicate what error occurred, if any. If the sequence is - empty, set to `net::error::not_found`. Otherwise, contains the error - from the last connection attempt. - - @returns On success, the successfully connected endpoint. Otherwise, a - default-constructed endpoint. -*/ -template< - class Protocol, class Executor, - class EndpointSequence, class ConnectCondition -#if ! BOOST_BEAST_DOXYGEN - ,class = typename std::enable_if< - net::is_endpoint_sequence< - EndpointSequence>::value>::type -#endif -> -typename Protocol::endpoint -connect( - stranded_socket& socket, - EndpointSequence const& endpoints, - ConnectCondition connect_condition, - error_code& ec) -{ - return net::connect(socket.socket(), endpoints, connect_condition, ec); -} - -/** Establishes a socket connection by trying each endpoint in a sequence. - - This function attempts to connect a socket to one of a sequence of - endpoints. It does this by repeated calls to the socket's @c connect member - function, once for each endpoint in the sequence, until a connection is - successfully established. - - @param socket The socket 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, - 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. - - @returns An iterator denoting the successfully connected endpoint. - - @throws boost::system::system_error Thrown on failure. If the sequence is - empty, the associated @c error_code is `net::error::not_found`. - Otherwise, contains the error from the last connection attempt. -*/ -template< - class Protocol, class Executor, - class Iterator, class ConnectCondition> -Iterator -connect( - stranded_socket& socket, - Iterator begin, Iterator end, - ConnectCondition connect_condition) -{ - return net::connect(socket.socket(), begin, end, connect_condition); -} - -/** Establishes a socket connection by trying each endpoint in a sequence. - - This function attempts to connect a socket to one of a sequence of - endpoints. It does this by repeated calls to the socket's @c connect member - function, once for each endpoint in the sequence, until a connection is - successfully established. - - @param socket The socket 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, - 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 ec Set to indicate what error occurred, if any. If the sequence is - empty, set to `net::error::not_found`. Otherwise, contains the error - from the last connection attempt. - - @returns On success, an iterator denoting the successfully connected - endpoint. Otherwise, the end iterator. -*/ -template< - class Protocol, class Executor, - class Iterator, class ConnectCondition> -Iterator -connect( - stranded_socket& socket, - Iterator begin, Iterator end, - ConnectCondition connect_condition, - error_code& ec) -{ - return net::connect(socket.socket(), begin, end, connect_condition, ec); -} - -/** 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 socket The socket 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 EndpointSequence. - - @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( - stranded_socket& socket, - EndpointSequence const& endpoints, - RangeConnectHandler&& handler) -{ - BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, - void(error_code, typename Protocol::endpoint)); - net::async_connect(socket.socket(), - endpoints, - detail::bind_default_executor( - socket.get_executor(), - std::move(init.completion_handler))); - return init.result.get(); -} - -/** 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 socket The socket 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 EndpointSequence. - - @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( - stranded_socket& socket, - EndpointSequence const& endpoints, - ConnectCondition connect_condition, - RangeConnectHandler&& handler) -{ - BOOST_BEAST_HANDLER_INIT(RangeConnectHandler, - void(error_code, typename Protocol::endpoint)); - net::async_connect(socket.socket(), - endpoints, - connect_condition, - detail::bind_default_executor( - socket.get_executor(), - std::move(init.completion_handler))); - return init.result.get(); -} - -/** 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 socket The socket 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( - stranded_socket& socket, - Iterator begin, Iterator end, - IteratorConnectHandler&& handler) -{ - BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, - void(error_code, Iterator)); - net::async_connect(socket.socket(), - begin, end, - detail::bind_default_executor( - socket.get_executor(), - std::move(init.completion_handler))); - return init.result.get(); -} - -/** 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 socket The socket 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, - 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, 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( - stranded_socket& socket, - Iterator begin, Iterator end, - ConnectCondition connect_condition, - IteratorConnectHandler&& handler) -{ - BOOST_BEAST_HANDLER_INIT(IteratorConnectHandler, - void(error_code, Iterator)); - net::async_connect(socket.socket(), - begin, end, - connect_condition, - detail::bind_default_executor( - socket.get_executor(), - std::move(init.completion_handler))); - return init.result.get(); -} - -} // beast -} // boost - -#endif diff --git a/include/boost/beast/core/timeout_stream.hpp b/include/boost/beast/core/tcp_stream.hpp similarity index 50% rename from include/boost/beast/core/timeout_stream.hpp rename to include/boost/beast/core/tcp_stream.hpp index 859b1aa8..3f3c4415 100644 --- a/include/boost/beast/core/timeout_stream.hpp +++ b/include/boost/beast/core/tcp_stream.hpp @@ -7,22 +7,25 @@ // Official repository: https://github.com/boostorg/beast // -#ifndef BOOST_BEAST_CORE_TIMEOUT_STREAM_HPP -#define BOOST_BEAST_CORE_TIMEOUT_STREAM_HPP +#ifndef BOOST_BEAST_CORE_TCP_STREAM_HPP +#define BOOST_BEAST_CORE_TCP_STREAM_HPP #include -#include -#include +#include #include namespace boost { namespace beast { -/** A TCP/IP stream socket which supports timeouts and rate limits +/** A TCP/IP stream socket with timeouts, rate limits, and executor. + + @tparam Executor The type of executor to use for all completion + handlers which do not already have an associated executor. + + @see basic_stream */ -using timeout_stream = basic_timeout_stream< - net::ip::tcp, - net::io_context::executor_type>; +template +using tcp_stream = basic_stream; } // beast } // boost diff --git a/include/boost/beast/websocket/stream.hpp b/include/boost/beast/websocket/stream.hpp index 19261958..2d91d14d 100644 --- a/include/boost/beast/websocket/stream.hpp +++ b/include/boost/beast/websocket/stream.hpp @@ -599,7 +599,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -646,7 +646,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -700,7 +700,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -764,7 +764,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -835,7 +835,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -884,7 +884,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -941,7 +941,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -1008,7 +1008,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -1428,7 +1428,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -1467,7 +1467,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -1518,7 +1518,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -1557,7 +1557,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -1610,7 +1610,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -1659,7 +1659,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -1722,7 +1722,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -1773,7 +1773,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -1836,7 +1836,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -1874,7 +1874,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -1925,7 +1925,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -1964,7 +1964,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -2424,7 +2424,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `write_some` function. After beginning the closing handshake, the program should not write @@ -2459,7 +2459,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `write_some` function. After beginning the closing handshake, the program should not write @@ -2551,7 +2551,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `write_some` function. @param payload The payload of the ping message, which may be empty. @@ -2574,7 +2574,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `write_some` function. @param payload The payload of the ping message, which may be empty. @@ -2640,7 +2640,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `write_some` function. WebSocket allows pong frames to be sent at any time, without first @@ -2667,7 +2667,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `write_some` function. WebSocket allows pong frames to be sent at any time, without first @@ -2747,7 +2747,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -2790,7 +2790,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -2899,7 +2899,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -2950,7 +2950,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -3032,6 +3032,10 @@ public: @param buffer A dynamic buffer to append message data to. + @param limit An upper limit on the number of bytes this function + will append into the buffer. If this value is zero, then a reasonable + size will be chosen automatically. + @param handler Invoked when the operation completes. Ownership of the handler will be transferred by move-construction as needed. The equivalent function signature of the handler must be: @@ -3069,7 +3073,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -3096,10 +3100,6 @@ public: The previous contents of the buffers will be overwritten, starting from the beginning. - @param limit An upper limit on the number of bytes this function - will append into the buffer. If this value is zero, then a reasonable - size will be chosen automatically. - @throws system_error Thrown on failure. */ template @@ -3120,7 +3120,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `read_some` and `write_some` functions. @@ -3147,10 +3147,6 @@ public: The previous contents of the buffers will be overwritten, starting from the beginning. - @param limit An upper limit on the number of bytes this function - will append into the buffer. If this value is zero, then a reasonable - size will be chosen automatically. - @param ec Set to indicate what error occurred, if any. */ template @@ -3246,7 +3242,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `write_some` function. The current setting of the @ref binary option controls @@ -3275,7 +3271,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `write_some` function. The current setting of the @ref binary option controls @@ -3359,7 +3355,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `write_some` function. If this is the beginning of a new message, the message opcode @@ -3389,7 +3385,7 @@ public: @li An error occurs. - The algorithm, known as a composed operation is implemented + The algorithm, known as a composed operation, is implemented in terms of calls to the next layer's `write_some` function. If this is the beginning of a new message, the message opcode @@ -3401,6 +3397,8 @@ public: @param buffers The buffers containing the message part to send. + @param ec Set to indicate what error occurred, if any. + @return The number of bytes sent from the buffers. @return The number of bytes consumed in the input buffers. diff --git a/test/beast/core/CMakeLists.txt b/test/beast/core/CMakeLists.txt index 34dfadcd..b22e7cde 100644 --- a/test/beast/core/CMakeLists.txt +++ b/test/beast/core/CMakeLists.txt @@ -30,7 +30,7 @@ add_executable (tests-beast-core _detail_variant.cpp _detail_varint.cpp async_op_base.cpp - basic_timeout_stream.cpp + basic_stream.cpp bind_handler.cpp buffer_size.cpp buffer_traits.cpp @@ -61,11 +61,10 @@ add_executable (tests-beast-core span.cpp static_buffer.cpp static_string.cpp - stranded_socket.cpp stream_traits.cpp string.cpp string_param.cpp - timeout_stream.cpp + tcp_stream.cpp ) set_property(TARGET tests-beast-core PROPERTY FOLDER "tests") diff --git a/test/beast/core/Jamfile b/test/beast/core/Jamfile index 073cc723..109328bb 100644 --- a/test/beast/core/Jamfile +++ b/test/beast/core/Jamfile @@ -18,7 +18,7 @@ local SOURCES = _detail_variant.cpp _detail_varint.cpp async_op_base.cpp - basic_timeout_stream.cpp + basic_stream.cpp bind_handler.cpp buffer_size.cpp buffer_traits.cpp @@ -49,11 +49,10 @@ local SOURCES = span.cpp static_buffer.cpp static_string.cpp - stranded_socket.cpp stream_traits.cpp string.cpp string_param.cpp - timeout_stream.cpp + tcp_stream.cpp ; local RUN_TESTS ; diff --git a/test/beast/core/basic_stream.cpp b/test/beast/core/basic_stream.cpp new file mode 100644 index 00000000..6189a71d --- /dev/null +++ b/test/beast/core/basic_stream.cpp @@ -0,0 +1,1245 @@ +// +// 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 + +#include "stream_tests.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { + +namespace { + +template +class test_executor +{ +public: + // VFALCO These need to be atomic or something + struct info + { + int dispatch = 0; + int post = 0; + int defer = 0; + int work = 0; + int total = 0; + }; + +private: + struct state + { + Executor ex; + info info_; + + state(Executor const& ex_) + : ex(ex_) + { + } + }; + + std::shared_ptr sp_; + +public: + test_executor(test_executor const&) = default; + test_executor& operator=(test_executor const&) = default; + + explicit + test_executor(Executor const& ex) + : sp_(std::make_shared(ex)) + { + } + + decltype(sp_->ex.context()) + context() const noexcept + { + return sp_->ex.context(); + } + + info& + operator*() noexcept + { + return sp_->info_; + } + + info* + operator->() noexcept + { + return &sp_->info_; + } + + void + on_work_started() const noexcept + { + ++sp_->info_.work; + } + + void + on_work_finished() const noexcept + { + } + + template + void + dispatch(F&& f, A const& a) + { + ++sp_->info_.dispatch; + ++sp_->info_.total; + sp_->ex.dispatch( + std::forward(f), a); + } + + template + void + post(F&& f, A const& a) + { + ++sp_->info_.post; + ++sp_->info_.total; + sp_->ex.post( + std::forward(f), a); + } + + template + void + defer(F&& f, A const& a) + { + ++sp_->info_.defer; + ++sp_->info_.total; + sp_->ex.defer( + std::forward(f), a); + } +}; + +struct test_acceptor +{ + net::io_context ioc; + net::ip::tcp::acceptor a; + net::ip::tcp::endpoint ep; + + test_acceptor() + : a(ioc) + , 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(0); + ep = a.local_endpoint(); + a.async_accept( + [](error_code, net::ip::tcp::socket) + { + }); + } +}; + +class test_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 const& ec, string_view what) + { + if(ec != net::error::operation_aborted) + log_ << what << ": " << ec.message() << "\n"; + } + +public: + test_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(); + }); + } + + ~test_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 + { + 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 const&) + { + } + + void + on_write(error_code const&, std::size_t) + { + } + }; + + void + on_accept(error_code const& ec) + { + if(! acceptor_.is_open()) + return; + if(ec) + fail(ec, "accept"); + else + std::make_shared( + s_, std::move(socket_), log_)->run(); + acceptor_.async_accept(socket_, + [this](error_code ec) + { + this->on_accept(ec); + }); + } +}; + + +} // (anon) + +class basic_stream_test + : public beast::unit_test::suite +{ +public: + using tcp = net::ip::tcp; + using strand = net::io_context::strand; + using executor = net::io_context::executor_type; + + //-------------------------------------------------------------------------- + + void + testSpecialMembers() + { + using stream_type = tcp_stream< + net::io_context::executor_type>; + + net::io_context ioc; + + // net::io_context::executor_type + + { + auto ex = ioc.get_executor(); + basic_stream s1(ioc); + basic_stream s2(ex); + basic_stream s3(ioc, tcp::v4()); + basic_stream s4(std::move(s1)); + s2.socket() = tcp::socket(ioc); + BEAST_EXPECT(s1.get_executor() == ex); + BEAST_EXPECT(s2.get_executor() == ex); + BEAST_EXPECT(s3.get_executor() == ex); + BEAST_EXPECT(s4.get_executor() == ex); + + BEAST_EXPECT((! static_cast< + basic_stream const&>( + s2).socket().is_open())); + + test_sync_stream< + basic_stream< + tcp, net::io_context::executor_type>>(); + + test_async_stream< + basic_stream< + tcp, net::io_context::executor_type>>(); + } + + // net::io_context::strand + + { + auto ex = strand{ioc}; + basic_stream s1(ex); + basic_stream s2(ex, tcp::v4()); + basic_stream s3(std::move(s1)); + s2.socket() = tcp::socket(ioc); + BEAST_EXPECT(s1.get_executor() == ex); + BEAST_EXPECT(s2.get_executor() == ex); + BEAST_EXPECT(s3.get_executor() == ex); + + BEAST_EXPECT((! static_cast< + basic_stream const&>( + s2).socket().is_open())); + + test_sync_stream< + basic_stream< + tcp, net::io_context::strand>>(); + + test_async_stream< + basic_stream< + tcp, net::io_context::strand>>(); + } + + // construction from existing socket + + { + tcp::socket sock(ioc); + basic_stream stream(std::move(sock)); + } + + { + tcp::socket sock(ioc); + basic_stream stream(std::move(sock)); + } + + struct other_type + { + }; + + BOOST_STATIC_ASSERT(! std::is_constructible< + stream_type, other_type>::value); + + BOOST_STATIC_ASSERT(! std::is_constructible< + stream_type, other_type, tcp::socket>::value); + + // layers + + { + net::socket_base::keep_alive opt; + stream_type s(ioc); + s.socket().open(tcp::v4()); + s.socket().get_option(opt); + BEAST_EXPECT(! opt.value()); + opt = true; + s.socket().set_option(opt); + opt = false; + BEAST_EXPECT(! opt.value()); + } + } + + class handler + { + boost::optional ec_; + std::size_t n_; + + public: + handler(error_code ec, std::size_t n) + : ec_(ec) + , n_(n) + { + } + + handler(handler&& other) + : ec_(other.ec_) + , n_(boost::exchange(other.n_, + (std::numeric_limits::max)())) + { + } + + ~handler() + { + BEAST_EXPECT( + n_ == (std::numeric_limits::max)()); + } + + void + operator()(error_code const& ec, std::size_t n) + { + BEAST_EXPECTS(ec == ec_, ec.message()); + BEAST_EXPECT(n == n_); + n_ = (std::numeric_limits::max)(); + } + }; + + void + testRead() + { + using stream_type = tcp_stream< + net::io_context::executor_type>; + + char buf[4]; + net::io_context ioc; + std::memset(buf, 0, sizeof(buf)); + net::mutable_buffer mb(buf, sizeof(buf)); + auto const ep = net::ip::tcp::endpoint( + net::ip::make_address("127.0.0.1"), 0); + + // read_some + + { + error_code ec; + stream_type s(ioc, tcp::v4()); + BEAST_EXPECT(s.read_some(net::mutable_buffer{}) == 0); + BEAST_EXPECT(s.read_some(net::mutable_buffer{}, ec) == 0); + BEAST_EXPECTS(! ec, ec.message()); + } + + // async_read_some + + { + // success + test_server srv("*", ep, log); + stream_type s(ioc); + s.socket().connect(srv.local_endpoint()); + s.expires_never(); + s.async_read_some(mb, handler({}, 1)); + ioc.run(); + ioc.restart(); + } + + { + // success, with timeout + test_server srv("*", ep, log); + stream_type s(ioc); + s.socket().connect(srv.local_endpoint()); + s.expires_after(std::chrono::seconds(30)); + s.async_read_some(mb, handler({}, 1)); + ioc.run(); + ioc.restart(); + } + + { + // empty buffer + test_server srv("*", ep, log); + stream_type s(ioc); + s.socket().connect(srv.local_endpoint()); + s.expires_never(); + s.async_read_some( + net::mutable_buffer{}, handler({}, 0)); + ioc.run(); + ioc.restart(); + } + + { + // empty buffer, timeout + test_server srv("*", ep, log); + stream_type s(ioc); + s.socket().connect(srv.local_endpoint()); + s.expires_after(std::chrono::seconds(0)); + s.async_read_some(net::mutable_buffer{}, + handler(error::timeout, 0)); + ioc.run(); + ioc.restart(); + } + + { + // expires_after + test_server srv("", ep, log); + stream_type s(ioc); + s.socket().connect(srv.local_endpoint()); + s.expires_after(std::chrono::seconds(0)); + s.async_read_some(mb, handler(error::timeout, 0)); + ioc.run(); + ioc.restart(); + } + + { + // expires_at + test_server srv("", ep, log); + stream_type s(ioc); + s.socket().connect(srv.local_endpoint()); + s.expires_at(std::chrono::steady_clock::now()); + s.async_read_some(mb, handler(error::timeout, 0)); + ioc.run(); + ioc.restart(); + } + + { + // stream destroyed + test_server srv("", ep, log); + { + stream_type s(ioc); + s.socket().connect(srv.local_endpoint()); + s.expires_after(std::chrono::seconds(0)); + s.async_read_some(mb, + [](error_code, std::size_t) + { + }); + } + ioc.run(); + ioc.restart(); + } + + { + // stale timer + test_acceptor a; + stream_type s(ioc); + s.expires_after(std::chrono::milliseconds(50)); + s.async_read_some(mb, + [](error_code, std::size_t) + { + }); + std::this_thread::sleep_for( + std::chrono::milliseconds(100)); + ioc.run(); + ioc.restart(); + } + + // abandoned operation + { + stream_type s(ioc); + s.async_read_some(net::mutable_buffer{}, + [](error_code, std::size_t) + { + BEAST_FAIL(); + }); + } + } + + void + testWrite() + { + using stream_type = tcp_stream< + net::io_context::executor_type>; + + char buf[4]; + net::io_context ioc; + std::memset(buf, 0, sizeof(buf)); + net::const_buffer cb(buf, sizeof(buf)); + auto const ep = net::ip::tcp::endpoint( + net::ip::make_address("127.0.0.1"), 0); + + // write_some + + { + error_code ec; + stream_type s(ioc, tcp::v4()); + BEAST_EXPECT(s.write_some(net::const_buffer{}) == 0); + BEAST_EXPECT(s.write_some(net::const_buffer{}, ec) == 0); + BEAST_EXPECTS(! ec, ec.message()); + } + + // async_write_some + + { + // success + test_server srv("*", ep, log); + stream_type s(ioc); + s.socket().connect(srv.local_endpoint()); + s.expires_never(); + s.async_write_some(cb, handler({}, 4)); + ioc.run(); + ioc.restart(); + } + + { + // success, with timeout + test_server srv("*", ep, log); + stream_type s(ioc); + s.socket().connect(srv.local_endpoint()); + s.expires_after(std::chrono::seconds(30)); + s.async_write_some(cb, handler({}, 4)); + ioc.run(); + ioc.restart(); + } + + { + // empty buffer + test_server srv("*", ep, log); + stream_type s(ioc); + s.socket().connect(srv.local_endpoint()); + s.expires_never(); + s.async_write_some( + net::const_buffer{}, handler({}, 0)); + ioc.run(); + ioc.restart(); + } + + { + // empty buffer, timeout + test_server srv("*", ep, log); + stream_type s(ioc); + s.socket().connect(srv.local_endpoint()); + s.expires_after(std::chrono::seconds(0)); + s.async_write_some(net::const_buffer{}, + handler(error::timeout, 0)); + ioc.run(); + ioc.restart(); + } + + // abandoned operation + { + stream_type s(ioc); + s.async_write_some(cb, + [](error_code, std::size_t) + { + BEAST_FAIL(); + }); + } + } + + void + testConnect() + { + using stream_type = tcp_stream< + net::io_context::executor_type>; + + struct range + { + tcp::endpoint ep; + + using iterator = + tcp::endpoint const*; + + // VFALCO This is here because asio mistakenly requires it + using const_iterator = + tcp::endpoint const*; + + iterator begin() const noexcept + { + return &ep; + } + + // VFALCO need to use const_iterator to silence + // warning about unused types + const_iterator end() const noexcept + { + return begin() + 1; + } + }; + + class connect_handler + { + bool pass_ = false; + boost::optional expected_ = {}; + + public: + ~connect_handler() + { + BEAST_EXPECT(pass_); + } + + connect_handler() + : expected_(error_code{}) + { + } + + explicit + connect_handler(error_code expected) + : expected_(expected) + { + } + + explicit + connect_handler(boost::none_t) + { + } + + connect_handler(connect_handler&& other) + : pass_(boost::exchange(other.pass_, true)) + , expected_(other.expected_) + { + } + + void operator()(error_code ec) + { + pass_ = true; + if(expected_) + BEAST_EXPECTS( + ec == expected_, ec.message()); + } + }; + + struct range_handler + { + bool pass = false; + + range_handler() = default; + + range_handler(range_handler&& other) + : pass(boost::exchange(other.pass, true)) + { + } + + ~range_handler() + { + BEAST_EXPECT(pass); + } + + void operator()(error_code ec, tcp::endpoint) + { + pass = true; + BEAST_EXPECTS(! ec, ec.message()); + } + }; + + struct iterator_handler + { + bool pass = false; + + iterator_handler() = default; + + iterator_handler(iterator_handler&& other) + : pass(boost::exchange(other.pass, true)) + { + } + + ~iterator_handler() + { + BEAST_EXPECT(pass); + } + + void operator()(error_code ec, tcp::endpoint const*) + { + pass = true; + BEAST_EXPECTS(! ec, ec.message()); + } + }; + + struct connect_condition + { + bool operator()(error_code, tcp::endpoint) const + { + return true; + }; + }; + + range r; + net::io_context ioc; + connect_condition cond; + + // connect (member) + + { + test_acceptor a; + stream_type s(ioc); + error_code ec; + s.connect(a.ep); + s.socket().close(); + s.connect(a.ep, ec); + BEAST_EXPECTS(! ec, ec.message()); + } + + // connect + + { + test_acceptor a; + stream_type s(ioc); + error_code ec; + r.ep = a.ep; + connect(s, r); + s.socket().close(); + connect(s, r, ec); + BEAST_EXPECTS(! ec, ec.message()); + } + + { + test_acceptor a; + stream_type s(ioc); + error_code ec; + r.ep = a.ep; + connect(s, r, cond); + s.socket().close(); + connect(s, r, cond, ec); + BEAST_EXPECTS(! ec, ec.message()); + } + + { + test_acceptor a; + stream_type s(ioc); + error_code ec; + r.ep = a.ep; + connect(s, r.begin(), r.end()); + s.socket().close(); + connect(s, r.begin(), r.end(), ec); + BEAST_EXPECTS(! ec, ec.message()); + } + + { + test_acceptor a; + stream_type s(ioc); + error_code ec; + r.ep = a.ep; + connect(s, r.begin(), r.end(), cond); + s.socket().close(); + connect(s, r.begin(), r.end(), cond, ec); + BEAST_EXPECTS(! ec, ec.message()); + } + + // async_connect (member) + + { + test_acceptor a; + stream_type s(ioc); + s.expires_never(); + s.async_connect(a.ep, connect_handler{}); + ioc.run(); + ioc.restart(); + s.socket().close(); + s.expires_after(std::chrono::seconds(30)); + s.async_connect(a.ep, connect_handler{}); + ioc.run(); + ioc.restart(); + } + + // async_connect + + { + test_acceptor a; + stream_type s(ioc); + r.ep = a.ep; + s.expires_never(); + async_connect(s, r, range_handler{}); + ioc.run(); + ioc.restart(); + s.socket().close(); + s.expires_after(std::chrono::seconds(30)); + async_connect(s, r, range_handler{}); + ioc.run(); + ioc.restart(); + } + + { + test_acceptor a; + stream_type s(ioc); + r.ep = a.ep; + s.expires_never(); + async_connect(s, r, cond, range_handler{}); + ioc.run(); + ioc.restart(); + s.socket().close(); + s.expires_after(std::chrono::seconds(30)); + async_connect(s, r, cond, range_handler{}); + ioc.run(); + ioc.restart(); + } + + { + test_acceptor a; + stream_type s(ioc); + r.ep = a.ep; + s.expires_never(); + async_connect(s, r.begin(), r.end(), + iterator_handler{}); + ioc.run(); + ioc.restart(); + s.socket().close(); + s.expires_after(std::chrono::seconds(30)); + async_connect(s, r.begin(), r.end(), + iterator_handler{}); + ioc.run(); + ioc.restart(); + } + + { + test_acceptor a; + stream_type s(ioc); + r.ep = a.ep; + s.expires_never(); + async_connect(s, r.begin(), r.end(), cond, + iterator_handler{}); + ioc.run(); + ioc.restart(); + s.socket().close(); + s.expires_after(std::chrono::seconds(30)); + async_connect(s, r.begin(), r.end(), cond, + iterator_handler{}); + ioc.run(); + ioc.restart(); + } + + // use_future + + BEAST_EXPECT(static_cast(*)(stream_type&, + std::array const&, + net::use_future_t<>&&)>( + &beast::async_connect)); + + BEAST_EXPECT(static_cast(*)(stream_type&, + std::array const&, + connect_condition const&, + net::use_future_t<>&&)>( + &beast::async_connect)); + + BEAST_EXPECT(static_cast(*)(stream_type&, + tcp::endpoint const*, + tcp::endpoint const*, + net::use_future_t<>&&)>( + &beast::async_connect)); + + BEAST_EXPECT(static_cast(*)(stream_type&, + tcp::endpoint const*, + tcp::endpoint const*, + connect_condition const&, + net::use_future_t<>&&)>( + &beast::async_connect)); + + // yield_context + + BEAST_EXPECT(static_cast< + tcp::endpoint(*)(stream_type&, + std::array const&, + net::yield_context&&)>( + &beast::async_connect)); + + BEAST_EXPECT(static_cast< + tcp::endpoint(*)(stream_type&, + std::array const&, + connect_condition const&, + net::yield_context&&)>( + &beast::async_connect)); + + BEAST_EXPECT(static_cast< + tcp::endpoint const*(*)(stream_type&, + tcp::endpoint const*, + tcp::endpoint const*, + net::yield_context&&)>( + &beast::async_connect)); + + BEAST_EXPECT(static_cast< + tcp::endpoint const*(*)(stream_type&, + tcp::endpoint const*, + tcp::endpoint const*, + connect_condition const&, + net::yield_context&&)>( + &beast::async_connect)); + + // + // async_connect timeout + // + + { + // normal timeout + // Requires timeout happen before ECONNREFUSED + stream_type s(ioc); + auto const ep = net::ip::tcp::endpoint( + #if 1 + // This address _should_ be unconnectible + net::ip::make_address("72.5.65.111"), 1); + #else + // On Travis ECONNREFUSED happens before the timeout + net::ip::make_address("127.0.0.1"), 1); + #endif + s.expires_after(std::chrono::seconds(0)); + s.async_connect(ep, connect_handler{error::timeout}); + ioc.run_for(std::chrono::seconds(1)); + ioc.restart(); + } + + { + // stream destroyed + { + stream_type s(ioc); + auto const ep = net::ip::tcp::endpoint( + net::ip::make_address("127.0.0.1"), 1); + s.expires_after(std::chrono::seconds(0)); + s.async_connect(ep, connect_handler{boost::none}); + } + ioc.run(); + ioc.restart(); + } + + { + // stale timer + test_acceptor a; + stream_type s(ioc); + s.expires_after(std::chrono::milliseconds(50)); + s.async_connect(a.ep, connect_handler{}); + std::this_thread::sleep_for( + std::chrono::milliseconds(100)); + ioc.run(); + ioc.restart(); + } + + // abandoned operation + { + stream_type s(ioc); + net::ip::tcp::endpoint ep( + net::ip::make_address_v4("127.0.0.1"), 1); + s.async_connect(ep, + [](error_code) + { + BEAST_FAIL(); + }); + } + } + + void + testMembers() + { + using stream_type = tcp_stream< + net::io_context::executor_type>; + + class handler + { + bool pass_ = false; + boost::optional expected_ = {}; + + public: + ~handler() + { + BEAST_EXPECT(pass_); + } + + handler() + : expected_(error_code{}) + { + } + + explicit + handler(error_code expected) + : expected_(expected) + { + } + + explicit + handler(boost::none_t) + { + } + + handler(handler&& other) + : pass_(boost::exchange(other.pass_, true)) + , expected_(other.expected_) + { + } + + void operator()(error_code ec, std::size_t) + { + pass_ = true; + if(expected_) + BEAST_EXPECTS( + ec == expected_, ec.message()); + } + }; + + auto const ep = net::ip::tcp::endpoint( + net::ip::make_address("127.0.0.1"), 0); + + char buf[4]; + net::io_context ioc; + auto mb = net::buffer(buf); + std::memset(buf, 0, sizeof(buf)); + + // cancel + + { + test_server srv("", ep, log); + stream_type s(ioc); + s.connect(srv.local_endpoint()); + s.expires_never(); + s.socket().async_read_some(mb, handler( + net::error::operation_aborted)); + s.cancel(); + ioc.run(); + ioc.restart(); + } + + // close + + { + test_server srv("", ep, log); + stream_type s(ioc); + s.connect(srv.local_endpoint()); + s.expires_never(); + s.socket().async_read_some(mb, + handler(boost::none)); + s.close(); + ioc.run(); + ioc.restart(); + } + + // destructor + + { + test_server srv("", ep, log); + { + stream_type s(ioc); + s.connect(srv.local_endpoint()); + s.expires_never(); + s.socket().async_read_some(mb, + handler(boost::none)); + } + ioc.run(); + ioc.restart(); + } + + // customization points + + { + stream_type s(ioc); + beast::close_socket(s); + } + + { + error_code ec; + stream_type s(ioc); + teardown(websocket::role_type::client, s, ec); + } + + { + error_code ec; + stream_type s(ioc); + async_teardown(websocket::role_type::server, s, + [](error_code) + { + }); + } + } + + //-------------------------------------------------------------------------- + + http::response + make_response(http::request) + { + return {}; + } + + void process_http_1 (tcp_stream& stream, net::yield_context yield) + { + flat_buffer buffer; + http::request 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 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 (tcp_stream& stream, net::yield_context yield) + { + flat_buffer buffer; + http::request 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 res = make_response(req); + http::async_write (stream, res, yield); + } + + void + testJavadocs() + { + BEAST_EXPECT(&basic_stream_test::process_http_1); + BEAST_EXPECT(&basic_stream_test::process_http_2); + } + + //-------------------------------------------------------------------------- + + void + run() + { + testSpecialMembers(); + testRead(); + testWrite(); + testConnect(); + testMembers(); + testJavadocs(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,core,basic_stream); + +} // beast +} // boost diff --git a/test/beast/core/basic_timeout_stream.cpp b/test/beast/core/basic_timeout_stream.cpp deleted file mode 100644 index 31ef5fa3..00000000 --- a/test/beast/core/basic_timeout_stream.cpp +++ /dev/null @@ -1,1058 +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 - -#include "stream_tests.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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) - { - boost::ignore_unused(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 const& 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 - { - 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 const& ec) - { - boost::ignore_unused(ec); - } - - void - on_write(error_code const& , std::size_t) - { - } - }; - - void - on_accept(error_code const& ec) - { - if(! acceptor_.is_open()) - return; - if(ec) - fail(ec, "accept"); - else - std::make_shared( - 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; - - 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(s).next_layer().get_option(opt); - BEAST_EXPECT(opt.value()); - } - } - - void - testAsyncStream() - { - test_async_stream>(); - } - - //-------------------------------------------------------------------------- - - struct match - { - error_code ec_; - std::size_t n_; - - match(error_code ec, std::size_t n) - : ec_(ec) - , n_(n) - { - } - - match(match&& other) - : ec_(other.ec_) - , n_(boost::exchange(other.n_, - (std::numeric_limits::max)())) - { - } - - ~match() - { - BEAST_EXPECT( - n_ == (std::numeric_limits::max)()); - } - - void - operator()(error_code const& ec, std::size_t n) - { - BEAST_EXPECTS(ec == ec_, ec.message()); - BEAST_EXPECT(n == n_); - n_ = (std::numeric_limits::max)(); - } - }; - - void - testRead() - { - using tcp = net::ip::tcp; - using stream_t = basic_timeout_stream; - - 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{{}, 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{{}, 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{ - 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{ - 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{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{{}, 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; - - 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{{}, 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; - - auto const ep = net::ip::tcp::endpoint( - net::ip::make_address("127.0.0.1"), 0); - - { - struct connect_condition - { - bool operator()( - error_code, tcp::endpoint) - { - return true; - } - }; - - struct range_connect_handler - { - void operator()( - error_code, tcp::endpoint) - { - } - }; - - struct iterator_connect_handler - { - void operator()( - error_code, tcp::endpoint const*) - { - } - }; - - // completion handler - - BEAST_EXPECT( - static_cast const&, - range_connect_handler&&)>( - &beast::async_connect)); - - BEAST_EXPECT( - static_cast const&, - connect_condition const&, - range_connect_handler&&)>( - &beast::async_connect)); - - BEAST_EXPECT( - static_cast( - &beast::async_connect)); - - BEAST_EXPECT( - static_cast( - &beast::async_connect)); - - // use_future - - BEAST_EXPECT(static_cast(*)(stream_t&, - std::array const&, - net::use_future_t<>&&)>( - &beast::async_connect)); - - BEAST_EXPECT(static_cast(*)(stream_t&, - std::array const&, - connect_condition const&, - net::use_future_t<>&&)>( - &beast::async_connect)); - - BEAST_EXPECT(static_cast(*)(stream_t&, - tcp::endpoint const*, - tcp::endpoint const*, - net::use_future_t<>&&)>( - &beast::async_connect)); - - BEAST_EXPECT(static_cast(*)(stream_t&, - tcp::endpoint const*, - tcp::endpoint const*, - connect_condition const&, - net::use_future_t<>&&)>( - &beast::async_connect)); - - // yield_context - - BEAST_EXPECT(static_cast< - tcp::endpoint(*)(stream_t&, - std::array const&, - net::yield_context&&)>( - &beast::async_connect)); - - BEAST_EXPECT(static_cast< - tcp::endpoint(*)(stream_t&, - std::array const&, - connect_condition const&, - net::yield_context&&)>( - &beast::async_connect)); - - BEAST_EXPECT(static_cast< - tcp::endpoint const*(*)(stream_t&, - tcp::endpoint const*, - tcp::endpoint const*, - net::yield_context&&)>( - &beast::async_connect)); - - BEAST_EXPECT(static_cast< - tcp::endpoint const*(*)(stream_t&, - tcp::endpoint const*, - tcp::endpoint const*, - connect_condition const&, - net::yield_context&&)>( - &beast::async_connect)); - } - - // overload 1 - { - //BEAST_EXPECT(); - - server srv("", ep, log); - net::io_context ioc; - stream_t s(ioc); - bool invoked = false; - std::array 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 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 epa{{ - srv.local_endpoint()}}; - using iter_type = - std::array::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 epa{{ - srv.local_endpoint()}}; - using iter_type = - std::array::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 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 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 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, 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 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 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 - make_response(http::request) - { - return {}; - } - - void process_http_1 (timeout_stream& stream, net::yield_context yield) - { - flat_buffer buffer; - http::request 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 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 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 res = make_response(req); - http::async_write (stream, res, yield); - } - - websocket::stream - process_websocket(timeout_stream&& stream, net::yield_context yield) - { - websocket::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(); - testAsyncStream(); - testRead(); - testWrite(); - testConnect(); - testJavadocs(); - } -}; - -BEAST_DEFINE_TESTSUITE(beast,core,basic_timeout_stream); - -} // beast -} // boost diff --git a/test/beast/core/stranded_socket.cpp b/test/beast/core/stranded_socket.cpp deleted file mode 100644 index f9ab9a9b..00000000 --- a/test/beast/core/stranded_socket.cpp +++ /dev/null @@ -1,519 +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 - -#include "stream_tests.hpp" - -#include -#include - -#include - -namespace boost { -namespace beast { - -namespace { - -template -class test_executor -{ -public: - // VFALCO These need to be atomic or something - struct info - { - int dispatch = 0; - int post = 0; - int defer = 0; - int work = 0; - int total = 0; - }; - -private: - struct state - { - Executor ex; - info info_; - - state(Executor const& ex_) - : ex(ex_) - { - } - }; - - std::shared_ptr sp_; - -public: - test_executor(test_executor const&) = default; - test_executor& operator=(test_executor const&) = default; - - explicit - test_executor(Executor const& ex) - : sp_(std::make_shared(ex)) - { - } - - decltype(sp_->ex.context()) - context() const noexcept - { - return sp_->ex.context(); - } - - info& - operator*() noexcept - { - return sp_->info_; - } - - info* - operator->() noexcept - { - return &sp_->info_; - } - - void - on_work_started() const noexcept - { - ++sp_->info_.work; - } - - void - on_work_finished() const noexcept - { - } - - template - void - dispatch(F&& f, A const& a) - { - ++sp_->info_.dispatch; - ++sp_->info_.total; - sp_->ex.dispatch( - std::forward(f), a); - } - - template - void - post(F&& f, A const& a) - { - ++sp_->info_.post; - ++sp_->info_.total; - sp_->ex.post( - std::forward(f), a); - } - - template - void - defer(F&& f, A const& a) - { - ++sp_->info_.defer; - ++sp_->info_.total; - sp_->ex.defer( - std::forward(f), a); - } -}; - -struct test_handler -{ - int& flags; - - void - operator()() - { - flags |= 1; - } - - template - friend - void - asio_handler_invoke(F&& f, test_handler* p) - { - p->flags |= 2; - std::move(f)(); - } -}; - -struct test_acceptor -{ - net::io_context ioc; - net::ip::tcp::acceptor a; - net::ip::tcp::endpoint ep; - - test_acceptor() - : a(ioc) - , 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); - ep = a.local_endpoint(); - a.async_accept( - [](error_code, net::ip::tcp::socket) - { - }); - } -}; - -} // (anon) - -//------------------------------------------------------------------------------ - -class stranded_socket_test - : public beast::unit_test::suite -{ -public: - using tcp = net::ip::tcp; - using strand = net::io_context::strand; - using executor = net::io_context::executor_type; - - void - testStream() - { - net::io_context ioc; - - // default Executor - - { - stranded_socket s1{strand(ioc)}; - stranded_socket s2{strand{ioc}}; - //stranded_socket s3{strand{ioc}}; // ambiguous parse - } - - // explicit Executor - - { - auto ex = ioc.get_executor(); - stranded_socket s1(ioc); - stranded_socket s2(ex); - stranded_socket s3(ioc, tcp::v4()); - stranded_socket s4(std::move(s1)); - s2.socket() = tcp::socket(ioc); - BEAST_EXPECT(s1.get_executor() == ex); - BEAST_EXPECT(s2.get_executor() == ex); - BEAST_EXPECT(s3.get_executor() == ex); - BEAST_EXPECT(s4.get_executor() == ex); - - BEAST_EXPECT((! static_cast< - stranded_socket const&>( - s2).socket().is_open())); - } - - { - auto ex = strand{ioc}; - stranded_socket s1(ex); - stranded_socket s2(ex, tcp::v4()); - stranded_socket s3(std::move(s1)); - s2.socket() = tcp::socket(ioc); - BEAST_EXPECT(s1.get_executor() == ex); - BEAST_EXPECT(s2.get_executor() == ex); - BEAST_EXPECT(s3.get_executor() == ex); - - BEAST_EXPECT((! static_cast< - stranded_socket const&>( - s2).socket().is_open())); - } - - { - test_sync_stream>(); - test_async_stream>(); - test_sync_stream>(); - test_async_stream>(); - } - } - - void - testMembers() - { - net::io_context ioc; - - // connect (member) - - auto const cond = - [](error_code, tcp::endpoint) - { - return true; - }; - - { - stranded_socket s(ioc); - error_code ec; - test_acceptor a; - try - { - s.connect(a.ep); - BEAST_PASS(); - } - catch(std::exception const&) - { - BEAST_FAIL(); - } - } - - { - stranded_socket s(ioc); - error_code ec; - test_acceptor a; - s.connect(a.ep, ec); - BEAST_EXPECT(! ec); - } - - // connect - - { - test_acceptor a; - std::array epa; - epa[0] = a.ep; - stranded_socket s(ioc); - error_code ec; - connect(s, epa); - connect(s, epa, ec); - } - - { - test_acceptor a; - std::array epa; - epa[0] = a.ep; - stranded_socket s(ioc); - error_code ec; - connect(s, epa, cond); - connect(s, epa, cond, ec); - } - - { - test_acceptor a; - std::array epa; - epa[0] = a.ep; - stranded_socket s(ioc); - error_code ec; - connect(s, epa.begin(), epa.end()); - connect(s, epa.begin(), epa.end(), ec); - } - - { - test_acceptor a; - std::array epa; - epa[0] = a.ep; - stranded_socket s(ioc); - error_code ec; - connect(s, epa.begin(), epa.end(), cond); - connect(s, epa.begin(), epa.end(), cond, ec); - } - - // async_connect - - { - stranded_socket s(ioc); - test_acceptor a; - error_code ec; - s.async_connect(a. ep, - [](error_code ec) - { - BEAST_EXPECT(! ec); - }); - ioc.run(); - ioc.restart(); - } - - { - std::array epa; - epa[0] = tcp::endpoint( - net::ip::make_address_v4("127.0.0.1"), 0); - stranded_socket s(ioc); - async_connect(s, epa, - [](error_code, tcp::endpoint) - { - }); - } - - { - std::array epa; - epa[0] = tcp::endpoint( - net::ip::make_address_v4("127.0.0.1"), 0); - stranded_socket s(ioc); - async_connect(s, epa, cond, - [](error_code, tcp::endpoint) - { - }); - } - - { - std::array epa; - epa[0] = tcp::endpoint( - net::ip::make_address_v4("127.0.0.1"), 0); - using iter_type = decltype(epa)::const_iterator; - stranded_socket s(ioc); - async_connect(s, epa.begin(), epa.end(), - [](error_code, iter_type) - { - }); - } - - { - std::array epa; - epa[0] = tcp::endpoint( - net::ip::make_address_v4("127.0.0.1"), 0); - using iter_type = decltype(epa)::const_iterator; - stranded_socket s(ioc); - async_connect(s, epa.begin(), epa.end(), cond, - [](error_code, iter_type) - { - }); - } - - // read/write - - { - error_code ec; - stranded_socket s(ioc, tcp::v4()); - - BEAST_EXPECT(s.read_some(net::mutable_buffer{}) == 0); - BEAST_EXPECT(s.read_some(net::mutable_buffer{}, ec) == 0); - BEAST_EXPECTS(! ec, ec.message()); - - BEAST_EXPECT(s.write_some(net::const_buffer{}) == 0); - BEAST_EXPECT(s.write_some(net::const_buffer{}, ec) == 0); - BEAST_EXPECTS(! ec, ec.message()); - - bool invoked; - - invoked = false; - s.async_read_some(net::mutable_buffer{}, - [&](error_code ec, std::size_t) - { - invoked = true; - BEAST_EXPECTS(! ec, ec.message()); - }); - ioc.run(); - ioc.restart(); - BEAST_EXPECT(invoked); - - invoked = false; - s.async_write_some(net::const_buffer{}, - [&](error_code ec, std::size_t) - { - invoked = true; - BEAST_EXPECTS(! ec, ec.message()); - }); - ioc.run(); - ioc.restart(); - BEAST_EXPECT(invoked); - } - - // stranded - - { - error_code ec; - stranded_socket s(strand(ioc), tcp::v4()); - - bool invoked; - - invoked = false; - s.async_read_some(net::mutable_buffer{}, - [&](error_code ec, std::size_t) - { - invoked = true; - BEAST_EXPECTS(! ec, ec.message()); - }); - ioc.run(); - ioc.restart(); - BEAST_EXPECT(invoked); - - invoked = false; - s.async_write_some(net::const_buffer{}, - [&](error_code ec, std::size_t) - { - invoked = true; - BEAST_EXPECTS(! ec, ec.message()); - }); - ioc.run(); - ioc.restart(); - BEAST_EXPECT(invoked); - } - - // test_executor - - { - error_code ec; - stranded_socket> s( - test_executor<>(ioc.get_executor()), tcp::v4()); - - bool invoked; - - invoked = false; - s.async_read_some(net::mutable_buffer{}, - [&](error_code ec, std::size_t) - { - invoked = true; - BEAST_EXPECTS(! ec, ec.message()); - }); - ioc.run(); - ioc.restart(); - BEAST_EXPECT(invoked); - BEAST_EXPECT(s.get_executor()->total > 0); - s.get_executor()->total = 0; - - invoked = false; - s.async_write_some(net::const_buffer{}, - [&](error_code ec, std::size_t) - { - invoked = true; - BEAST_EXPECTS(! ec, ec.message()); - }); - ioc.run(); - ioc.restart(); - BEAST_EXPECT(invoked); - BEAST_EXPECT(s.get_executor()->total > 0); - s.get_executor()->total = 0; - } - - // bind_default_executor::asio_handler_invoke - -#if 0 - // VFALCO This test fails, because it is unclear how - // asio_handler_invoke interacts with the wrapper. - // Need to ask Chris Kohlhoff about this one. - { - int flags = 0; - net::post( - ioc, - detail::bind_default_executor( - strand(ioc), - test_handler{flags})); - ioc.run(); - ioc.restart(); - BEAST_EXPECT(flags == 3); - } -#endif - } - - //-------------------------------------------------------------------------- - - void - testJavadocs() - { - } - - //-------------------------------------------------------------------------- - - void - run() - { - testStream(); - testJavadocs(); - testMembers(); - } -}; - -BEAST_DEFINE_TESTSUITE(beast,core,stranded_socket); - -} // beast -} // boost diff --git a/test/beast/core/timeout_stream.cpp b/test/beast/core/tcp_stream.cpp similarity index 87% rename from test/beast/core/timeout_stream.cpp rename to test/beast/core/tcp_stream.cpp index ac8f616d..422d96a2 100644 --- a/test/beast/core/timeout_stream.cpp +++ b/test/beast/core/tcp_stream.cpp @@ -8,4 +8,4 @@ // // Test that header file is self-contained. -#include +#include