diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ab07baf..0eae1407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Version 170: * Remove deprecated serializer::reader_impl() * Remove deprecated Body reader and writer ctor signatures * Add is_mutable_body_writer metafunction +* Add const and non-const overloads for message based http writes -------------------------------------------------------------------------------- diff --git a/include/boost/beast/http/impl/write.ipp b/include/boost/beast/http/impl/write.ipp index 40cafeff..36a1ed49 100644 --- a/include/boost/beast/http/impl/write.ipp +++ b/include/boost/beast/http/impl/write.ipp @@ -333,6 +333,14 @@ class write_msg_op , sr(m_) { } + + data(Handler const&, Stream& s_, message< + isRequest, Body, Fields> const& m_) + : s(s_) + , wg(s.get_executor()) + , sr(m_) + { + } }; handler_ptr d_; @@ -751,7 +759,33 @@ async_write( template< class SyncWriteStream, bool isRequest, class Body, class Fields> -std::size_t +typename std::enable_if< + is_mutable_body_writer::value, + std::size_t>::type +write( + SyncWriteStream& stream, + message& msg) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_writer::value, + "BodyWriter requirements not met"); + error_code ec; + auto const bytes_transferred = + write(stream, msg, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); + return bytes_transferred; +} + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +typename std::enable_if< + ! is_mutable_body_writer::value, + std::size_t>::type write( SyncWriteStream& stream, message const& msg) @@ -773,7 +807,30 @@ write( template< class SyncWriteStream, bool isRequest, class Body, class Fields> -std::size_t +typename std::enable_if< + is_mutable_body_writer::value, + std::size_t>::type +write( + SyncWriteStream& stream, + message& msg, + error_code& ec) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_writer::value, + "BodyWriter requirements not met"); + serializer sr{msg}; + return write(stream, sr, ec); +} + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +typename std::enable_if< + ! is_mutable_body_writer::value, + std::size_t>::type write( SyncWriteStream& stream, message const& msg, @@ -793,8 +850,10 @@ template< class AsyncWriteStream, bool isRequest, class Body, class Fields, class WriteHandler> -BOOST_ASIO_INITFN_RESULT_TYPE( - WriteHandler, void(error_code, std::size_t)) +typename std::enable_if< + is_mutable_body_writer::value, + BOOST_ASIO_INITFN_RESULT_TYPE( + WriteHandler, void(error_code, std::size_t))>::type async_write( AsyncWriteStream& stream, message& msg, @@ -818,6 +877,37 @@ async_write( return init.result.get(); } +template< + class AsyncWriteStream, + bool isRequest, class Body, class Fields, + class WriteHandler> +typename std::enable_if< + ! is_mutable_body_writer::value, + BOOST_ASIO_INITFN_RESULT_TYPE( + WriteHandler, void(error_code, std::size_t))>::type +async_write( + AsyncWriteStream& stream, + message const& msg, + WriteHandler&& handler) +{ + static_assert( + is_async_write_stream::value, + "AsyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_writer::value, + "BodyWriter requirements not met"); + BOOST_BEAST_HANDLER_INIT( + WriteHandler, void(error_code, std::size_t)); + detail::write_msg_op< + AsyncWriteStream, + BOOST_ASIO_HANDLER_TYPE(WriteHandler, + void(error_code, std::size_t)), + isRequest, Body, Fields>{ + std::move(init.completion_handler), stream, msg}(); + return init.result.get(); +} + //------------------------------------------------------------------------------ namespace detail { diff --git a/include/boost/beast/http/write.hpp b/include/boost/beast/http/write.hpp index 3b916b1d..422f9340 100644 --- a/include/boost/beast/http/write.hpp +++ b/include/boost/beast/http/write.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -427,6 +428,9 @@ async_write( `write_some` function. The algorithm will use a temporary @ref serializer with an empty chunk decorator to produce buffers. + @note This function only participates in overload resolution + if @ref is_mutable_body_writer for @b Body returns `true`. + @param stream The stream to which the data is to be written. The type must support the @b SyncWriteStream concept. @@ -441,7 +445,54 @@ async_write( template< class SyncWriteStream, bool isRequest, class Body, class Fields> +#if BOOST_BEAST_DOXYGEN std::size_t +#else +typename std::enable_if< + is_mutable_body_writer::value, + std::size_t>::type +#endif +write( + SyncWriteStream& stream, + message& msg); + +/** Write a complete message to a stream. + + This function is used to write a complete message to a stream using + HTTP/1. The call will block until one of the following conditions is true: + + @li The entire message is written. + + @li An error occurs. + + This operation is implemented in terms of one or more calls to the stream's + `write_some` function. The algorithm will use a temporary @ref serializer + with an empty chunk decorator to produce buffers. + + @note This function only participates in overload resolution + if @ref is_mutable_body_writer for @b Body returns `false`. + + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. + + @param msg The message to write. + + @return The number of bytes written to the stream. + + @throws system_error Thrown on failure. + + @see @ref message +*/ +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +#if BOOST_BEAST_DOXYGEN +std::size_t +#else +typename std::enable_if< + ! is_mutable_body_writer::value, + std::size_t>::type +#endif write( SyncWriteStream& stream, message const& msg); @@ -459,6 +510,9 @@ write( `write_some` function. The algorithm will use a temporary @ref serializer with an empty chunk decorator to produce buffers. + @note This function only participates in overload resolution + if @ref is_mutable_body_writer for @b Body returns `true`. + @param stream The stream to which the data is to be written. The type must support the @b SyncWriteStream concept. @@ -473,7 +527,55 @@ write( template< class SyncWriteStream, bool isRequest, class Body, class Fields> +#if BOOST_BEAST_DOXYGEN std::size_t +#else +typename std::enable_if< + is_mutable_body_writer::value, + std::size_t>::type +#endif +write( + SyncWriteStream& stream, + message& msg, + error_code& ec); + +/** Write a complete message to a stream. + + This function is used to write a complete message to a stream using + HTTP/1. The call will block until one of the following conditions is true: + + @li The entire message is written. + + @li An error occurs. + + This operation is implemented in terms of one or more calls to the stream's + `write_some` function. The algorithm will use a temporary @ref serializer + with an empty chunk decorator to produce buffers. + + @note This function only participates in overload resolution + if @ref is_mutable_body_writer for @b Body returns `false`. + + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. + + @param msg The message to write. + + @param ec Set to the error, if any occurred. + + @return The number of bytes written to the stream. + + @see @ref message +*/ +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields> +#if BOOST_BEAST_DOXYGEN +std::size_t +#else +typename std::enable_if< + ! is_mutable_body_writer::value, + std::size_t>::type +#endif write( SyncWriteStream& stream, message const& msg, @@ -495,6 +597,9 @@ write( until this operation completes. The algorithm will use a temporary @ref serializer with an empty chunk decorator to produce buffers. + @note This function only participates in overload resolution + if @ref is_mutable_body_writer for @b Body returns `true`. + @param stream The stream to which the data is to be written. The type must support the @b AsyncWriteStream concept. @@ -520,13 +625,78 @@ template< class AsyncWriteStream, bool isRequest, class Body, class Fields, class WriteHandler> +#if BOOST_BEAST_DOXYGEN BOOST_ASIO_INITFN_RESULT_TYPE( WriteHandler, void(error_code, std::size_t)) +#else +typename std::enable_if< + is_mutable_body_writer::value, + BOOST_ASIO_INITFN_RESULT_TYPE( + WriteHandler, void(error_code, std::size_t))>::type +#endif async_write( AsyncWriteStream& stream, message& msg, WriteHandler&& handler); +/** Write a complete message to a stream asynchronously. + + This function is used to write a complete message to a stream asynchronously + using HTTP/1. The function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is true: + + @li The entire message is written. + + @li An error occurs. + + This operation is implemented in terms of zero or more calls to the stream's + `async_write_some` function, and is known as a composed operation. + The program must ensure that the stream performs no other writes + until this operation completes. The algorithm will use a temporary + @ref serializer with an empty chunk decorator to produce buffers. + + @note This function only participates in overload resolution + if @ref is_mutable_body_writer for @b Body returns `false`. + + @param stream The stream to which the data is to be written. + The type must support the @b AsyncWriteStream concept. + + @param msg The message to write. + The object must remain valid at least until the + handler is called; ownership is not transferred. + + @param handler Invoked when the operation completes. + The handler may be moved or copied as needed. + The equivalent function signature of the handler must be: + @code void handler( + error_code const& error, // result of operation + std::size_t bytes_transferred // the number of bytes written to the stream + ); @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 `boost::asio::io_context::post`. + + @see @ref message +*/ +template< + class AsyncWriteStream, + bool isRequest, class Body, class Fields, + class WriteHandler> +#if BOOST_BEAST_DOXYGEN +BOOST_ASIO_INITFN_RESULT_TYPE( + WriteHandler, void(error_code, std::size_t)) +#else +typename std::enable_if< + ! is_mutable_body_writer::value, + BOOST_ASIO_INITFN_RESULT_TYPE( + WriteHandler, void(error_code, std::size_t))>::type +#endif +async_write( + AsyncWriteStream& stream, + message const& msg, + WriteHandler&& handler); + //------------------------------------------------------------------------------ /** Serialize an HTTP/1 header to a `std::ostream`. diff --git a/test/beast/http/write.cpp b/test/beast/http/write.cpp index 4e2c49b9..a043be74 100644 --- a/test/beast/http/write.cpp +++ b/test/beast/http/write.cpp @@ -50,8 +50,9 @@ public: boost::asio::const_buffer; template - explicit - writer(header const&, value_type const& b) + writer( + header const&, + value_type const& b) : body_(b) { } @@ -94,8 +95,9 @@ public: boost::asio::const_buffer; template - explicit - writer(header const&, value_type const& b) + writer( + header const&, + value_type const& b) : body_(b) { } @@ -890,6 +892,107 @@ public: } } + struct const_body_writer + { + struct value_type{}; + + struct writer + { + using const_buffers_type = + boost::asio::const_buffer; + + template + writer( + header const&, + value_type const&) + { + } + + void + init(error_code& ec) + { + ec.assign(0, ec.category()); + } + + boost::optional> + get(error_code& ec) + { + ec.assign(0, ec.category()); + return {{const_buffers_type{"", 0}, false}}; + } + }; + }; + + struct mutable_body_writer + { + struct value_type{}; + + struct writer + { + using const_buffers_type = + boost::asio::const_buffer; + + template + writer( + header&, + value_type&) + { + } + + void + init(error_code& ec) + { + ec.assign(0, ec.category()); + } + + boost::optional> + get(error_code& ec) + { + ec.assign(0, ec.category()); + return {{const_buffers_type{"", 0}, false}}; + } + }; + }; + + void + testBodyWriters() + { + { + test::stream s{ioc_}; + message m; + try + { + write(s, m); + } + catch(std::exception const&) + { + } + } + { + error_code ec; + test::stream s{ioc_}; + message m; + write(s, m, ec); + } + { + test::stream s{ioc_}; + message m; + try + { + write(s, m); + } + catch(std::exception const&) + { + } + } + { + error_code ec; + test::stream s{ioc_}; + message m; + write(s, m, ec); + } + } + void run() override { @@ -912,6 +1015,7 @@ public: testWriteStream>(yield); }); testAsioHandlerInvoke(); + testBodyWriters(); } };