Add pause option to on_headers interface:

* on_headers returns enum body_what
* body_what::pause lets caller receive headers during the parse
This commit is contained in:
Vinnie Falco
2016-10-10 07:55:39 -04:00
parent ce18124566
commit 2106f364f6
10 changed files with 90 additions and 48 deletions

View File

@ -5,6 +5,7 @@
* Fix on_headers called twice from basic_parser_v1
* Constrain parser_v1 constructor
* Improve first line serialization
* Add pause option to on_headers interface
* Refine Parser concept
API Changes:

View File

@ -68,6 +68,7 @@
</simplelist>
<bridgehead renderas="sect3">Constants</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="beast.ref.http__body_what">body_what</link></member>
<member><link linkend="beast.ref.http__connection">connection</link></member>
</simplelist>
<bridgehead renderas="sect3">Concepts</bridgehead>

View File

@ -80,6 +80,49 @@ struct body_max_size
}
};
/** A value indicating how the parser should treat the body.
This value is returned from the `on_headers` callback in
the derived class. It controls what the parser does next
in terms of the message body.
*/
enum class body_what
{
/** The parser should expect a body, keep reading.
*/
normal,
/** Skip parsing of the body.
When returned by `on_headers` this causes parsing to
complete and control to return to the caller. This
could be used when sending a response to a HEAD
request, for example.
*/
skip,
/** The message represents an UPGRADE request.
When returned by `on_headers` this causes parsing
to complete and control to return to the caller.
*/
upgrade,
/** Suspend parsing before reading the body.
When returned by `on_headers` this causes parsing to
pause. Control is returned to the caller, and the parser
state is preserved such that a subsequent call to the
parser will begin reading the message body.
This could be used by callers to inspect the HTTP
headers before committing to read the body. For example,
to choose the body type based on the headers. Or to
respond to an Expect: 100-continue request.
*/
pause
};
/// The value returned when no content length is known or applicable.
static std::uint64_t constexpr no_content_length =
std::numeric_limits<std::uint64_t>::max();
@ -133,7 +176,7 @@ static std::uint64_t constexpr no_content_length =
Called for each piece of the current header value.
@li `int on_headers(std::uint64_t content_length, error_code&)`
@li `body_what on_headers(std::uint64_t content_length, error_code&)`
Called when all the headers have been parsed successfully.
@ -151,18 +194,11 @@ static std::uint64_t constexpr no_content_length =
would return `true`.
The return value of `on_headers` is special, it controls whether
or not the parser should expect a body. These are the return values:
@li *0* The parser should expect a body
@li *1* The parser should skip the body. For example, this is
used when sending a response to a HEAD request.
@li *2* The parser should skip ths body, this is an
upgrade to a different protocol.
or not the parser should expect a body. See @ref body_what for
choices of the return value.
The parser uses traits to determine if the callback is possible.
If the Derived type omits one or more callbacks, they are simply
If the @b Derived type omits one or more callbacks, they are simply
skipped with no compilation error. The default behavior of `on_body`
when the derived class does not provide the member, is to specify that
the body should not be skipped.
@ -172,6 +208,9 @@ static std::uint64_t constexpr no_content_length =
@tparam isRequest A `bool` indicating whether the parser will be
presented with request or response message.
@tparam Derived The derived class type. This is part of the
Curiously Recurring Template Pattern interface.
*/
template<bool isRequest, class Derived>
class basic_parser_v1 : public detail::parser_base
@ -626,7 +665,7 @@ private:
template<class C>
class has_on_headers_t
{
template<class T, class R = std::is_same<int,
template<class T, class R = std::is_convertible<body_what,
decltype(std::declval<T>().on_headers(
std::declval<std::uint64_t>(), std::declval<error_code&>()))>>
static R check(int);
@ -845,18 +884,21 @@ private:
}
}
int call_on_headers(error_code& ec,
body_what
call_on_headers(error_code& ec,
std::uint64_t content_length, std::true_type)
{
return impl().on_headers(content_length, ec);
}
int call_on_headers(error_code& ec, std::uint64_t, std::false_type)
body_what
call_on_headers(error_code& ec, std::uint64_t, std::false_type)
{
return 0;
return body_what::normal;
}
int call_on_headers(error_code& ec)
body_what
call_on_headers(error_code& ec)
{
return call_on_headers(ec, content_length_,
has_on_headers<Derived>{});

View File

@ -899,21 +899,22 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
return err(parse_error::illegal_content_length);
upgrade_ = ((flags_ & (parse_flag::upgrade | parse_flag::connection_upgrade)) ==
(parse_flag::upgrade | parse_flag::connection_upgrade)) /*|| method == "connect"*/;
auto const maybe_skip = call_on_headers(ec);
auto const what = call_on_headers(ec);
if(ec)
return errc();
switch(maybe_skip)
switch(what)
{
case 0:
case body_what::normal:
break;
case 2:
case body_what::upgrade:
upgrade_ = true;
// fall through
case 1:
case body_what::skip:
flags_ |= parse_flag::skipbody;
break;
default:
return err(parse_error::bad_on_headers_rv);
case body_what::pause:
s_ = s_headers_done;
return used();
}
s_ = s_headers_done;
// fall through

View File

@ -47,7 +47,6 @@ public:
case parse_error::bad_value: return "bad field-value";
case parse_error::bad_content_length: return "bad Content-Length";
case parse_error::illegal_content_length: return "illegal Content-Length with chunked Transfer-Encoding";
case parse_error::bad_on_headers_rv: return "on_headers returned an unknown value";
case parse_error::invalid_chunk_size: return "invalid chunk size";
case parse_error::invalid_ext_name: return "invalid ext name";
case parse_error::invalid_ext_val: return "invalid ext val";

View File

@ -29,7 +29,6 @@ enum class parse_error
bad_value,
bad_content_length,
illegal_content_length,
bad_on_headers_rv,
invalid_chunk_size,
invalid_ext_name,

View File

@ -231,11 +231,14 @@ private:
m_.reason = std::move(this->reason_);
}
int on_headers(std::uint64_t, error_code&)
body_what
on_headers(std::uint64_t, error_code&)
{
flush();
m_.version = 10 * this->http_major() + this->http_minor();
return skip_body_;
if(skip_body_)
return body_what::skip;
return body_what::normal;
}
void on_request(error_code& ec)

View File

@ -90,10 +90,11 @@ public:
{
value = true;
}
int on_headers(std::uint64_t, error_code&)
body_what
on_headers(std::uint64_t, error_code&)
{
headers = true;
return 0;
return body_what::normal;
}
void on_body(boost::string_ref const&, error_code&)
{
@ -194,7 +195,7 @@ public:
template<bool isRequest, class F>
void
good(int onBodyRv, std::string const& s, F const& f)
good(body_what onBodyRv, std::string const& s, F const& f)
{
using boost::asio::buffer;
for_split(s,
@ -237,12 +238,12 @@ public:
void
good(std::string const& s, F const& f = {})
{
return good<isRequest>(0, s, f);
return good<isRequest>(body_what::normal, s, f);
}
template<bool isRequest>
void
bad(int onBodyRv, std::string const& s, error_code ev)
bad(body_what onBodyRv, std::string const& s, error_code ev)
{
using boost::asio::buffer;
for_split(s,
@ -294,7 +295,7 @@ public:
void
bad(std::string const& s, error_code ev = {})
{
return bad<isRequest>(0, s, ev);
return bad<isRequest>(body_what::normal, s, ev);
}
//--------------------------------------------------------------------------
@ -658,7 +659,7 @@ public:
auto const length =
[&](std::string const& s, std::uint64_t v)
{
good<true>(1, s,
good<true>(body_what::skip, s,
[&](fail_parser<true> const& p)
{
BEAST_EXPECT(p.content_length() == v);
@ -757,7 +758,7 @@ public:
good<true>(m("Transfer-Encodings: 2\r\n"), flags{*this, 0});
good<true>(m("Transfer-Encoded: false\r\n"), flags{*this, 0});
bad<false>(1,
bad<false>(body_what::skip,
"HTTP/1.1 200 OK\r\n"
"Content-Length: 1\r\n"
"Transfer-Encoding: chunked\r\n"
@ -848,8 +849,8 @@ public:
{
error_code ec;
test::fail_counter fc(1000);
fail_parser<true> p(fc);
p.on_body_rv(2);
fail_parser<true> p{fc};
p.on_body_rv(body_what::upgrade);
p.write(buf(
"GET / HTTP/1.1\r\n"
"Content-Length: 1\r\n"
@ -1007,12 +1008,7 @@ public:
BEAST_EXPECT(p.complete());
}
bad<false>(3,
"HTTP/1.1 200 OK\r\n"
"Content-Length: 1\r\n"
"\r\n*", parse_error::bad_on_headers_rv);
bad<true>(0,
bad<true>(body_what::normal,
"GET / HTTP/1.1\r\n"
"Content-Length: 1\r\n"
"\r\n",

View File

@ -20,7 +20,7 @@ class fail_parser
{
test::fail_counter& fc_;
std::uint64_t content_length_ = no_content_length;
int body_rv_ = 0;
body_what body_rv_ = body_what::normal;
public:
std::string body;
@ -33,7 +33,7 @@ public:
}
void
on_body_rv(int rv)
on_body_rv(body_what rv)
{
body_rv_ = rv;
}
@ -85,10 +85,11 @@ public:
fc_.fail(ec);
}
int on_headers(std::uint64_t content_length, error_code& ec)
body_what
on_headers(std::uint64_t content_length, error_code& ec)
{
if(fc_.fail(ec))
return 0;
return body_what::normal;
content_length_ = content_length;
return body_rv_;
}

View File

@ -45,7 +45,6 @@ public:
check("http", parse_error::bad_value);
check("http", parse_error::bad_content_length);
check("http", parse_error::illegal_content_length);
check("http", parse_error::bad_on_headers_rv);
check("http", parse_error::invalid_chunk_size);
check("http", parse_error::short_read);
check("http", parse_error::general);