mirror of
https://github.com/boostorg/beast.git
synced 2025-07-31 05:17:26 +02:00
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:
@ -5,6 +5,7 @@
|
|||||||
* Fix on_headers called twice from basic_parser_v1
|
* Fix on_headers called twice from basic_parser_v1
|
||||||
* Constrain parser_v1 constructor
|
* Constrain parser_v1 constructor
|
||||||
* Improve first line serialization
|
* Improve first line serialization
|
||||||
|
* Add pause option to on_headers interface
|
||||||
* Refine Parser concept
|
* Refine Parser concept
|
||||||
|
|
||||||
API Changes:
|
API Changes:
|
||||||
|
@ -68,6 +68,7 @@
|
|||||||
</simplelist>
|
</simplelist>
|
||||||
<bridgehead renderas="sect3">Constants</bridgehead>
|
<bridgehead renderas="sect3">Constants</bridgehead>
|
||||||
<simplelist type="vert" columns="1">
|
<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>
|
<member><link linkend="beast.ref.http__connection">connection</link></member>
|
||||||
</simplelist>
|
</simplelist>
|
||||||
<bridgehead renderas="sect3">Concepts</bridgehead>
|
<bridgehead renderas="sect3">Concepts</bridgehead>
|
||||||
|
@ -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.
|
/// The value returned when no content length is known or applicable.
|
||||||
static std::uint64_t constexpr no_content_length =
|
static std::uint64_t constexpr no_content_length =
|
||||||
std::numeric_limits<std::uint64_t>::max();
|
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.
|
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.
|
Called when all the headers have been parsed successfully.
|
||||||
|
|
||||||
@ -151,18 +194,11 @@ static std::uint64_t constexpr no_content_length =
|
|||||||
would return `true`.
|
would 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. These are the return values:
|
or not the parser should expect a body. See @ref body_what for
|
||||||
|
choices of the return value.
|
||||||
@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.
|
|
||||||
|
|
||||||
The parser uses traits to determine if the callback is possible.
|
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`
|
skipped with no compilation error. The default behavior of `on_body`
|
||||||
when the derived class does not provide the member, is to specify that
|
when the derived class does not provide the member, is to specify that
|
||||||
the body should not be skipped.
|
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
|
@tparam isRequest A `bool` indicating whether the parser will be
|
||||||
presented with request or response message.
|
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>
|
template<bool isRequest, class Derived>
|
||||||
class basic_parser_v1 : public detail::parser_base
|
class basic_parser_v1 : public detail::parser_base
|
||||||
@ -626,7 +665,7 @@ private:
|
|||||||
template<class C>
|
template<class C>
|
||||||
class has_on_headers_t
|
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(
|
decltype(std::declval<T>().on_headers(
|
||||||
std::declval<std::uint64_t>(), std::declval<error_code&>()))>>
|
std::declval<std::uint64_t>(), std::declval<error_code&>()))>>
|
||||||
static R check(int);
|
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)
|
std::uint64_t content_length, std::true_type)
|
||||||
{
|
{
|
||||||
return impl().on_headers(content_length, ec);
|
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_,
|
return call_on_headers(ec, content_length_,
|
||||||
has_on_headers<Derived>{});
|
has_on_headers<Derived>{});
|
||||||
|
@ -899,21 +899,22 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
|
|||||||
return err(parse_error::illegal_content_length);
|
return err(parse_error::illegal_content_length);
|
||||||
upgrade_ = ((flags_ & (parse_flag::upgrade | parse_flag::connection_upgrade)) ==
|
upgrade_ = ((flags_ & (parse_flag::upgrade | parse_flag::connection_upgrade)) ==
|
||||||
(parse_flag::upgrade | parse_flag::connection_upgrade)) /*|| method == "connect"*/;
|
(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)
|
if(ec)
|
||||||
return errc();
|
return errc();
|
||||||
switch(maybe_skip)
|
switch(what)
|
||||||
{
|
{
|
||||||
case 0:
|
case body_what::normal:
|
||||||
break;
|
break;
|
||||||
case 2:
|
case body_what::upgrade:
|
||||||
upgrade_ = true;
|
upgrade_ = true;
|
||||||
// fall through
|
// fall through
|
||||||
case 1:
|
case body_what::skip:
|
||||||
flags_ |= parse_flag::skipbody;
|
flags_ |= parse_flag::skipbody;
|
||||||
break;
|
break;
|
||||||
default:
|
case body_what::pause:
|
||||||
return err(parse_error::bad_on_headers_rv);
|
s_ = s_headers_done;
|
||||||
|
return used();
|
||||||
}
|
}
|
||||||
s_ = s_headers_done;
|
s_ = s_headers_done;
|
||||||
// fall through
|
// fall through
|
||||||
|
@ -47,7 +47,6 @@ public:
|
|||||||
case parse_error::bad_value: return "bad field-value";
|
case parse_error::bad_value: return "bad field-value";
|
||||||
case parse_error::bad_content_length: return "bad Content-Length";
|
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::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_chunk_size: return "invalid chunk size";
|
||||||
case parse_error::invalid_ext_name: return "invalid ext name";
|
case parse_error::invalid_ext_name: return "invalid ext name";
|
||||||
case parse_error::invalid_ext_val: return "invalid ext val";
|
case parse_error::invalid_ext_val: return "invalid ext val";
|
||||||
|
@ -29,7 +29,6 @@ enum class parse_error
|
|||||||
bad_value,
|
bad_value,
|
||||||
bad_content_length,
|
bad_content_length,
|
||||||
illegal_content_length,
|
illegal_content_length,
|
||||||
bad_on_headers_rv,
|
|
||||||
|
|
||||||
invalid_chunk_size,
|
invalid_chunk_size,
|
||||||
invalid_ext_name,
|
invalid_ext_name,
|
||||||
|
@ -231,11 +231,14 @@ private:
|
|||||||
m_.reason = std::move(this->reason_);
|
m_.reason = std::move(this->reason_);
|
||||||
}
|
}
|
||||||
|
|
||||||
int on_headers(std::uint64_t, error_code&)
|
body_what
|
||||||
|
on_headers(std::uint64_t, error_code&)
|
||||||
{
|
{
|
||||||
flush();
|
flush();
|
||||||
m_.version = 10 * this->http_major() + this->http_minor();
|
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)
|
void on_request(error_code& ec)
|
||||||
|
@ -90,10 +90,11 @@ public:
|
|||||||
{
|
{
|
||||||
value = true;
|
value = true;
|
||||||
}
|
}
|
||||||
int on_headers(std::uint64_t, error_code&)
|
body_what
|
||||||
|
on_headers(std::uint64_t, error_code&)
|
||||||
{
|
{
|
||||||
headers = true;
|
headers = true;
|
||||||
return 0;
|
return body_what::normal;
|
||||||
}
|
}
|
||||||
void on_body(boost::string_ref const&, error_code&)
|
void on_body(boost::string_ref const&, error_code&)
|
||||||
{
|
{
|
||||||
@ -194,7 +195,7 @@ public:
|
|||||||
|
|
||||||
template<bool isRequest, class F>
|
template<bool isRequest, class F>
|
||||||
void
|
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;
|
using boost::asio::buffer;
|
||||||
for_split(s,
|
for_split(s,
|
||||||
@ -237,12 +238,12 @@ public:
|
|||||||
void
|
void
|
||||||
good(std::string const& s, F const& f = {})
|
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>
|
template<bool isRequest>
|
||||||
void
|
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;
|
using boost::asio::buffer;
|
||||||
for_split(s,
|
for_split(s,
|
||||||
@ -294,7 +295,7 @@ public:
|
|||||||
void
|
void
|
||||||
bad(std::string const& s, error_code ev = {})
|
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 =
|
auto const length =
|
||||||
[&](std::string const& s, std::uint64_t v)
|
[&](std::string const& s, std::uint64_t v)
|
||||||
{
|
{
|
||||||
good<true>(1, s,
|
good<true>(body_what::skip, s,
|
||||||
[&](fail_parser<true> const& p)
|
[&](fail_parser<true> const& p)
|
||||||
{
|
{
|
||||||
BEAST_EXPECT(p.content_length() == v);
|
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-Encodings: 2\r\n"), flags{*this, 0});
|
||||||
good<true>(m("Transfer-Encoded: false\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"
|
"HTTP/1.1 200 OK\r\n"
|
||||||
"Content-Length: 1\r\n"
|
"Content-Length: 1\r\n"
|
||||||
"Transfer-Encoding: chunked\r\n"
|
"Transfer-Encoding: chunked\r\n"
|
||||||
@ -848,8 +849,8 @@ public:
|
|||||||
{
|
{
|
||||||
error_code ec;
|
error_code ec;
|
||||||
test::fail_counter fc(1000);
|
test::fail_counter fc(1000);
|
||||||
fail_parser<true> p(fc);
|
fail_parser<true> p{fc};
|
||||||
p.on_body_rv(2);
|
p.on_body_rv(body_what::upgrade);
|
||||||
p.write(buf(
|
p.write(buf(
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Content-Length: 1\r\n"
|
"Content-Length: 1\r\n"
|
||||||
@ -1007,12 +1008,7 @@ public:
|
|||||||
BEAST_EXPECT(p.complete());
|
BEAST_EXPECT(p.complete());
|
||||||
}
|
}
|
||||||
|
|
||||||
bad<false>(3,
|
bad<true>(body_what::normal,
|
||||||
"HTTP/1.1 200 OK\r\n"
|
|
||||||
"Content-Length: 1\r\n"
|
|
||||||
"\r\n*", parse_error::bad_on_headers_rv);
|
|
||||||
|
|
||||||
bad<true>(0,
|
|
||||||
"GET / HTTP/1.1\r\n"
|
"GET / HTTP/1.1\r\n"
|
||||||
"Content-Length: 1\r\n"
|
"Content-Length: 1\r\n"
|
||||||
"\r\n",
|
"\r\n",
|
||||||
|
@ -20,7 +20,7 @@ class fail_parser
|
|||||||
{
|
{
|
||||||
test::fail_counter& fc_;
|
test::fail_counter& fc_;
|
||||||
std::uint64_t content_length_ = no_content_length;
|
std::uint64_t content_length_ = no_content_length;
|
||||||
int body_rv_ = 0;
|
body_what body_rv_ = body_what::normal;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::string body;
|
std::string body;
|
||||||
@ -33,7 +33,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
on_body_rv(int rv)
|
on_body_rv(body_what rv)
|
||||||
{
|
{
|
||||||
body_rv_ = rv;
|
body_rv_ = rv;
|
||||||
}
|
}
|
||||||
@ -85,10 +85,11 @@ public:
|
|||||||
fc_.fail(ec);
|
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))
|
if(fc_.fail(ec))
|
||||||
return 0;
|
return body_what::normal;
|
||||||
content_length_ = content_length;
|
content_length_ = content_length;
|
||||||
return body_rv_;
|
return body_rv_;
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,6 @@ public:
|
|||||||
check("http", parse_error::bad_value);
|
check("http", parse_error::bad_value);
|
||||||
check("http", parse_error::bad_content_length);
|
check("http", parse_error::bad_content_length);
|
||||||
check("http", parse_error::illegal_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::invalid_chunk_size);
|
||||||
check("http", parse_error::short_read);
|
check("http", parse_error::short_read);
|
||||||
check("http", parse_error::general);
|
check("http", parse_error::general);
|
||||||
|
Reference in New Issue
Block a user