Parser concept, fixes:

A new concept Parser is introduced with routines to read from a stream
into the parser. This solves a problem with the old read interface where
messages must be default constructible and move assignable.

Parser fixes:

* Fix detect invalid reason-phrase octets
* Fix write_eof to set the 'complete' state on success
* Fix consider parse complete if eof received on empty body

WebSocket:

* Increase coverage
This commit is contained in:
Vinnie Falco
2016-04-30 10:29:39 -04:00
parent 8921da91b8
commit 2a8de0fd6b
28 changed files with 1536 additions and 701 deletions

View File

@@ -855,8 +855,11 @@ std::size_t
read_size_helper(basic_streambuf<
Allocator> const& streambuf, std::size_t max_size)
{
return std::min<std::size_t>(max_size,
std::max<std::size_t>(512, streambuf.prepare_size()));
auto const avail = streambuf.prepare_size();
if(avail == 0)
return std::min(max_size,
std::max<std::size_t>(512, streambuf.alloc_size_));
return std::min(max_size, avail);
}
template<class Alloc, class T>

View File

@@ -36,11 +36,26 @@ enum values
};
} // parse_flag
/** Base class for parsing HTTP/1 requests and responses.
/** A parser for decoding HTTP/1 wire format messages.
During parsing, callbacks will be made to the derived class
if those members are present (detected through SFINAE). The
signatures which can be present in the derived class are:<br>
This parser is designed to efficiently parse messages in the
HTTP/1 wire format. It allocates no memory and uses minimal
state. It will handle chunked encoding and it understands the
semantics of the Connection and Content-Length header fields.
The interface uses CRTP (Curiously Recurring Template Pattern).
To use this class, derive from basic_parser. When bytes are
presented, the implementation will make a series of zero or
more calls to derived class members functions (referred to as
"callbacks" from here on) matching a specific signature.
Callbacks are detected through SFINAE. The derived class may
implement as few or as many of the members as needed.
These are the signatures of the callbacks:<br>
@li `void on_start(error_code&)`
Called when the first valid octet of a new message is received
@li `void on_method(boost::string_ref const&, error_code&)`
@@ -106,6 +121,9 @@ enum values
If a callback sets an error, parsing stops at the current octet
and the error is returned to the caller.
@tparam isRequest A `bool` indicating whether the parser will be
presented with request or response message.
*/
template<bool isRequest, class Derived>
class basic_parser_v1
@@ -188,7 +206,8 @@ private:
s_chunk_data_done,
s_complete,
s_restart
s_restart,
s_closed_complete
};
enum field_state : std::uint8_t
@@ -341,7 +360,7 @@ public:
bool
complete() const
{
return s_ == s_restart;
return s_ == s_restart || s_ == s_closed_complete;
}
/** Write a sequence of buffers to the parser.
@@ -411,6 +430,24 @@ private:
bool
needs_eof(std::false_type) const;
template<class C>
class has_on_start_t
{
template<class T, class R =
decltype(std::declval<T>().on_start(
std::declval<error_code&>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_start =
std::integral_constant<bool, has_on_start_t<C>::value>;
template<class C>
class has_on_method_t
{
@@ -596,6 +633,20 @@ private:
using has_on_complete =
std::integral_constant<bool, has_on_complete_t<C>::value>;
void call_on_start(error_code& ec, std::true_type)
{
impl().on_start(ec);
}
void call_on_start(error_code& ec, std::false_type)
{
}
void call_on_start(error_code& ec)
{
call_on_start(ec, has_on_start<Derived>{});
}
void call_on_method(error_code& ec,
boost::string_ref const& s, std::true_type)
{

View File

@@ -132,7 +132,7 @@ to_value_char(char c)
}
inline
std::uint8_t
std::int8_t
unhex(char c)
{
static std::array<std::int8_t, 256> constexpr tab = {{

View File

@@ -9,6 +9,7 @@
#define BEAST_HTTP_IMPL_BASIC_PARSER_V1_IPP
#include <beast/core/buffer_concepts.hpp>
#include <cassert>
namespace beast {
namespace http {
@@ -88,6 +89,11 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
s_ = s_closed;
return used();
};
auto errc = [&]
{
s_ = s_closed;
return used();
};
auto piece = [&]
{
return boost::string_ref{
@@ -113,6 +119,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
switch(s_)
{
case s_closed:
case s_closed_complete:
return err(parse_error::connection_closed);
break;
@@ -126,6 +133,9 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
case s_req_method_start:
if(! is_token(ch))
return err(parse_error::bad_method);
call_on_start(ec);
if(ec)
return errc();
cb_ = &self::call_on_method;
s_ = s_req_method;
break;
@@ -134,7 +144,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
if(! is_token(ch))
{
if(cb(nullptr))
return used();
return errc();
s_ = s_req_space_before_url;
goto redo;
}
@@ -147,21 +157,23 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
break;
case s_req_url_start:
{
if(ch == ' ')
return err(parse_error::bad_uri);
// VFALCO TODO Better checking for valid URL characters
if(! is_text(ch))
return err(parse_error::bad_uri);
if(cb(&self::call_on_uri))
return used();
assert(! cb_);
cb(&self::call_on_uri);
s_ = s_req_url;
break;
}
case s_req_url:
if(ch == ' ')
{
if(cb(nullptr))
return used();
return errc();
s_ = s_req_http_start;
break;
}
@@ -245,7 +257,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
return err(parse_error::bad_crlf);
call_on_request(ec);
if(ec)
return used();
return errc();
s_ = s_header_field_start;
break;
@@ -257,7 +269,14 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
content_length_ = no_content_length;
switch(ch)
{
case 'H': s_ = s_res_H; break;
case 'H':
call_on_start(ec);
if(ec)
return errc();
s_ = s_res_H;
break;
// VFALCO NOTE this allows whitespace at the beginning,
// need to check rfc7230
case '\r':
case '\n':
break;
@@ -365,13 +384,16 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
s_ = s_res_line_almost_done;
break;
}
// VFALCO Is this up to spec?
if(ch == '\n')
{
s_ = s_header_field_start;
break;
}
if(! is_text(ch))
return err(parse_error::bad_status);
if(cb(&self::call_on_reason))
return used();
return errc();
pos_ = 0;
s_ = s_res_status;
break;
@@ -380,17 +402,19 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
if(ch == '\r')
{
if(cb(nullptr))
return used();
return errc();
s_ = s_res_line_almost_done;
break;
}
if(ch == '\n')
{
if(cb(nullptr))
return used();
return errc();
s_ = s_header_field_start;
break;
}
if(! is_text(ch))
return err(parse_error::bad_status);
break;
case s_res_line_almost_done:
@@ -402,7 +426,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
case s_res_line_done:
call_on_response(ec);
if(ec)
return used();
return errc();
s_ = s_header_field_start;
goto redo;
@@ -431,8 +455,8 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
fs_ = h_general;
break;
}
if(cb(&self::call_on_field))
return used();
assert(! cb_);
cb(&self::call_on_field);
s_ = s_header_field;
break;
}
@@ -529,7 +553,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
if(ch == ':')
{
if(cb(nullptr))
return used();
return errc();
s_ = s_header_value_start;
break;
}
@@ -579,7 +603,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
}
call_on_value(ec, boost::string_ref{"", 0});
if(ec)
return used();
return errc();
s_ = s_header_field_start;
goto redo;
@@ -629,7 +653,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
}
pos_ = 0;
if(cb(&self::call_on_value))
return used();
return errc();
s_ = s_header_value_text;
break;
}
@@ -641,7 +665,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
if(ch == '\r')
{
if(cb(nullptr))
return used();
return errc();
s_ = s_header_value_discard_lWs;
break;
}
@@ -775,9 +799,9 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
return err(parse_error::bad_value);
call_on_value(ec, boost::string_ref(" ", 1));
if(ec)
return used();
return errc();
if(cb(&self::call_on_value))
return used();
return errc();
s_ = s_header_value_text;
break;
@@ -811,7 +835,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
return err(parse_error::bad_crlf);
if(flags_ & parse_flag::trailing)
{
//if(cb(&self::call_on_chunk_complete)) return used();
//if(cb(&self::call_on_chunk_complete)) return errc();
s_ = s_complete;
goto redo;
}
@@ -821,7 +845,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
(parse_flag::upgrade | parse_flag::connection_upgrade)) /*|| method == "connect"*/;
auto const maybe_skip = call_on_headers(ec);
if(ec)
return used();
return errc();
switch(maybe_skip)
{
case 0: break;
@@ -839,7 +863,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
assert(! cb_);
call_on_headers(ec);
if(ec)
return used();
return errc();
bool const hasBody =
(flags_ & parse_flag::chunked) || (content_length_ > 0 &&
content_length_ != no_content_length);
@@ -878,8 +902,8 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
}
case s_body_identity0:
if(cb(&self::call_on_body))
return used();
assert(! cb_);
cb(&self::call_on_body);
s_ = s_body_identity;
goto redo; // VFALCO fall through?
@@ -903,8 +927,8 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
}
case s_body_identity_eof0:
if(cb(&self::call_on_body))
return used();
assert(! cb_);
cb(&self::call_on_body);
s_ = s_body_identity_eof;
goto redo; // VFALCO fall through?
@@ -963,13 +987,13 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
s_ = s_header_field_start;
break;
}
//call_chunk_header(ec); if(ec) return used();
//call_chunk_header(ec); if(ec) return errc();
s_ = s_chunk_data_start;
break;
case s_chunk_data_start:
if(cb(&self::call_on_body))
return used();
assert(! cb_);
cb(&self::call_on_body);
s_ = s_chunk_data;
goto redo; // VFALCO fall through?
@@ -991,7 +1015,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
if(ch != '\r')
return err(parse_error::bad_crlf);
if(cb(nullptr))
return used();
return errc();
s_ = s_chunk_data_done;
break;
@@ -1005,10 +1029,10 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
case s_complete:
++p;
if(cb(nullptr))
return used();
return errc();
call_on_complete(ec);
if(ec)
return used();
return errc();
s_ = s_restart;
return used();
@@ -1024,7 +1048,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
{
(this->*cb_)(ec, piece());
if(ec)
return used();
return errc();
}
return used();
}
@@ -1036,17 +1060,31 @@ write_eof(error_code& ec)
{
switch(s_)
{
case s_restart:
s_ = s_closed_complete;
break;
case s_closed:
case s_closed_complete:
break;
case s_body_identity_eof0:
case s_body_identity_eof:
cb_ = nullptr;
call_on_complete(ec);
if(ec)
return;
return;
{
s_ = s_closed;
break;
}
s_ = s_closed_complete;
break;
default:
s_ = s_closed;
ec = parse_error::short_read;
break;
}
ec = parse_error::short_read;
s_ = s_closed;
}
template<bool isRequest, class Derived>

View File

@@ -9,6 +9,7 @@
#define BEAST_HTTP_IMPL_READ_IPP_HPP
#include <beast/http/parser_v1.hpp>
#include <beast/http/type_check.hpp>
#include <beast/core/bind_handler.hpp>
#include <beast/core/handler_alloc.hpp>
#include <beast/core/stream_concepts.hpp>
@@ -19,6 +20,185 @@ namespace http {
namespace detail {
template<class Stream,
class Streambuf, class Parser, class Handler>
class parse_op
{
using alloc_type =
handler_alloc<char, Handler>;
struct data
{
Stream& s;
Streambuf& sb;
Parser& p;
Handler h;
bool started = false;
bool cont;
int state = 0;
template<class DeducedHandler>
data(DeducedHandler&& h_, Stream& s_,
Streambuf& sb_, Parser& p_)
: s(s_)
, sb(sb_)
, p(p_)
, h(std::forward<DeducedHandler>(h_))
, cont(boost_asio_handler_cont_helpers::
is_continuation(h))
{
}
};
std::shared_ptr<data> d_;
public:
parse_op(parse_op&&) = default;
parse_op(parse_op const&) = default;
template<class DeducedHandler, class... Args>
parse_op(DeducedHandler&& h, Stream& s, Args&&... args)
: d_(std::allocate_shared<data>(alloc_type{h},
std::forward<DeducedHandler>(h), s,
std::forward<Args>(args)...))
{
(*this)(error_code{}, 0, false);
}
void
operator()(error_code ec,
std::size_t bytes_transferred, bool again = true);
friend
void* asio_handler_allocate(
std::size_t size, parse_op* op)
{
return boost_asio_handler_alloc_helpers::
allocate(size, op->d_->h);
}
friend
void asio_handler_deallocate(
void* p, std::size_t size, parse_op* op)
{
return boost_asio_handler_alloc_helpers::
deallocate(p, size, op->d_->h);
}
friend
bool asio_handler_is_continuation(parse_op* op)
{
return op->d_->cont;
}
template <class Function>
friend
void asio_handler_invoke(Function&& f, parse_op* op)
{
return boost_asio_handler_invoke_helpers::
invoke(f, op->d_->h);
}
};
template<class Stream,
class Streambuf, class Parser, class Handler>
void
parse_op<Stream, Streambuf, Parser, Handler>::
operator()(error_code ec, std::size_t bytes_transferred, bool again)
{
auto& d = *d_;
d.cont = d.cont || again;
while(d.state != 99)
{
switch(d.state)
{
case 0:
{
auto const used =
d.p.write(d.sb.data(), ec);
if(ec)
{
// call handler
d.state = 99;
d.s.get_io_service().post(
bind_handler(std::move(*this), ec, 0));
return;
}
if(used > 0)
d.started = true;
d.sb.consume(used);
if(d.p.complete())
{
// call handler
d.state = 99;
d.s.get_io_service().post(
bind_handler(std::move(*this), ec, 0));
return;
}
d.state = 1;
break;
}
case 1:
// read
d.state = 2;
d.s.async_read_some(d.sb.prepare(
read_size_helper(d.sb, 65536)),
std::move(*this));
return;
// got data
case 2:
{
if(ec == boost::asio::error::eof)
{
if(! d.started)
{
// call handler
d.state = 99;
break;
}
// Caller will see eof on next read.
ec = {};
d.p.write_eof(ec);
assert(ec || d.p.complete());
// call handler
d.state = 99;
break;
}
if(ec)
{
// call handler
d.state = 99;
break;
}
d.sb.commit(bytes_transferred);
auto const used = d.p.write(d.sb.data(), ec);
if(ec)
{
// call handler
d.state = 99;
break;
}
if(used > 0)
d.started = true;
d.sb.consume(used);
if(d.p.complete())
{
// call handler
d.state = 99;
break;
}
d.state = 1;
break;
}
}
}
d.h(ec);
}
//------------------------------------------------------------------------------
template<class Stream, class Streambuf,
bool isRequest, class Body, class Headers,
class Handler>
@@ -69,12 +249,11 @@ public:
std::forward<DeducedHandler>(h), s,
std::forward<Args>(args)...))
{
(*this)(error_code{}, 0, false);
(*this)(error_code{}, false);
}
void
operator()(error_code ec,
std::size_t bytes_transferred, bool again = true);
operator()(error_code ec, bool again = true);
friend
void* asio_handler_allocate(
@@ -112,98 +291,25 @@ template<class Stream, class Streambuf,
class Handler>
void
read_op<Stream, Streambuf, isRequest, Body, Headers, Handler>::
operator()(error_code ec, std::size_t bytes_transferred, bool again)
operator()(error_code ec, bool again)
{
auto& d = *d_;
d.cont = d.cont || again;
while(d.state != 99)
while(! ec && d.state != 99)
{
switch(d.state)
{
case 0:
{
auto const used =
d.p.write(d.sb.data(), ec);
if(ec)
{
// call handler
d.state = 99;
d.s.get_io_service().post(
bind_handler(std::move(*this), ec, 0));
return;
}
if(used > 0)
d.started = true;
d.sb.consume(used);
if(d.p.complete())
{
// call handler
d.state = 99;
d.m = d.p.release();
d.s.get_io_service().post(
bind_handler(std::move(*this), ec, 0));
return;
}
d.state = 1;
break;
}
case 1:
// read
d.state = 2;
d.s.async_read_some(d.sb.prepare(
read_size_helper(d.sb, 65536)),
std::move(*this));
async_parse(d.s, d.sb, d.p, std::move(*this));
return;
// got data
case 2:
{
if(ec == boost::asio::error::eof)
{
if(! d.started)
{
// call handler
d.state = 99;
break;
}
// Caller will see eof on next read.
ec = {};
d.p.write_eof(ec);
if(! ec)
{
assert(d.p.complete());
d.m = d.p.release();
}
// call handler
d.state = 99;
break;
}
if(ec)
{
// call handler
d.state = 99;
break;
}
d.sb.commit(bytes_transferred);
d.sb.consume(d.p.write(d.sb.data(), ec));
if(ec)
{
// call handler
d.state = 99;
break;
}
if(d.p.complete())
{
// call handler
d.state = 99;
d.m = d.p.release();
break;
}
d.state = 1;
case 1:
// call handler
d.state = 99;
d.m = d.p.release();
break;
}
}
}
d.h(ec);
}
@@ -212,12 +318,91 @@ operator()(error_code ec, std::size_t bytes_transferred, bool again)
//------------------------------------------------------------------------------
template<class SyncReadStream, class Streambuf, class Parser>
void
parse(SyncReadStream& stream,
Streambuf& streambuf, Parser& parser)
{
error_code ec;
parse(stream, streambuf, parser, ec);
if(ec)
throw boost::system::system_error{ec};
}
template<class SyncReadStream, class Streambuf, class Parser>
void
parse(SyncReadStream& stream, Streambuf& streambuf,
Parser& parser, error_code& ec)
{
static_assert(is_SyncReadStream<SyncReadStream>::value,
"SyncReadStream requirements not met");
static_assert(is_Streambuf<Streambuf>::value,
"Streambuf requirements not met");
static_assert(is_Parser<Parser>::value,
"Parser requirements not met");
bool started = false;
for(;;)
{
auto used =
parser.write(streambuf.data(), ec);
if(ec)
return;
streambuf.consume(used);
if(used > 0)
started = true;
if(parser.complete())
break;
streambuf.commit(stream.read_some(
streambuf.prepare(read_size_helper(
streambuf, 65536)), ec));
if(ec && ec != boost::asio::error::eof)
return;
if(ec == boost::asio::error::eof)
{
if(! started)
return;
// Caller will see eof on next read.
ec = {};
parser.write_eof(ec);
if(ec)
return;
assert(parser.complete());
break;
}
}
}
template<class AsyncReadStream,
class Streambuf, class Parser, class ReadHandler>
typename async_completion<
ReadHandler, void(error_code)>::result_type
async_parse(AsyncReadStream& stream,
Streambuf& streambuf, Parser& parser, ReadHandler&& handler)
{
static_assert(is_AsyncReadStream<AsyncReadStream>::value,
"AsyncReadStream requirements not met");
static_assert(is_Streambuf<Streambuf>::value,
"Streambuf requirements not met");
static_assert(is_Parser<Parser>::value,
"Parser requirements not met");
beast::async_completion<ReadHandler,
void(error_code)> completion(handler);
detail::parse_op<AsyncReadStream, Streambuf,
Parser, decltype(completion.handler)>{
completion.handler, stream, streambuf, parser};
return completion.result.get();
}
template<class SyncReadStream, class Streambuf,
bool isRequest, class Body, class Headers>
void
read(SyncReadStream& stream, Streambuf& streambuf,
message_v1<isRequest, Body, Headers>& msg)
{
static_assert(is_SyncReadStream<SyncReadStream>::value,
"SyncReadStream requirements not met");
static_assert(is_Streambuf<Streambuf>::value,
"Streambuf requirements not met");
error_code ec;
read(stream, streambuf, msg, ec);
if(ec)
@@ -236,40 +421,11 @@ read(SyncReadStream& stream, Streambuf& streambuf,
static_assert(is_Streambuf<Streambuf>::value,
"Streambuf requirements not met");
parser_v1<isRequest, Body, Headers> p;
bool started = false;
for(;;)
{
auto used =
p.write(streambuf.data(), ec);
if(ec)
return;
streambuf.consume(used);
if(used > 0)
started = true;
if(p.complete())
{
m = p.release();
break;
}
streambuf.commit(stream.read_some(
streambuf.prepare(read_size_helper(
streambuf, 65536)), ec));
if(ec && ec != boost::asio::error::eof)
return;
if(ec == boost::asio::error::eof)
{
if(! started)
return;
// Caller will see eof on next read.
ec = {};
p.write_eof(ec);
if(ec)
return;
assert(p.complete());
m = p.release();
break;
}
}
parse(stream, streambuf, p, ec);
if(ec)
return;
assert(p.complete());
m = p.release();
}
template<class AsyncReadStream, class Streambuf,

View File

@@ -11,7 +11,6 @@
#include <beast/http/basic_parser_v1.hpp>
#include <beast/http/message_v1.hpp>
#include <beast/core/error.hpp>
#include <boost/optional.hpp>
#include <functional>
#include <string>
#include <type_traits>
@@ -39,6 +38,8 @@ struct parser_response
This class uses the basic HTTP/1 wire format parser to convert
a series of octets into a `message_v1`.
@note A new instance of the parser is required for each message.
*/
template<bool isRequest, class Body, class Headers>
class parser_v1
@@ -47,9 +48,12 @@ class parser_v1
, private std::conditional<isRequest,
detail::parser_request, detail::parser_response>::type
{
public:
/// The type of message this parser produces.
using message_type =
message_v1<isRequest, Body, Headers>;
private:
std::string field_;
std::string value_;
message_type m_;
@@ -57,15 +61,55 @@ class parser_v1
public:
parser_v1(parser_v1&&) = default;
parser_v1(parser_v1 const&) = delete;
parser_v1& operator=(parser_v1&&) = delete;
parser_v1& operator=(parser_v1 const&) = delete;
parser_v1()
: r_(m_)
/** Construct the parser.
@param args A list of arguments forwarded to the message constructor.
*/
template<class... Args>
explicit
parser_v1(Args&&... args)
: m_(std::forward<Args>(args)...)
, r_(m_)
{
}
/** Returns the parsed message.
Only valid if `complete()` would return `true`.
*/
message_type const&
get() const
{
return m_;
}
/** Returns the parsed message.
Only valid if `complete()` would return `true`.
*/
message_type&
get()
{
return m_;
}
/** Returns the parsed message.
Ownership is transferred to the caller.
Only valid if `complete()` would return `true`.
Requires:
`message<isRequest, Body, Headers>` is MoveConstructible
*/
message_type
release()
{
static_assert(std::is_move_constructible<decltype(m_)>::value,
"MoveConstructible requirements not met");
return std::move(m_);
}
@@ -84,6 +128,10 @@ private:
}
}
void on_start(error_code&)
{
}
void on_method(boost::string_ref const& s, error_code&)
{
this->method_.append(s.data(), s.size());

View File

@@ -17,6 +17,127 @@
namespace beast {
namespace http {
/** Parse a HTTP/1 message from a stream.
This function synchronously reads from a stream and passes
data to the specified parser. The call will block until one
of the following conditions are met:
@li A complete message is read in.
@li An error occurs in the stream or parser.
This function is implemented in terms of one or more calls
to the stream's `read_some` function. The implementation may
read additional octets that lie past the end of the message
being parsed. This additional data is stored in the stream
buffer, which may be used in subsequent calls.
@param stream The stream from which the data is to be read.
The type must support the @b `SyncReadStream` concept.
@param streambuf A `Streambuf` holding additional bytes
read by the implementation from the stream. This is both
an input and an output parameter; on entry, any data in the
stream buffer's input sequence will be given to the parser
first.
@param parser An object meeting the requirements of Parser
which will receive the data.
@throws boost::system::system_error on failure.
*/
template<class SyncReadStream, class Streambuf, class Parser>
void
parse(SyncReadStream& stream,
Streambuf& streambuf, Parser& parser);
/** Parse a HTTP/1 message from a stream.
This function synchronously reads from a stream and passes
data to the specified parser. The call will block until one
of the following conditions are met:
@li A complete message is read in.
@li An error occurs in the stream or parser.
This function is implemented in terms of one or more calls
to the stream's `read_some` function. The implementation may
read additional octets that lie past the end of the message
being parsed. This additional data is stored in the stream
buffer, which may be used in subsequent calls.
@param stream The stream from which the data is to be read.
The type must support the @b `SyncReadStream` concept.
@param streambuf A `Streambuf` holding additional bytes
read by the implementation from the stream. This is both
an input and an output parameter; on entry, any data in the
stream buffer's input sequence will be given to the parser
first.
@param parser An object meeting the requirements of `Parser`
which will receive the data.
@param ec Set to the error, if any occurred.
*/
template<class SyncReadStream, class Streambuf, class Parser>
void
parse(SyncReadStream& stream,
Streambuf& streambuf, Parser& parser, error_code& ec);
/** Start an asynchronous operation to parse a HTTP/1 message from a stream.
This function is used to asynchronously read from a stream and
pass the data to the specified parser. The function call always
returns immediately. The asynchronous operation will continue
until one of the following conditions is true:
@li A complete message is read in.
@li An error occurs in the stream or parser.
This operation is implemented in terms of one or more calls to
the next layer's `async_read_some` function, and is known as a
<em>composed operation</em>. The program must ensure that the
stream performs no other operations until this operation completes.
@param stream The stream from which the data is to be read.
The type must support the @b `AsyncReadStream` concept.
@param streambuf A `Streambuf` holding additional bytes
read by the implementation from the stream. This is both
an input and an output parameter; on entry, any data in the
stream buffer's input sequence will be given to the parser
first.
@param parser An object meeting the requirements of `Parser`
which will receive the data. This object must remain valid
until the completion handler is invoked.
@param handler The handler to be called when the request completes.
Copies will be made of the handler as required. The equivalent
function signature of the handler must be:
@code void handler(
error_code const& error // result of operation
); @endcode
Regardless of whether the asynchronous operation completes
immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a
manner equivalent to using `boost::asio::io_service::post`.
*/
template<class AsyncReadStream,
class Streambuf, class Parser, class ReadHandler>
#if GENERATING_DOCS
void_or_deduced
#else
typename async_completion<
ReadHandler, void(error_code)>::result_type
#endif
async_parse(AsyncReadStream& stream, Streambuf& streambuf,
Parser& parser, ReadHandler&& handler);
/** Read a HTTP/1 message from a stream.
This function is used to synchronously read a message from
@@ -25,18 +146,22 @@ namespace http {
@li A complete message is read in.
@li An error occurs on the stream.
@li An error occurs in the stream or parser.
This function is implemented in terms of one or more calls to the
stream's `read_some` function.
This function is implemented in terms of one or more calls
to the stream's `read_some` function. The implementation may
read additional octets that lie past the end of the message
being parsed. This additional data is stored in the stream
buffer, which may be used in subsequent calls.
@param stream The stream to which the data is to be written.
@param stream The stream from which the data is to be read.
The type must support the @b `SyncReadStream` concept.
@param streambuf An object meeting the @b `Streambuf` type requirements
used to hold unread bytes. The implementation may read past the end of
the message. The extra bytes are stored here, to be presented in a
subsequent call to @ref read.
@param streambuf A `Streambuf` holding additional bytes
read by the implementation from the stream. This is both
an input and an output parameter; on entry, any data in the
stream buffer's input sequence will be given to the parser
first.
@param msg An object used to store the message. Any
contents will be overwritten.
@@ -57,21 +182,25 @@ read(SyncReadStream& stream, Streambuf& streambuf,
@li A complete message is read in.
@li An error occurs on the stream.
@li An error occurs in the stream or parser.
This function is implemented in terms of one or more calls to the
stream's `read_some` function.
This function is implemented in terms of one or more calls
to the stream's `read_some` function. The implementation may
read additional octets that lie past the end of the message
being parsed. This additional data is stored in the stream
buffer, which may be used in subsequent calls.
@param stream The stream to which the data is to be written.
@param stream The stream from which the data is to be read.
The type must support the @b `SyncReadStream` concept.
@param streambuf An object meeting the @b `Streambuf` type requirements
used to hold unread bytes. The implementation may read past the end of
the message. The extra bytes are stored here, to be presented in a
subsequent call to @ref read.
@param streambuf A `Streambuf` holding additional bytes
read by the implementation from the stream. This is both
an input and an output parameter; on entry, any data in the
stream buffer's input sequence will be given to the parser
first.
@param msg An object used to store the message. Any contents
will be overwritten.
@param msg An object used to store the message. Any
contents will be overwritten.
@param ec Set to the error, if any occurred.
*/
@@ -90,7 +219,7 @@ read(SyncReadStream& stream, Streambuf& streambuf,
@li A complete message is read in.
@li An error occurs on the stream.
@li An error occurs in the stream or parser.
This operation is implemented in terms of one or more calls to the
next layer's `async_read_some` function, and is known as a
@@ -100,10 +229,11 @@ read(SyncReadStream& stream, Streambuf& streambuf,
@param stream The stream to read the message from.
The type must support the @b `AsyncReadStream` concept.
@param streambuf A Streambuf used to hold unread bytes. The
implementation may read past the end of the message. The extra
bytes are stored here, to be presented in a subsequent call to
@ref async_read.
@param streambuf A `Streambuf` holding additional bytes
read by the implementation from the stream. This is both
an input and an output parameter; on entry, any data in the
stream buffer's input sequence will be given to the parser
first.
@param msg An object used to store the message. Any contents
will be overwritten.

View File

@@ -40,11 +40,9 @@ class is_Parser
static std::false_type check2(...);
using type2 = decltype(check2<T>(0));
template<class U, class R =
std::is_convertible<decltype(
std::declval<U>().write_eof(
std::declval<error_code&>())),
std::size_t>>
template<class U, class R = decltype(
std::declval<U>().write_eof(std::declval<error_code&>()),
std::true_type{})>
static R check3(int);
template<class>
static std::false_type check3(...);