From 6647e9ea564730ddef697b20b0e31d2357e48289 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 8 Jun 2018 12:14:29 -0700 Subject: [PATCH] Add experimental icy_stream Shoutcast stream filter: fix #595, fix #1151 This provides a stream filter which converts the ICY HTTP response handshake at the beginning of a stream to HTTP/1.1. --- CHANGELOG.md | 1 + doc/qbk/quickref.xml | 1 + doc/source.dox | 1 + .../beast/experimental/core/flat_stream.hpp | 1 + .../beast/experimental/core/ssl_stream.hpp | 2 + .../beast/experimental/http/icy_stream.hpp | 345 ++++++++++ .../experimental/http/impl/icy_stream.ipp | 628 ++++++++++++++++++ .../boost/beast/experimental/test/stream.hpp | 2 +- test/beast/experimental/CMakeLists.txt | 1 + test/beast/experimental/Jamfile | 1 + test/beast/experimental/flat_stream.cpp | 2 +- test/beast/experimental/icy_stream.cpp | 127 ++++ 12 files changed, 1110 insertions(+), 2 deletions(-) create mode 100644 include/boost/beast/experimental/http/icy_stream.hpp create mode 100644 include/boost/beast/experimental/http/impl/icy_stream.ipp create mode 100644 test/beast/experimental/icy_stream.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ae17eb7..6fc3f715 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Version 173: * Fix buffers_adapter max_size * Fix buffers_prefix iterator decrement * buffers_adapter improvements +* Add icy_stream Shoutcast stream filter -------------------------------------------------------------------------------- diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index 6559e2f1..760fcf0d 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -303,6 +303,7 @@ flat_stream ssl_stream + http::icy_stream test::fail_count test::stream diff --git a/doc/source.dox b/doc/source.dox index 52ddb673..b12527ac 100644 --- a/doc/source.dox +++ b/doc/source.dox @@ -106,6 +106,7 @@ INPUT = \ $(LIB_DIR)/include/boost/beast/ \ $(LIB_DIR)/include/boost/beast/core \ $(LIB_DIR)/include/boost/beast/experimental/core \ + $(LIB_DIR)/include/boost/beast/experimental/http \ $(LIB_DIR)/include/boost/beast/experimental/test \ $(LIB_DIR)/include/boost/beast/http \ $(LIB_DIR)/include/boost/beast/websocket \ diff --git a/include/boost/beast/experimental/core/flat_stream.hpp b/include/boost/beast/experimental/core/flat_stream.hpp index 329a4ae6..ab22e544 100644 --- a/include/boost/beast/experimental/core/flat_stream.hpp +++ b/include/boost/beast/experimental/core/flat_stream.hpp @@ -10,6 +10,7 @@ #ifndef BOOST_BEAST_CORE_FLAT_STREAM_HPP #define BOOST_BEAST_CORE_FLAT_STREAM_HPP +#include #include #include #include diff --git a/include/boost/beast/experimental/core/ssl_stream.hpp b/include/boost/beast/experimental/core/ssl_stream.hpp index ca05ba02..b61bfbde 100644 --- a/include/boost/beast/experimental/core/ssl_stream.hpp +++ b/include/boost/beast/experimental/core/ssl_stream.hpp @@ -10,6 +10,8 @@ #ifndef BOOST_BEAST_CORE_SSL_STREAM_HPP #define BOOST_BEAST_CORE_SSL_STREAM_HPP +#include + // This include is necessary to work with `ssl::stream` and `boost::beast::websocket::stream` #include diff --git a/include/boost/beast/experimental/http/icy_stream.hpp b/include/boost/beast/experimental/http/icy_stream.hpp new file mode 100644 index 00000000..b597b244 --- /dev/null +++ b/include/boost/beast/experimental/http/icy_stream.hpp @@ -0,0 +1,345 @@ +// +// 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_HTTP_ICY_STREAM_HPP +#define BOOST_BEAST_HTTP_ICY_STREAM_HPP + +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace http { + +/** Stream wrapper to process Shoutcast HTTP responses + + This wrapper replaces the word "ICY" in the first + HTTP response received on the connection, with "HTTP/1.1". + This allows the Beast parser to be used with Shoutcast + servers, which send a non-standard HTTP message as the + response. + + For asynchronous operations, the application must ensure + that they are are all performed within the same implicit + or explicit strand. + + @par Thread Safety + @e Distinct @e objects: Safe.@n + @e Shared @e objects: Unsafe. + The application must also ensure that all asynchronous + operations are performed within the same implicit or explicit strand. + + @par Example + + To use the @ref stream template with an `ip::tcp::socket`, + you would write: + + @code + http::icy_stream is{io_context}; + @endcode + Alternatively, you can write: + @code + ip::tcp::socket sock{io_context}; + http::icy_stream is{sock}; + @endcode + + @tparam NextLayer The type representing the next layer, to which + data will be read and written during operations. For synchronous + operations, the type must support the @b SyncStream concept. + For asynchronous operations, the type must support the + @b AsyncStream concept. + + @note A stream object must not be moved or destroyed while there + are pending asynchronous operations associated with it. + + @par Concepts + @b AsyncStream, + @b SyncStream +*/ +template +class icy_stream +{ + template class read_op; + + NextLayer stream_; + bool detect_ = true; + unsigned char copy_ = 0; + char buf_[8]; + + static + boost::asio::const_buffer + version() + { + return {"HTTP/1.1", 8}; + } + +public: + /// The type of the next layer. + using next_layer_type = + typename std::remove_reference::type; + + /// The type of the lowest layer. + using lowest_layer_type = boost::beast::get_lowest_layer; + + /// The type of the executor associated with the object. + using executor_type = typename next_layer_type::executor_type; + + icy_stream(icy_stream&&) = default; + icy_stream(icy_stream const&) = default; + icy_stream& operator=(icy_stream&&) = default; + icy_stream& operator=(icy_stream const&) = default; + + /** Destructor + + The treatment of pending operations will be the same as that + of the next layer. + */ + ~icy_stream() = default; + + /** Constructor + + Arguments, if any, are forwarded to the next layer's constructor. + */ + template + explicit + icy_stream(Args&&... args); + + //-------------------------------------------------------------------------- + + /** Get the executor associated with the object. + + This function may be used to obtain the executor object that the + stream uses to dispatch handlers for asynchronous operations. + + @return A copy of the executor that stream will use to dispatch handlers. + */ + executor_type + get_executor() noexcept + { + return stream_.get_executor(); + } + + /** Get a reference to the next layer + + This function returns a reference to the next layer + in a stack of stream layers. + + @return A reference to the next layer in the stack of + stream layers. + */ + next_layer_type& + next_layer() + { + return stream_; + } + + /** Get a reference to the next layer + + This function returns a reference to the next layer in a + stack of stream layers. + + @return A reference to the next layer in the stack of + stream layers. + */ + next_layer_type const& + next_layer() const + { + return stream_; + } + + /** Get a reference to the lowest layer + + This function returns a reference to the lowest layer + in a stack of stream layers. + + @return A reference to the lowest layer in the stack of + stream layers. + */ + lowest_layer_type& + lowest_layer() + { + return stream_.lowest_layer(); + } + + /** Get a reference to the lowest layer + + This function returns a reference to the lowest layer + in a stack of stream layers. + + @return A reference to the lowest layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + lowest_layer_type const& + lowest_layer() const + { + return stream_.lowest_layer(); + } + + //-------------------------------------------------------------------------- + + /** Read some data from the stream. + + This function is used to read data from the stream. The function call will + block until one or more bytes of data has been read successfully, or until + an error occurs. + + @param buffers The buffers into which the data will be read. + + @returns The number of bytes read. + + @throws boost::system::system_error Thrown on failure. + + @note The `read_some` operation may not read all of the requested number of + bytes. Consider using the function `boost::asio::read` if you need to ensure + that the requested amount of data is read before the blocking operation + completes. + */ + template + std::size_t + read_some(MutableBufferSequence const& buffers); + + /** Read some data from the stream. + + This function is used to read data from the stream. The function call will + block until one or more bytes of data has been read successfully, or until + an error occurs. + + @param buffers The buffers into which the data will be read. + + @param ec Set to indicate what error occurred, if any. + + @returns The number of bytes read. + + @note The `read_some` operation may not read all of the requested number of + bytes. Consider using the function `boost::asio::read` if you need to ensure + that the requested amount of data is read before the blocking operation + completes. + */ + template + std::size_t + read_some( + MutableBufferSequence const& buffers, + error_code& ec); + + /** Start an asynchronous read. + + This function is used to asynchronously read one or more bytes of data from + the stream. The function call always returns immediately. + + @param buffers The buffers into which the data will be read. Although the + buffers object may be copied as necessary, ownership of the underlying + buffers is retained by the caller, which must guarantee that they remain + valid until the handler is called. + + @param handler The handler to be called when the read operation completes. + Copies will be made of the handler as required. The equivalent function + signature of the handler must be: + @code void handler( + const boost::system::error_code& error, // Result of operation. + std::size_t bytes_transferred // Number of bytes read. + ); @endcode + + @note The `read_some` operation may not read all of the requested number of + bytes. Consider using the function `boost::asio::async_read` if you need + to ensure that the requested amount of data is read before the asynchronous + operation completes. + */ + template< + class MutableBufferSequence, + class ReadHandler> + BOOST_ASIO_INITFN_RESULT_TYPE( + ReadHandler, void(error_code, std::size_t)) + async_read_some( + MutableBufferSequence const& buffers, + ReadHandler&& handler); + + /** Write some data to the stream. + + This function is used to write data on the stream. The function call will + block until one or more bytes of data has been written successfully, or + until an error occurs. + + @param buffers The data to be written. + + @returns The number of bytes written. + + @throws boost::system::system_error Thrown on failure. + + @note The `write_some` operation may not transmit all of the data to the + peer. Consider using the function `boost::asio::write` if you need to + ensure that all data is written before the blocking operation completes. + */ + template + std::size_t + write_some(ConstBufferSequence const& buffers); + + /** Write some data to the stream. + + This function is used to write data on the stream. The function call will + block until one or more bytes of data has been written successfully, or + until an error occurs. + + @param buffers The data to be written. + + @param ec Set to indicate what error occurred, if any. + + @returns The number of bytes written. + + @note The `write_some` operation may not transmit all of the data to the + peer. Consider using the function `boost::asio::write` if you need to + ensure that all data is written before the blocking operation completes. + */ + template + std::size_t + write_some( + ConstBufferSequence const& buffers, + error_code& ec); + + /** Start an asynchronous write. + + This function is used to asynchronously write one or more bytes of data to + the stream. The function call always returns immediately. + + @param buffers The data to be written to the stream. Although the buffers + object may be copied as necessary, ownership of the underlying buffers is + retained by the caller, which must guarantee that they remain valid until + the handler is called. + + @param handler The handler to be called when the write operation completes. + Copies will be made of the handler as required. The equivalent function + signature of the handler must be: + @code void handler( + const boost::system::error_code& error, // Result of operation. + std::size_t bytes_transferred // Number of bytes written. + ); @endcode + + @note The `async_write_some` operation may not transmit all of the data to + the peer. Consider using the function `boost::asio::async_write` if you need + to ensure that all data is written before the asynchronous operation completes. + */ + template< + class ConstBufferSequence, + class WriteHandler> + BOOST_ASIO_INITFN_RESULT_TYPE( + WriteHandler, void(error_code, std::size_t)) + async_write_some( + ConstBufferSequence const& buffers, + WriteHandler&& handler); +}; + +} // http +} // beast +} // boost + +#include + +#endif diff --git a/include/boost/beast/experimental/http/impl/icy_stream.ipp b/include/boost/beast/experimental/http/impl/icy_stream.ipp new file mode 100644 index 00000000..8e32dd0a --- /dev/null +++ b/include/boost/beast/experimental/http/impl/icy_stream.ipp @@ -0,0 +1,628 @@ +// +// Copyright (c) 2018 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_CORE_IMPL_ICY_STREAM_IPP +#define BOOST_BEAST_CORE_IMPL_ICY_STREAM_IPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace http { + +namespace detail { + +template +class dynamic_buffer_ref +{ + DynamicBuffer& b_; + +public: + using const_buffers_type = + typename DynamicBuffer::const_buffers_type; + + using mutable_buffers_type = + typename DynamicBuffer::mutable_buffers_type; + + dynamic_buffer_ref(dynamic_buffer_ref&&) = default; + + explicit + dynamic_buffer_ref(DynamicBuffer& b) + : b_(b) + { + } + + std::size_t + size() const + { + return b_.size(); + } + + std::size_t + max_size() const + { + return b_.max_size(); + } + + std::size_t + capacity() const + { + return b_.capacity(); + } + + const_buffers_type + data() const + { + return b_.data(); + } + + mutable_buffers_type + prepare(std::size_t n) + { + return b_.prepare(n); + } + + void + commit(std::size_t n) + { + b_.commit(n); + } + + void + consume(std::size_t n) + { + b_.consume(n); + } +}; + +template +typename std::enable_if< + boost::asio::is_dynamic_buffer::value, + dynamic_buffer_ref>::type +ref(DynamicBuffer& b) +{ + return dynamic_buffer_ref(b); +} + +template +void +buffer_shift(MutableBuffers const& out, ConstBuffers const& in) +{ + using boost::asio::buffer_size; + auto in_pos = boost::asio::buffer_sequence_end(in); + auto out_pos = boost::asio::buffer_sequence_end(out); + auto const in_begin = boost::asio::buffer_sequence_begin(in); + auto const out_begin = boost::asio::buffer_sequence_begin(out); + BOOST_ASSERT(buffer_size(in) == buffer_size(out)); + if(in_pos == in_begin || out_pos == out_begin) + return; + boost::asio::const_buffer cb{*--in_pos}; + boost::asio::mutable_buffer mb{*--out_pos}; + for(;;) + { + if(mb.size() >= cb.size()) + { + std::memmove( + reinterpret_cast( + mb.data()) + mb.size() - cb.size(), + cb.data(), + cb.size()); + mb = boost::asio::mutable_buffer{ + mb.data(), mb.size() - cb.size()}; + if(in_pos == in_begin) + break; + cb = *--in_pos; + } + else + { + std::memmove( + mb.data(), + reinterpret_cast( + cb.data()) + cb.size() - mb.size(), + mb.size()); + cb = boost::asio::const_buffer{ + cb.data(), cb.size() - mb.size()}; + if(out_pos == out_begin) + break; + mb = *--out_pos; + } + } +} + +template +class match_icy +{ + bool& match_; + +public: + using result_type = std::pair; + explicit + match_icy(bool& b) + : match_(b) + { + } + + result_type + operator()(FwdIt first, FwdIt last) const + { + auto it = first; + if(it == last) + return {first, false}; + if(*it != 'I') + return {last, true}; + if(++it == last) + return {first, false}; + if(*it != 'C') + return {last, true}; + if(++it == last) + return {first, false}; + if(*it != 'Y') + return {last, true}; + match_ = true; + return {last, true}; + }; +}; + +} // detail + +template +template +class icy_stream::read_op + : public boost::asio::coroutine +{ + using alloc_type = typename + boost::asio::associated_allocator_t::template + rebind::other; + + struct data + { + icy_stream& s; + buffers_adapter b; + bool match = false; + + data( + Handler const&, + icy_stream& s_, + MutableBufferSequence const& b_) + : s(s_) + , b(b_) + { + } + }; + + handler_ptr d_; + +public: + read_op(read_op&&) = default; + read_op(read_op const&) = delete; + + template + read_op( + DeducedHandler&& h, + icy_stream& s, + MutableBufferSequence const& b) + : d_(std::forward(h), s, b) + { + } + + using allocator_type = + boost::asio::associated_allocator_t; + + allocator_type + get_allocator() const noexcept + { + return (boost::asio::get_associated_allocator)(d_.handler()); + } + + using executor_type = boost::asio::associated_executor_t< + Handler, decltype(std::declval().get_executor())>; + + executor_type + get_executor() const noexcept + { + return (boost::asio::get_associated_executor)( + d_.handler(), d_->s.get_executor()); + } + + void + operator()( + boost::system::error_code ec, + std::size_t bytes_transferred); + + template + friend + void asio_handler_invoke(Function&& f, read_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke(f, std::addressof(op->d_.handler())); + } +}; + +template +template +void +icy_stream:: +read_op:: +operator()( + error_code ec, + std::size_t bytes_transferred) +{ + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using iterator = boost::asio::buffers_iterator< + typename detail::dynamic_buffer_ref< + buffers_adapter>::const_buffers_type>; + auto& d = *d_; + BOOST_ASIO_CORO_REENTER(*this) + { + if(d.b.max_size() == 0) + { + BOOST_ASIO_CORO_YIELD + boost::asio::post(d.s.get_executor(), + bind_handler(std::move(*this), ec, 0)); + goto upcall; + } + if(! d.s.detect_) + { + if(d.s.copy_ > 0) + { + auto const n = buffer_copy( + d.b.prepare(std::min( + d.s.copy_, d.b.max_size())), + boost::asio::buffer(d.s.buf_)); + d.b.commit(n); + d.s.copy_ = static_cast( + d.s.copy_ - n); + if(d.s.copy_ > 0) + std::memmove( + d.s.buf_, + &d.s.buf_[n], + d.s.copy_); + } + if(d.b.size() < d.b.max_size()) + { + BOOST_ASIO_CORO_YIELD + d.s.next_layer().async_read_some( + d.b.prepare(d.b.max_size() - d.b.size()), + std::move(*this)); + d.b.commit(bytes_transferred); + } + bytes_transferred = d.b.size(); + goto upcall; + } + + d.s.detect_ = false; + if(d.b.max_size() < 8) + { + BOOST_ASIO_CORO_YIELD + boost::asio::async_read( + d.s.next_layer(), + boost::asio::buffer(d.s.buf_, 3), + std::move(*this)); + if(ec) + goto upcall; + auto n = bytes_transferred; + BOOST_ASSERT(n == 3); + if( + d.s.buf_[0] != 'I' || + d.s.buf_[1] != 'C' || + d.s.buf_[2] != 'Y') + { + buffer_copy( + d.b.value(), + boost::asio::buffer(d.s.buf_, n)); + if(d.b.max_size() < 3) + { + d.s.copy_ = static_cast( + 3 - d.b.max_size()); + std::memmove( + d.s.buf_, + &d.s.buf_[d.b.max_size()], + d.s.copy_); + + } + bytes_transferred = (std::min)( + n, d.b.max_size()); + goto upcall; + } + d.s.copy_ = static_cast( + buffer_copy( + boost::asio::buffer(d.s.buf_), + icy_stream::version() + d.b.max_size())); + bytes_transferred = buffer_copy( + d.b.value(), + icy_stream::version()); + goto upcall; + } + + BOOST_ASIO_CORO_YIELD + boost::asio::async_read_until( + d.s.next_layer(), + detail::ref(d.b), + detail::match_icy(d.match), + std::move(*this)); + if(ec) + goto upcall; + { + auto n = bytes_transferred; + BOOST_ASSERT(n == d.b.size()); + if(! d.match) + goto upcall; + if(d.b.size() + 5 > d.b.max_size()) + { + d.s.copy_ = static_cast( + n + 5 - d.b.max_size()); + std::copy( + boost::asio::buffers_begin(d.b.value()) + n - d.s.copy_, + boost::asio::buffers_begin(d.b.value()) + n, + d.s.buf_); + n = d.b.max_size() - 5; + } + { + buffers_suffix> dest( + boost::in_place_init, d.b.value()); + dest.consume(5); + detail::buffer_shift( + buffers_prefix(n, dest), + buffers_prefix(n, d.b.value())); + buffer_copy(d.b.value(), icy_stream::version()); + n += 5; + bytes_transferred = n; + } + } + upcall: + d_.invoke(ec, bytes_transferred); + } +} + +//------------------------------------------------------------------------------ + +template +template +icy_stream:: +icy_stream(Args&&... args) + : stream_(std::forward(args)...) +{ +} + +template +template +std::size_t +icy_stream:: +read_some(MutableBufferSequence const& buffers) +{ + static_assert(boost::beast::is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(boost::asio::is_mutable_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + error_code ec; + auto n = read_some(buffers, ec); + if(ec) + BOOST_THROW_EXCEPTION(boost::system::system_error{ec}); + return n; +} + +template +template +std::size_t +icy_stream:: +read_some(MutableBufferSequence const& buffers, error_code& ec) +{ + static_assert(boost::beast::is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(boost::asio::is_mutable_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using iterator = boost::asio::buffers_iterator< + typename detail::dynamic_buffer_ref< + buffers_adapter>::const_buffers_type>; + buffers_adapter b(buffers); + if(b.max_size() == 0) + { + ec.assign(0, ec.category()); + return 0; + } + if(! detect_) + { + if(copy_ > 0) + { + auto const n = buffer_copy( + b.prepare(std::min( + copy_, b.max_size())), + boost::asio::buffer(buf_)); + b.commit(n); + copy_ = static_cast( + copy_ - n); + if(copy_ > 0) + std::memmove( + buf_, + &buf_[n], + copy_); + } + if(b.size() < b.max_size()) + b.commit(stream_.read_some( + b.prepare(b.max_size() - b.size()), ec)); + return b.size(); + } + + detect_ = false; + if(b.max_size() < 8) + { + auto n = boost::asio::read( + stream_, + boost::asio::buffer(buf_, 3), + ec); + if(ec) + return 0; + BOOST_ASSERT(n == 3); + if( + buf_[0] != 'I' || + buf_[1] != 'C' || + buf_[2] != 'Y') + { + buffer_copy( + buffers, + boost::asio::buffer(buf_, n)); + if(b.max_size() < 3) + { + copy_ = static_cast( + 3 - b.max_size()); + std::memmove( + buf_, + &buf_[b.max_size()], + copy_); + + } + return (std::min)(n, b.max_size()); + } + copy_ = static_cast( + buffer_copy( + boost::asio::buffer(buf_), + version() + b.max_size())); + return buffer_copy( + buffers, + version()); + } + + bool match = false; + auto n = boost::asio::read_until( + stream_, + detail::ref(b), + detail::match_icy(match), + ec); + if(ec) + return n; + BOOST_ASSERT(n == b.size()); + if(! match) + return n; + if(b.size() + 5 > b.max_size()) + { + copy_ = static_cast( + n + 5 - b.max_size()); + std::copy( + boost::asio::buffers_begin(buffers) + n - copy_, + boost::asio::buffers_begin(buffers) + n, + buf_); + n = b.max_size() - 5; + } + buffers_suffix> dest( + boost::in_place_init, buffers); + dest.consume(5); + detail::buffer_shift( + buffers_prefix(n, dest), + buffers_prefix(n, buffers)); + buffer_copy(buffers, version()); + n += 5; + return n; +} + +template +template< + class MutableBufferSequence, + class ReadHandler> +BOOST_ASIO_INITFN_RESULT_TYPE( + ReadHandler, void(error_code, std::size_t)) +icy_stream:: +async_read_some( + MutableBufferSequence const& buffers, + ReadHandler&& handler) +{ + static_assert(boost::beast::is_async_read_stream::value, + "AsyncReadStream requirements not met"); + static_assert(boost::asio::is_mutable_buffer_sequence< + MutableBufferSequence >::value, + "MutableBufferSequence requirements not met"); + BOOST_BEAST_HANDLER_INIT( + ReadHandler, void(error_code, std::size_t)); + read_op< + MutableBufferSequence, + BOOST_ASIO_HANDLER_TYPE( + ReadHandler, void(error_code, std::size_t))>{ + std::move(init.completion_handler), *this, buffers}( + {}, 0); + return init.result.get(); +} + +template +template +std::size_t +icy_stream:: +write_some(MutableBufferSequence const& buffers) +{ + static_assert(boost::beast::is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(boost::asio::is_const_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + return stream_.write_some(buffers); +} + +template +template +std::size_t +icy_stream:: +write_some(MutableBufferSequence const& buffers, error_code& ec) +{ + static_assert(boost::beast::is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(boost::asio::is_const_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + return stream_.write_some(buffers, ec); +} + +template +template< + class MutableBufferSequence, + class WriteHandler> +BOOST_ASIO_INITFN_RESULT_TYPE( + WriteHandler, void(error_code, std::size_t)) +icy_stream:: +async_write_some( + MutableBufferSequence const& buffers, + WriteHandler&& handler) +{ + static_assert(boost::beast::is_async_write_stream::value, + "AsyncWriteStream requirements not met"); + static_assert(boost::asio::is_const_buffer_sequence< + MutableBufferSequence>::value, + "MutableBufferSequence requirements not met"); + return stream_.async_write_some(buffers, std::forward(handler)); +} + +} // http +} // beast +} // boost + +#endif diff --git a/include/boost/beast/experimental/test/stream.hpp b/include/boost/beast/experimental/test/stream.hpp index d583e796..17f8c55a 100644 --- a/include/boost/beast/experimental/test/stream.hpp +++ b/include/boost/beast/experimental/test/stream.hpp @@ -73,7 +73,7 @@ namespace test { This allows predefined test vectors to be set up for testing read algorithms. - @li The stream may be constructed with a @ref fail count. The + @li The stream may be constructed with a fail count. The stream will eventually fail with a predefined error after a certain number of operations, where the number of operations is controlled by the test. When a test loops over a range of diff --git a/test/beast/experimental/CMakeLists.txt b/test/beast/experimental/CMakeLists.txt index 795df861..012907bc 100644 --- a/test/beast/experimental/CMakeLists.txt +++ b/test/beast/experimental/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable (tests-beast-experimental Jamfile error.cpp flat_stream.cpp + icy_stream.cpp ssl_stream.cpp stream.cpp ) diff --git a/test/beast/experimental/Jamfile b/test/beast/experimental/Jamfile index 040b0a1c..b96b8ebc 100644 --- a/test/beast/experimental/Jamfile +++ b/test/beast/experimental/Jamfile @@ -10,6 +10,7 @@ local SOURCES = error.cpp flat_stream.cpp + icy_stream.cpp ssl_stream.cpp stream.cpp ; diff --git a/test/beast/experimental/flat_stream.cpp b/test/beast/experimental/flat_stream.cpp index 977c7d54..ba02f835 100644 --- a/test/beast/experimental/flat_stream.cpp +++ b/test/beast/experimental/flat_stream.cpp @@ -103,7 +103,7 @@ public: } }; -BEAST_DEFINE_TESTSUITE(beast,example,flat_stream); +BEAST_DEFINE_TESTSUITE(beast,core,flat_stream); } // beast } // boost diff --git a/test/beast/experimental/icy_stream.cpp b/test/beast/experimental/icy_stream.cpp new file mode 100644 index 00000000..8205ee13 --- /dev/null +++ b/test/beast/experimental/icy_stream.cpp @@ -0,0 +1,127 @@ +// +// 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 +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace http { + +class icy_stream_test + : public unit_test::suite +{ +public: + void + doMatrix(string_view in, string_view out) + { + using boost::asio::mutable_buffer; + boost::asio::io_context ioc; + auto len = out.size() + 8; + std::unique_ptr p(new char[len]); + for(std::size_t i = 1; i < len; ++i) + { + std::array mbs{ + mutable_buffer(p.get(), i), + mutable_buffer(p.get() + i, len - i)}; + for(std::size_t j = 1; j < in.size(); ++j) + { + for(std::size_t k = 1; k < len; ++k) + { + // sync + { + buffers_adapter ba(mbs); + std::memset(p.get(), 0, len); + + icy_stream is{ioc}; + is.next_layer().read_size(j); + is.next_layer().append(in); + connect(is.next_layer()).close(); + + error_code ec; + for(;;) + { + ba.commit(is.read_some( + ba.prepare(read_size(ba, k)), ec)); + if(ec) + break; + } + if(! BEAST_EXPECTS( + ec == boost::asio::error::eof, ec.message())) + continue; + auto const s = buffers_to_string(ba.data()); + BEAST_EXPECTS(s == out, s); + } + // async + { + buffers_adapter ba(mbs); + std::memset(p.get(), 0, len); + + icy_stream is{ioc}; + is.next_layer().read_size(j); + is.next_layer().append(in); + connect(is.next_layer()).close(); + + error_code ec; + for(;;) + { + is.async_read_some( + ba.prepare(read_size(ba, k)), + [&ec, &ba](error_code ec_, std::size_t n) + { + ec = ec_; + ba.commit(n); + }); + ioc.run(); + ioc.reset(); + if(ec) + break; + } + if(! BEAST_EXPECTS( + ec == boost::asio::error::eof, ec.message())) + continue; + auto const s = buffers_to_string(ba.data()); + if(! BEAST_EXPECTS(s == out, s)) + { + s.size(); + } + } + } + } + } + } + + void + testStream() + { + doMatrix("HTTP/1.1 200 OK\r\n", "HTTP/1.1 200 OK\r\n"); + doMatrix("ICY 200 OK\r\n", "HTTP/1.1 200 OK\r\n"); + } + + void + run() override + { + testStream(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,http,icy_stream); + +} // http +} // beast +} // boost