mirror of
https://github.com/boostorg/beast.git
synced 2025-07-31 21:34:46 +02:00
Add http::message_generator
This commit is contained in:
@@ -16,6 +16,11 @@ The generator will be asked to produce buffers. The consuming code will signal
|
|||||||
how much of the data has been consumed, and repeatedly query for buffers until
|
how much of the data has been consumed, and repeatedly query for buffers until
|
||||||
no more data is available, or the generator indicates an error condition.
|
no more data is available, or the generator indicates an error condition.
|
||||||
|
|
||||||
|
In this way, serializers can be adapted as [*BuffersGenerator], for example
|
||||||
|
[link beast.ref.boost__beast__http__message_generator
|
||||||
|
`http::message_generator`] which provides a type-erased interface for a variety
|
||||||
|
of concrete http message types.
|
||||||
|
|
||||||
Overloads of [link beast.ref.boost__beast__write `write`] and [link
|
Overloads of [link beast.ref.boost__beast__write `write`] and [link
|
||||||
beast.ref.boost__beast__async_write `async_write`] operations are provided as
|
beast.ref.boost__beast__async_write `async_write`] operations are provided as
|
||||||
free functions. These operations will consume the output of a
|
free functions. These operations will consume the output of a
|
||||||
@@ -33,6 +38,7 @@ In this table:
|
|||||||
|
|
||||||
* `G` denotes a type meeting the requirements of [*BuffersGenerator].
|
* `G` denotes a type meeting the requirements of [*BuffersGenerator].
|
||||||
* `g` denotes a value of type `G`.
|
* `g` denotes a value of type `G`.
|
||||||
|
* `c` denotes a possibly-const value of type `G`.
|
||||||
* `n` is a value of type `std::size_t`.
|
* `n` is a value of type `std::size_t`.
|
||||||
* `ec` is a value of type [link beast.ref.boost__beast__error_code `error_code&`].
|
* `ec` is a value of type [link beast.ref.boost__beast__error_code `error_code&`].
|
||||||
|
|
||||||
@@ -46,7 +52,7 @@ In this table:
|
|||||||
This is the type of buffer returned by `g.prepare(ec)`.
|
This is the type of buffer returned by `g.prepare(ec)`.
|
||||||
]
|
]
|
||||||
][
|
][
|
||||||
[`g.is_done()`]
|
[`c.is_done()`]
|
||||||
[`bool`]
|
[`bool`]
|
||||||
[
|
[
|
||||||
Called to ask the generator for its completion status.
|
Called to ask the generator for its completion status.
|
||||||
@@ -56,8 +62,6 @@ In this table:
|
|||||||
|
|
||||||
[*Note:] The result of invoking `prepare` on `g` once it has completed
|
[*Note:] The result of invoking `prepare` on `g` once it has completed
|
||||||
is unspecified.
|
is unspecified.
|
||||||
|
|
||||||
This member function does not alter the state of the generator.
|
|
||||||
]
|
]
|
||||||
][
|
][
|
||||||
[`g.prepare(ec)`]
|
[`g.prepare(ec)`]
|
||||||
@@ -106,7 +110,7 @@ In this table:
|
|||||||
]
|
]
|
||||||
][
|
][
|
||||||
[`is_buffers_generator<G>`]
|
[`is_buffers_generator<G>`]
|
||||||
[`std::true_type`]
|
[`std::bool_constant`]
|
||||||
[
|
[
|
||||||
An alias for `std::true_type` for `G`, otherwise an alias
|
An alias for `std::true_type` for `G`, otherwise an alias
|
||||||
for `std::false_type`.
|
for `std::false_type`.
|
||||||
@@ -121,7 +125,7 @@ In this table:
|
|||||||
{
|
{
|
||||||
using const_buffers_type = net::const_buffer;
|
using const_buffers_type = net::const_buffer;
|
||||||
|
|
||||||
bool is_done();
|
bool is_done() const;
|
||||||
const_buffers_type prepare( error_code& ec );
|
const_buffers_type prepare( error_code& ec );
|
||||||
void consume( std::size_t n );
|
void consume( std::size_t n );
|
||||||
};
|
};
|
||||||
@@ -132,6 +136,8 @@ In this table:
|
|||||||
|
|
||||||
[heading Models]
|
[heading Models]
|
||||||
|
|
||||||
|
* [link beast.ref.boost__beast__http__message_generator `http::message_generator`]
|
||||||
|
|
||||||
[heading Algorithms]
|
[heading Algorithms]
|
||||||
|
|
||||||
* [link beast.ref.boost__beast__async_write `async_write`]
|
* [link beast.ref.boost__beast__async_write `async_write`]
|
||||||
|
@@ -205,6 +205,7 @@
|
|||||||
<member><link linkend="beast.ref.boost__beast__http__file_body">file_body</link></member>
|
<member><link linkend="beast.ref.boost__beast__http__file_body">file_body</link></member>
|
||||||
<member><link linkend="beast.ref.boost__beast__http__header">header</link></member>
|
<member><link linkend="beast.ref.boost__beast__http__header">header</link></member>
|
||||||
<member><link linkend="beast.ref.boost__beast__http__message">message</link></member>
|
<member><link linkend="beast.ref.boost__beast__http__message">message</link></member>
|
||||||
|
<member><link linkend="beast.ref.boost__beast__http__message_generator">message_generator</link></member>
|
||||||
<member><link linkend="beast.ref.boost__beast__http__parser">parser</link></member>
|
<member><link linkend="beast.ref.boost__beast__http__parser">parser</link></member>
|
||||||
<member><link linkend="beast.ref.boost__beast__http__request">request</link></member>
|
<member><link linkend="beast.ref.boost__beast__http__request">request</link></member>
|
||||||
<member><link linkend="beast.ref.boost__beast__http__request_header">request_header</link></member>
|
<member><link linkend="beast.ref.boost__beast__http__request_header">request_header</link></member>
|
||||||
|
@@ -11,11 +11,9 @@
|
|||||||
#define BOOST_BEAST_CORE_BUFFERS_GENERATOR_HPP
|
#define BOOST_BEAST_CORE_BUFFERS_GENERATOR_HPP
|
||||||
|
|
||||||
#include <boost/beast/core/detail/config.hpp>
|
#include <boost/beast/core/detail/config.hpp>
|
||||||
|
|
||||||
#include <boost/beast/core/detail/type_traits.hpp>
|
#include <boost/beast/core/detail/type_traits.hpp>
|
||||||
#include <boost/beast/core/error.hpp>
|
#include <boost/beast/core/error.hpp>
|
||||||
#include <boost/asio/async_result.hpp>
|
#include <boost/asio/async_result.hpp>
|
||||||
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
namespace boost {
|
namespace boost {
|
||||||
@@ -63,7 +61,7 @@ struct is_buffers_generator<
|
|||||||
by a caller-provided BuffersGenerator to a stream. The call
|
by a caller-provided BuffersGenerator to a stream. The call
|
||||||
will block until one of the following conditions is true:
|
will block until one of the following conditions is true:
|
||||||
|
|
||||||
@li The generator returns an empty buffers sequence.
|
@li A call to the generator's `is_done` returns `false`.
|
||||||
|
|
||||||
@li An error occurs.
|
@li An error occurs.
|
||||||
|
|
||||||
@@ -85,8 +83,7 @@ template<
|
|||||||
class SyncWriteStream,
|
class SyncWriteStream,
|
||||||
class BuffersGenerator
|
class BuffersGenerator
|
||||||
#if ! BOOST_BEAST_DOXYGEN
|
#if ! BOOST_BEAST_DOXYGEN
|
||||||
,
|
, typename std::enable_if<is_buffers_generator<
|
||||||
typename std::enable_if<is_buffers_generator<
|
|
||||||
typename std::decay<BuffersGenerator>::
|
typename std::decay<BuffersGenerator>::
|
||||||
type>::value>::type* = nullptr
|
type>::value>::type* = nullptr
|
||||||
#endif
|
#endif
|
||||||
@@ -103,7 +100,7 @@ write(
|
|||||||
by a caller-provided BuffersGenerator to a stream. The call
|
by a caller-provided BuffersGenerator to a stream. The call
|
||||||
will block until one of the following conditions is true:
|
will block until one of the following conditions is true:
|
||||||
|
|
||||||
@li The generator returns an empty buffers sequence.
|
@li A call to the generator's `is_done` returns `false`.
|
||||||
|
|
||||||
@li An error occurs.
|
@li An error occurs.
|
||||||
|
|
||||||
@@ -125,8 +122,7 @@ template<
|
|||||||
class SyncWriteStream,
|
class SyncWriteStream,
|
||||||
class BuffersGenerator
|
class BuffersGenerator
|
||||||
#if ! BOOST_BEAST_DOXYGEN
|
#if ! BOOST_BEAST_DOXYGEN
|
||||||
,
|
, typename std::enable_if<is_buffers_generator<
|
||||||
typename std::enable_if<is_buffers_generator<
|
|
||||||
typename std::decay<BuffersGenerator>::
|
typename std::decay<BuffersGenerator>::
|
||||||
type>::value>::type* = nullptr
|
type>::value>::type* = nullptr
|
||||||
#endif
|
#endif
|
||||||
@@ -145,7 +141,7 @@ write(
|
|||||||
operation will continue until one of the following
|
operation will continue until one of the following
|
||||||
conditions is true:
|
conditions is true:
|
||||||
|
|
||||||
@li The generator returns an empty buffers sequence.
|
@li A call to the generator's `is_done` returns `false`.
|
||||||
|
|
||||||
@li An error occurs.
|
@li An error occurs.
|
||||||
|
|
||||||
@@ -182,8 +178,7 @@ template<
|
|||||||
class BuffersGenerator,
|
class BuffersGenerator,
|
||||||
class CompletionToken
|
class CompletionToken
|
||||||
#if !BOOST_BEAST_DOXYGEN
|
#if !BOOST_BEAST_DOXYGEN
|
||||||
,
|
, typename std::enable_if<is_buffers_generator<
|
||||||
typename std::enable_if<is_buffers_generator<
|
|
||||||
BuffersGenerator>::value>::type* = nullptr
|
BuffersGenerator>::value>::type* = nullptr
|
||||||
#endif
|
#endif
|
||||||
>
|
>
|
||||||
@@ -191,16 +186,15 @@ auto
|
|||||||
async_write(
|
async_write(
|
||||||
AsyncWriteStream& stream,
|
AsyncWriteStream& stream,
|
||||||
BuffersGenerator generator,
|
BuffersGenerator generator,
|
||||||
CompletionToken&& token) //
|
CompletionToken&& token) ->
|
||||||
-> typename net::async_result<
|
typename net::async_result<
|
||||||
typename std::decay<
|
typename std::decay<
|
||||||
CompletionToken>::type,
|
CompletionToken>::type,
|
||||||
void(error_code, std::size_t)>::
|
void(error_code, std::size_t)>::return_type;
|
||||||
return_type;
|
|
||||||
|
|
||||||
} // namespace beast
|
} // beast
|
||||||
} // namespace boost
|
} // boost
|
||||||
|
|
||||||
#include <include/boost/beast/core/impl/buffers_generator.hpp>
|
#include <boost/beast/core/impl/buffers_generator.hpp>
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
#include <boost/beast/http/field.hpp>
|
#include <boost/beast/http/field.hpp>
|
||||||
#include <boost/beast/http/fields.hpp>
|
#include <boost/beast/http/fields.hpp>
|
||||||
#include <boost/beast/http/file_body.hpp>
|
#include <boost/beast/http/file_body.hpp>
|
||||||
|
#include <boost/beast/http/message_generator.hpp>
|
||||||
#include <boost/beast/http/message.hpp>
|
#include <boost/beast/http/message.hpp>
|
||||||
#include <boost/beast/http/parser.hpp>
|
#include <boost/beast/http/parser.hpp>
|
||||||
#include <boost/beast/http/read.hpp>
|
#include <boost/beast/http/read.hpp>
|
||||||
|
104
include/boost/beast/http/impl/message_generator.hpp
Normal file
104
include/boost/beast/http/impl/message_generator.hpp
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2022 Seth Heeren (sgheeren 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_IMPL_MESSAGE_GENERATOR_HPP
|
||||||
|
#define BOOST_BEAST_HTTP_IMPL_MESSAGE_GENERATOR_HPP
|
||||||
|
|
||||||
|
#include <boost/beast/http/message_generator.hpp>
|
||||||
|
#include <boost/smart_ptr/make_unique.hpp>
|
||||||
|
#include <boost/beast/core/buffers_generator.hpp>
|
||||||
|
|
||||||
|
namespace boost {
|
||||||
|
namespace beast {
|
||||||
|
namespace http {
|
||||||
|
|
||||||
|
template <bool isRequest, class Body, class Fields>
|
||||||
|
message_generator::message_generator(
|
||||||
|
http::message<isRequest, Body, Fields>&& m)
|
||||||
|
: impl_(boost::make_unique<
|
||||||
|
generator_impl<isRequest, Body, Fields>>(
|
||||||
|
std::move(m)))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template <bool isRequest, class Body, class Fields>
|
||||||
|
struct message_generator::generator_impl
|
||||||
|
: message_generator::impl_base
|
||||||
|
{
|
||||||
|
explicit generator_impl(
|
||||||
|
http::message<isRequest, Body, Fields>&& m)
|
||||||
|
: m_(std::move(m))
|
||||||
|
, sr_(m_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
is_done() override
|
||||||
|
{
|
||||||
|
return sr_.is_done();
|
||||||
|
}
|
||||||
|
|
||||||
|
const_buffers_type
|
||||||
|
prepare(error_code& ec) override
|
||||||
|
{
|
||||||
|
sr_.next(ec, visit{*this});
|
||||||
|
return current_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
consume(std::size_t n) override
|
||||||
|
{
|
||||||
|
sr_.consume((std::min)(n, beast::buffer_bytes(current_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
keep_alive() const noexcept override
|
||||||
|
{
|
||||||
|
return m_.keep_alive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr unsigned max_fixed_bufs = 12;
|
||||||
|
|
||||||
|
http::message<isRequest, Body, Fields> m_;
|
||||||
|
http::serializer<isRequest, Body, Fields> sr_;
|
||||||
|
|
||||||
|
std::array<net::const_buffer, max_fixed_bufs> bs_;
|
||||||
|
const_buffers_type current_ = bs_; // subspan
|
||||||
|
|
||||||
|
struct visit
|
||||||
|
{
|
||||||
|
generator_impl& self_;
|
||||||
|
|
||||||
|
template<class ConstBufferSequence>
|
||||||
|
void
|
||||||
|
operator()(error_code&, ConstBufferSequence const& buffers)
|
||||||
|
{
|
||||||
|
auto& s = self_.bs_;
|
||||||
|
auto& cur = self_.current_;
|
||||||
|
|
||||||
|
auto it = net::buffer_sequence_begin(buffers);
|
||||||
|
|
||||||
|
std::size_t n =
|
||||||
|
std::distance(it, net::buffer_sequence_end(buffers));
|
||||||
|
|
||||||
|
n = (std::min)(s.size(), n);
|
||||||
|
|
||||||
|
cur = { s.data(), n };
|
||||||
|
std::copy_n(it, n, cur.begin());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace http
|
||||||
|
} // namespace beast
|
||||||
|
} // namespace boost
|
||||||
|
|
||||||
|
#endif
|
99
include/boost/beast/http/message_generator.hpp
Normal file
99
include/boost/beast/http/message_generator.hpp
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2022 Seth Heeren (sgheeren 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_MESSAGE_GENERATOR_HPP
|
||||||
|
#define BOOST_BEAST_HTTP_MESSAGE_GENERATOR_HPP
|
||||||
|
|
||||||
|
#include <boost/beast/core/span.hpp>
|
||||||
|
#include <boost/beast/http/message.hpp>
|
||||||
|
#include <boost/beast/http/serializer.hpp>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace boost {
|
||||||
|
namespace beast {
|
||||||
|
namespace http {
|
||||||
|
|
||||||
|
/** Type-erased buffers generator for @ref http::message
|
||||||
|
|
||||||
|
Implements the BuffersGenerator concept for any concrete instance of the
|
||||||
|
@ref http::message template.
|
||||||
|
|
||||||
|
@ref http::message_generator takes ownership of a message on construction,
|
||||||
|
erasing the concrete type from the interface.
|
||||||
|
|
||||||
|
This makes it practical for use in server applications to implement request
|
||||||
|
handling:
|
||||||
|
|
||||||
|
@code
|
||||||
|
template <class Body, class Fields>
|
||||||
|
http::message_generator handle_request(
|
||||||
|
string_view doc_root,
|
||||||
|
http::request<Body, Fields>&& request);
|
||||||
|
@endcode
|
||||||
|
|
||||||
|
The @ref beast::write and @ref beast::async_write operations are provided
|
||||||
|
for BuffersGenerator. The @ref http::message::keep_alive property is made
|
||||||
|
available for use after writing the message.
|
||||||
|
*/
|
||||||
|
class message_generator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using const_buffers_type = span<net::const_buffer>;
|
||||||
|
|
||||||
|
template <bool isRequest, class Body, class Fields>
|
||||||
|
message_generator(http::message<isRequest, Body, Fields>&&);
|
||||||
|
|
||||||
|
/// @ref BuffersGenerator
|
||||||
|
bool is_done() const {
|
||||||
|
return impl_->is_done();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @ref BuffersGenerator
|
||||||
|
const_buffers_type
|
||||||
|
prepare(error_code& ec)
|
||||||
|
{
|
||||||
|
return impl_->prepare(ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @ref BuffersGenerator
|
||||||
|
void
|
||||||
|
consume(std::size_t n)
|
||||||
|
{
|
||||||
|
impl_->consume(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the result of `m.keep_alive()` on the underlying message
|
||||||
|
bool
|
||||||
|
keep_alive() const noexcept
|
||||||
|
{
|
||||||
|
return impl_->keep_alive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct impl_base
|
||||||
|
{
|
||||||
|
virtual ~impl_base() = default;
|
||||||
|
virtual bool is_done() = 0;
|
||||||
|
virtual const_buffers_type prepare(error_code& ec) = 0;
|
||||||
|
virtual void consume(std::size_t n) = 0;
|
||||||
|
virtual bool keep_alive() const noexcept = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<impl_base> impl_;
|
||||||
|
|
||||||
|
template <bool isRequest, class Body, class Fields>
|
||||||
|
struct generator_impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace http
|
||||||
|
} // namespace beast
|
||||||
|
} // namespace boost
|
||||||
|
|
||||||
|
#include <boost/beast/http/impl/message_generator.hpp>
|
||||||
|
|
||||||
|
#endif
|
@@ -27,7 +27,8 @@ namespace beast {
|
|||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
struct test_buffers_generator {
|
struct test_buffers_generator
|
||||||
|
{
|
||||||
using underlying_buffer_sequence = std::array<net::const_buffer, 2>;
|
using underlying_buffer_sequence = std::array<net::const_buffer, 2>;
|
||||||
using const_buffers_type = buffers_suffix<underlying_buffer_sequence>;
|
using const_buffers_type = buffers_suffix<underlying_buffer_sequence>;
|
||||||
std::size_t iterations_ = 5;
|
std::size_t iterations_ = 5;
|
||||||
@@ -212,7 +213,8 @@ public:
|
|||||||
BEAST_EXPECT(30 == in.nwrite_bytes());
|
BEAST_EXPECT(30 == in.nwrite_bytes());
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
"abcde12345abcd1234abc123ab12a1" == in.str());
|
"abcde12345abcd1234abc123ab12a1" == in.str());
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
BEAST_EXPECT(1 == out.nwrite());
|
BEAST_EXPECT(1 == out.nwrite());
|
||||||
BEAST_EXPECT(10 == in.nwrite_bytes());
|
BEAST_EXPECT(10 == in.nwrite_bytes());
|
||||||
@@ -254,7 +256,8 @@ public:
|
|||||||
BEAST_EXPECT(30 == in.nwrite_bytes());
|
BEAST_EXPECT(30 == in.nwrite_bytes());
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
"abcde12345abcd1234abc123ab12a1" == in.str());
|
"abcde12345abcd1234abc123ab12a1" == in.str());
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
BEAST_EXPECT(1 == out.nwrite());
|
BEAST_EXPECT(1 == out.nwrite());
|
||||||
BEAST_EXPECT(10 == in.nwrite_bytes());
|
BEAST_EXPECT(10 == in.nwrite_bytes());
|
||||||
|
@@ -29,6 +29,7 @@ add_executable (tests-beast-http
|
|||||||
field_compiles.cpp
|
field_compiles.cpp
|
||||||
fields.cpp
|
fields.cpp
|
||||||
file_body.cpp
|
file_body.cpp
|
||||||
|
message_generator.cpp
|
||||||
message.cpp
|
message.cpp
|
||||||
parser.cpp
|
parser.cpp
|
||||||
read.cpp
|
read.cpp
|
||||||
|
@@ -19,6 +19,7 @@ local SOURCES =
|
|||||||
field_compiles.cpp
|
field_compiles.cpp
|
||||||
fields.cpp
|
fields.cpp
|
||||||
file_body.cpp
|
file_body.cpp
|
||||||
|
message_generator.cpp
|
||||||
message.cpp
|
message.cpp
|
||||||
parser.cpp
|
parser.cpp
|
||||||
read.cpp
|
read.cpp
|
||||||
|
315
test/beast/http/message_generator.cpp
Normal file
315
test/beast/http/message_generator.cpp
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2022 Seth Heeren (sgheeren at gmail dot com)
|
||||||
|
//
|
||||||
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||||
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
//
|
||||||
|
// Official repository: https://github.com/boostorg/beast
|
||||||
|
//
|
||||||
|
|
||||||
|
// Test that header file is self-contained.
|
||||||
|
#include <boost/beast/http/message_generator.hpp>
|
||||||
|
|
||||||
|
#include <boost/beast/http/message.hpp>
|
||||||
|
#include <boost/beast/http/string_body.hpp>
|
||||||
|
#include <boost/beast/http/write.hpp> //for operator<<
|
||||||
|
|
||||||
|
#include <boost/beast/core/buffers_to_string.hpp>
|
||||||
|
#include <boost/beast/core/multi_buffer.hpp>
|
||||||
|
#include <boost/beast/core/buffers_generator.hpp> // for is_buffers_generator and [async_]write overloads
|
||||||
|
#include <boost/beast/_experimental/test/stream.hpp>
|
||||||
|
|
||||||
|
#include <boost/beast/_experimental/unit_test/suite.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
namespace boost {
|
||||||
|
namespace beast {
|
||||||
|
namespace http {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
// Degenerate body generator to trigger dynamic generator buffer
|
||||||
|
// allocation
|
||||||
|
//
|
||||||
|
// Warning: magic numbers ahead
|
||||||
|
//
|
||||||
|
// Arbitrarily decided on 65 buffers of which two are "some" and "body", the
|
||||||
|
// other bufs are chosen by the "seed fragment"
|
||||||
|
struct fragmented_test_body
|
||||||
|
{
|
||||||
|
using value_type = net::const_buffer;
|
||||||
|
|
||||||
|
static std::uint64_t
|
||||||
|
size(net::const_buffer fragment)
|
||||||
|
{
|
||||||
|
return 8 + 63 * fragment.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct writer
|
||||||
|
{
|
||||||
|
using const_buffers_type = std::array<net::const_buffer, 65>;
|
||||||
|
|
||||||
|
bool done_ = false;
|
||||||
|
net::const_buffer seed_fragment_;
|
||||||
|
|
||||||
|
template<typename Fields>
|
||||||
|
writer(Fields const&, net::const_buffer fragment)
|
||||||
|
: seed_fragment_(fragment)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
init(beast::error_code&) const
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
|
get(beast::error_code&)
|
||||||
|
{
|
||||||
|
const_buffers_type cb;
|
||||||
|
std::fill(begin(cb), end(cb), seed_fragment_);
|
||||||
|
cb.at(7) = { "some", 4 };
|
||||||
|
cb.at(27) = { "body", 4 };
|
||||||
|
return std::make_pair(cb, exchange(done_, true));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template<>
|
||||||
|
struct is_body_sized<detail::fragmented_test_body>
|
||||||
|
: std::true_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
class message_generator_test : public beast::unit_test::suite
|
||||||
|
{
|
||||||
|
static_assert(
|
||||||
|
is_buffers_generator<message_generator>::value,
|
||||||
|
"buffers_generator not modeled");
|
||||||
|
BOOST_STATIC_ASSERT(
|
||||||
|
std::is_constructible<
|
||||||
|
message_generator,
|
||||||
|
message<true, string_body>&&>::value);
|
||||||
|
BOOST_STATIC_ASSERT(
|
||||||
|
std::is_constructible<
|
||||||
|
message_generator,
|
||||||
|
message<false, string_body>&&>::value);
|
||||||
|
|
||||||
|
// only rvalue refs
|
||||||
|
BOOST_STATIC_ASSERT(
|
||||||
|
not std::is_constructible<
|
||||||
|
message_generator,
|
||||||
|
message<true, string_body>&>::value);
|
||||||
|
BOOST_STATIC_ASSERT(
|
||||||
|
not std::is_constructible<
|
||||||
|
message_generator,
|
||||||
|
message<true, string_body> const&>::value);
|
||||||
|
|
||||||
|
static request<string_body> make_get() {
|
||||||
|
return request<string_body>{
|
||||||
|
verb::get, "/path/query?1", 11,
|
||||||
|
"Serializable but ignored on GET"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static http::response<detail::fragmented_test_body>
|
||||||
|
make_fragmented_body_response(net::const_buffer seed_fragment)
|
||||||
|
{
|
||||||
|
http::response<detail::fragmented_test_body> msg(
|
||||||
|
http::status::ok, 11, seed_fragment);
|
||||||
|
msg.prepare_payload();
|
||||||
|
|
||||||
|
BEAST_EXPECT(msg.has_content_length());
|
||||||
|
BEAST_EXPECT(
|
||||||
|
msg.at(http::field::content_length) ==
|
||||||
|
std::to_string(8 + 63 * seed_fragment.size()));
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void
|
||||||
|
testGenerate()
|
||||||
|
{
|
||||||
|
message_generator gen(make_get());
|
||||||
|
error_code ec;
|
||||||
|
|
||||||
|
std::string received;
|
||||||
|
|
||||||
|
while(! gen.is_done())
|
||||||
|
{
|
||||||
|
message_generator::const_buffers_type b = gen.prepare(ec);
|
||||||
|
BEAST_EXPECT(! ec);
|
||||||
|
received += buffers_to_string(b);
|
||||||
|
|
||||||
|
gen.consume(buffer_bytes(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
BEAST_EXPECT(received ==
|
||||||
|
"GET /path/query?1 HTTP/1.1\r\n\r\n"
|
||||||
|
"Serializable but ignored on GET");
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testGenerateSlowConsumer()
|
||||||
|
{
|
||||||
|
message_generator gen(make_get());
|
||||||
|
error_code ec;
|
||||||
|
|
||||||
|
std::vector<std::string> received;
|
||||||
|
|
||||||
|
while(! gen.is_done())
|
||||||
|
{
|
||||||
|
message_generator::const_buffers_type b = gen.prepare(ec);
|
||||||
|
BEAST_EXPECT(! ec);
|
||||||
|
received.push_back(buffers_to_string(b).substr(0, 3));
|
||||||
|
|
||||||
|
gen.consume(3); // allowed > buffer_bytes(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
BEAST_EXPECT(
|
||||||
|
(received ==
|
||||||
|
std::vector<std::string>{
|
||||||
|
"GET", " /p", "ath", "/qu", "ery",
|
||||||
|
"?1 ", "HTT", "P/1", ".1\r", "\n\r\n",
|
||||||
|
"Ser", "ial", "iza", "ble", " bu",
|
||||||
|
"t i", "gno", "red", " on", " GE",
|
||||||
|
"T",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testAsyncWrite()
|
||||||
|
{
|
||||||
|
net::io_context ioc;
|
||||||
|
test::stream out(ioc), in(ioc);
|
||||||
|
{
|
||||||
|
test::connect(out, in);
|
||||||
|
|
||||||
|
message_generator gen(make_get());
|
||||||
|
|
||||||
|
beast::async_write(out, std::move(gen),
|
||||||
|
[&](error_code ec, size_t total) {
|
||||||
|
BEAST_EXPECT(total == 61);
|
||||||
|
BEAST_EXPECT(!ec.failed());
|
||||||
|
});
|
||||||
|
|
||||||
|
ioc.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
BEAST_EXPECT(61 == in.nwrite_bytes());
|
||||||
|
BEAST_EXPECT(in.str() ==
|
||||||
|
"GET /path/query?1 HTTP/1.1\r\n\r\n"
|
||||||
|
"Serializable but ignored on GET");
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testWrite()
|
||||||
|
{
|
||||||
|
net::io_context ioc;
|
||||||
|
test::stream out(ioc), in(ioc);
|
||||||
|
test::connect(out, in);
|
||||||
|
|
||||||
|
{
|
||||||
|
message_generator gen(make_get());
|
||||||
|
|
||||||
|
error_code ec;
|
||||||
|
std::size_t total = beast::write(out, gen, ec);
|
||||||
|
|
||||||
|
BEAST_EXPECT(total == 61);
|
||||||
|
BEAST_EXPECT(!ec.failed());
|
||||||
|
|
||||||
|
BEAST_EXPECT(61 == in.nwrite_bytes());
|
||||||
|
BEAST_EXPECT(in.str() ==
|
||||||
|
"GET /path/query?1 HTTP/1.1\r\n\r\n"
|
||||||
|
"Serializable but ignored on GET");
|
||||||
|
}
|
||||||
|
|
||||||
|
in.clear();
|
||||||
|
|
||||||
|
{
|
||||||
|
// rvalue accepted
|
||||||
|
std::size_t total =
|
||||||
|
beast::write(out, message_generator{ make_get() });
|
||||||
|
BEAST_EXPECT(total == 61);
|
||||||
|
BEAST_EXPECT(in.str() ==
|
||||||
|
"GET /path/query?1 HTTP/1.1\r\n\r\n"
|
||||||
|
"Serializable but ignored on GET");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testFragmentedBody()
|
||||||
|
{
|
||||||
|
net::io_context ioc;
|
||||||
|
test::stream out(ioc), in(ioc);
|
||||||
|
test::connect(out, in);
|
||||||
|
|
||||||
|
{
|
||||||
|
message_generator gen(make_fragmented_body_response(net::buffer("", 0)));
|
||||||
|
|
||||||
|
error_code ec;
|
||||||
|
std::size_t total = beast::write(out, gen, ec);
|
||||||
|
|
||||||
|
BEAST_EXPECT(total == 46);
|
||||||
|
BEAST_EXPECT(! ec.failed());
|
||||||
|
|
||||||
|
BEAST_EXPECT(
|
||||||
|
in.str() ==
|
||||||
|
"HTTP/1.1 200 OK\r\n"
|
||||||
|
"Content-Length: 8\r\n\r\nsomebody");
|
||||||
|
}
|
||||||
|
|
||||||
|
in.clear();
|
||||||
|
|
||||||
|
{
|
||||||
|
message_generator gen(make_fragmented_body_response(net::buffer("x", 1)));
|
||||||
|
|
||||||
|
error_code ec;
|
||||||
|
std::size_t total = beast::write(out, gen, ec);
|
||||||
|
|
||||||
|
BEAST_EXPECT(total == 47 + 63);
|
||||||
|
BEAST_EXPECT(! ec.failed());
|
||||||
|
|
||||||
|
BEAST_EXPECT(
|
||||||
|
in.str() ==
|
||||||
|
"HTTP/1.1 200 OK\r\n"
|
||||||
|
"Content-Length: 71\r\n\r\nxxxxxxxsomexxxxxxxxxxxxxxxxxxxbodyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testKeepAlive()
|
||||||
|
{
|
||||||
|
auto request = [](int version)
|
||||||
|
{
|
||||||
|
return http::request<http::string_body>(
|
||||||
|
http::verb::post, "/", version);
|
||||||
|
};
|
||||||
|
BEAST_EXPECT(
|
||||||
|
! http::message_generator(request(10)).keep_alive());
|
||||||
|
BEAST_EXPECT(
|
||||||
|
http::message_generator(request(11)).keep_alive());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
run() override
|
||||||
|
{
|
||||||
|
testGenerate();
|
||||||
|
testGenerateSlowConsumer();
|
||||||
|
testAsyncWrite();
|
||||||
|
testWrite();
|
||||||
|
testFragmentedBody();
|
||||||
|
testKeepAlive();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BEAST_DEFINE_TESTSUITE(beast,http,message_generator);
|
||||||
|
|
||||||
|
} // http
|
||||||
|
} // beast
|
||||||
|
} // boost
|
Reference in New Issue
Block a user