Refactor HTTP serialization and parsing (API Change):

fix #404

Parsing and serialization interfaces have been fine tuned and unified.

For parsing these stream operations are defined:

* read
* read_header
* read_some
* async_read
* async_read_header
* async_read_some

For serialization these stream operations are defined:

* write
* write_header
* write_some
* async_write
* async_write_header
* async_write_some
This commit is contained in:
Vinnie Falco
2017-06-02 19:12:34 -07:00
parent 50cba32565
commit fe75a7ca2a
44 changed files with 3655 additions and 3250 deletions
+189 -351
View File
@@ -21,41 +21,6 @@
namespace beast {
namespace http {
/** Describes the parser's current state.
The state is expressed as the type of data that
@ref basic_parser is expecting to see in subsequently
provided octets.
*/
enum class parse_state
{
/// Expecting one or more header octets
header = 0,
/// Expecting one or more body octets
body = 1,
/// Expecting zero or more body octets followed by EOF
body_to_eof = 2,
/// Expecting additional chunk header octets
chunk_header = 3,
/// Expecting one or more chunk body octets
chunk_body = 4,
/** The parsing is complete.
The parse is considered complete when the full header
is received and either the full body is received, or
the semantics of the message indicate that no body
is expected. This includes the case where the caller
has indicated to the parser that no body is expected,
for example when receiving a response to a HEAD request.
*/
complete = 5
};
/** A parser for decoding HTTP/1 wire format messages.
This parser is designed to efficiently parse messages in the
@@ -83,18 +48,13 @@ enum class parse_state
struct derived
: basic_parser<isRequest, derived<isRequest>>
{
// The type used when providing a mutable
// buffer sequence in which to store body data.
//
using mutable_buffers_type = ...;
// When isRequest == true, called
// after the Request Line is received.
//
void
on_request(
string_view const& method,
string_view const& target,
string_view method,
string_view target,
int version,
error_code& ec);
@@ -104,7 +64,7 @@ enum class parse_state
void
on_response(
int status,
string_view const& reason,
string_view reason,
int version,
error_code& ec);
@@ -112,8 +72,8 @@ enum class parse_state
//
void
on_field(
string_view const& name,
string_view const& value,
string_view name,
string_view value,
error_code& ec);
// Called after the header is complete.
@@ -123,40 +83,19 @@ enum class parse_state
error_code& ec);
// Called once before the body, if any, is started.
// This will only be called if the semantics of the
// message indicate that a body exists, including
// an indicated body of zero length.
//
void
on_body();
on_body(
boost::optional<std::uint64_t> content_length,
error_code& ec);
// Called zero or more times to provide body data.
//
// Only used if isDirect == false
//
void
on_data(
string_view const& s,
string_view s,
error_code& ec);
// Called zero or more times to retrieve a mutable
// buffer sequence in which to store body data.
//
// Only used if isDirect == true
//
mutable_buffers_type
on_prepare(
std::size_t n);
// Called after body data has been stored in the
// buffer returned by the previous call to on_prepare.
//
// Only used if isDirect == true
//
void
on_commit(
std::size_t n);
// If the Transfer-Encoding is specified, and the
// last item in the list of encodings is "chunked",
// called after receiving a chunk header or a final
@@ -164,8 +103,8 @@ enum class parse_state
//
void
on_chunk(
std::uint64_t length, // Length of this chunk
string_view const& ext, // The chunk extensions, if any
std::uint64_t length, // Length of this chunk
string_view const& ext, // The chunk extensions, if any
error_code& ec);
// Called once when the message is complete.
@@ -177,16 +116,7 @@ enum class parse_state
@endcode
If a callback sets the error code, the error will be propagated
to the caller of the parser. Behavior of parsing after an error
is returned is undefined.
When the parser state is positioned to read bytes belonging to
the body, calling @ref write or @ref write will implicitly
cause a buffer copy (because bytes are first transferred to the
dynamic buffer). To avoid this copy, the additional functions
@ref copy_body, @ref prepare_body, and @ref commit_body are
provided to allow the caller to read bytes directly into buffers
supplied by the parser.
to the caller of the parser.
The parser is optimized for the case where the input buffer
sequence consists of a single contiguous buffer. The
@@ -203,26 +133,21 @@ enum class parse_state
@tparam isRequest A `bool` indicating whether the parser will be
presented with request or response message.
@tparam isDirect A `bool` indicating whether the parser interface
supports reading body data directly into parser-provided buffers.
@tparam Derived The derived class type. This is part of the
Curiously Recurring Template Pattern interface.
*/
template<bool isRequest, bool isDirect, class Derived>
template<bool isRequest, class Derived>
class basic_parser
: private detail::basic_parser_base
{
template<bool OtherIsRequest,
bool OtherIsDirect, class OtherDerived>
template<bool OtherIsRequest, class OtherDerived>
friend class basic_parser;
// Message will be complete after reading header
static unsigned constexpr flagSkipBody = 1<< 0;
static unsigned constexpr flagOnBody = 1<< 1;
// Consume input buffers across semantic boundaries
static unsigned constexpr flagEager = 1<< 1;
// The parser has read at least one byte
static unsigned constexpr flagGotSome = 1<< 2;
@@ -248,10 +173,8 @@ class basic_parser
std::size_t buf_len_ = 0;
std::size_t skip_ = 0; // search from here
std::size_t x_; // scratch variable
state state_ = state::nothing_yet;
unsigned f_ = 0; // flags
parse_state state_ = parse_state::header;
string_view ext_;
string_view body_;
public:
/// Copy constructor (disallowed)
@@ -264,7 +187,8 @@ public:
basic_parser() = default;
/// `true` if this parser parses requests, `false` for responses.
static bool constexpr is_request = isRequest;
using is_request =
std::integral_constant<bool, isRequest>;
/// Destructor
~basic_parser() = default;
@@ -274,83 +198,70 @@ public:
After the move, the only valid operation on the
moved-from object is destruction.
*/
template<bool OtherIsDirect, class OtherDerived>
basic_parser(basic_parser<
isRequest, OtherIsDirect, OtherDerived>&&);
template<class OtherDerived>
basic_parser(basic_parser<isRequest, OtherDerived>&&);
/** Set the skip body option.
/** Returns a reference to this object as a @ref basic_parser.
The option controls whether or not the parser expects to
see an HTTP body, regardless of the presence or absence of
certain fields such as Content-Length.
Depending on the request, some responses do not carry a body.
For example, a 200 response to a CONNECT request from a
tunneling proxy. In these cases, callers may use this function
inform the parser that no body is expected. The parser will
consider the message complete after the header has been received.
@note This function must called before any bytes are processed.
This is used to pass a derived class where a base class is
expected, to choose a correct function overload when the
resolution would be ambiguous.
*/
void
skip_body();
/** Returns the current parser state.
The parser state indicates what octets the parser
expects to see next in the input stream.
*/
parse_state
state() const
basic_parser&
base()
{
return state_;
return *this;
}
/** Returns a constant reference to this object as a @ref basic_parser.
This is used to pass a derived class where a base class is
expected, to choose a correct function overload when the
resolution would be ambiguous.
*/
basic_parser const&
base() const
{
return *this;
}
/// Returns `true` if the parser has received at least one byte of input.
bool
got_some() const
{
return (f_ & flagGotSome) != 0;
}
/// Returns `true` if the complete header has been parsed.
bool
got_header() const
{
return state_ != parse_state::header;
}
/** Returns `true` if a Content-Length is specified.
@note Only valid after parsing a complete header.
*/
bool
got_content_length() const
{
return (f_ & flagContentLength) != 0;
return state_ != state::nothing_yet;
}
/** Returns `true` if the message is complete.
The message is complete after a full header is
parsed and one of the following is true:
The message is complete after the full header is prduced
and one of the following is true:
@li @ref skip_body was called
@li The skip body option was set.
@li The semantics of the message indicate there is no body.
@li The semantics of the message indicate a body is
expected, and the entire body was received.
@li The semantics of the message indicate a body is expected,
and the entire body was parsed.
*/
bool
is_complete() const
is_done() const
{
return state_ == parse_state::complete;
return state_ == state::complete;
}
/** Returns `true` if a the parser has produced the full header.
*/
bool
is_header_done() const
{
return state_ > state::header;
}
/** Returns `true` if the message is an upgrade message.
@note Only valid after parsing a complete header.
@note The return value is undefined unless
@ref is_header_done would return `true`.
*/
bool
is_upgrade() const
@@ -358,16 +269,10 @@ public:
return (f_ & flagConnectionUpgrade) != 0;
}
/** Returns `true` if keep-alive is specified
/** Returns `true` if the last value for Transfer-Encoding is "chunked".
@note Only valid after parsing a complete header.
*/
bool
is_keep_alive() const;
/** Returns `true` if the chunked Transfer-Encoding is specified.
@note Only valid after parsing a complete header.
@note The return value is undefined unless
@ref is_header_done would return `true`.
*/
bool
is_chunked() const
@@ -375,34 +280,125 @@ public:
return (f_ & flagChunked) != 0;
}
/** Write part of a buffer sequence to the parser.
/** Returns `true` if the message has keep-alive connection semantics.
This function attempts to parse the HTTP message
stored in the caller provided buffers. Upon success,
a positive return value indicates that the parser
@note The return value is undefined unless
@ref is_header_done would return `true`.
*/
bool
is_keep_alive() const;
/** Returns the optional value of Content-Length if known.
@note The return value is undefined unless
@ref is_header_done would return `true`.
*/
boost::optional<std::uint64_t>
content_length() const;
/** Returns `true` if the message semantics require an end of file.
Depending on the contents of the header, the parser may
require and end of file notification to know where the end
of the body lies. If this function returns `true` it will be
necessary to call @ref put_eof when there will never be additional
data from the input.
*/
bool
need_eof() const
{
return (f_ & flagNeedEOF) != 0;
}
/// Returns `true` if the eager parse option is set.
bool
eager() const
{
return (f_ & flagEager) != 0;
}
/** Set the eager parse option.
This option controls whether or not the parser will attempt
to consume all of the octets provided during parsing, or
if it will stop when it reaches one of the following positions
within the serialized message:
@li Immediately after the header
@li Immediately after any chunk header
The default is to stop after the header or any chunk header.
*/
void
eager(bool v)
{
if(v)
f_ |= flagEager;
else
f_ &= ~flagEager;
}
/// Returns `true` if the parser will ignore the message body.
bool
skip()
{
return (f_ & flagSkipBody) != 0;
}
/** Set the skip body option.
The option controls whether or not the parser expects to
see an HTTP body, regardless of the presence or absence of
certain fields such as Content-Length.
Depending on the request, some responses do not carry a
body. For example, a 200 response to a CONNECT request
from a tunneling proxy. In these cases, callers may use
this function inform the parser that no body is expected.
The parser will consider the message complete after the
header has been received.
@note This function must called before any bytes are processed.
*/
void
skip(bool v);
/** Write a buffer sequence to the parser.
This function attempts to incrementally parse the HTTP
message data stored in the caller provided buffers. Upon
success, a positive return value indicates that the parser
made forward progress, consuming that number of
bytes.
A return value of zero indicates that the parser
requires additional input. In this case the caller
should append additional bytes to the input buffer
sequence and call @ref write again.
In some cases there may be an insufficient number of octets
in the input buffer in order to make forward progress. This
is indicated by the the code @ref error::need_more. When
this happens, the caller should place additional bytes into
the buffer sequence and call @ref put again.
The error code @ref error::need_more is special. When this
error is returned, a subsequent call to @ref put may succeed
if the buffers have been updated. Otherwise, upon error
the parser may not be restarted.
@param buffers An object meeting the requirements of
@b ConstBufferSequence that represents the message.
@param ec Set to the error, if any occurred.
@return The number of bytes consumed in the buffer
sequence.
@return The number of octets consumed in the buffer
sequence. The caller should remove these octets even if the
error is set.
*/
template<class ConstBufferSequence>
std::size_t
write(ConstBufferSequence const& buffers, error_code& ec);
put(ConstBufferSequence const& buffers, error_code& ec);
#if ! BEAST_DOXYGEN
std::size_t
write(boost::asio::const_buffers_1 const& buffer,
put(boost::asio::const_buffers_1 const& buffer,
error_code& ec);
#endif
@@ -423,139 +419,7 @@ public:
@param ec Set to the error, if any occurred.
*/
void
write_eof(error_code& ec);
/** Returns the number of bytes remaining in the body or chunk.
If a Content-Length is specified and the parser state
is equal to @ref beast::http::parse_state::body, this will return
the number of bytes remaining in the body. If the
chunked Transfer-Encoding is indicated and the parser
state is equal to @ref beast::http::parse_state::chunk_body, this
will return the number of bytes remaining in the chunk.
Otherwise, the function behavior is undefined.
*/
std::uint64_t
size() const
{
BOOST_ASSERT(
state_ == parse_state::body ||
state_ == parse_state::chunk_body);
return len_;
}
/** Returns the body data parsed in the last call to @ref write.
This buffer is invalidated after any call to @ref write
or @ref write_eof.
@note If the last call to @ref write came from the input
area of a @b DynamicBuffer object, a call to the dynamic
buffer's `consume` function may invalidate this return
value.
*/
string_view const&
body() const
{
// This function not available when isDirect==true
BOOST_STATIC_ASSERT(! isDirect);
return body_;
}
/** Returns the chunk extension parsed in the last call to @ref write.
This buffer is invalidated after any call to @ref write
or @ref write_eof.
@note If the last call to @ref write came from the input
area of a @b DynamicBuffer object, a call to the dynamic
buffer's `consume` function may invalidate this return
value.
*/
string_view const&
chunk_extension() const
{
// This function not available when isDirect==true
BOOST_STATIC_ASSERT(! isDirect);
return ext_;
}
/** Returns the optional value of Content-Length if known.
@note The return value is undefined unless a complete
header has been parsed.
*/
boost::optional<std::uint64_t>
content_length() const
{
BOOST_ASSERT(got_header());
if(! (f_ & flagContentLength))
return boost::none;
return len_;
}
/** Copy leftover body data from the dynamic buffer.
@note This member function is only available when
`isDirect==true`.
@return The number of bytes processed from the dynamic
buffer. The caller should remove these bytes by calling
`consume` on the buffer.
*/
template<class DynamicBuffer>
std::size_t
copy_body(DynamicBuffer& buffer);
/** Returns a set of buffers for storing body data.
@param buffers A writable output parameter into which
the function will place the buffers upon success.
@param limit The maximum number of bytes in the
size of the returned buffer sequence. The actual size
of the buffer sequence may be lower than this number.
@note This member function is only available when
`isDirect==true`.
*/
template<class MutableBufferSequence>
void
prepare_body(boost::optional<
MutableBufferSequence>& buffers, std::size_t limit);
/** Commit body data.
@param n The number of bytes to commit. This must
be less than or equal to the size of the buffer
sequence returned by @ref prepare_body.
@note This member function is only available when
`isDirect==true`.
*/
void
commit_body(std::size_t n);
/** Indicate that body octets have been consumed.
@param n The number of bytes to consume.
*/
void
consume(std::size_t n)
{
BOOST_ASSERT(n <= len_);
BOOST_ASSERT(
state_ == parse_state::body ||
state_ == parse_state::chunk_body);
len_ -= n;
if(len_ == 0)
{
if(state_ == parse_state::body)
state_ = parse_state::complete;
else
state_ = parse_state::chunk_header;
}
}
put_eof(error_code& ec);
private:
inline
@@ -570,68 +434,42 @@ private:
maybe_flatten(
ConstBufferSequence const& buffers);
std::size_t
do_write(boost::asio::const_buffers_1 const& buffer,
void
parse_header(char const*& p,
std::size_t n, error_code& ec);
void
parse_header(char const*& p, char const* term,
error_code& ec, std::true_type);
std::size_t
do_write(boost::asio::const_buffers_1 const& buffer,
void
parse_header(char const*& p, char const* term,
error_code& ec, std::false_type);
void
parse_startline(char const*& it,
int& version, int& status,
error_code& ec, std::true_type);
parse_body(char const*& p,
std::size_t n, error_code& ec);
void
parse_startline(char const*& it,
int& version, int& status,
error_code& ec, std::false_type);
parse_body_to_eof(char const*& p,
std::size_t n, error_code& ec);
void
parse_fields(char const*& it,
parse_chunk_header(char const*& p,
std::size_t n, error_code& ec);
void
parse_chunk_body(char const*& p,
std::size_t n, error_code& ec);
void
parse_fields(char const*& p,
char const* last, error_code& ec);
void
do_field(
string_view const& name,
string_view const& value,
error_code& ec);
std::size_t
parse_header(char const* p,
std::size_t n, error_code& ec);
void
do_header(int, std::true_type);
void
do_header(int status, std::false_type);
void
maybe_do_body_direct();
void
maybe_do_body_indirect(error_code& ec);
std::size_t
parse_chunk_header(char const* p,
std::size_t n, error_code& ec);
std::size_t
parse_body(char const* p,
std::size_t n, error_code& ec);
std::size_t
parse_body_to_eof(char const* p,
std::size_t n, error_code& ec);
std::size_t
parse_chunk_body(char const* p,
std::size_t n, error_code& ec);
void
do_complete(error_code& ec);
do_field(string_view const& name,
string_view const& value,
error_code& ec);
};
} // http