Add const and non-const overloads for message based http writes:

fix #1113

This solves a problem where the message-oriented synchronous
HTTP write algorithms do not support messages whose body
writer requires a non-const reference to the message in
order to construct.

In addition, the message-oriented async_write algorithm is
modified to support messages passed by const reference when
possible, based on the body writer attributes.
This commit is contained in:
Vinnie Falco
2018-05-04 19:23:58 -07:00
parent 07aead170a
commit 24bbda7b0d
4 changed files with 373 additions and 8 deletions

View File

@ -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
--------------------------------------------------------------------------------

View File

@ -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<data, Handler> 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<Body>::value,
std::size_t>::type
write(
SyncWriteStream& stream,
message<isRequest, Body, Fields>& msg)
{
static_assert(is_sync_write_stream<SyncWriteStream>::value,
"SyncWriteStream requirements not met");
static_assert(is_body<Body>::value,
"Body requirements not met");
static_assert(is_body_writer<Body>::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<Body>::value,
std::size_t>::type
write(
SyncWriteStream& stream,
message<isRequest, Body, Fields> 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<Body>::value,
std::size_t>::type
write(
SyncWriteStream& stream,
message<isRequest, Body, Fields>& msg,
error_code& ec)
{
static_assert(is_sync_write_stream<SyncWriteStream>::value,
"SyncWriteStream requirements not met");
static_assert(is_body<Body>::value,
"Body requirements not met");
static_assert(is_body_writer<Body>::value,
"BodyWriter requirements not met");
serializer<isRequest, Body, Fields> sr{msg};
return write(stream, sr, ec);
}
template<
class SyncWriteStream,
bool isRequest, class Body, class Fields>
typename std::enable_if<
! is_mutable_body_writer<Body>::value,
std::size_t>::type
write(
SyncWriteStream& stream,
message<isRequest, Body, Fields> 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<Body>::value,
BOOST_ASIO_INITFN_RESULT_TYPE(
WriteHandler, void(error_code, std::size_t))>::type
async_write(
AsyncWriteStream& stream,
message<isRequest, Body, Fields>& 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<Body>::value,
BOOST_ASIO_INITFN_RESULT_TYPE(
WriteHandler, void(error_code, std::size_t))>::type
async_write(
AsyncWriteStream& stream,
message<isRequest, Body, Fields> const& msg,
WriteHandler&& handler)
{
static_assert(
is_async_write_stream<AsyncWriteStream>::value,
"AsyncWriteStream requirements not met");
static_assert(is_body<Body>::value,
"Body requirements not met");
static_assert(is_body_writer<Body>::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 {

View File

@ -16,6 +16,7 @@
#include <boost/beast/core/multi_buffer.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/serializer.hpp>
#include <boost/beast/http/type_traits.hpp>
#include <boost/beast/http/detail/chunk_encode.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/core/string.hpp>
@ -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<Body>::value,
std::size_t>::type
#endif
write(
SyncWriteStream& stream,
message<isRequest, Body, Fields>& 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<Body>::value,
std::size_t>::type
#endif
write(
SyncWriteStream& stream,
message<isRequest, Body, Fields> 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<Body>::value,
std::size_t>::type
#endif
write(
SyncWriteStream& stream,
message<isRequest, Body, Fields>& 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<Body>::value,
std::size_t>::type
#endif
write(
SyncWriteStream& stream,
message<isRequest, Body, Fields> 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<Body>::value,
BOOST_ASIO_INITFN_RESULT_TYPE(
WriteHandler, void(error_code, std::size_t))>::type
#endif
async_write(
AsyncWriteStream& stream,
message<isRequest, Body, Fields>& 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 <em>composed operation</em>.
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<Body>::value,
BOOST_ASIO_INITFN_RESULT_TYPE(
WriteHandler, void(error_code, std::size_t))>::type
#endif
async_write(
AsyncWriteStream& stream,
message<isRequest, Body, Fields> const& msg,
WriteHandler&& handler);
//------------------------------------------------------------------------------
/** Serialize an HTTP/1 header to a `std::ostream`.

View File

@ -50,8 +50,9 @@ public:
boost::asio::const_buffer;
template<bool isRequest, class Fields>
explicit
writer(header<isRequest, Fields> const&, value_type const& b)
writer(
header<isRequest, Fields> const&,
value_type const& b)
: body_(b)
{
}
@ -94,8 +95,9 @@ public:
boost::asio::const_buffer;
template<bool isRequest, class Fields>
explicit
writer(header<isRequest, Fields> const&, value_type const& b)
writer(
header<isRequest, Fields> 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<bool isRequest, class Fields>
writer(
header<isRequest, Fields> const&,
value_type const&)
{
}
void
init(error_code& ec)
{
ec.assign(0, ec.category());
}
boost::optional<std::pair<const_buffers_type, bool>>
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<bool isRequest, class Fields>
writer(
header<isRequest, Fields>&,
value_type&)
{
}
void
init(error_code& ec)
{
ec.assign(0, ec.category());
}
boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec)
{
ec.assign(0, ec.category());
return {{const_buffers_type{"", 0}, false}};
}
};
};
void
testBodyWriters()
{
{
test::stream s{ioc_};
message<true, const_body_writer> m;
try
{
write(s, m);
}
catch(std::exception const&)
{
}
}
{
error_code ec;
test::stream s{ioc_};
message<true, const_body_writer> m;
write(s, m, ec);
}
{
test::stream s{ioc_};
message<true, mutable_body_writer> m;
try
{
write(s, m);
}
catch(std::exception const&)
{
}
}
{
error_code ec;
test::stream s{ioc_};
message<true, mutable_body_writer> m;
write(s, m, ec);
}
}
void
run() override
{
@ -912,6 +1015,7 @@ public:
testWriteStream<test_body< true, true>>(yield);
});
testAsioHandlerInvoke();
testBodyWriters();
}
};