Add http::message_generator

This commit is contained in:
sehe
2022-05-20 17:04:57 -07:00
committed by Vinnie Falco
parent 84e689c447
commit 740879a995
10 changed files with 553 additions and 28 deletions

View File

@ -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
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
beast.ref.boost__beast__async_write `async_write`] operations are provided as
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 value of type `G`.
* `c` denotes a possibly-const value of type `G`.
* `n` is a value of type `std::size_t`.
* `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)`.
]
][
[`g.is_done()`]
[`c.is_done()`]
[`bool`]
[
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
is unspecified.
This member function does not alter the state of the generator.
]
][
[`g.prepare(ec)`]
@ -106,7 +110,7 @@ In this table:
]
][
[`is_buffers_generator<G>`]
[`std::true_type`]
[`std::bool_constant`]
[
An alias for `std::true_type` for `G`, otherwise an alias
for `std::false_type`.
@ -121,7 +125,7 @@ In this table:
{
using const_buffers_type = net::const_buffer;
bool is_done();
bool is_done() const;
const_buffers_type prepare( error_code& ec );
void consume( std::size_t n );
};
@ -132,6 +136,8 @@ In this table:
[heading Models]
* [link beast.ref.boost__beast__http__message_generator `http::message_generator`]
[heading Algorithms]
* [link beast.ref.boost__beast__async_write `async_write`]

View File

@ -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__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_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__request">request</link></member>
<member><link linkend="beast.ref.boost__beast__http__request_header">request_header</link></member>

View File

@ -11,11 +11,9 @@
#define BOOST_BEAST_CORE_BUFFERS_GENERATOR_HPP
#include <boost/beast/core/detail/config.hpp>
#include <boost/beast/core/detail/type_traits.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/asio/async_result.hpp>
#include <type_traits>
namespace boost {
@ -63,7 +61,7 @@ struct is_buffers_generator<
by a caller-provided BuffersGenerator to a stream. The call
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.
@ -85,8 +83,7 @@ template<
class SyncWriteStream,
class BuffersGenerator
#if ! BOOST_BEAST_DOXYGEN
,
typename std::enable_if<is_buffers_generator<
, typename std::enable_if<is_buffers_generator<
typename std::decay<BuffersGenerator>::
type>::value>::type* = nullptr
#endif
@ -103,7 +100,7 @@ write(
by a caller-provided BuffersGenerator to a stream. The call
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.
@ -125,8 +122,7 @@ template<
class SyncWriteStream,
class BuffersGenerator
#if ! BOOST_BEAST_DOXYGEN
,
typename std::enable_if<is_buffers_generator<
, typename std::enable_if<is_buffers_generator<
typename std::decay<BuffersGenerator>::
type>::value>::type* = nullptr
#endif
@ -145,7 +141,7 @@ write(
operation will continue 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.
@ -182,8 +178,7 @@ template<
class BuffersGenerator,
class CompletionToken
#if !BOOST_BEAST_DOXYGEN
,
typename std::enable_if<is_buffers_generator<
, typename std::enable_if<is_buffers_generator<
BuffersGenerator>::value>::type* = nullptr
#endif
>
@ -191,16 +186,15 @@ auto
async_write(
AsyncWriteStream& stream,
BuffersGenerator generator,
CompletionToken&& token) //
-> typename net::async_result<
typename std::decay<
CompletionToken>::type,
void(error_code, std::size_t)>::
return_type;
CompletionToken&& token) ->
typename net::async_result<
typename std::decay<
CompletionToken>::type,
void(error_code, std::size_t)>::return_type;
} // namespace beast
} // namespace boost
} // beast
} // boost
#include <include/boost/beast/core/impl/buffers_generator.hpp>
#include <boost/beast/core/impl/buffers_generator.hpp>
#endif

View File

@ -23,6 +23,7 @@
#include <boost/beast/http/field.hpp>
#include <boost/beast/http/fields.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/parser.hpp>
#include <boost/beast/http/read.hpp>

View 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

View 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

View File

@ -27,7 +27,8 @@ namespace beast {
namespace detail {
struct test_buffers_generator {
struct test_buffers_generator
{
using underlying_buffer_sequence = std::array<net::const_buffer, 2>;
using const_buffers_type = buffers_suffix<underlying_buffer_sequence>;
std::size_t iterations_ = 5;
@ -212,7 +213,8 @@ public:
BEAST_EXPECT(30 == in.nwrite_bytes());
BEAST_EXPECT(
"abcde12345abcd1234abc123ab12a1" == in.str());
} else
}
else
{
BEAST_EXPECT(1 == out.nwrite());
BEAST_EXPECT(10 == in.nwrite_bytes());
@ -254,7 +256,8 @@ public:
BEAST_EXPECT(30 == in.nwrite_bytes());
BEAST_EXPECT(
"abcde12345abcd1234abc123ab12a1" == in.str());
} else
}
else
{
BEAST_EXPECT(1 == out.nwrite());
BEAST_EXPECT(10 == in.nwrite_bytes());

View File

@ -29,6 +29,7 @@ add_executable (tests-beast-http
field_compiles.cpp
fields.cpp
file_body.cpp
message_generator.cpp
message.cpp
parser.cpp
read.cpp

View File

@ -19,6 +19,7 @@ local SOURCES =
field_compiles.cpp
fields.cpp
file_body.cpp
message_generator.cpp
message.cpp
parser.cpp
read.cpp

View 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