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
* Add basic_streambuf::alloc_size
* 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]
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.
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
`std::is_same<decltype(m.body), Body::value_type>::value == true`
* `m` denotes a value of type `message&` where
`std::is_same<decltype(m.body), Body::value_type>::value == true`.
[table Reader requirements
[[operation] [type] [semantics, pre/post-conditions]]
@@ -32,22 +32,38 @@ In this table:
[`X a(m);`]
[]
[
`a` is constructible from `m`. The lifetime of `m` is
guaranteed to end no earlier than after `a` is destroyed.
`a` is constructible from `m`. The lifetime of `m` is guaranteed
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)`]
[`void`]
[
Deserializes the input sequence into the body.
If `ec` is set, the deserialization is aborted and the error
is returned to the caller.
Deserializes the input sequence into the body. If `ec` is set,
the deserialization is aborted and the error is propagated to
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
inline so the generated code becomes part of the implementation. ]
[note
Definitions for required `Reader` member functions should be declared
inline so the generated code can become part of the implementation.
]
[endsect]

View File

@@ -28,13 +28,13 @@ In this table:
* `m` denotes a value of type `message const&` where
`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
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
[[operation] [type] [semantics, pre/post-conditions]]
@@ -42,17 +42,18 @@ In this table:
[`X a(m);`]
[]
[
`a` is constructible from `m`. The lifetime of `m` is
guaranteed to end no earlier than after `a` is destroyed.
`a` is constructible from `m`. The lifetime of `m` is guaranteed
to end no earlier than after `a` is destroyed. This function must
be `noexcept`.
]
]
[
[`a.init(ec)`]
[`void`]
[
Called immediately after construction.
If `ec` is set, the serialization is aborted and the error
is propagated to the caller.
Called immediately after construction. If the function sets an
error code in `ec`, the serialization is aborted and the error
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
error `boost::asio::error::eof` returned to the caller, to notify
they should close the connection to indicate the end of the message.
This function must be `noexcept`.
]
]
[
[`a(rc, ec, wf)`]
[`boost::tribool`]
[
Called repeatedly after `init` succeeds.
`wf` is a function object which takes as its single parameter,
any value meeting the requirements of `ConstBufferSequence`.
Buffers provided by the `writer` to this [*write function] must
remain valid until the next member function of `writer` is
Called repeatedly after `init` succeeds. `wf` is a function object
which takes as its single parameter any value meeting the requirements
of __ConstBufferSequence__. Buffers provided to this write function
must remain valid until the next member function of `writer` is
invoked (which may be the destructor). This function returns `true`
to indicate all message body data has been written, or `false`
if there is more body data. If the return value is
`boost::indeterminate`, the implementation will suspend the operation
until the writer invokes `rc`. It is the writers responsibility when
returning `boost::indeterminate`, to acquire ownership of the
`resume_context` via move construction and eventually call it or else
undefined behavior results.
to indicate all message body data has been written, or `false` if
there is more body data. If the return value is `boost::indeterminate`,
the implementation will suspend the operation until the writer invokes
`rc`. It is the writers responsibility when returning
`boost::indeterminate`, to acquire ownership of `rc` via move
construction and eventually call it or else undefined behavior
results. This function must be `noexcept`.
]
]
]
[note Definitions for required `Writer` member functions should be declared
inline so the generated code becomes part of the implementation. ]
[note
Definitions for required `Writer` member functions should be declared
inline so the generated code can become part of the implementation.
]
Exemplar:
```
@@ -109,7 +112,7 @@ public:
*/
template<bool isRequest, class Body, class Headers>
explicit
writer(message<isRequest, Body, Headers> const& msg);
writer(message<isRequest, Body, Headers> const& msg) noexcept;
/** Initialize the writer.
@@ -119,7 +122,7 @@ public:
@param ec Contains the error code if any errors occur.
*/
void
init(error_code& ec);
init(error_code& ec) noexcept;
/** Returns the content length.
@@ -129,7 +132,7 @@ public:
of the message.
*/
std::uint64_t
content_length();
content_length() noexcept;
/** Write zero or one buffer representing the message body.
@@ -172,7 +175,10 @@ public:
*/
template<class WriteFunction>
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
write(void const* data,
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&)`
Called when the entire message has been parsed successfully.
At this point, @ref basic_parser_v1::complete returns `true`, and
the parser is ready to parse another message if keep_alive
would return `true`.
At this point, @ref complete returns `true`, and the parser
is ready to parse another message if @ref keep_alive would
return `true`.
The return value of `on_headers` is special, it controls whether
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/core/error.hpp>
#include <boost/assert.hpp>
#include <boost/optional.hpp>
#include <functional>
#include <string>
#include <type_traits>
@@ -87,17 +88,28 @@ public:
message_v1<isRequest, Body, Headers>;
private:
using reader =
typename message_type::body_type::reader;
static_assert(is_ReadableBody<Body>::value,
"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 value_;
message_type m_;
typename message_type::body_type::reader r_;
boost::optional<reader> r_;
std::uint8_t skip_body_ = 0;
bool flush_ = false;
public:
/// Default constructor
parser_v1() = default;
/// Move constructor
parser_v1(parser_v1&&) = default;
@@ -110,12 +122,6 @@ public:
/// Copy assignment (disallowed)
parser_v1& operator=(parser_v1 const&) = delete;
/// Default constructor
parser_v1()
: r_(m_)
{
}
/** Construct the parser.
@param args A list of arguments forwarded to the message constructor.
@@ -127,7 +133,6 @@ public:
parser_v1(Arg1&& arg1, ArgN&&... argn)
: m_(std::forward<Arg1>(arg1),
std::forward<ArgN>(argn)...)
, r_(m_)
{
}
@@ -232,12 +237,14 @@ private:
}
body_what
on_headers(std::uint64_t, error_code&)
on_headers(std::uint64_t, error_code& ec)
{
flush();
m_.version = 10 * this->http_major() + this->http_minor();
if(skip_body_)
return body_what::skip;
r_.emplace(m_);
r_->init(ec);
return body_what::normal;
}
@@ -255,7 +262,7 @@ private:
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&)

View File

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

View File

@@ -26,8 +26,63 @@ class read_test
, public test::enable_yield_to
{
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>
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_copy;
@@ -96,6 +151,20 @@ public:
break;
}
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()