HTTP Reader (API Change):

fix #114, fix #117, fix #136

* Added init() to Reader requirements
* Reader must be nothrow constructible
* Reader is now constructed right before reading the body
  - The message passed on construction is filled in
This commit is contained in:
Vinnie Falco
2016-10-15 09:29:14 -04:00
parent 35d1ee54bc
commit f110e51dd1
8 changed files with 171 additions and 55 deletions

View File

@@ -5,6 +5,14 @@
* Fix basic_streambuf::capacity * Fix basic_streambuf::capacity
* Add basic_streambuf::alloc_size * Add basic_streambuf::alloc_size
* Parser callbacks may not throw * Parser callbacks may not throw
* Fix Reader concept doc typo
API Changes:
* Added init() to Reader requirements
* Reader must be nothrow constructible
* Reader is now constructed right before reading the body
- The message passed on construction is filled in
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@@ -8,23 +8,23 @@
[section:Reader Reader requirements] [section:Reader Reader requirements]
Parsers provided by the implementation will construct the corresponding Parsers provided by the implementation will construct the corresponding
`reader` object during the parse. This customization point allows the `reader` object during parsing. This customization point allows the
Body to determine the strategy for storing incoming message body data. Body to determine the strategy for storing incoming message body data.
In this table: In this table:
* `X` denotes a type meeting the requirements of [*`Reader`] * `X` denotes a type meeting the requirements of [*`Reader`].
* `a` denotes a value of type `X` * `a` denotes a value of type `X`.
* `p` is any pointer * `n` is a value convertible to `std::size_t`.
* `n` is a value convertible to `std::size_t` * `p` is a `void const*` to valid memory of at least `n` bytes.
* `ec` is a value of type [link beast.ref.error_code `error_code&`] * `ec` is a value of type [link beast.ref.error_code `error_code&`].
* `m` denotes a value of type `message const&` where * `m` denotes a value of type `message&` where
`std::is_same<decltype(m.body), Body::value_type>::value == true` `std::is_same<decltype(m.body), Body::value_type>::value == true`.
[table Reader requirements [table Reader requirements
[[operation] [type] [semantics, pre/post-conditions]] [[operation] [type] [semantics, pre/post-conditions]]
@@ -32,22 +32,38 @@ In this table:
[`X a(m);`] [`X a(m);`]
[] []
[ [
`a` is constructible from `m`. The lifetime of `m` is `a` is constructible from `m`. The lifetime of `m` is guaranteed
guaranteed to end no earlier than after `a` is destroyed. to end no earlier than after `a` is destroyed. The constructor
will be called after all headers have been stored in `m`, and
before any body data is deserialized. This function must be
`noexcept`.
]
]
[
[`a.init(ec)`]
[`void`]
[
Called immediately after construction. If the function sets
an error code in `ec`, the parse is aborted and the error is
propagated to the caller. This function must be `noexcept`.
] ]
] ]
[ [
[`a.write(p, n, ec)`] [`a.write(p, n, ec)`]
[`void`] [`void`]
[ [
Deserializes the input sequence into the body. Deserializes the input sequence into the body. If `ec` is set,
If `ec` is set, the deserialization is aborted and the error the deserialization is aborted and the error is propagated to
is returned to the caller. the caller. If the message headers specify a chunked transfer
encoding, the reader will receive the decoded version of the
body. This function must be `noexcept`.
] ]
] ]
] ]
[note Definitions for required `Reader` member functions should be declared [note
inline so the generated code becomes part of the implementation. ] Definitions for required `Reader` member functions should be declared
inline so the generated code can become part of the implementation.
]
[endsect] [endsect]

View File

@@ -28,13 +28,13 @@ In this table:
* `m` denotes a value of type `message const&` where * `m` denotes a value of type `message const&` where
`std::is_same<decltype(m.body), Body::value_type>:value == true`. `std::is_same<decltype(m.body), Body::value_type>:value == true`.
* `rc` is an object of type [link beast.ref.http__resume_context resume_context]. * `rc` is an object of type [link beast.ref.http__resume_context `resume_context`].
* `ec` is a value of type `error_code&`. * `ec` is a value of type [link beast.ref.error_code `error_code&`]
* `wf` is a [*write function]: a function object of unspecified type provided * `wf` is a [*write function]: a function object of unspecified type provided
by the implementation which accepts any value meeting the requirements by the implementation which accepts any value meeting the requirements
of `ConstBufferSequence` as its single parameter. of __ConstBufferSequence__ as its single parameter.
[table Writer requirements [table Writer requirements
[[operation] [type] [semantics, pre/post-conditions]] [[operation] [type] [semantics, pre/post-conditions]]
@@ -42,17 +42,18 @@ In this table:
[`X a(m);`] [`X a(m);`]
[] []
[ [
`a` is constructible from `m`. The lifetime of `m` is `a` is constructible from `m`. The lifetime of `m` is guaranteed
guaranteed to end no earlier than after `a` is destroyed. to end no earlier than after `a` is destroyed. This function must
be `noexcept`.
] ]
] ]
[ [
[`a.init(ec)`] [`a.init(ec)`]
[`void`] [`void`]
[ [
Called immediately after construction. Called immediately after construction. If the function sets an
If `ec` is set, the serialization is aborted and the error error code in `ec`, the serialization is aborted and the error
is propagated to the caller. is propagated to the caller. This function must be `noexcept`.
] ]
] ]
[ [
@@ -67,31 +68,33 @@ In this table:
the serialized message body will be sent unmodified, with the the serialized message body will be sent unmodified, with the
error `boost::asio::error::eof` returned to the caller, to notify error `boost::asio::error::eof` returned to the caller, to notify
they should close the connection to indicate the end of the message. they should close the connection to indicate the end of the message.
This function must be `noexcept`.
] ]
] ]
[ [
[`a(rc, ec, wf)`] [`a(rc, ec, wf)`]
[`boost::tribool`] [`boost::tribool`]
[ [
Called repeatedly after `init` succeeds. Called repeatedly after `init` succeeds. `wf` is a function object
`wf` is a function object which takes as its single parameter, which takes as its single parameter any value meeting the requirements
any value meeting the requirements of `ConstBufferSequence`. of __ConstBufferSequence__. Buffers provided to this write function
Buffers provided by the `writer` to this [*write function] must must remain valid until the next member function of `writer` is
remain valid until the next member function of `writer` is
invoked (which may be the destructor). This function returns `true` invoked (which may be the destructor). This function returns `true`
to indicate all message body data has been written, or `false` to indicate all message body data has been written, or `false` if
if there is more body data. If the return value is there is more body data. If the return value is `boost::indeterminate`,
`boost::indeterminate`, the implementation will suspend the operation the implementation will suspend the operation until the writer invokes
until the writer invokes `rc`. It is the writers responsibility when `rc`. It is the writers responsibility when returning
returning `boost::indeterminate`, to acquire ownership of the `boost::indeterminate`, to acquire ownership of `rc` via move
`resume_context` via move construction and eventually call it or else construction and eventually call it or else undefined behavior
undefined behavior results. results. This function must be `noexcept`.
] ]
] ]
] ]
[note Definitions for required `Writer` member functions should be declared [note
inline so the generated code becomes part of the implementation. ] Definitions for required `Writer` member functions should be declared
inline so the generated code can become part of the implementation.
]
Exemplar: Exemplar:
``` ```
@@ -109,7 +112,7 @@ public:
*/ */
template<bool isRequest, class Body, class Headers> template<bool isRequest, class Body, class Headers>
explicit explicit
writer(message<isRequest, Body, Headers> const& msg); writer(message<isRequest, Body, Headers> const& msg) noexcept;
/** Initialize the writer. /** Initialize the writer.
@@ -119,7 +122,7 @@ public:
@param ec Contains the error code if any errors occur. @param ec Contains the error code if any errors occur.
*/ */
void void
init(error_code& ec); init(error_code& ec) noexcept;
/** Returns the content length. /** Returns the content length.
@@ -129,7 +132,7 @@ public:
of the message. of the message.
*/ */
std::uint64_t std::uint64_t
content_length(); content_length() noexcept;
/** Write zero or one buffer representing the message body. /** Write zero or one buffer representing the message body.
@@ -172,7 +175,10 @@ public:
*/ */
template<class WriteFunction> template<class WriteFunction>
boost::tribool boost::tribool
operator()(resume_context&&, error_code&, WriteFunction&& write); operator()(
resume_context&&,
error_code&,
WriteFunction&& write) noexcept;
}; };
``` ```

View File

@@ -41,6 +41,11 @@ private:
{ {
} }
void
init(error_code&) noexcept
{
}
void void
write(void const* data, write(void const* data,
std::size_t size, error_code&) noexcept std::size_t size, error_code&) noexcept

View File

@@ -190,9 +190,9 @@ static std::uint64_t constexpr no_content_length =
@li `void on_complete(error_code&)` @li `void on_complete(error_code&)`
Called when the entire message has been parsed successfully. Called when the entire message has been parsed successfully.
At this point, @ref basic_parser_v1::complete returns `true`, and At this point, @ref complete returns `true`, and the parser
the parser is ready to parse another message if keep_alive is ready to parse another message if @ref keep_alive would
would return `true`. return `true`.
The return value of `on_headers` is special, it controls whether The return value of `on_headers` is special, it controls whether
or not the parser should expect a body. See @ref body_what for or not the parser should expect a body. See @ref body_what for

View File

@@ -13,6 +13,7 @@
#include <beast/http/message_v1.hpp> #include <beast/http/message_v1.hpp>
#include <beast/core/error.hpp> #include <beast/core/error.hpp>
#include <boost/assert.hpp> #include <boost/assert.hpp>
#include <boost/optional.hpp>
#include <functional> #include <functional>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
@@ -87,17 +88,28 @@ public:
message_v1<isRequest, Body, Headers>; message_v1<isRequest, Body, Headers>;
private: private:
using reader =
typename message_type::body_type::reader;
static_assert(is_ReadableBody<Body>::value, static_assert(is_ReadableBody<Body>::value,
"ReadableBody requirements not met"); "ReadableBody requirements not met");
// Reader must be nothrow constructible
static_assert(std::is_nothrow_constructible<
reader, message_type&>::value,
"Reader requirements not met");
std::string field_; std::string field_;
std::string value_; std::string value_;
message_type m_; message_type m_;
typename message_type::body_type::reader r_; boost::optional<reader> r_;
std::uint8_t skip_body_ = 0; std::uint8_t skip_body_ = 0;
bool flush_ = false; bool flush_ = false;
public: public:
/// Default constructor
parser_v1() = default;
/// Move constructor /// Move constructor
parser_v1(parser_v1&&) = default; parser_v1(parser_v1&&) = default;
@@ -110,12 +122,6 @@ public:
/// Copy assignment (disallowed) /// Copy assignment (disallowed)
parser_v1& operator=(parser_v1 const&) = delete; parser_v1& operator=(parser_v1 const&) = delete;
/// Default constructor
parser_v1()
: r_(m_)
{
}
/** Construct the parser. /** Construct the parser.
@param args A list of arguments forwarded to the message constructor. @param args A list of arguments forwarded to the message constructor.
@@ -127,7 +133,6 @@ public:
parser_v1(Arg1&& arg1, ArgN&&... argn) parser_v1(Arg1&& arg1, ArgN&&... argn)
: m_(std::forward<Arg1>(arg1), : m_(std::forward<Arg1>(arg1),
std::forward<ArgN>(argn)...) std::forward<ArgN>(argn)...)
, r_(m_)
{ {
} }
@@ -232,12 +237,14 @@ private:
} }
body_what body_what
on_headers(std::uint64_t, error_code&) on_headers(std::uint64_t, error_code& ec)
{ {
flush(); flush();
m_.version = 10 * this->http_major() + this->http_minor(); m_.version = 10 * this->http_major() + this->http_minor();
if(skip_body_) if(skip_body_)
return body_what::skip; return body_what::skip;
r_.emplace(m_);
r_->init(ec);
return body_what::normal; return body_what::normal;
} }
@@ -255,7 +262,7 @@ private:
void on_body(boost::string_ref const& s, error_code& ec) void on_body(boost::string_ref const& s, error_code& ec)
{ {
r_.write(s.data(), s.size(), ec); r_->write(s.data(), s.size(), ec);
} }
void on_complete(error_code&) void on_complete(error_code&)

View File

@@ -42,6 +42,11 @@ private:
{ {
} }
void
init(error_code&) noexcept
{
}
void void
write(void const* data, write(void const* data,
std::size_t size, error_code&) noexcept std::size_t size, error_code&) noexcept

View File

@@ -26,8 +26,63 @@ class read_test
, public test::enable_yield_to , public test::enable_yield_to
{ {
public: public:
struct fail_body
{
class reader;
class value_type
{
friend class reader;
std::string s_;
test::fail_counter& fc_;
public:
explicit
value_type(test::fail_counter& fc)
: fc_(fc)
{
}
value_type&
operator=(std::string s)
{
s_ = std::move(s);
return *this;
}
};
class reader
{
std::size_t n_ = 0;
value_type& body_;
public:
template<bool isRequest, class Allocator>
explicit
reader(message<isRequest, fail_body, Allocator>& msg) noexcept
: body_(msg.body)
{
}
void
init(error_code& ec) noexcept
{
body_.fc_.fail(ec);
}
void
write(void const* data,
std::size_t size, error_code& ec) noexcept
{
if(body_.fc_.fail(ec))
return;
}
};
};
template<bool isRequest> template<bool isRequest>
void failMatrix(const char* s, yield_context do_yield) void failMatrix(char const* s, yield_context do_yield)
{ {
using boost::asio::buffer; using boost::asio::buffer;
using boost::asio::buffer_copy; using boost::asio::buffer_copy;
@@ -96,6 +151,20 @@ public:
break; break;
} }
BEAST_EXPECT(n < limit); BEAST_EXPECT(n < limit);
for(n = 0; n < limit; ++n)
{
streambuf sb;
sb.commit(buffer_copy(
sb.prepare(len), buffer(s, len)));
test::fail_counter fc{n};
test::string_stream ss{ios_, s};
parser_v1<isRequest, fail_body, headers> p{fc};
error_code ec;
parse(ss, sb, p, ec);
if(! ec)
break;
}
BEAST_EXPECT(n < limit);
} }
void testThrow() void testThrow()