mirror of
https://github.com/boostorg/beast.git
synced 2025-07-30 12:57:31 +02:00
New HTTP interfaces (API Change):
fix #123 fix #154 fix #265 This completely replaces the HTTP parser used to read and parse incoming messages. The new version is faster than the old one, and written in fewer lines. A summary of changes: * parse and async_parse renamed to read and async_read * basic_parser is optimized for linear buffers, use flat_streambuf for the best performance with these functions: - http::read - http::read_some - http::async_read - http::async_read_some * The overloads of read() and async_read() which take just a header have been removed, since they would throw away important parse metadata. * The derived class callbacks for basic_parser have been streamlined. All strings passed to callbacks are presented in their entirety, instead of being provided in pieces. These changes allow use-cases that were previously difficult or impossible, such as: - Efficient relaying - Late body type commitment - Expect: 100-continue handling
This commit is contained in:
@ -6,6 +6,10 @@
|
||||
* Add flat_streambuf
|
||||
* Rename to BEAST_DOXYGEN
|
||||
|
||||
API Changes:
|
||||
|
||||
* New HTTP interfaces
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
1.0.0-b34
|
||||
|
@ -208,7 +208,7 @@ int main()
|
||||
r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"}));
|
||||
|
||||
// Send HTTP request using beast
|
||||
beast::http::request<beast::http::empty_body> req;
|
||||
beast::http::request<beast::http::string_body> req;
|
||||
req.method = "GET";
|
||||
req.url = "/";
|
||||
req.version = 11;
|
||||
|
@ -34,7 +34,7 @@ int main()
|
||||
r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"}));
|
||||
|
||||
// Send HTTP request using beast
|
||||
beast::http::request<beast::http::empty_body> req;
|
||||
beast::http::request<beast::http::string_body> req;
|
||||
req.method = "GET";
|
||||
req.url = "/";
|
||||
req.version = 11;
|
||||
|
16
doc/http.qbk
16
doc/http.qbk
@ -26,7 +26,6 @@ contents:
|
||||
Algorithms
|
||||
Write
|
||||
Read
|
||||
Parse
|
||||
Examples
|
||||
Send Request
|
||||
Receive Response
|
||||
@ -136,7 +135,7 @@ object:
|
||||
[[HTTP Request] [HTTP Response]]
|
||||
[[
|
||||
```
|
||||
request<empty_body> req;
|
||||
request<string_body> req;
|
||||
req.version = 11; // HTTP/1.1
|
||||
req.method = "GET";
|
||||
req.url = "/index.htm"
|
||||
@ -227,15 +226,6 @@ The message [*`Body`] template parameter controls both the type of the data
|
||||
member of the resulting message object, and the algorithms used during parsing
|
||||
and serialization. Beast provides three very common [*`Body`] types:
|
||||
|
||||
* [link beast.ref.http__empty_body [*`empty_body`:]] An empty message body.
|
||||
Used in GET requests where there is no message body. Example:
|
||||
```
|
||||
request<empty_body> req;
|
||||
req.version = 11;
|
||||
req.method = "GET";
|
||||
req.url = "/index.html";
|
||||
```
|
||||
|
||||
* [link beast.ref.http__string_body [*`string_body`:]] A body with a
|
||||
`value_type` as `std::string`. Useful for quickly putting together a request
|
||||
or response with simple text in the message body (such as an error message).
|
||||
@ -303,7 +293,7 @@ operations performed). To send messages synchronously, use one of the
|
||||
```
|
||||
void send_request(boost::asio::ip::tcp::socket& sock)
|
||||
{
|
||||
request<empty_body> req;
|
||||
request<string_body> req;
|
||||
req.version = 11;
|
||||
req.method = "GET";
|
||||
req.url = "/index.html";
|
||||
@ -322,7 +312,7 @@ An asynchronous interface is available:
|
||||
```
|
||||
void handle_write(boost::system::error_code);
|
||||
...
|
||||
request<empty_body> req;
|
||||
request<string_body> req;
|
||||
...
|
||||
async_write(sock, req, std::bind(&handle_write, std::placeholders::_1));
|
||||
```
|
||||
|
@ -107,7 +107,6 @@ provides implementations of the HTTP and WebSocket protocols.
|
||||
[include types/DynamicBuffer.qbk]
|
||||
[include types/Field.qbk]
|
||||
[include types/FieldSequence.qbk]
|
||||
[include types/Parser.qbk]
|
||||
[include types/Reader.qbk]
|
||||
[include types/Streams.qbk]
|
||||
[include types/Writer.qbk]
|
||||
|
@ -31,13 +31,12 @@
|
||||
<simplelist type="vert" columns="1">
|
||||
<member><link linkend="beast.ref.http__basic_dynabuf_body">basic_dynabuf_body</link></member>
|
||||
<member><link linkend="beast.ref.http__basic_fields">basic_fields</link></member>
|
||||
<member><link linkend="beast.ref.http__basic_parser_v1">basic_parser_v1</link></member>
|
||||
<member><link linkend="beast.ref.http__empty_body">empty_body</link></member>
|
||||
<member><link linkend="beast.ref.http__basic_parser">basic_parser</link></member>
|
||||
<member><link linkend="beast.ref.http__fields">fields</link></member>
|
||||
<member><link linkend="beast.ref.http__header">header</link></member>
|
||||
<member><link linkend="beast.ref.http__header_parser_v1">header_parser_v1</link></member>
|
||||
<member><link linkend="beast.ref.http__header_parser">header_parser</link></member>
|
||||
<member><link linkend="beast.ref.http__message">message</link></member>
|
||||
<member><link linkend="beast.ref.http__parser_v1">parser_v1</link></member>
|
||||
<member><link linkend="beast.ref.http__message_parser">message_parser</link></member>
|
||||
<member><link linkend="beast.ref.http__request">request</link></member>
|
||||
<member><link linkend="beast.ref.http__request_header">request_header</link></member>
|
||||
<member><link linkend="beast.ref.http__response">response</link></member>
|
||||
@ -49,6 +48,7 @@
|
||||
<simplelist type="vert" columns="1">
|
||||
|
||||
<member><link linkend="beast.ref.http__ext_list">ext_list</link></member>
|
||||
<member><link linkend="beast.ref.http__opt_token_list">opt_token_list</link></member>
|
||||
<member><link linkend="beast.ref.http__param_list">param_list</link></member>
|
||||
<member><link linkend="beast.ref.http__token_list">token_list</link></member>
|
||||
</simplelist>
|
||||
@ -57,7 +57,7 @@
|
||||
<bridgehead renderas="sect3">Functions</bridgehead>
|
||||
<simplelist type="vert" columns="1">
|
||||
<member><link linkend="beast.ref.http__async_read">async_read</link></member>
|
||||
<member><link linkend="beast.ref.http__async_parse">async_parse</link></member>
|
||||
<member><link linkend="beast.ref.http__async_read_some">async_read_some</link></member>
|
||||
<member><link linkend="beast.ref.http__async_write">async_write</link></member>
|
||||
<member><link linkend="beast.ref.http__chunk_encode">chunk_encode</link></member>
|
||||
<member><link linkend="beast.ref.http__chunk_encode_final">chunk_encode_final</link></member>
|
||||
@ -65,17 +65,15 @@
|
||||
<member><link linkend="beast.ref.http__is_keep_alive">is_keep_alive</link></member>
|
||||
<member><link linkend="beast.ref.http__is_upgrade">is_upgrade</link></member>
|
||||
<member><link linkend="beast.ref.http__operator_ls_">operator<<</link></member>
|
||||
<member><link linkend="beast.ref.http__parse">parse</link></member>
|
||||
<member><link linkend="beast.ref.http__prepare">prepare</link></member>
|
||||
<member><link linkend="beast.ref.http__read">read</link></member>
|
||||
<member><link linkend="beast.ref.http__read_some">read_some</link></member>
|
||||
<member><link linkend="beast.ref.http__reason_string">reason_string</link></member>
|
||||
<member><link linkend="beast.ref.http__with_body">with_body</link></member>
|
||||
<member><link linkend="beast.ref.http__write">write</link></member>
|
||||
</simplelist>
|
||||
<bridgehead renderas="sect3">Type Traits</bridgehead>
|
||||
<simplelist type="vert" columns="1">
|
||||
<member><link linkend="beast.ref.http__is_Body">is_Body</link></member>
|
||||
<member><link linkend="beast.ref.http__is_Parser">is_Parser</link></member>
|
||||
<member><link linkend="beast.ref.http__is_Reader">is_Reader</link></member>
|
||||
<member><link linkend="beast.ref.http__is_Writer">is_Writer</link></member>
|
||||
<member><link linkend="beast.ref.http__has_reader">has_reader</link></member>
|
||||
@ -83,26 +81,16 @@
|
||||
</simplelist>
|
||||
</entry>
|
||||
<entry valign="top">
|
||||
<bridgehead renderas="sect3">Options</bridgehead>
|
||||
<simplelist type="vert" columns="1">
|
||||
<member><link linkend="beast.ref.http__header_max_size">header_max_size</link></member>
|
||||
<member><link linkend="beast.ref.http__body_max_size">body_max_size</link></member>
|
||||
<member><link linkend="beast.ref.http__skip_body">skip_body</link></member>
|
||||
</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>
|
||||
<member><link linkend="beast.ref.http__no_content_length">no_content_length</link></member>
|
||||
<member><link linkend="beast.ref.http__parse_error">parse_error</link></member>
|
||||
<member><link linkend="beast.ref.http__parse_flag">parse_flag</link></member>
|
||||
<member><link linkend="beast.ref.http__error">error</link></member>
|
||||
</simplelist>
|
||||
<bridgehead renderas="sect3">Concepts</bridgehead>
|
||||
<simplelist type="vert" columns="1">
|
||||
<member><link linkend="beast.ref.Body">Body</link></member>
|
||||
<member><link linkend="beast.ref.Field">Field</link></member>
|
||||
<member><link linkend="beast.ref.FieldSequence">FieldSequence</link></member>
|
||||
<member><link linkend="beast.ref.Parser">Parser</link></member>
|
||||
<member><link linkend="beast.ref.Reader">Reader</link></member>
|
||||
<member><link linkend="beast.ref.Writer">Writer</link></member>
|
||||
</simplelist>
|
||||
|
@ -1,61 +0,0 @@
|
||||
[/
|
||||
Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
|
||||
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
]
|
||||
|
||||
[section:Parser Parser requirements]
|
||||
|
||||
A [*Parser] is used to deserialize objects from
|
||||
[link beast.ref.streams streams]. Objects of this type are used with
|
||||
[link beast.ref.http__parse http::parse] and
|
||||
[link beast.ref.http__async_parse http::async_parse]. The definition of
|
||||
an object, and the predicate defining when the parse is complete, are
|
||||
determined by the implementation.
|
||||
|
||||
In this table:
|
||||
|
||||
* `X` denotes a type meeting the requirements of [*Parser].
|
||||
|
||||
* `a` denotes a value of type `X`.
|
||||
|
||||
* `b` is a value meeting the requirements of __ConstBufferSequence__.
|
||||
|
||||
* `ec` is a value of type [link beast.ref.error_code `error_code&`].
|
||||
|
||||
[table Parser requirements
|
||||
[[operation] [type] [semantics, pre/post-conditions]]
|
||||
[
|
||||
[`a.complete()`]
|
||||
[`bool`]
|
||||
[
|
||||
Returns `true` when parsing is complete.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`a.write(b, ec)`]
|
||||
[`std::size_t`]
|
||||
[
|
||||
Sequentially parses the octets in the specified input buffer sequence
|
||||
until an error occurs, the end of the buffer is reached, or parsing is
|
||||
complete. Upon success, this function returns the number of bytes used
|
||||
from the input. If an error occurs, `ec` is set to the error code and
|
||||
parsing stops.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`a.write_eof(ec)`]
|
||||
[`void`]
|
||||
[
|
||||
Indicates to the parser that no more octets will be available.
|
||||
Typically this function is called when the end of stream is reached.
|
||||
For example, if a call to `boost::asio::ip::tcp::socket::read_some`
|
||||
generates a `boost::asio::error::eof` error. Some objects, such as
|
||||
certain HTTP/1 messages, determine the end of the message body by
|
||||
an end of file marker or closing of the connection.
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
[endsect]
|
@ -10,57 +10,198 @@
|
||||
Parsers provided by the implementation will construct the corresponding
|
||||
`reader` object during parsing. This customization point allows the
|
||||
Body to determine the strategy for storing incoming message body data.
|
||||
Readers come in two flavors, direct and indirect:
|
||||
|
||||
In this table:
|
||||
Direct readers provide a buffer to callers, in which body data is placed.
|
||||
This type of reader is used when the bytes corresponding to the body data
|
||||
are stored without transformation. The parse algorithm performs stream or
|
||||
socket reads directly into the reader-provided buffer, hence the name
|
||||
"direct." This model avoids an unnecessary buffer copy. An example of
|
||||
a [*Body] type with a direct reader is
|
||||
[link beast.ref.http__string_body `string_body`].
|
||||
|
||||
Indirect readers are passed body data in a buffer managed by the parser
|
||||
algorithm. This reader is appropriate when the body data is transformed
|
||||
or not otherwised stored verbatim. Some examples of when an indirect
|
||||
reader is appropriate:
|
||||
|
||||
* When bytes corresponding to the body are written to a file
|
||||
as they are parsed.
|
||||
|
||||
* The content of the message is JSON, which is parsed as it is
|
||||
being read in, and stored in a structured, hierarchical format.
|
||||
|
||||
In the tables below:
|
||||
|
||||
* `X` denotes a type meeting the requirements of [*`Reader`].
|
||||
|
||||
* `a` denotes a value of type `X`.
|
||||
|
||||
* `n` is a value convertible to `std::size_t`.
|
||||
* `n` is a value convertible to `std::size_t` without loss of precision.
|
||||
|
||||
* `p` is a `void const*` to valid memory of at least `n` bytes.
|
||||
* `v` is a value convertible to `std::uint64_t` without loss of precision.
|
||||
|
||||
* `s` is a value of type `boost::string_ref`.
|
||||
|
||||
* `ec` is a value of type [link beast.ref.error_code `error_code&`].
|
||||
|
||||
* `m` denotes a value of type `message&` where
|
||||
`std::is_same<decltype(m.body), Body::value_type>::value == true`.
|
||||
|
||||
[table Reader requirements
|
||||
[table Direct Reader requirements
|
||||
[[operation] [type] [semantics, pre/post-conditions]]
|
||||
[
|
||||
[`X a(m);`]
|
||||
[`X::is_direct`]
|
||||
[`bool`]
|
||||
[
|
||||
This static constant must be set to `true` to indicate that
|
||||
the reader is a direct reader.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`X::mutable_buffers_type`]
|
||||
[]
|
||||
[
|
||||
This member type must be present, and meet the requirements
|
||||
of [*MutableBufferSequence]. It represents the type of
|
||||
the writable buffers returned by the reader, in which
|
||||
bytes representing the body are stored by the implementation.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`X a{m};`]
|
||||
[]
|
||||
[
|
||||
`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`.
|
||||
just before parsing bytes corresponding to the body for messages
|
||||
whose semantics indicate that a body is present with non-zero
|
||||
length.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`a.init(ec)`]
|
||||
[`void`]
|
||||
[`a.init()`]
|
||||
[]
|
||||
[
|
||||
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`.
|
||||
This function is called once before any bytes corresponding
|
||||
to the body are presented to the reader, for messages whose
|
||||
body is determined by the end-of-file marker on a stream,
|
||||
or for messages where the chunked Transfer-Encoding is
|
||||
specified.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`a.write(p, n, ec)`]
|
||||
[`void`]
|
||||
[`a.init(v)`]
|
||||
[]
|
||||
[
|
||||
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`.
|
||||
This function is called once before any bytes corresponding
|
||||
to the body are presented to the reader, for messages where
|
||||
the Content-Length is specified. The value of `v` will be
|
||||
set to the number of bytes indicated by the content length.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`a.prepare(n)`]
|
||||
[`mutable_buffers_type`]
|
||||
[
|
||||
The implementation calls this function to obtain a mutable
|
||||
buffer sequence of up to `n` bytes in size in which to place
|
||||
data corresponding to the body. The buffer returned must
|
||||
be at least one byte in size, and may be smaller than `n`.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`a.commit(n)`]
|
||||
[]
|
||||
[
|
||||
The implementation calls this function to indicate to the
|
||||
reader that `n` bytes of data have been successfully placed
|
||||
into the buffer obtained through a prior call to `prepare`.
|
||||
The value of `n` will be less than or equal to the size of
|
||||
the buffer returned in the previous call to `prepare`.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`a.finish()`]
|
||||
[]
|
||||
[
|
||||
This function is called after all the bytes corresponding
|
||||
to the body have been written to the buffers and committed.
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
[table Indirect Reader requirements
|
||||
[[operation] [type] [semantics, pre/post-conditions]]
|
||||
[
|
||||
[`X::is_direct`]
|
||||
[`bool`]
|
||||
[
|
||||
This static constant must be set to `false` to indicate that
|
||||
the reader is an indirect reader.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`X a{m};`]
|
||||
[]
|
||||
[
|
||||
`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
|
||||
just before parsing bytes corresponding to the body for messages
|
||||
whose semantics indicate that a body is present with non-zero
|
||||
length.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`a.init(ec)`]
|
||||
[]
|
||||
[
|
||||
This function is called once before any bytes corresponding
|
||||
to the body are presented to the reader, for messages whose
|
||||
body is determined by the end-of-file market on a stream,
|
||||
or for messages where the chunked Transfer-Encoding is
|
||||
specified.
|
||||
If `ec` is set before returning, parsing will stop
|
||||
and the error will be returned to the caller.
|
||||
|
||||
]
|
||||
]
|
||||
[
|
||||
[`a.init(v,ec)`]
|
||||
[]
|
||||
[
|
||||
This function is called once before any bytes corresponding
|
||||
to the body are presented to the reader, for messages where
|
||||
the Content-Length is specified. The value of `v` will be
|
||||
set to the number of bytes indicated by the content length.
|
||||
If `ec` is set before returning, parsing will stop
|
||||
and the error will be returned to the caller.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`a.write(s,ec)`]
|
||||
[]
|
||||
[
|
||||
The implementation calls this function with `s` containing
|
||||
bytes corresponding to the body, after removing any present
|
||||
chunked encoding transformation.
|
||||
If `ec` is set before returning, parsing will stop
|
||||
and the error will be returned to the caller.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`a.finish(ec)`]
|
||||
[]
|
||||
[
|
||||
This function is called after all the bytes corresponding
|
||||
to the body have been written to the buffers and committed.
|
||||
If `ec` is set before returning, parsing will stop
|
||||
and the error will be returned to the caller.
|
||||
]
|
||||
]
|
||||
]
|
||||
[note
|
||||
Definitions for required `Reader` member functions should be declared
|
||||
inline so the generated code can become part of the implementation.
|
||||
|
@ -50,7 +50,7 @@ The WebSocket protocol is described fully in
|
||||
The interface to Beast's WebSocket implementation is a single template
|
||||
class [link beast.ref.websocket__stream `beast::websocket::stream`] which
|
||||
wraps a "next layer" object. The next layer object must meet the requirements
|
||||
of [link beast.ref.streams.SyncStream [*`SyncReadStream`]] if synchronous
|
||||
of [link beast.ref.streams.SyncStream [*`SyncStream`]] if synchronous
|
||||
operations are performed, or
|
||||
[link beast.ref.streams.AsyncStream [*`AsyncStream`]] if asynchronous
|
||||
operations are performed, or both. Arguments supplied during construction are
|
||||
|
@ -36,7 +36,7 @@ int main(int, char const*[])
|
||||
ip::tcp::socket sock(ios);
|
||||
connect(sock, it);
|
||||
auto ep = sock.remote_endpoint();
|
||||
request<empty_body> req;
|
||||
request<string_body> req;
|
||||
req.method = "GET";
|
||||
req.url = "/";
|
||||
req.version = 11;
|
||||
|
@ -22,7 +22,7 @@ int main()
|
||||
r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"}));
|
||||
|
||||
// Send HTTP request using beast
|
||||
beast::http::request<beast::http::empty_body> req;
|
||||
beast::http::request<beast::http::string_body> req;
|
||||
req.method = "GET";
|
||||
req.url = "/";
|
||||
req.version = 11;
|
||||
|
@ -34,7 +34,7 @@ int main()
|
||||
stream.handshake(ssl::stream_base::client);
|
||||
|
||||
// Send HTTP request over SSL using Beast
|
||||
beast::http::request<beast::http::empty_body> req;
|
||||
beast::http::request<beast::http::string_body> req;
|
||||
req.method = "GET";
|
||||
req.url = "/";
|
||||
req.version = 11;
|
||||
|
@ -10,17 +10,15 @@
|
||||
|
||||
#include <boost/range/algorithm/equal.hpp>
|
||||
#include <boost/utility/string_ref.hpp>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace beast {
|
||||
namespace detail {
|
||||
|
||||
inline
|
||||
char
|
||||
tolower(char c)
|
||||
tolower(signed char c)
|
||||
{
|
||||
static std::array<std::uint8_t, 256> constexpr tab = {{
|
||||
static unsigned char constexpr tab[256] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
|
||||
@ -37,8 +35,9 @@ tolower(char c)
|
||||
208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
|
||||
224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
|
||||
240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
|
||||
}};
|
||||
return static_cast<char>(tab[static_cast<std::uint8_t>(c)]);
|
||||
};
|
||||
return static_cast<char>(
|
||||
tab[static_cast<unsigned char>(c)]);
|
||||
}
|
||||
|
||||
template<std::size_t N>
|
||||
|
@ -11,14 +11,14 @@
|
||||
#include <beast/config.hpp>
|
||||
|
||||
#include <beast/http/basic_fields.hpp>
|
||||
#include <beast/http/basic_parser_v1.hpp>
|
||||
#include <beast/http/basic_parser.hpp>
|
||||
#include <beast/http/chunk_encode.hpp>
|
||||
#include <beast/http/empty_body.hpp>
|
||||
#include <beast/http/error.hpp>
|
||||
#include <beast/http/fields.hpp>
|
||||
#include <beast/http/header_parser.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/http/parse.hpp>
|
||||
#include <beast/http/parse_error.hpp>
|
||||
#include <beast/http/parser_v1.hpp>
|
||||
#include <beast/http/message_parser.hpp>
|
||||
#include <beast/http/read.hpp>
|
||||
#include <beast/http/read.hpp>
|
||||
#include <beast/http/reason.hpp>
|
||||
#include <beast/http/rfc7230.hpp>
|
||||
|
@ -33,30 +33,47 @@ private:
|
||||
|
||||
class reader
|
||||
{
|
||||
value_type& sb_;
|
||||
value_type& body_;
|
||||
|
||||
public:
|
||||
static bool constexpr is_direct = true;
|
||||
|
||||
using mutable_buffers_type =
|
||||
typename DynamicBuffer::mutable_buffers_type;
|
||||
|
||||
template<bool isRequest, class Fields>
|
||||
explicit
|
||||
reader(message<isRequest,
|
||||
basic_dynabuf_body, Fields>& m) noexcept
|
||||
: sb_(m.body)
|
||||
basic_dynabuf_body, Fields>& msg)
|
||||
: body_(msg.body)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
init(error_code&) noexcept
|
||||
init()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
write(void const* data,
|
||||
std::size_t size, error_code&) noexcept
|
||||
init(std::uint64_t content_length)
|
||||
{
|
||||
}
|
||||
|
||||
mutable_buffers_type
|
||||
prepare(std::size_t n)
|
||||
{
|
||||
return body_.prepare(n);
|
||||
}
|
||||
|
||||
void
|
||||
commit(std::size_t n)
|
||||
{
|
||||
body_.commit(n);
|
||||
}
|
||||
|
||||
void
|
||||
finish()
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
using boost::asio::buffer_copy;
|
||||
sb_.commit(buffer_copy(
|
||||
sb_.prepare(size), buffer(data, size)));
|
||||
}
|
||||
};
|
||||
|
||||
|
643
include/beast/http/basic_parser.hpp
Normal file
643
include/beast/http/basic_parser.hpp
Normal file
@ -0,0 +1,643 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_BASIC_PARSER_HPP
|
||||
#define BEAST_HTTP_BASIC_PARSER_HPP
|
||||
|
||||
#include <beast/config.hpp>
|
||||
#include <beast/core/error.hpp>
|
||||
#include <beast/http/detail/basic_parser.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/utility/string_ref.hpp>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
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
|
||||
HTTP/1 wire format. It allocates no memory when input is
|
||||
presented as a single contiguous buffer, and uses minimal
|
||||
state. It will handle chunked encoding and it understands
|
||||
the semantics of the Connection, Content-Length, and Upgrade
|
||||
fields.
|
||||
|
||||
The interface uses CRTP (Curiously Recurring Template Pattern).
|
||||
To use this class, derive from @ref 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.
|
||||
|
||||
Every callback must be provided by the derived class, or else
|
||||
a compilation error will be generated. This exemplar shows
|
||||
the signature and description of the callbacks required in
|
||||
the derived class.
|
||||
|
||||
@par Derived Example
|
||||
|
||||
@code
|
||||
template<bool isRequest>
|
||||
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(
|
||||
boost::string_ref const& method,
|
||||
boost::string_ref const& path,
|
||||
int version,
|
||||
error_code& ec);
|
||||
|
||||
// When isRequest == false, called
|
||||
// after the Status Line is received.
|
||||
//
|
||||
void
|
||||
on_response(
|
||||
int status,
|
||||
boost::string_ref const& reason,
|
||||
int version,
|
||||
error_code& ec);
|
||||
|
||||
// Called after receiving a field/value pair.
|
||||
//
|
||||
void
|
||||
on_field(
|
||||
boost::string_ref const& name,
|
||||
boost::string_ref const& value,
|
||||
error_code& ec);
|
||||
|
||||
// Called after the header is complete.
|
||||
//
|
||||
void
|
||||
on_header(
|
||||
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();
|
||||
|
||||
// Called zero or more times to provide body data.
|
||||
//
|
||||
// Only used if isDirect == false
|
||||
//
|
||||
void
|
||||
on_data(
|
||||
boost::string_ref const& 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
|
||||
// chunk.
|
||||
//
|
||||
void
|
||||
on_chunk(
|
||||
std::uint64_t length, // Length of this chunk
|
||||
boost::string_ref const& ext, // The chunk extensions, if any
|
||||
error_code& ec);
|
||||
|
||||
// Called once when the message is complete.
|
||||
// This will be called even if there is no body.
|
||||
//
|
||||
void
|
||||
on_complete(error_code& ec);
|
||||
};
|
||||
@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.
|
||||
|
||||
The parser is optimized for the case where the input buffer
|
||||
sequence consists of a single contiguous buffer. The
|
||||
@ref beast::flat_streambuf class is provided, which guarantees
|
||||
that the input sequence of the stream buffer will be represented
|
||||
by exactly one contiguous buffer. To ensure the optimum performance
|
||||
of the parser, use @ref beast::flat_streambuf with HTTP algorithms
|
||||
such as @ref beast::http::read, @ref beast::http::read_some,
|
||||
@ref beast::http::async_read, and @ref beast::http::async_read_some.
|
||||
Alternatively, the caller may use custom techniques to ensure that
|
||||
the structured portion of the HTTP message (header or chunk header)
|
||||
is contained in a linear buffer.
|
||||
|
||||
@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>
|
||||
class basic_parser
|
||||
: private detail::basic_parser_base
|
||||
{
|
||||
template<bool OtherIsRequest,
|
||||
bool OtherIsDirect, 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;
|
||||
|
||||
// The parser has read at least one byte
|
||||
static unsigned constexpr flagGotSome = 1<< 2;
|
||||
|
||||
// Message semantics indicate a body is expected.
|
||||
// cleared if flagSkipBody set
|
||||
//
|
||||
static unsigned constexpr flagHasBody = 1<< 3;
|
||||
|
||||
static unsigned constexpr flagHTTP11 = 1<< 4;
|
||||
static unsigned constexpr flagNeedEOF = 1<< 5;
|
||||
static unsigned constexpr flagExpectCRLF = 1<< 6;
|
||||
static unsigned constexpr flagFinalChunk = 1<< 7;
|
||||
static unsigned constexpr flagConnectionClose = 1<< 8;
|
||||
static unsigned constexpr flagConnectionUpgrade = 1<< 9;
|
||||
static unsigned constexpr flagConnectionKeepAlive = 1<< 10;
|
||||
static unsigned constexpr flagContentLength = 1<< 11;
|
||||
static unsigned constexpr flagChunked = 1<< 12;
|
||||
static unsigned constexpr flagUpgrade = 1<< 13;
|
||||
|
||||
std::uint64_t len_; // size of chunk or body
|
||||
std::unique_ptr<char[]> buf_;
|
||||
std::size_t buf_len_ = 0;
|
||||
std::size_t skip_ = 0; // search from here
|
||||
std::size_t x_; // scratch variable
|
||||
unsigned f_ = 0; // flags
|
||||
parse_state state_ = parse_state::header;
|
||||
boost::string_ref ext_;
|
||||
boost::string_ref body_;
|
||||
|
||||
public:
|
||||
/// Copy constructor (disallowed)
|
||||
basic_parser(basic_parser const&) = delete;
|
||||
|
||||
/// Copy assignment (disallowed)
|
||||
basic_parser& operator=(basic_parser const&) = delete;
|
||||
|
||||
/// Default constructor
|
||||
basic_parser() = default;
|
||||
|
||||
/// `true` if this parser parses requests, `false` for responses.
|
||||
static bool constexpr is_request = isRequest;
|
||||
|
||||
/// Destructor
|
||||
~basic_parser() = default;
|
||||
|
||||
/** Move constructor
|
||||
|
||||
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>&&);
|
||||
|
||||
/** 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_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
|
||||
{
|
||||
return state_;
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/** Returns `true` if the message is complete.
|
||||
|
||||
The message is complete after a full header is
|
||||
parsed and one of the following is true:
|
||||
|
||||
@li @ref skip_body was called
|
||||
|
||||
@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.
|
||||
*/
|
||||
bool
|
||||
is_complete() const
|
||||
{
|
||||
return state_ == parse_state::complete;
|
||||
}
|
||||
|
||||
/** Returns `true` if the message is an upgrade message.
|
||||
|
||||
@note Only valid after parsing a complete header.
|
||||
*/
|
||||
bool
|
||||
is_upgrade() const
|
||||
{
|
||||
return (f_ & flagConnectionUpgrade) != 0;
|
||||
}
|
||||
|
||||
/** Returns `true` if keep-alive is specified
|
||||
|
||||
@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.
|
||||
*/
|
||||
bool
|
||||
is_chunked() const
|
||||
{
|
||||
return (f_ & flagChunked) != 0;
|
||||
}
|
||||
|
||||
/** Write part of a buffer sequence to the parser.
|
||||
|
||||
This function attempts to parse the HTTP message
|
||||
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.
|
||||
|
||||
@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.
|
||||
*/
|
||||
template<class ConstBufferSequence>
|
||||
std::size_t
|
||||
write(ConstBufferSequence const& buffers, error_code& ec);
|
||||
|
||||
#if ! GENERATING_DOCS
|
||||
std::size_t
|
||||
write(boost::asio::const_buffers_1 const& buffer,
|
||||
error_code& ec);
|
||||
#endif
|
||||
|
||||
/** Inform the parser that the end of stream was reached.
|
||||
|
||||
In certain cases, HTTP needs to know where the end of
|
||||
the stream is. For example, sometimes servers send
|
||||
responses without Content-Length and expect the client
|
||||
to consume input (for the body) until EOF. Callbacks
|
||||
and errors will still be processed as usual.
|
||||
|
||||
This is typically called when a read from the
|
||||
underlying stream object sets the error code to
|
||||
`boost::asio::error::eof`.
|
||||
|
||||
@note Only valid after parsing a complete header.
|
||||
|
||||
@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.
|
||||
*/
|
||||
boost::string_ref const&
|
||||
body() const
|
||||
{
|
||||
// This function not available when isDirect==true
|
||||
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.
|
||||
*/
|
||||
boost::string_ref const&
|
||||
chunk_extension() const
|
||||
{
|
||||
// This function not available when isDirect==true
|
||||
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& dynabuf);
|
||||
|
||||
/** Returns a set of buffers for storing body data.
|
||||
|
||||
@note This member function is only available when
|
||||
`isDirect==true`.
|
||||
|
||||
@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.
|
||||
*/
|
||||
template<class MutableBufferSequence>
|
||||
void
|
||||
prepare_body(boost::optional<
|
||||
MutableBufferSequence>& buffers, std::size_t limit);
|
||||
|
||||
/** Commit body data.
|
||||
|
||||
@note This member function is only available when
|
||||
`isDirect==true`.
|
||||
*/
|
||||
void
|
||||
commit_body(std::size_t n);
|
||||
|
||||
/** Indicate that body octets have been consumed.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/** Consume all remaining body data.
|
||||
|
||||
This function instructs the parser to advance the
|
||||
state past any expected body octets. Callers who
|
||||
wish to read and process the body themselves will
|
||||
call this function.
|
||||
*/
|
||||
void
|
||||
consume_body(error_code& ec);
|
||||
|
||||
private:
|
||||
inline
|
||||
Derived&
|
||||
impl()
|
||||
{
|
||||
return *static_cast<Derived*>(this);
|
||||
}
|
||||
|
||||
template<class ConstBufferSequence>
|
||||
boost::string_ref
|
||||
maybe_flatten(
|
||||
ConstBufferSequence const& buffers);
|
||||
|
||||
std::size_t
|
||||
do_write(boost::asio::const_buffers_1 const& buffer,
|
||||
error_code& ec, std::true_type);
|
||||
|
||||
std::size_t
|
||||
do_write(boost::asio::const_buffers_1 const& buffer,
|
||||
error_code& ec, std::false_type);
|
||||
|
||||
void
|
||||
parse_startline(char const*& it,
|
||||
int& version, int& status,
|
||||
error_code& ec, std::true_type);
|
||||
|
||||
void
|
||||
parse_startline(char const*& it,
|
||||
int& version, int& status,
|
||||
error_code& ec, std::false_type);
|
||||
|
||||
void
|
||||
parse_fields(char const*& it,
|
||||
char const* last, error_code& ec);
|
||||
|
||||
void
|
||||
do_field(
|
||||
boost::string_ref const& name,
|
||||
boost::string_ref 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);
|
||||
};
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#include <beast/http/impl/basic_parser.ipp>
|
||||
|
||||
#endif
|
@ -1,856 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_BASIC_PARSER_v1_HPP
|
||||
#define BEAST_HTTP_BASIC_PARSER_v1_HPP
|
||||
|
||||
#include <beast/config.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/http/parse_error.hpp>
|
||||
#include <beast/http/rfc7230.hpp>
|
||||
#include <beast/http/detail/basic_parser_v1.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
/** Parse flags
|
||||
|
||||
The set of parser bit flags are returned by @ref basic_parser_v1::flags.
|
||||
*/
|
||||
enum parse_flag
|
||||
{
|
||||
chunked = 1,
|
||||
connection_keep_alive = 2,
|
||||
connection_close = 4,
|
||||
connection_upgrade = 8,
|
||||
trailing = 16,
|
||||
upgrade = 32,
|
||||
skipbody = 64,
|
||||
contentlength = 128,
|
||||
paused = 256
|
||||
};
|
||||
|
||||
/** Body maximum size option.
|
||||
|
||||
Sets the maximum number of cumulative bytes allowed including
|
||||
all body octets. Octets in chunk-encoded bodies are counted
|
||||
after decoding. A value of zero indicates no limit on
|
||||
the number of body octets.
|
||||
|
||||
The default body maximum size for requests is 4MB (four
|
||||
megabytes or 4,194,304 bytes) and unlimited for responses.
|
||||
|
||||
@note Objects of this type are used with @ref basic_parser_v1::set_option.
|
||||
*/
|
||||
struct body_max_size
|
||||
{
|
||||
std::size_t value;
|
||||
|
||||
explicit
|
||||
body_max_size(std::size_t v)
|
||||
: value(v)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/** Header maximum size option.
|
||||
|
||||
Sets the maximum number of cumulative bytes allowed
|
||||
including all header octets. A value of zero indicates
|
||||
no limit on the number of header octets.
|
||||
|
||||
The default header maximum size is 16KB (16,384 bytes).
|
||||
|
||||
@note Objects of this type are used with @ref basic_parser_v1::set_option.
|
||||
*/
|
||||
struct header_max_size
|
||||
{
|
||||
std::size_t value;
|
||||
|
||||
explicit
|
||||
header_max_size(std::size_t v)
|
||||
: value(v)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/** A value indicating how the parser should treat the body.
|
||||
|
||||
This value is returned from the `on_header` 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_header` 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_body_prepare` this causes parsing
|
||||
to complete and control to return to the caller.
|
||||
*/
|
||||
upgrade,
|
||||
|
||||
/** Suspend parsing before reading the body.
|
||||
|
||||
When returned by `on_body_prepare` 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
|
||||
header before committing to read the body. For example,
|
||||
to choose the body type based on the fields. 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)();
|
||||
|
||||
/** A parser for decoding HTTP/1 wire format messages.
|
||||
|
||||
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.
|
||||
|
||||
Every callback must be provided by the derived class, or else
|
||||
a compilation error will be generated. This exemplar shows
|
||||
the signature and description of the callbacks required in
|
||||
the derived class.
|
||||
|
||||
@code
|
||||
template<bool isRequest>
|
||||
struct exemplar : basic_parser_v1<isRequest, exemplar>
|
||||
{
|
||||
// Called when the first valid octet of a new message is received
|
||||
//
|
||||
void on_start(error_code&);
|
||||
|
||||
// Called for each piece of the Request-Method
|
||||
//
|
||||
void on_method(boost::string_ref const&, error_code&);
|
||||
|
||||
// Called for each piece of the Request-URI
|
||||
//
|
||||
void on_uri(boost::string_ref const&, error_code&);
|
||||
|
||||
// Called for each piece of the reason-phrase
|
||||
//
|
||||
void on_reason(boost::string_ref const&, error_code&);
|
||||
|
||||
// Called after the entire Request-Line has been parsed successfully.
|
||||
//
|
||||
void on_request(error_code&);
|
||||
|
||||
// Called after the entire Response-Line has been parsed successfully.
|
||||
//
|
||||
void on_response(error_code&);
|
||||
|
||||
// Called for each piece of the current header field.
|
||||
//
|
||||
void on_field(boost::string_ref const&, error_code&);
|
||||
|
||||
// Called for each piece of the current header value.
|
||||
//
|
||||
void on_value(boost::string_ref const&, error_code&)
|
||||
|
||||
// Called when the entire header has been parsed successfully.
|
||||
//
|
||||
void
|
||||
on_header(std::uint64_t content_length, error_code&);
|
||||
|
||||
// Called after on_header, before the body is parsed
|
||||
//
|
||||
body_what
|
||||
on_body_what(std::uint64_t content_length, error_code&);
|
||||
|
||||
// Called for each piece of the body.
|
||||
//
|
||||
// If the header indicates chunk encoding, the chunk
|
||||
// encoding is removed from the buffer before being
|
||||
// passed to the callback.
|
||||
//
|
||||
void on_body(boost::string_ref const&, error_code&);
|
||||
|
||||
// Called when the entire message has been parsed successfully.
|
||||
// At this point, @ref complete returns `true`, and the parser
|
||||
// is ready to parse another message if `keep_alive` would
|
||||
// return `true`.
|
||||
//
|
||||
void on_complete(error_code&) {}
|
||||
};
|
||||
@endcode
|
||||
|
||||
The return value of `on_body_what` is special, it controls
|
||||
whether or not the parser should expect a body. See @ref body_what
|
||||
for choices of the return value.
|
||||
|
||||
If a callback sets an error, parsing stops at the current octet
|
||||
and the error is returned to the caller. Callbacks must not throw
|
||||
exceptions.
|
||||
|
||||
@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
|
||||
{
|
||||
private:
|
||||
template<bool, class>
|
||||
friend class basic_parser_v1;
|
||||
|
||||
using self = basic_parser_v1;
|
||||
typedef void(self::*pmf_t)(error_code&, boost::string_ref const&);
|
||||
|
||||
enum field_state : std::uint8_t
|
||||
{
|
||||
h_general = 0,
|
||||
h_C,
|
||||
h_CO,
|
||||
h_CON,
|
||||
|
||||
h_matching_connection,
|
||||
h_matching_proxy_connection,
|
||||
h_matching_content_length,
|
||||
h_matching_transfer_encoding,
|
||||
h_matching_upgrade,
|
||||
|
||||
h_connection,
|
||||
h_content_length0,
|
||||
h_content_length,
|
||||
h_content_length_ows,
|
||||
h_transfer_encoding,
|
||||
h_upgrade,
|
||||
|
||||
h_matching_transfer_encoding_chunked,
|
||||
h_matching_transfer_encoding_general,
|
||||
h_matching_connection_keep_alive,
|
||||
h_matching_connection_close,
|
||||
h_matching_connection_upgrade,
|
||||
|
||||
h_transfer_encoding_chunked,
|
||||
h_transfer_encoding_chunked_ows,
|
||||
|
||||
h_connection_keep_alive,
|
||||
h_connection_keep_alive_ows,
|
||||
h_connection_close,
|
||||
h_connection_close_ows,
|
||||
h_connection_upgrade,
|
||||
h_connection_upgrade_ows,
|
||||
h_connection_token,
|
||||
h_connection_token_ows
|
||||
};
|
||||
|
||||
std::size_t h_max_;
|
||||
std::size_t h_left_;
|
||||
std::size_t b_max_;
|
||||
std::size_t b_left_;
|
||||
std::uint64_t content_length_;
|
||||
pmf_t cb_;
|
||||
state s_ : 8;
|
||||
unsigned fs_ : 8;
|
||||
unsigned pos_ : 8; // position in field state
|
||||
unsigned http_major_ : 16;
|
||||
unsigned http_minor_ : 16;
|
||||
unsigned status_code_ : 16;
|
||||
unsigned flags_ : 9;
|
||||
bool upgrade_ : 1; // true if parser exited for upgrade
|
||||
|
||||
public:
|
||||
/// Default constructor
|
||||
basic_parser_v1();
|
||||
|
||||
/// Copy constructor.
|
||||
template<class OtherDerived>
|
||||
basic_parser_v1(basic_parser_v1<
|
||||
isRequest, OtherDerived> const& other);
|
||||
|
||||
/// Copy assignment.
|
||||
template<class OtherDerived>
|
||||
basic_parser_v1& operator=(basic_parser_v1<
|
||||
isRequest, OtherDerived> const& other);
|
||||
|
||||
/** Set options on the parser.
|
||||
|
||||
@param args One or more parser options to set.
|
||||
*/
|
||||
#if BEAST_DOXYGEN
|
||||
template<class... Args>
|
||||
void
|
||||
set_option(Args&&... args)
|
||||
#else
|
||||
template<class A1, class A2, class... An>
|
||||
void
|
||||
set_option(A1&& a1, A2&& a2, An&&... an)
|
||||
#endif
|
||||
{
|
||||
set_option(std::forward<A1>(a1));
|
||||
set_option(std::forward<A2>(a2),
|
||||
std::forward<An>(an)...);
|
||||
}
|
||||
|
||||
/// Set the header maximum size option
|
||||
void
|
||||
set_option(header_max_size const& o)
|
||||
{
|
||||
h_max_ = o.value;
|
||||
h_left_ = h_max_;
|
||||
}
|
||||
|
||||
/// Set the body maximum size option
|
||||
void
|
||||
set_option(body_max_size const& o)
|
||||
{
|
||||
b_max_ = o.value;
|
||||
b_left_ = b_max_;
|
||||
}
|
||||
|
||||
/// Returns internal flags associated with the parser.
|
||||
unsigned
|
||||
flags() const
|
||||
{
|
||||
return flags_;
|
||||
}
|
||||
|
||||
/** Returns `true` if the message end is indicated by eof.
|
||||
|
||||
This function returns true if the semantics of the message require
|
||||
that the end of the message is signaled by an end of file. For
|
||||
example, if the message is a HTTP/1.0 message and the Content-Length
|
||||
is unspecified, the end of the message is indicated by an end of file.
|
||||
|
||||
@return `true` if write_eof must be used to indicate the message end.
|
||||
*/
|
||||
bool
|
||||
needs_eof() const
|
||||
{
|
||||
return needs_eof(
|
||||
std::integral_constant<bool, isRequest>{});
|
||||
}
|
||||
|
||||
/** Returns the major HTTP version number.
|
||||
|
||||
Examples:
|
||||
* Returns 1 for HTTP/1.1
|
||||
* Returns 1 for HTTP/1.0
|
||||
|
||||
@return The HTTP major version number.
|
||||
*/
|
||||
unsigned
|
||||
http_major() const
|
||||
{
|
||||
return http_major_;
|
||||
}
|
||||
|
||||
/** Returns the minor HTTP version number.
|
||||
|
||||
Examples:
|
||||
* Returns 1 for HTTP/1.1
|
||||
* Returns 0 for HTTP/1.0
|
||||
|
||||
@return The HTTP minor version number.
|
||||
*/
|
||||
unsigned
|
||||
http_minor() const
|
||||
{
|
||||
return http_minor_;
|
||||
}
|
||||
|
||||
/** Returns `true` if the message is an upgrade message.
|
||||
|
||||
A value of `true` indicates that the parser has successfully
|
||||
completed parsing a HTTP upgrade message.
|
||||
|
||||
@return `true` if the message is an upgrade message.
|
||||
*/
|
||||
bool
|
||||
upgrade() const
|
||||
{
|
||||
return upgrade_;
|
||||
}
|
||||
|
||||
/** Returns the numeric HTTP Status-Code of a response.
|
||||
|
||||
@return The Status-Code.
|
||||
*/
|
||||
unsigned
|
||||
status_code() const
|
||||
{
|
||||
return status_code_;
|
||||
}
|
||||
|
||||
/** Returns `true` if the connection should be kept open.
|
||||
|
||||
@note This function is only valid to call when the parser
|
||||
is complete.
|
||||
*/
|
||||
bool
|
||||
keep_alive() const;
|
||||
|
||||
/** Returns `true` if the parse has completed succesfully.
|
||||
|
||||
When the parse has completed successfully, and the semantics
|
||||
of the parsed message indicate that the connection is still
|
||||
active, a subsequent call to `write` will begin parsing a
|
||||
new message.
|
||||
|
||||
@return `true` If the parsing has completed successfully.
|
||||
*/
|
||||
bool
|
||||
complete() const
|
||||
{
|
||||
return
|
||||
s_ == s_restart ||
|
||||
s_ == s_closed_complete ||
|
||||
(flags_ & parse_flag::paused);
|
||||
}
|
||||
|
||||
/** Write a sequence of buffers to the parser.
|
||||
|
||||
@param buffers An object meeting the requirements of
|
||||
ConstBufferSequence that represents the input sequence.
|
||||
|
||||
@param ec Set to the error, if any error occurred.
|
||||
|
||||
@return The number of bytes consumed in the input sequence.
|
||||
*/
|
||||
template<class ConstBufferSequence>
|
||||
#if BEAST_DOXYGEN
|
||||
std::size_t
|
||||
#else
|
||||
typename std::enable_if<
|
||||
! std::is_convertible<ConstBufferSequence,
|
||||
boost::asio::const_buffer>::value,
|
||||
std::size_t>::type
|
||||
#endif
|
||||
write(ConstBufferSequence const& buffers, error_code& ec);
|
||||
|
||||
/** Write a single buffer of data to the parser.
|
||||
|
||||
@param buffer The buffer to write.
|
||||
@param ec Set to the error, if any error occurred.
|
||||
|
||||
@return The number of bytes consumed in the buffer.
|
||||
*/
|
||||
std::size_t
|
||||
write(boost::asio::const_buffer const& buffer, error_code& ec);
|
||||
|
||||
/** Called to indicate the end of file.
|
||||
|
||||
HTTP needs to know where the end of the stream is. For example,
|
||||
sometimes servers send responses without Content-Length and
|
||||
expect the client to consume input (for the body) until EOF.
|
||||
Callbacks and errors will still be processed as usual.
|
||||
|
||||
@note This is typically called when a socket read returns eof.
|
||||
*/
|
||||
void
|
||||
write_eof(error_code& ec);
|
||||
|
||||
protected:
|
||||
/** Reset the parsing state.
|
||||
|
||||
The state of the parser is reset to expect the beginning of
|
||||
a new request or response. The old state is discarded.
|
||||
*/
|
||||
void
|
||||
reset();
|
||||
|
||||
private:
|
||||
Derived&
|
||||
impl()
|
||||
{
|
||||
return *static_cast<Derived*>(this);
|
||||
}
|
||||
|
||||
void
|
||||
reset(std::true_type)
|
||||
{
|
||||
s_ = s_req_start;
|
||||
}
|
||||
|
||||
void
|
||||
reset(std::false_type)
|
||||
{
|
||||
s_ = s_res_start;
|
||||
}
|
||||
|
||||
void
|
||||
init(std::true_type)
|
||||
{
|
||||
// Request: 16KB max header, 4MB max body
|
||||
h_max_ = 16 * 1024;
|
||||
b_max_ = 4 * 1024 * 1024;
|
||||
}
|
||||
|
||||
void
|
||||
init(std::false_type)
|
||||
{
|
||||
// Response: 16KB max header, unlimited body
|
||||
h_max_ = 16 * 1024;
|
||||
b_max_ = 0;
|
||||
}
|
||||
|
||||
void
|
||||
init()
|
||||
{
|
||||
init(std::integral_constant<bool, isRequest>{});
|
||||
reset();
|
||||
}
|
||||
|
||||
bool
|
||||
needs_eof(std::true_type) const;
|
||||
|
||||
bool
|
||||
needs_eof(std::false_type) const;
|
||||
|
||||
template<class T, class = beast::detail::void_t<>>
|
||||
struct check_on_start : std::false_type {};
|
||||
|
||||
template<class T>
|
||||
struct check_on_start<T, beast::detail::void_t<decltype(
|
||||
std::declval<T>().on_start(
|
||||
std::declval<error_code&>())
|
||||
)>> : std::true_type { };
|
||||
|
||||
template<class T, class = beast::detail::void_t<>>
|
||||
struct check_on_method : std::false_type {};
|
||||
|
||||
template<class T>
|
||||
struct check_on_method<T, beast::detail::void_t<decltype(
|
||||
std::declval<T>().on_method(
|
||||
std::declval<boost::string_ref>(),
|
||||
std::declval<error_code&>())
|
||||
)>> : std::true_type {};
|
||||
|
||||
template<class T, class = beast::detail::void_t<>>
|
||||
struct check_on_uri : std::false_type {};
|
||||
|
||||
template<class T>
|
||||
struct check_on_uri<T, beast::detail::void_t<decltype(
|
||||
std::declval<T>().on_uri(
|
||||
std::declval<boost::string_ref>(),
|
||||
std::declval<error_code&>())
|
||||
)>> : std::true_type {};
|
||||
|
||||
template<class T, class = beast::detail::void_t<>>
|
||||
struct check_on_reason : std::false_type {};
|
||||
|
||||
template<class T>
|
||||
struct check_on_reason<T, beast::detail::void_t<decltype(
|
||||
std::declval<T>().on_reason(
|
||||
std::declval<boost::string_ref>(),
|
||||
std::declval<error_code&>())
|
||||
)>> : std::true_type {};
|
||||
|
||||
template<class T, class = beast::detail::void_t<>>
|
||||
struct check_on_request : std::false_type {};
|
||||
|
||||
template<class T>
|
||||
struct check_on_request<T, beast::detail::void_t<decltype(
|
||||
std::declval<T>().on_request(
|
||||
std::declval<error_code&>())
|
||||
)>> : std::true_type {};
|
||||
|
||||
template<class T, class = beast::detail::void_t<>>
|
||||
struct check_on_response : std::false_type {};
|
||||
|
||||
template<class T>
|
||||
struct check_on_response<T, beast::detail::void_t<decltype(
|
||||
std::declval<T>().on_response(
|
||||
std::declval<error_code&>())
|
||||
)>> : std::true_type {};
|
||||
|
||||
template<class T, class = beast::detail::void_t<>>
|
||||
struct check_on_field : std::false_type {};
|
||||
|
||||
template<class T>
|
||||
struct check_on_field<T, beast::detail::void_t<decltype(
|
||||
std::declval<T>().on_field(
|
||||
std::declval<boost::string_ref>(),
|
||||
std::declval<error_code&>())
|
||||
)>> : std::true_type {};
|
||||
|
||||
template<class T, class = beast::detail::void_t<>>
|
||||
struct check_on_value : std::false_type {};
|
||||
|
||||
template<class T>
|
||||
struct check_on_value<T, beast::detail::void_t<decltype(
|
||||
std::declval<T>().on_value(
|
||||
std::declval<boost::string_ref>(),
|
||||
std::declval<error_code&>())
|
||||
)>> : std::true_type {};
|
||||
|
||||
template<class T, class = beast::detail::void_t<>>
|
||||
struct check_on_headers : std::false_type {};
|
||||
|
||||
template<class T>
|
||||
struct check_on_headers<T, beast::detail::void_t<decltype(
|
||||
std::declval<T>().on_header(
|
||||
std::declval<std::uint64_t>(),
|
||||
std::declval<error_code&>())
|
||||
)>> : std::true_type {};
|
||||
|
||||
// VFALCO Can we use std::is_detected? Is C++11 capable?
|
||||
template<class C>
|
||||
class check_on_body_what_t
|
||||
{
|
||||
template<class T, class R = std::is_convertible<decltype(
|
||||
std::declval<T>().on_body_what(
|
||||
std::declval<std::uint64_t>(),
|
||||
std::declval<error_code&>())),
|
||||
body_what>>
|
||||
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 check_on_body_what =
|
||||
std::integral_constant<bool, check_on_body_what_t<C>::value>;
|
||||
|
||||
template<class T, class = beast::detail::void_t<>>
|
||||
struct check_on_body : std::false_type {};
|
||||
|
||||
template<class T>
|
||||
struct check_on_body<T, beast::detail::void_t<decltype(
|
||||
std::declval<T>().on_body(
|
||||
std::declval<boost::string_ref>(),
|
||||
std::declval<error_code&>())
|
||||
)>> : std::true_type {};
|
||||
|
||||
template<class T, class = beast::detail::void_t<>>
|
||||
struct check_on_complete : std::false_type {};
|
||||
|
||||
template<class T>
|
||||
struct check_on_complete<T, beast::detail::void_t<decltype(
|
||||
std::declval<T>().on_complete(
|
||||
std::declval<error_code&>())
|
||||
)>> : std::true_type {};
|
||||
|
||||
void call_on_start(error_code& ec)
|
||||
{
|
||||
static_assert(check_on_start<Derived>::value,
|
||||
"on_start requirements not met");
|
||||
impl().on_start(ec);
|
||||
}
|
||||
|
||||
void call_on_method(error_code& ec,
|
||||
boost::string_ref const& s, std::true_type)
|
||||
{
|
||||
static_assert(check_on_method<Derived>::value,
|
||||
"on_method requirements not met");
|
||||
if(h_max_ && s.size() > h_left_)
|
||||
{
|
||||
ec = parse_error::header_too_big;
|
||||
return;
|
||||
}
|
||||
h_left_ -= s.size();
|
||||
impl().on_method(s, ec);
|
||||
}
|
||||
|
||||
void call_on_method(error_code&,
|
||||
boost::string_ref const&, std::false_type)
|
||||
{
|
||||
}
|
||||
|
||||
void call_on_method(error_code& ec,
|
||||
boost::string_ref const& s)
|
||||
{
|
||||
call_on_method(ec, s,
|
||||
std::integral_constant<bool, isRequest>{});
|
||||
}
|
||||
|
||||
void call_on_uri(error_code& ec,
|
||||
boost::string_ref const& s, std::true_type)
|
||||
{
|
||||
static_assert(check_on_uri<Derived>::value,
|
||||
"on_uri requirements not met");
|
||||
if(h_max_ && s.size() > h_left_)
|
||||
{
|
||||
ec = parse_error::header_too_big;
|
||||
return;
|
||||
}
|
||||
h_left_ -= s.size();
|
||||
impl().on_uri(s, ec);
|
||||
}
|
||||
|
||||
void call_on_uri(error_code&,
|
||||
boost::string_ref const&, std::false_type)
|
||||
{
|
||||
}
|
||||
|
||||
void call_on_uri(error_code& ec,
|
||||
boost::string_ref const& s)
|
||||
{
|
||||
call_on_uri(ec, s,
|
||||
std::integral_constant<bool, isRequest>{});
|
||||
}
|
||||
|
||||
void call_on_reason(error_code& ec,
|
||||
boost::string_ref const& s, std::true_type)
|
||||
{
|
||||
static_assert(check_on_reason<Derived>::value,
|
||||
"on_reason requirements not met");
|
||||
if(h_max_ && s.size() > h_left_)
|
||||
{
|
||||
ec = parse_error::header_too_big;
|
||||
return;
|
||||
}
|
||||
h_left_ -= s.size();
|
||||
impl().on_reason(s, ec);
|
||||
}
|
||||
|
||||
void call_on_reason(error_code&,
|
||||
boost::string_ref const&, std::false_type)
|
||||
{
|
||||
}
|
||||
|
||||
void call_on_reason(error_code& ec, boost::string_ref const& s)
|
||||
{
|
||||
call_on_reason(ec, s,
|
||||
std::integral_constant<bool, ! isRequest>{});
|
||||
}
|
||||
|
||||
void call_on_request(error_code& ec, std::true_type)
|
||||
{
|
||||
static_assert(check_on_request<Derived>::value,
|
||||
"on_request requirements not met");
|
||||
impl().on_request(ec);
|
||||
}
|
||||
|
||||
void call_on_request(error_code&, std::false_type)
|
||||
{
|
||||
}
|
||||
|
||||
void call_on_request(error_code& ec)
|
||||
{
|
||||
call_on_request(ec,
|
||||
std::integral_constant<bool, isRequest>{});
|
||||
}
|
||||
|
||||
void call_on_response(error_code& ec, std::true_type)
|
||||
{
|
||||
static_assert(check_on_response<Derived>::value,
|
||||
"on_response requirements not met");
|
||||
impl().on_response(ec);
|
||||
}
|
||||
|
||||
void call_on_response(error_code&, std::false_type)
|
||||
{
|
||||
}
|
||||
|
||||
void call_on_response(error_code& ec)
|
||||
{
|
||||
call_on_response(ec,
|
||||
std::integral_constant<bool, ! isRequest>{});
|
||||
}
|
||||
|
||||
void call_on_field(error_code& ec,
|
||||
boost::string_ref const& s)
|
||||
{
|
||||
static_assert(check_on_field<Derived>::value,
|
||||
"on_field requirements not met");
|
||||
if(h_max_ && s.size() > h_left_)
|
||||
{
|
||||
ec = parse_error::header_too_big;
|
||||
return;
|
||||
}
|
||||
h_left_ -= s.size();
|
||||
impl().on_field(s, ec);
|
||||
}
|
||||
|
||||
void call_on_value(error_code& ec,
|
||||
boost::string_ref const& s)
|
||||
{
|
||||
static_assert(check_on_value<Derived>::value,
|
||||
"on_value requirements not met");
|
||||
if(h_max_ && s.size() > h_left_)
|
||||
{
|
||||
ec = parse_error::header_too_big;
|
||||
return;
|
||||
}
|
||||
h_left_ -= s.size();
|
||||
impl().on_value(s, ec);
|
||||
}
|
||||
|
||||
void
|
||||
call_on_headers(error_code& ec)
|
||||
{
|
||||
static_assert(check_on_headers<Derived>::value,
|
||||
"on_header requirements not met");
|
||||
impl().on_header(content_length_, ec);
|
||||
}
|
||||
|
||||
body_what
|
||||
call_on_body_what(error_code& ec)
|
||||
{
|
||||
static_assert(check_on_body_what<Derived>::value,
|
||||
"on_body_what requirements not met");
|
||||
return impl().on_body_what(content_length_, ec);
|
||||
}
|
||||
|
||||
void call_on_body(error_code& ec,
|
||||
boost::string_ref const& s)
|
||||
{
|
||||
static_assert(check_on_body<Derived>::value,
|
||||
"on_body requirements not met");
|
||||
if(b_max_ && s.size() > b_left_)
|
||||
{
|
||||
ec = parse_error::body_too_big;
|
||||
return;
|
||||
}
|
||||
b_left_ -= s.size();
|
||||
impl().on_body(s, ec);
|
||||
}
|
||||
|
||||
void call_on_complete(error_code& ec)
|
||||
{
|
||||
static_assert(check_on_complete<Derived>::value,
|
||||
"on_complete requirements not met");
|
||||
impl().on_complete(ec);
|
||||
}
|
||||
};
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#include <beast/http/impl/basic_parser_v1.ipp>
|
||||
|
||||
#endif
|
@ -5,13 +5,15 @@
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_TYPE_CHECK_HPP
|
||||
#define BEAST_HTTP_TYPE_CHECK_HPP
|
||||
#ifndef BEAST_HTTP_CONCEPTS_HPP
|
||||
#define BEAST_HTTP_CONCEPTS_HPP
|
||||
|
||||
#include <beast/config.hpp>
|
||||
#include <beast/core/error.hpp>
|
||||
#include <beast/core/buffer_concepts.hpp>
|
||||
#include <beast/core/detail/type_traits.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
@ -86,45 +88,6 @@ public:
|
||||
>;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class is_Parser
|
||||
{
|
||||
template<class U, class R =
|
||||
std::is_convertible<decltype(
|
||||
std::declval<U>().complete()),
|
||||
bool>>
|
||||
static R check1(int);
|
||||
template<class>
|
||||
static std::false_type check1(...);
|
||||
using type1 = decltype(check1<T>(0));
|
||||
|
||||
template<class U, class R =
|
||||
std::is_convertible<decltype(
|
||||
std::declval<U>().write(
|
||||
std::declval<boost::asio::const_buffers_1 const&>(),
|
||||
std::declval<error_code&>())),
|
||||
std::size_t>>
|
||||
static R check2(int);
|
||||
template<class>
|
||||
static std::false_type check2(...);
|
||||
using type2 = decltype(check2<T>(0));
|
||||
|
||||
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(...);
|
||||
using type3 = decltype(check3<T>(0));
|
||||
|
||||
public:
|
||||
using type = std::integral_constant<bool,
|
||||
type1::value &&
|
||||
type2::value &&
|
||||
type3::value
|
||||
>;
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
/// Determine if `T` meets the requirements of @b Body.
|
||||
@ -183,19 +146,27 @@ template<class T, class M>
|
||||
struct is_Reader : std::integral_constant<bool, ...> {};
|
||||
#else
|
||||
template<class T, class M, class = beast::detail::void_t<>>
|
||||
struct is_Reader : std::false_type {};
|
||||
struct is_Reader : std::true_type {};
|
||||
|
||||
template<class T, class M>
|
||||
struct is_Reader<T, M, beast::detail::void_t<decltype(
|
||||
std::declval<typename T::mutable_buffers_type>(),
|
||||
std::declval<T>().init(
|
||||
std::declval<error_code&>()),
|
||||
std::declval<T>().write(
|
||||
std::declval<void const*>(),
|
||||
std::declval<std::size_t>(),
|
||||
std::declval<error_code&>())
|
||||
std::declval<boost::optional<std::uint64_t>>()),
|
||||
std::declval<T>().prepare(
|
||||
std::declval<std::size_t>()),
|
||||
std::declval<T>().commit(
|
||||
std::declval<std::size_t>()),
|
||||
std::declval<T>().finish()
|
||||
)> > : std::integral_constant<bool,
|
||||
std::is_nothrow_constructible<T, M&>::value
|
||||
>
|
||||
is_MutableBufferSequence<
|
||||
typename T::mutable_buffers_type>::value &&
|
||||
std::is_convertible<decltype(
|
||||
std::declval<T>().prepare(
|
||||
std::declval<std::size_t>())),
|
||||
typename T::mutable_buffers_type
|
||||
>::value>
|
||||
|
||||
{
|
||||
static_assert(std::is_same<
|
||||
typename M::body_type::reader, T>::value,
|
||||
@ -217,14 +188,6 @@ struct is_Writer : std::integral_constant<bool, ...> {};
|
||||
using is_Writer = typename detail::is_Writer<T, M>::type;
|
||||
#endif
|
||||
|
||||
/// Determine if `T` meets the requirements of @b Parser.
|
||||
template<class T>
|
||||
#if BEAST_DOXYGEN
|
||||
struct is_Parser : std::integral_constant<bool, ...>{};
|
||||
#else
|
||||
using is_Parser = typename detail::is_Parser<T>::type;
|
||||
#endif
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
|
194
include/beast/http/detail/basic_parsed_list.hpp
Normal file
194
include/beast/http/detail/basic_parsed_list.hpp
Normal file
@ -0,0 +1,194 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_DETAIL_BASIC_PARSED_LIST_HPP
|
||||
#define BEAST_HTTP_DETAIL_BASIC_PARSED_LIST_HPP
|
||||
|
||||
#include <beast/core/detail/empty_base_optimization.hpp>
|
||||
#include <boost/utility/string_ref.hpp>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
namespace detail {
|
||||
|
||||
/** A list parser which presents the sequence as a container.
|
||||
*/
|
||||
template<class Policy>
|
||||
class basic_parsed_list
|
||||
{
|
||||
boost::string_ref s_;
|
||||
|
||||
public:
|
||||
/// The type of policy this list uses for parsing.
|
||||
using policy_type = Policy;
|
||||
|
||||
/// The type of each element in the list.
|
||||
using value_type = typename Policy::value_type;
|
||||
|
||||
/// A constant iterator to a list element.
|
||||
#if GENERATING_DOCS
|
||||
using const_iterator = implementation_defined;
|
||||
#else
|
||||
class const_iterator;
|
||||
#endif
|
||||
|
||||
class const_iterator
|
||||
: private beast::detail::
|
||||
empty_base_optimization<Policy>
|
||||
{
|
||||
basic_parsed_list const* list_ = nullptr;
|
||||
char const* it_ = nullptr;
|
||||
typename Policy::value_type v_;
|
||||
bool error_ = false;
|
||||
|
||||
public:
|
||||
using value_type =
|
||||
typename Policy::value_type;
|
||||
using reference = value_type const&;
|
||||
using pointer = value_type const*;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using iterator_category =
|
||||
std::forward_iterator_tag;
|
||||
|
||||
const_iterator() = default;
|
||||
|
||||
bool
|
||||
operator==(
|
||||
const_iterator const& other) const
|
||||
{
|
||||
return
|
||||
other.list_ == list_ &&
|
||||
other.it_ == it_;
|
||||
}
|
||||
|
||||
bool
|
||||
operator!=(
|
||||
const_iterator const& other) const
|
||||
{
|
||||
return ! (*this == other);
|
||||
}
|
||||
|
||||
reference
|
||||
operator*() const
|
||||
{
|
||||
return v_;
|
||||
}
|
||||
|
||||
const_iterator&
|
||||
operator++()
|
||||
{
|
||||
increment();
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_iterator
|
||||
operator++(int)
|
||||
{
|
||||
auto temp = *this;
|
||||
++(*this);
|
||||
return temp;
|
||||
}
|
||||
|
||||
bool
|
||||
error() const
|
||||
{
|
||||
return error_;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class basic_parsed_list;
|
||||
|
||||
const_iterator(
|
||||
basic_parsed_list const& list, bool at_end)
|
||||
: list_(&list)
|
||||
, it_(at_end ? nullptr :
|
||||
list.s_.begin())
|
||||
{
|
||||
if(! at_end)
|
||||
increment();
|
||||
}
|
||||
|
||||
void
|
||||
increment()
|
||||
{
|
||||
if(! this->member()(
|
||||
v_, it_, list_->s_))
|
||||
{
|
||||
it_ = nullptr;
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Construct a list from a string
|
||||
explicit
|
||||
basic_parsed_list(boost::string_ref const& s)
|
||||
: s_(s)
|
||||
{
|
||||
}
|
||||
|
||||
/// Return a const iterator to the beginning of the list
|
||||
const_iterator begin() const;
|
||||
|
||||
/// Return a const iterator to the end of the list
|
||||
const_iterator end() const;
|
||||
|
||||
/// Return a const iterator to the beginning of the list
|
||||
const_iterator cbegin() const;
|
||||
|
||||
/// Return a const iterator to the end of the list
|
||||
const_iterator cend() const;
|
||||
};
|
||||
|
||||
template<class Policy>
|
||||
inline
|
||||
auto
|
||||
basic_parsed_list<Policy>::
|
||||
begin() const ->
|
||||
const_iterator
|
||||
{
|
||||
return const_iterator{*this, false};
|
||||
}
|
||||
|
||||
template<class Policy>
|
||||
inline
|
||||
auto
|
||||
basic_parsed_list<Policy>::
|
||||
end() const ->
|
||||
const_iterator
|
||||
{
|
||||
return const_iterator{*this, true};
|
||||
}
|
||||
|
||||
template<class Policy>
|
||||
inline
|
||||
auto
|
||||
basic_parsed_list<Policy>::
|
||||
cbegin() const ->
|
||||
const_iterator
|
||||
{
|
||||
return const_iterator{*this, false};
|
||||
}
|
||||
|
||||
template<class Policy>
|
||||
inline
|
||||
auto
|
||||
basic_parsed_list<Policy>::
|
||||
cend() const ->
|
||||
const_iterator
|
||||
{
|
||||
return const_iterator{*this, true};
|
||||
}
|
||||
|
||||
} // detail
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
||||
|
446
include/beast/http/detail/basic_parser.hpp
Normal file
446
include/beast/http/detail/basic_parser.hpp
Normal file
@ -0,0 +1,446 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_DETAIL_BASIC_PARSER_HPP
|
||||
#define BEAST_HTTP_DETAIL_BASIC_PARSER_HPP
|
||||
|
||||
#include <beast/core/detail/ci_char_traits.hpp>
|
||||
#include <beast/http/error.hpp>
|
||||
#include <beast/http/detail/rfc7230.hpp>
|
||||
#include <boost/utility/string_ref.hpp>
|
||||
#include <boost/version.hpp>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
/*
|
||||
Portions of this file based on code from picophttpparser,
|
||||
copyright notice below.
|
||||
https://github.com/h2o/picohttpparser
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
|
||||
* Shigeo Mitsunari
|
||||
*
|
||||
* The software is licensed under either the MIT License (below) or the Perl
|
||||
* license.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
namespace detail {
|
||||
|
||||
#if __GNUC__ >= 3
|
||||
# define BEAST_LIKELY(x) __builtin_expect(!!(x), 1)
|
||||
# define BEAST_UNLIKELY(x) __builtin_expect(!!(x), 0)
|
||||
#else
|
||||
#define BEAST_LIKELY(x) (x)
|
||||
#define BEAST_UNLIKELY(x) (x)
|
||||
#endif
|
||||
|
||||
class basic_parser_base
|
||||
{
|
||||
protected:
|
||||
static
|
||||
bool
|
||||
is_pathchar(char c)
|
||||
{
|
||||
// VFALCO This looks the same as the one below...
|
||||
|
||||
// TEXT = <any OCTET except CTLs, and excluding LWS>
|
||||
static bool constexpr tab[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 144
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 160
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 176
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 192
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 208
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240
|
||||
};
|
||||
return tab[static_cast<unsigned char>(c)];
|
||||
}
|
||||
|
||||
static
|
||||
bool
|
||||
is_value_char(char c)
|
||||
{
|
||||
// any OCTET except CTLs and LWS
|
||||
static bool constexpr tab[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 144
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 160
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 176
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 192
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 208
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240
|
||||
};
|
||||
return tab[static_cast<unsigned char>(c)];
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
bool
|
||||
is_text(char c)
|
||||
{
|
||||
// VCHAR / SP / HT / obs-text
|
||||
static bool constexpr tab[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 144
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 160
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 176
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 192
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 208
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240
|
||||
};
|
||||
return tab[static_cast<unsigned char>(c)];
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
bool
|
||||
unhex(unsigned char& d, char c)
|
||||
{
|
||||
static signed char constexpr tab[256] = {
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 16
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 32
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1, // 48
|
||||
-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 64
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 80
|
||||
-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 96
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 112
|
||||
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 128
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 144
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 160
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 176
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 192
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 208
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 224
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 240
|
||||
};
|
||||
d = static_cast<unsigned char>(
|
||||
tab[static_cast<unsigned char>(c)]);
|
||||
return d != static_cast<unsigned char>(-1);
|
||||
}
|
||||
|
||||
static
|
||||
bool
|
||||
is_digit(char c)
|
||||
{
|
||||
return static_cast<unsigned char>(c-'0') < 10;
|
||||
}
|
||||
|
||||
static
|
||||
bool
|
||||
is_print(char c)
|
||||
{
|
||||
return static_cast<unsigned char>(c-33) < 94;
|
||||
}
|
||||
|
||||
static
|
||||
boost::string_ref
|
||||
make_string(char const* first, char const* last)
|
||||
{
|
||||
return {first, static_cast<
|
||||
std::size_t>(last - first)};
|
||||
}
|
||||
|
||||
template<class = void>
|
||||
static
|
||||
bool
|
||||
strieq(boost::string_ref const& s1,
|
||||
boost::string_ref const& s2)
|
||||
{
|
||||
if(s1.size() != s2.size())
|
||||
return false;
|
||||
auto p1 = s1.data();
|
||||
auto p2 = s2.data();
|
||||
for(auto n = s1.size(); n--; ++p1, ++p2)
|
||||
if(*p1 != tolower(*p2))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<std::size_t N>
|
||||
bool
|
||||
strieq(const char (&s1)[N],
|
||||
boost::string_ref const& s2)
|
||||
{
|
||||
return strieq({s1, N-1}, s2);
|
||||
}
|
||||
|
||||
template<class Iter, class Unsigned>
|
||||
static
|
||||
bool
|
||||
parse_dec(Iter it, Iter last, Unsigned& v)
|
||||
{
|
||||
if(! is_digit(*it))
|
||||
return false;
|
||||
v = *it - '0';
|
||||
for(;;)
|
||||
{
|
||||
if(! is_digit(*++it))
|
||||
break;
|
||||
auto const d = *it - '0';
|
||||
if(v > ((std::numeric_limits<
|
||||
Unsigned>::max)() - 10) / 10)
|
||||
return false;
|
||||
v = 10 * v + d;
|
||||
}
|
||||
return it == last;
|
||||
}
|
||||
|
||||
template<class Iter, class Unsigned>
|
||||
bool
|
||||
parse_hex(Iter& it, Unsigned& v)
|
||||
{
|
||||
unsigned char d;
|
||||
if(! unhex(d, *it))
|
||||
return false;
|
||||
v = d;
|
||||
for(;;)
|
||||
{
|
||||
if(! unhex(d, *++it))
|
||||
break;
|
||||
auto const v0 = v;
|
||||
v = 16 * v + d;
|
||||
if(v <= v0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
bool
|
||||
parse_crlf(char const*& it)
|
||||
{
|
||||
if(*it != '\r')
|
||||
return false;
|
||||
if(*++it != '\n')
|
||||
return false;
|
||||
++it;
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
boost::string_ref
|
||||
parse_method(char const*& it)
|
||||
{
|
||||
auto const first = it;
|
||||
while(detail::is_tchar(*it))
|
||||
++it;
|
||||
return {first, static_cast<
|
||||
boost::string_ref::size_type>(
|
||||
it - first)};
|
||||
}
|
||||
|
||||
static
|
||||
boost::string_ref
|
||||
parse_path(char const*& it)
|
||||
{
|
||||
auto const first = it;
|
||||
while(is_pathchar(*it))
|
||||
++it;
|
||||
if(*it != ' ')
|
||||
return {};
|
||||
return {first, static_cast<
|
||||
boost::string_ref::size_type>(
|
||||
it - first)};
|
||||
}
|
||||
|
||||
static
|
||||
boost::string_ref
|
||||
parse_name(char const*& it)
|
||||
{
|
||||
auto const first = it;
|
||||
while(to_field_char(*it))
|
||||
++it;
|
||||
return {first, static_cast<
|
||||
boost::string_ref::size_type>(
|
||||
it - first)};
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
parse_version(char const*& it)
|
||||
{
|
||||
if(*it != 'H')
|
||||
return -1;
|
||||
if(*++it != 'T')
|
||||
return -1;
|
||||
if(*++it != 'T')
|
||||
return -1;
|
||||
if(*++it != 'P')
|
||||
return -1;
|
||||
if(*++it != '/')
|
||||
return -1;
|
||||
if(! is_digit(*++it))
|
||||
return -1;
|
||||
int v = 10 * (*it - '0');
|
||||
if(*++it != '.')
|
||||
return -1;
|
||||
if(! is_digit(*++it))
|
||||
return -1;
|
||||
v += *it++ - '0';
|
||||
return v;
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
parse_status(char const*& it)
|
||||
{
|
||||
int v;
|
||||
if(! is_digit(*it))
|
||||
return -1;
|
||||
v = 100 * (*it - '0');
|
||||
if(! is_digit(*++it))
|
||||
return -1;
|
||||
v += 10 * (*it - '0');
|
||||
if(! is_digit(*++it))
|
||||
return -1;
|
||||
v += (*it++ - '0');
|
||||
return v;
|
||||
}
|
||||
|
||||
static
|
||||
boost::string_ref
|
||||
parse_reason(char const*& it)
|
||||
{
|
||||
auto const first = it;
|
||||
while(*it != '\r')
|
||||
{
|
||||
if(! is_text(*it))
|
||||
return {};
|
||||
++it;
|
||||
}
|
||||
return {first, static_cast<
|
||||
std::size_t>(it - first)};
|
||||
}
|
||||
|
||||
// VFALCO Can SIMD help this?
|
||||
static
|
||||
char const*
|
||||
find_eol(
|
||||
char const* first, char const* last,
|
||||
error_code& ec)
|
||||
{
|
||||
auto it = first;
|
||||
for(;;)
|
||||
{
|
||||
if(it == last)
|
||||
return nullptr;
|
||||
if(*it == '\r')
|
||||
{
|
||||
if(++it == last)
|
||||
return nullptr;
|
||||
if(*it != '\n')
|
||||
{
|
||||
ec = error::bad_line_ending;
|
||||
return nullptr;
|
||||
}
|
||||
return ++it;
|
||||
}
|
||||
// VFALCO Should we handle the legacy case
|
||||
// for lines terminated with a single '\n'?
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// VFALCO Can SIMD help this?
|
||||
static
|
||||
char const*
|
||||
find_eom(
|
||||
char const* first, char const* last,
|
||||
error_code& ec)
|
||||
{
|
||||
auto it = first;
|
||||
for(;;)
|
||||
{
|
||||
if(it == last)
|
||||
return nullptr;
|
||||
if(*it == '\r')
|
||||
{
|
||||
if(++it == last)
|
||||
return nullptr;
|
||||
if(*it != '\n')
|
||||
{
|
||||
ec = error::bad_line_ending;
|
||||
return nullptr;
|
||||
}
|
||||
if(++it == last)
|
||||
return nullptr;
|
||||
if(*it != '\r')
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
if(++it == last)
|
||||
return nullptr;
|
||||
if(*it != '\n')
|
||||
{
|
||||
ec = error::bad_line_ending;
|
||||
return nullptr;
|
||||
}
|
||||
return ++it;
|
||||
}
|
||||
// VFALCO Should we handle the legacy case
|
||||
// for lines terminated with a single '\n'?
|
||||
++it;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
@ -1,146 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_DETAIL_BASIC_PARSER_V1_HPP
|
||||
#define BEAST_HTTP_DETAIL_BASIC_PARSER_V1_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
namespace detail {
|
||||
|
||||
template<class = void>
|
||||
struct parser_str_t
|
||||
{
|
||||
static char constexpr close[6] = "close";
|
||||
static char constexpr chunked[8] = "chunked";
|
||||
static char constexpr keep_alive[11] = "keep-alive";
|
||||
|
||||
static char constexpr upgrade[8] = "upgrade";
|
||||
static char constexpr connection[11] = "connection";
|
||||
static char constexpr content_length[15] = "content-length";
|
||||
static char constexpr proxy_connection[17] = "proxy-connection";
|
||||
static char constexpr transfer_encoding[18] = "transfer-encoding";
|
||||
};
|
||||
|
||||
template<class _>
|
||||
char constexpr
|
||||
parser_str_t<_>::close[6];
|
||||
|
||||
template<class _>
|
||||
char constexpr
|
||||
parser_str_t<_>::chunked[8];
|
||||
|
||||
template<class _>
|
||||
char constexpr
|
||||
parser_str_t<_>::keep_alive[11];
|
||||
|
||||
template<class _>
|
||||
char constexpr
|
||||
parser_str_t<_>::upgrade[8];
|
||||
|
||||
template<class _>
|
||||
char constexpr
|
||||
parser_str_t<_>::connection[11];
|
||||
|
||||
template<class _>
|
||||
char constexpr
|
||||
parser_str_t<_>::content_length[15];
|
||||
|
||||
template<class _>
|
||||
char constexpr
|
||||
parser_str_t<_>::proxy_connection[17];
|
||||
|
||||
template<class _>
|
||||
char constexpr
|
||||
parser_str_t<_>::transfer_encoding[18];
|
||||
|
||||
using parser_str = parser_str_t<>;
|
||||
|
||||
class parser_base
|
||||
{
|
||||
protected:
|
||||
enum state : std::uint8_t
|
||||
{
|
||||
s_dead = 1,
|
||||
|
||||
s_req_start,
|
||||
s_req_method0,
|
||||
s_req_method,
|
||||
s_req_url0,
|
||||
s_req_url,
|
||||
s_req_http,
|
||||
s_req_http_H,
|
||||
s_req_http_HT,
|
||||
s_req_http_HTT,
|
||||
s_req_http_HTTP,
|
||||
s_req_major,
|
||||
s_req_dot,
|
||||
s_req_minor,
|
||||
s_req_cr,
|
||||
s_req_lf,
|
||||
|
||||
s_res_start,
|
||||
s_res_H,
|
||||
s_res_HT,
|
||||
s_res_HTT,
|
||||
s_res_HTTP,
|
||||
s_res_major,
|
||||
s_res_dot,
|
||||
s_res_minor,
|
||||
s_res_space_1,
|
||||
s_res_status0,
|
||||
s_res_status1,
|
||||
s_res_status2,
|
||||
s_res_space_2,
|
||||
s_res_reason0,
|
||||
s_res_reason,
|
||||
s_res_line_lf,
|
||||
s_res_line_done,
|
||||
|
||||
s_header_name0,
|
||||
s_header_name,
|
||||
s_header_value0_lf,
|
||||
s_header_value0_almost_done,
|
||||
s_header_value0,
|
||||
s_header_value,
|
||||
s_header_value_lf,
|
||||
s_header_value_almost_done,
|
||||
s_header_value_unfold,
|
||||
|
||||
s_headers_almost_done,
|
||||
s_headers_done,
|
||||
|
||||
s_chunk_size0,
|
||||
s_chunk_size,
|
||||
s_chunk_ext_name0,
|
||||
s_chunk_ext_name,
|
||||
s_chunk_ext_val,
|
||||
s_chunk_size_lf,
|
||||
s_chunk_data0,
|
||||
s_chunk_data,
|
||||
s_chunk_data_cr,
|
||||
s_chunk_data_lf,
|
||||
|
||||
s_body_pause,
|
||||
s_body_identity0,
|
||||
s_body_identity,
|
||||
s_body_identity_eof0,
|
||||
s_body_identity_eof,
|
||||
|
||||
s_complete,
|
||||
s_restart,
|
||||
s_closed_complete
|
||||
};
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
@ -46,7 +46,7 @@ is_alpha(char c)
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 240
|
||||
};
|
||||
static_assert(sizeof(tab) == 256, "");
|
||||
return tab[static_cast<std::uint8_t>(c)];
|
||||
return tab[static_cast<unsigned char>(c)];
|
||||
}
|
||||
|
||||
inline
|
||||
@ -73,7 +73,7 @@ is_text(char c)
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240
|
||||
};
|
||||
static_assert(sizeof(tab) == 256, "");
|
||||
return tab[static_cast<std::uint8_t>(c)];
|
||||
return tab[static_cast<unsigned char>(c)];
|
||||
}
|
||||
|
||||
inline
|
||||
@ -105,7 +105,7 @@ is_tchar(char c)
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 240
|
||||
};
|
||||
static_assert(sizeof(tab) == 256, "");
|
||||
return tab[static_cast<std::uint8_t>(c)];
|
||||
return tab[static_cast<unsigned char>(c)];
|
||||
}
|
||||
|
||||
inline
|
||||
@ -134,7 +134,7 @@ is_qdchar(char c)
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240
|
||||
};
|
||||
static_assert(sizeof(tab) == 256, "");
|
||||
return tab[static_cast<std::uint8_t>(c)];
|
||||
return tab[static_cast<unsigned char>(c)];
|
||||
}
|
||||
|
||||
inline
|
||||
@ -164,7 +164,7 @@ is_qpchar(char c)
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240
|
||||
};
|
||||
static_assert(sizeof(tab) == 256, "");
|
||||
return tab[static_cast<std::uint8_t>(c)];
|
||||
return tab[static_cast<unsigned char>(c)];
|
||||
}
|
||||
|
||||
// converts to lower case,
|
||||
@ -200,7 +200,7 @@ to_field_char(char c)
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
static_assert(sizeof(tab) == 256, "");
|
||||
return tab[static_cast<std::uint8_t>(c)];
|
||||
return tab[static_cast<unsigned char>(c)];
|
||||
}
|
||||
|
||||
// converts to lower case,
|
||||
@ -230,9 +230,10 @@ to_value_char(char c)
|
||||
240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 // 240
|
||||
};
|
||||
static_assert(sizeof(tab) == 256, "");
|
||||
return static_cast<char>(tab[static_cast<std::uint8_t>(c)]);
|
||||
return static_cast<char>(tab[static_cast<unsigned char>(c)]);
|
||||
}
|
||||
|
||||
// VFALCO TODO Make this return unsigned?
|
||||
inline
|
||||
std::int8_t
|
||||
unhex(char c)
|
||||
@ -256,22 +257,68 @@ unhex(char c)
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 240
|
||||
};
|
||||
static_assert(sizeof(tab) == 256, "");
|
||||
return tab[static_cast<std::uint8_t>(c)];
|
||||
return tab[static_cast<unsigned char>(c)];
|
||||
}
|
||||
|
||||
template<class FwdIt>
|
||||
inline
|
||||
void
|
||||
skip_ows(FwdIt& it, FwdIt const& end)
|
||||
{
|
||||
while(it != end)
|
||||
{
|
||||
auto const c = *it;
|
||||
if(c != ' ' && c != '\t')
|
||||
if(*it != ' ' && *it != '\t')
|
||||
break;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
template<class RanIt>
|
||||
inline
|
||||
void
|
||||
skip_ows_rev(
|
||||
RanIt& it, RanIt const& first)
|
||||
{
|
||||
while(it != first)
|
||||
{
|
||||
auto const c = it[-1];
|
||||
if(c != ' ' && c != '\t')
|
||||
break;
|
||||
--it;
|
||||
}
|
||||
}
|
||||
|
||||
// obs-fold = CRLF 1*( SP / HTAB )
|
||||
// return `false` on parse error
|
||||
//
|
||||
template<class FwdIt>
|
||||
inline
|
||||
bool
|
||||
skip_obs_fold(
|
||||
FwdIt& it, FwdIt const& last)
|
||||
{
|
||||
for(;;)
|
||||
{
|
||||
if(*it != '\r')
|
||||
return true;
|
||||
if(++it == last)
|
||||
return false;
|
||||
if(*it != '\n')
|
||||
return false;
|
||||
if(++it == last)
|
||||
return false;
|
||||
if(*it != ' ' && *it != '\t')
|
||||
return false;
|
||||
for(;;)
|
||||
{
|
||||
if(++it == last)
|
||||
return true;
|
||||
if(*it != ' ' && *it != '\t')
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class FwdIt>
|
||||
void
|
||||
skip_token(FwdIt& it, FwdIt const& last)
|
||||
@ -403,6 +450,53 @@ increment()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#token = [ ( "," / token ) *( OWS "," [ OWS token ] ) ]
|
||||
*/
|
||||
struct opt_token_list_policy
|
||||
{
|
||||
using value_type = boost::string_ref;
|
||||
|
||||
bool
|
||||
operator()(value_type& v,
|
||||
char const*& it, boost::string_ref const& s) const
|
||||
{
|
||||
v = {};
|
||||
auto need_comma = it != s.begin();
|
||||
for(;;)
|
||||
{
|
||||
detail::skip_ows(it, s.end());
|
||||
if(it == s.end())
|
||||
{
|
||||
it = nullptr;
|
||||
return true;
|
||||
}
|
||||
auto const c = *it;
|
||||
if(detail::is_tchar(c))
|
||||
{
|
||||
if(need_comma)
|
||||
return false;
|
||||
auto const p0 = it;
|
||||
for(;;)
|
||||
{
|
||||
++it;
|
||||
if(it == s.end())
|
||||
break;
|
||||
if(! detail::is_tchar(*it))
|
||||
break;
|
||||
}
|
||||
v = boost::string_ref{&*p0,
|
||||
static_cast<std::size_t>(it - p0)};
|
||||
return true;
|
||||
}
|
||||
if(c != ',')
|
||||
return false;
|
||||
need_comma = false;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // http
|
||||
} // beast
|
||||
|
83
include/beast/http/error.hpp
Normal file
83
include/beast/http/error.hpp
Normal file
@ -0,0 +1,83 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_ERROR_HPP
|
||||
#define BEAST_HTTP_ERROR_HPP
|
||||
|
||||
#include <beast/config.hpp>
|
||||
#include <beast/core/error.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
/// Error codes returned from HTTP parsing
|
||||
enum class error
|
||||
{
|
||||
/** The end of the stream was reached.
|
||||
|
||||
This error is returned by @ref basic_parser::write_eof
|
||||
when the end of stream is reached and there are no
|
||||
unparsed bytes in the stream buffer.
|
||||
*/
|
||||
end_of_stream = 1,
|
||||
|
||||
/** The incoming message is incomplete.
|
||||
|
||||
This happens when the end of stream is reached
|
||||
and some bytes have been received, but not the
|
||||
entire message.
|
||||
*/
|
||||
partial_message,
|
||||
|
||||
/** Buffer maximum exceeded.
|
||||
|
||||
This error is returned when reading HTTP content
|
||||
into a dynamic buffer, and the operation would
|
||||
exceed the maximum size of the buffer.
|
||||
*/
|
||||
buffer_overflow,
|
||||
|
||||
/// The line ending was malformed
|
||||
bad_line_ending,
|
||||
|
||||
/// The method is invalid.
|
||||
bad_method,
|
||||
|
||||
/// The request-target is invalid.
|
||||
bad_path,
|
||||
|
||||
/// The HTTP-version is invalid.
|
||||
bad_version,
|
||||
|
||||
/// The status-code is invalid.
|
||||
bad_status,
|
||||
|
||||
/// The reason-phrase is invalid.
|
||||
bad_reason,
|
||||
|
||||
/// The field name is invalid.
|
||||
bad_field,
|
||||
|
||||
/// The field value is invalid.
|
||||
bad_value,
|
||||
|
||||
/// The Content-Length is invalid.
|
||||
bad_content_length,
|
||||
|
||||
/// The Transfer-Encoding is invalid.
|
||||
bad_transfer_encoding,
|
||||
|
||||
/// The chunk syntax is invalid.
|
||||
bad_chunk
|
||||
};
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#include <beast/http/impl/error.ipp>
|
||||
|
||||
#endif
|
190
include/beast/http/header_parser.hpp
Normal file
190
include/beast/http/header_parser.hpp
Normal file
@ -0,0 +1,190 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_HEADER_PARSER_HPP
|
||||
#define BEAST_HTTP_HEADER_PARSER_HPP
|
||||
|
||||
#include <beast/config.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/http/basic_parser.hpp>
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
/** A parser for producing HTTP/1 headers.
|
||||
|
||||
This class uses the basic HTTP/1 wire format parser to convert
|
||||
a series of octets into a @ref header.
|
||||
|
||||
@note A new instance of the parser is required for each message.
|
||||
|
||||
@tparam isRequest Indicates whether a request or response
|
||||
will be parsed.
|
||||
|
||||
@tparam Fields The type of container used to represent the fields.
|
||||
*/
|
||||
template<bool isRequest, class Fields>
|
||||
class header_parser
|
||||
: public basic_parser<isRequest, false,
|
||||
header_parser<isRequest, Fields>>
|
||||
{
|
||||
header<isRequest, Fields> h_;
|
||||
|
||||
public:
|
||||
using mutable_buffers_type =
|
||||
boost::asio::null_buffers;
|
||||
|
||||
/// The type of @ref header this object produces.
|
||||
using value_type = header<isRequest, Fields>;
|
||||
|
||||
/// Copy constructor.
|
||||
header_parser(header_parser const&) = default;
|
||||
|
||||
/// Copy assignment.
|
||||
header_parser& operator=(header_parser const&) = default;
|
||||
|
||||
/** Move constructor.
|
||||
|
||||
After the move, the only valid operation
|
||||
on the moved-from object is destruction.
|
||||
*/
|
||||
header_parser(header_parser&&) = default;
|
||||
|
||||
/** Constructor
|
||||
|
||||
@param args If present, additional arguments to be
|
||||
forwarded to the @ref beast::http::header constructor.
|
||||
*/
|
||||
template<class... Args>
|
||||
explicit
|
||||
header_parser(Args&&... args);
|
||||
|
||||
/** Returns the parsed header
|
||||
|
||||
Only valid if @ref got_header would return `true`.
|
||||
*/
|
||||
value_type const&
|
||||
get() const
|
||||
{
|
||||
return h_;
|
||||
}
|
||||
|
||||
/** Returns the parsed header.
|
||||
|
||||
Only valid if @ref got_header would return `true`.
|
||||
*/
|
||||
value_type&
|
||||
get()
|
||||
{
|
||||
return h_;
|
||||
}
|
||||
|
||||
/** Returns ownership of the parsed header.
|
||||
|
||||
Ownership is transferred to the caller. Only
|
||||
valid if @ref got_header would return `true`.
|
||||
|
||||
Requires:
|
||||
@ref value_type is @b MoveConstructible
|
||||
*/
|
||||
value_type
|
||||
release()
|
||||
{
|
||||
static_assert(std::is_move_constructible<decltype(h_)>::value,
|
||||
"MoveConstructible requirements not met");
|
||||
return std::move(h_);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class basic_parser<
|
||||
isRequest, false, header_parser>;
|
||||
|
||||
void
|
||||
on_request(
|
||||
boost::string_ref const& method,
|
||||
boost::string_ref const& path,
|
||||
int version, error_code&)
|
||||
{
|
||||
h_.url = std::string{
|
||||
path.data(), path.size()};
|
||||
h_.method = std::string{
|
||||
method.data(), method.size()};
|
||||
h_.version = version;
|
||||
}
|
||||
|
||||
void
|
||||
on_response(int status,
|
||||
boost::string_ref const& reason,
|
||||
int version, error_code&)
|
||||
{
|
||||
h_.status = status;
|
||||
h_.reason = std::string{
|
||||
reason.data(), reason.size()};
|
||||
h_.version = version;
|
||||
}
|
||||
|
||||
void
|
||||
on_field(boost::string_ref const& name,
|
||||
boost::string_ref const& value,
|
||||
error_code&)
|
||||
{
|
||||
h_.fields.insert(name, value);
|
||||
}
|
||||
|
||||
void
|
||||
on_header(error_code&)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_body(error_code& ec)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_body(std::uint64_t content_length,
|
||||
error_code& ec)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_data(boost::string_ref const& s,
|
||||
error_code& ec)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_commit(std::size_t n)
|
||||
{
|
||||
// Can't write body data with header-only parser!
|
||||
BOOST_ASSERT(false);
|
||||
throw std::logic_error{
|
||||
"invalid member function call"};
|
||||
}
|
||||
|
||||
void
|
||||
on_chunk(std::uint64_t n,
|
||||
boost::string_ref const& ext,
|
||||
error_code& ec)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_complete(error_code&)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#include <beast/http/impl/header_parser.ipp>
|
||||
|
||||
#endif
|
@ -1,233 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_HEADERS_PARSER_V1_HPP
|
||||
#define BEAST_HTTP_HEADERS_PARSER_V1_HPP
|
||||
|
||||
#include <beast/config.hpp>
|
||||
#include <beast/http/basic_parser_v1.hpp>
|
||||
#include <beast/http/concepts.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/core/error.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct request_parser_base
|
||||
{
|
||||
std::string method_;
|
||||
std::string uri_;
|
||||
};
|
||||
|
||||
struct response_parser_base
|
||||
{
|
||||
std::string reason_;
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
/** A parser for a HTTP/1 request or response header.
|
||||
|
||||
This class uses the HTTP/1 wire format parser to
|
||||
convert a series of octets into a request or
|
||||
response @ref header.
|
||||
|
||||
@note A new instance of the parser is required for each message.
|
||||
*/
|
||||
template<bool isRequest, class Fields>
|
||||
class header_parser_v1
|
||||
: public basic_parser_v1<isRequest,
|
||||
header_parser_v1<isRequest, Fields>>
|
||||
, private std::conditional<isRequest,
|
||||
detail::request_parser_base,
|
||||
detail::response_parser_base>::type
|
||||
{
|
||||
public:
|
||||
/// The type of the header this parser produces.
|
||||
using header_type = header<isRequest, Fields>;
|
||||
|
||||
private:
|
||||
// VFALCO Check Fields requirements?
|
||||
|
||||
std::string field_;
|
||||
std::string value_;
|
||||
header_type h_;
|
||||
bool flush_ = false;
|
||||
|
||||
public:
|
||||
/// Default constructor
|
||||
header_parser_v1() = default;
|
||||
|
||||
/// Move constructor
|
||||
header_parser_v1(header_parser_v1&&) = default;
|
||||
|
||||
/// Copy constructor (disallowed)
|
||||
header_parser_v1(header_parser_v1 const&) = delete;
|
||||
|
||||
/// Move assignment (disallowed)
|
||||
header_parser_v1& operator=(header_parser_v1&&) = delete;
|
||||
|
||||
/// Copy assignment (disallowed)
|
||||
header_parser_v1& operator=(header_parser_v1 const&) = delete;
|
||||
|
||||
/** Construct the parser.
|
||||
|
||||
@param args Forwarded to the header constructor.
|
||||
*/
|
||||
#if BEAST_DOXYGEN
|
||||
template<class... Args>
|
||||
explicit
|
||||
header_parser_v1(Args&&... args);
|
||||
#else
|
||||
template<class Arg1, class... ArgN,
|
||||
class = typename std::enable_if<! std::is_same<
|
||||
typename std::decay<Arg1>::type, header_parser_v1>::value>>
|
||||
explicit
|
||||
header_parser_v1(Arg1&& arg1, ArgN&&... argn)
|
||||
: h_(std::forward<Arg1>(arg1),
|
||||
std::forward<ArgN>(argn)...)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Returns the parsed header
|
||||
|
||||
Only valid if @ref complete would return `true`.
|
||||
*/
|
||||
header_type const&
|
||||
get() const
|
||||
{
|
||||
return h_;
|
||||
}
|
||||
|
||||
/** Returns the parsed header.
|
||||
|
||||
Only valid if @ref complete would return `true`.
|
||||
*/
|
||||
header_type&
|
||||
get()
|
||||
{
|
||||
return h_;
|
||||
}
|
||||
|
||||
/** Returns ownership of the parsed header.
|
||||
|
||||
Ownership is transferred to the caller. Only
|
||||
valid if @ref complete would return `true`.
|
||||
|
||||
Requires:
|
||||
@ref header_type is @b MoveConstructible
|
||||
*/
|
||||
header_type
|
||||
release()
|
||||
{
|
||||
static_assert(std::is_move_constructible<decltype(h_)>::value,
|
||||
"MoveConstructible requirements not met");
|
||||
return std::move(h_);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class basic_parser_v1<isRequest, header_parser_v1>;
|
||||
|
||||
void flush()
|
||||
{
|
||||
if(! flush_)
|
||||
return;
|
||||
flush_ = false;
|
||||
BOOST_ASSERT(! field_.empty());
|
||||
h_.fields.insert(field_, value_);
|
||||
field_.clear();
|
||||
value_.clear();
|
||||
}
|
||||
|
||||
void on_start(error_code&)
|
||||
{
|
||||
}
|
||||
|
||||
void on_method(boost::string_ref const& s, error_code&)
|
||||
{
|
||||
this->method_.append(s.data(), s.size());
|
||||
}
|
||||
|
||||
void on_uri(boost::string_ref const& s, error_code&)
|
||||
{
|
||||
this->uri_.append(s.data(), s.size());
|
||||
}
|
||||
|
||||
void on_reason(boost::string_ref const& s, error_code&)
|
||||
{
|
||||
this->reason_.append(s.data(), s.size());
|
||||
}
|
||||
|
||||
void on_request_or_response(std::true_type)
|
||||
{
|
||||
h_.method = std::move(this->method_);
|
||||
h_.url = std::move(this->uri_);
|
||||
}
|
||||
|
||||
void on_request_or_response(std::false_type)
|
||||
{
|
||||
h_.status = this->status_code();
|
||||
h_.reason = std::move(this->reason_);
|
||||
}
|
||||
|
||||
void on_request(error_code& ec)
|
||||
{
|
||||
on_request_or_response(
|
||||
std::integral_constant<bool, isRequest>{});
|
||||
}
|
||||
|
||||
void on_response(error_code& ec)
|
||||
{
|
||||
on_request_or_response(
|
||||
std::integral_constant<bool, isRequest>{});
|
||||
}
|
||||
|
||||
void on_field(boost::string_ref const& s, error_code&)
|
||||
{
|
||||
flush();
|
||||
field_.append(s.data(), s.size());
|
||||
}
|
||||
|
||||
void on_value(boost::string_ref const& s, error_code&)
|
||||
{
|
||||
value_.append(s.data(), s.size());
|
||||
flush_ = true;
|
||||
}
|
||||
|
||||
void
|
||||
on_header(std::uint64_t, error_code&)
|
||||
{
|
||||
flush();
|
||||
h_.version = 10 * this->http_major() + this->http_minor();
|
||||
}
|
||||
|
||||
body_what
|
||||
on_body_what(std::uint64_t, error_code&)
|
||||
{
|
||||
return body_what::pause;
|
||||
}
|
||||
|
||||
void on_body(boost::string_ref const&, error_code&)
|
||||
{
|
||||
}
|
||||
|
||||
void on_complete(error_code&)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
716
include/beast/http/impl/async_read.ipp
Normal file
716
include/beast/http/impl/async_read.ipp
Normal file
@ -0,0 +1,716 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_IMPL_ASYNC_READ_IPP_HPP
|
||||
#define BEAST_HTTP_IMPL_ASYNC_READ_IPP_HPP
|
||||
|
||||
#include <beast/http/concepts.hpp>
|
||||
#include <beast/http/error.hpp>
|
||||
#include <beast/http/message_parser.hpp>
|
||||
#include <beast/http/read.hpp>
|
||||
#include <beast/core/bind_handler.hpp>
|
||||
#include <beast/core/handler_helpers.hpp>
|
||||
#include <beast/core/handler_ptr.hpp>
|
||||
#include <beast/core/stream_concepts.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
namespace detail {
|
||||
|
||||
template<class Stream, class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived,
|
||||
class Handler>
|
||||
class read_some_buffer_op
|
||||
{
|
||||
struct data
|
||||
{
|
||||
bool cont;
|
||||
Stream& s;
|
||||
DynamicBuffer& db;
|
||||
basic_parser<isRequest, isDirect, Derived>& p;
|
||||
boost::optional<typename
|
||||
DynamicBuffer::mutable_buffers_type> mb;
|
||||
boost::optional<typename
|
||||
Derived::mutable_buffers_type> bb;
|
||||
std::size_t bytes_used;
|
||||
int state = 0;
|
||||
|
||||
data(Handler& handler, Stream& s_, DynamicBuffer& db_,
|
||||
basic_parser<isRequest, isDirect, Derived>& p_)
|
||||
: cont(beast_asio_helpers::
|
||||
is_continuation(handler))
|
||||
, s(s_)
|
||||
, db(db_)
|
||||
, p(p_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
handler_ptr<data, Handler> d_;
|
||||
|
||||
public:
|
||||
read_some_buffer_op(read_some_buffer_op&&) = default;
|
||||
read_some_buffer_op(read_some_buffer_op const&) = default;
|
||||
|
||||
template<class DeducedHandler, class... Args>
|
||||
read_some_buffer_op(DeducedHandler&& h,
|
||||
Stream& s, Args&&... args)
|
||||
: d_(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,
|
||||
read_some_buffer_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
allocate(size, op->d_.handler());
|
||||
}
|
||||
|
||||
friend
|
||||
void
|
||||
asio_handler_deallocate(
|
||||
void* p, std::size_t size,
|
||||
read_some_buffer_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
deallocate(p, size, op->d_.handler());
|
||||
}
|
||||
|
||||
friend
|
||||
bool
|
||||
asio_handler_is_continuation(
|
||||
read_some_buffer_op* op)
|
||||
{
|
||||
return op->d_->cont;
|
||||
}
|
||||
|
||||
template<class Function>
|
||||
friend
|
||||
void
|
||||
asio_handler_invoke(Function&& f,
|
||||
read_some_buffer_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
invoke(f, op->d_.handler());
|
||||
}
|
||||
};
|
||||
|
||||
template<class Stream, class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived,
|
||||
class Handler>
|
||||
void
|
||||
read_some_buffer_op<Stream, DynamicBuffer,
|
||||
isRequest, isDirect, Derived, Handler>::
|
||||
operator()(error_code ec,
|
||||
std::size_t bytes_transferred, bool again)
|
||||
{
|
||||
auto& d = *d_;
|
||||
d.cont = d.cont || again;
|
||||
if(d.state == 99)
|
||||
goto upcall;
|
||||
for(;;)
|
||||
{
|
||||
switch(d.state)
|
||||
{
|
||||
case 0:
|
||||
if(d.db.size() == 0)
|
||||
{
|
||||
d.state = 2;
|
||||
break;
|
||||
}
|
||||
//[[fallthrough]]
|
||||
|
||||
case 1:
|
||||
{
|
||||
BOOST_ASSERT(d.db.size() > 0);
|
||||
d.bytes_used =
|
||||
d.p.write(d.db.data(), ec);
|
||||
if(d.bytes_used > 0 || ec)
|
||||
{
|
||||
// call handler
|
||||
if(d.state == 1)
|
||||
goto upcall;
|
||||
d.state = 99;
|
||||
d.s.get_io_service().post(
|
||||
bind_handler(std::move(*this), ec, 0));
|
||||
return;
|
||||
}
|
||||
//[[fallthrough]]
|
||||
}
|
||||
|
||||
case 2:
|
||||
case 3:
|
||||
{
|
||||
auto const size =
|
||||
read_size_helper(d.db, 65536);
|
||||
BOOST_ASSERT(size > 0);
|
||||
try
|
||||
{
|
||||
d.mb.emplace(d.db.prepare(size));
|
||||
}
|
||||
catch(std::length_error const&)
|
||||
{
|
||||
// call handler
|
||||
if(d.state == 3)
|
||||
goto upcall;
|
||||
d.state = 99;
|
||||
d.s.get_io_service().post(
|
||||
bind_handler(std::move(*this),
|
||||
error::buffer_overflow, 0));
|
||||
return;
|
||||
}
|
||||
// read
|
||||
d.state = 4;
|
||||
d.s.async_read_some(*d.mb, std::move(*this));
|
||||
return;
|
||||
}
|
||||
|
||||
case 4:
|
||||
if(ec == boost::asio::error::eof)
|
||||
{
|
||||
BOOST_ASSERT(bytes_transferred == 0);
|
||||
d.bytes_used = 0;
|
||||
if(! d.p.got_some())
|
||||
goto upcall;
|
||||
// caller sees EOF on next read.
|
||||
ec = {};
|
||||
d.p.write_eof(ec);
|
||||
if(ec)
|
||||
goto upcall;
|
||||
BOOST_ASSERT(d.p.is_complete());
|
||||
goto upcall;
|
||||
}
|
||||
else if(ec)
|
||||
{
|
||||
d.bytes_used = 0;
|
||||
goto upcall;
|
||||
}
|
||||
BOOST_ASSERT(bytes_transferred > 0);
|
||||
d.db.commit(bytes_transferred);
|
||||
d.state = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
upcall:
|
||||
// can't pass any members of `d` otherwise UB
|
||||
auto const bytes_used = d.bytes_used;
|
||||
d_.invoke(ec, bytes_used);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class Stream, class DynamicBuffer,
|
||||
bool isRequest, class Derived, class Handler>
|
||||
class read_some_body_op
|
||||
{
|
||||
struct data
|
||||
{
|
||||
bool cont;
|
||||
Stream& s;
|
||||
DynamicBuffer& db;
|
||||
basic_parser<isRequest, true, Derived>& p;
|
||||
boost::optional<typename
|
||||
Derived::mutable_buffers_type> mb;
|
||||
std::size_t bytes_used;
|
||||
int state = 0;
|
||||
|
||||
data(Handler& handler, Stream& s_, DynamicBuffer& db_,
|
||||
basic_parser<isRequest, true, Derived>& p_)
|
||||
: cont(beast_asio_helpers::
|
||||
is_continuation(handler))
|
||||
, s(s_)
|
||||
, db(db_)
|
||||
, p(p_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
handler_ptr<data, Handler> d_;
|
||||
|
||||
public:
|
||||
read_some_body_op(read_some_body_op&&) = default;
|
||||
read_some_body_op(read_some_body_op const&) = default;
|
||||
|
||||
template<class DeducedHandler, class... Args>
|
||||
read_some_body_op(DeducedHandler&& h,
|
||||
Stream& s, Args&&... args)
|
||||
: d_(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,
|
||||
read_some_body_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
allocate(size, op->d_.handler());
|
||||
}
|
||||
|
||||
friend
|
||||
void
|
||||
asio_handler_deallocate(
|
||||
void* p, std::size_t size,
|
||||
read_some_body_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
deallocate(p, size, op->d_.handler());
|
||||
}
|
||||
|
||||
friend
|
||||
bool
|
||||
asio_handler_is_continuation(
|
||||
read_some_body_op* op)
|
||||
{
|
||||
return op->d_->cont;
|
||||
}
|
||||
|
||||
template<class Function>
|
||||
friend
|
||||
void
|
||||
asio_handler_invoke(Function&& f,
|
||||
read_some_body_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
invoke(f, op->d_.handler());
|
||||
}
|
||||
};
|
||||
|
||||
template<class Stream, class DynamicBuffer,
|
||||
bool isRequest, class Derived, class Handler>
|
||||
void
|
||||
read_some_body_op<Stream, DynamicBuffer,
|
||||
isRequest, Derived, Handler>::
|
||||
operator()(error_code ec,
|
||||
std::size_t bytes_transferred, bool again)
|
||||
{
|
||||
auto& d = *d_;
|
||||
d.cont = d.cont || again;
|
||||
if(d.state == 99)
|
||||
goto upcall;
|
||||
for(;;)
|
||||
{
|
||||
switch(d.state)
|
||||
{
|
||||
case 0:
|
||||
if(d.db.size() > 0)
|
||||
{
|
||||
d.bytes_used = d.p.copy_body(d.db);
|
||||
// call handler
|
||||
d.state = 99;
|
||||
d.s.get_io_service().post(
|
||||
bind_handler(std::move(*this),
|
||||
ec, 0));
|
||||
return;
|
||||
}
|
||||
d.p.prepare_body(d.mb, 65536);
|
||||
// read
|
||||
d.state = 1;
|
||||
d.s.async_read_some(
|
||||
*d.mb, std::move(*this));
|
||||
return;
|
||||
|
||||
case 1:
|
||||
d.bytes_used = 0;
|
||||
if(ec == boost::asio::error::eof)
|
||||
{
|
||||
BOOST_ASSERT(bytes_transferred == 0);
|
||||
// caller sees EOF on next read
|
||||
ec = {};
|
||||
d.p.write_eof(ec);
|
||||
if(ec)
|
||||
goto upcall;
|
||||
BOOST_ASSERT(d.p.is_complete());
|
||||
}
|
||||
else if(! ec)
|
||||
{
|
||||
d.p.commit_body(bytes_transferred);
|
||||
}
|
||||
goto upcall;
|
||||
}
|
||||
}
|
||||
upcall:
|
||||
// can't pass any members of `d` otherwise UB
|
||||
auto const bytes_used = d.bytes_used;
|
||||
d_.invoke(ec, bytes_used);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class Stream, class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived,
|
||||
class Handler>
|
||||
class parse_op
|
||||
{
|
||||
struct data
|
||||
{
|
||||
bool cont;
|
||||
Stream& s;
|
||||
DynamicBuffer& db;
|
||||
basic_parser<isRequest, isDirect, Derived>& p;
|
||||
|
||||
data(Handler& handler, Stream& s_, DynamicBuffer& db_,
|
||||
basic_parser<isRequest, isDirect, Derived>& p_)
|
||||
: cont(beast_asio_helpers::
|
||||
is_continuation(handler))
|
||||
, s(s_)
|
||||
, db(db_)
|
||||
, p(p_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
handler_ptr<data, Handler> 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::forward<DeducedHandler>(h),
|
||||
s, std::forward<Args>(args)...)
|
||||
{
|
||||
(*this)(error_code{}, 0, false);
|
||||
}
|
||||
|
||||
void
|
||||
operator()(error_code const& ec,
|
||||
std::size_t bytes_used, bool again = true);
|
||||
|
||||
friend
|
||||
void*
|
||||
asio_handler_allocate(
|
||||
std::size_t size, parse_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
allocate(size, op->d_.handler());
|
||||
}
|
||||
|
||||
friend
|
||||
void
|
||||
asio_handler_deallocate(
|
||||
void* p, std::size_t size,
|
||||
parse_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
deallocate(p, size, op->d_.handler());
|
||||
}
|
||||
|
||||
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 beast_asio_helpers::
|
||||
invoke(f, op->d_.handler());
|
||||
}
|
||||
};
|
||||
|
||||
template<class Stream, class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived,
|
||||
class Handler>
|
||||
void
|
||||
parse_op<Stream, DynamicBuffer,
|
||||
isRequest, isDirect, Derived, Handler>::
|
||||
operator()(error_code const& ec,
|
||||
std::size_t bytes_used, bool again)
|
||||
{
|
||||
auto& d = *d_;
|
||||
d.cont = d.cont || again;
|
||||
if(! ec)
|
||||
{
|
||||
d.db.consume(bytes_used);
|
||||
if(! d.p.is_complete())
|
||||
return async_read_some(
|
||||
d.s, d.db, d.p, std::move(*this));
|
||||
}
|
||||
d_.invoke(ec);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class Stream, class DynamicBuffer,
|
||||
bool isRequest, class Body, class Fields,
|
||||
class Handler>
|
||||
class read_message_op
|
||||
{
|
||||
using parser_type =
|
||||
message_parser<isRequest, Body, Fields>;
|
||||
|
||||
using message_type =
|
||||
message<isRequest, Body, Fields>;
|
||||
|
||||
struct data
|
||||
{
|
||||
bool cont;
|
||||
Stream& s;
|
||||
DynamicBuffer& db;
|
||||
message_type& m;
|
||||
parser_type p;
|
||||
bool started = false;
|
||||
int state = 0;
|
||||
|
||||
data(Handler& handler, Stream& s_,
|
||||
DynamicBuffer& sb_, message_type& m_)
|
||||
: cont(beast_asio_helpers::
|
||||
is_continuation(handler))
|
||||
, s(s_)
|
||||
, db(sb_)
|
||||
, m(m_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
handler_ptr<data, Handler> d_;
|
||||
|
||||
public:
|
||||
read_message_op(read_message_op&&) = default;
|
||||
read_message_op(read_message_op const&) = default;
|
||||
|
||||
template<class DeducedHandler, class... Args>
|
||||
read_message_op(DeducedHandler&& h, Stream& s, Args&&... args)
|
||||
: d_(std::forward<DeducedHandler>(h),
|
||||
s, std::forward<Args>(args)...)
|
||||
{
|
||||
(*this)(error_code{}, false);
|
||||
}
|
||||
|
||||
void
|
||||
operator()(error_code ec, bool again = true);
|
||||
|
||||
friend
|
||||
void* asio_handler_allocate(
|
||||
std::size_t size, read_message_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
allocate(size, op->d_.handler());
|
||||
}
|
||||
|
||||
friend
|
||||
void asio_handler_deallocate(
|
||||
void* p, std::size_t size, read_message_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
deallocate(p, size, op->d_.handler());
|
||||
}
|
||||
|
||||
friend
|
||||
bool asio_handler_is_continuation(read_message_op* op)
|
||||
{
|
||||
return op->d_->cont;
|
||||
}
|
||||
|
||||
template<class Function>
|
||||
friend
|
||||
void asio_handler_invoke(Function&& f, read_message_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
invoke(f, op->d_.handler());
|
||||
}
|
||||
};
|
||||
|
||||
template<class Stream, class DynamicBuffer,
|
||||
bool isRequest, class Body, class Fields,
|
||||
class Handler>
|
||||
void
|
||||
read_message_op<Stream, DynamicBuffer, isRequest, Body, Fields, Handler>::
|
||||
operator()(error_code ec, bool again)
|
||||
{
|
||||
auto& d = *d_;
|
||||
d.cont = d.cont || again;
|
||||
if(ec)
|
||||
goto upcall;
|
||||
switch(d.state)
|
||||
{
|
||||
case 0:
|
||||
d.state = 1;
|
||||
beast::http::async_read(
|
||||
d.s, d.db, d.p, std::move(*this));
|
||||
return;
|
||||
|
||||
case 1:
|
||||
d.m = d.p.release();
|
||||
goto upcall;
|
||||
}
|
||||
upcall:
|
||||
d_.invoke(ec);
|
||||
}
|
||||
|
||||
template<
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, class Derived,
|
||||
class ReadHandler>
|
||||
typename async_completion<
|
||||
ReadHandler, void(error_code, std::size_t)>::result_type
|
||||
async_read_some(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, true, Derived>& parser,
|
||||
ReadHandler&& handler)
|
||||
{
|
||||
beast::async_completion<ReadHandler,
|
||||
void(error_code, std::size_t)> completion{handler};
|
||||
switch(parser.state())
|
||||
{
|
||||
case parse_state::header:
|
||||
case parse_state::chunk_header:
|
||||
detail::read_some_buffer_op<AsyncReadStream,
|
||||
DynamicBuffer, isRequest, true, Derived,
|
||||
decltype(completion.handler)>{
|
||||
completion.handler, stream, dynabuf, parser};
|
||||
break;
|
||||
|
||||
default:
|
||||
detail::read_some_body_op<AsyncReadStream,
|
||||
DynamicBuffer, isRequest, Derived,
|
||||
decltype(completion.handler)>{
|
||||
completion.handler, stream, dynabuf, parser};
|
||||
break;
|
||||
}
|
||||
return completion.result.get();
|
||||
}
|
||||
|
||||
template<
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, class Derived,
|
||||
class ReadHandler>
|
||||
inline
|
||||
typename async_completion<
|
||||
ReadHandler, void(error_code, std::size_t)>::result_type
|
||||
async_read_some(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, false, Derived>& parser,
|
||||
ReadHandler&& handler)
|
||||
{
|
||||
beast::async_completion<ReadHandler,
|
||||
void(error_code, std::size_t)> completion{handler};
|
||||
detail::read_some_buffer_op<AsyncReadStream,
|
||||
DynamicBuffer, isRequest, false, Derived,
|
||||
decltype(completion.handler)>{
|
||||
completion.handler, stream, dynabuf, parser};
|
||||
return completion.result.get();
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived,
|
||||
class ReadHandler>
|
||||
typename async_completion<
|
||||
ReadHandler, void(error_code, std::size_t)>::result_type
|
||||
async_read_some(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser,
|
||||
ReadHandler&& handler)
|
||||
{
|
||||
static_assert(is_AsyncReadStream<AsyncReadStream>::value,
|
||||
"AsyncReadStream requirements not met");
|
||||
static_assert(is_DynamicBuffer<DynamicBuffer>::value,
|
||||
"DynamicBuffer requirements not met");
|
||||
BOOST_ASSERT(! parser.is_complete());
|
||||
return detail::async_read_some(stream, dynabuf, parser,
|
||||
std::forward<ReadHandler>(handler));
|
||||
}
|
||||
|
||||
template<
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived,
|
||||
class ReadHandler>
|
||||
typename async_completion<
|
||||
ReadHandler, void(error_code)>::result_type
|
||||
async_read(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser,
|
||||
ReadHandler&& handler)
|
||||
{
|
||||
static_assert(is_AsyncReadStream<AsyncReadStream>::value,
|
||||
"AsyncReadStream requirements not met");
|
||||
static_assert(is_DynamicBuffer<DynamicBuffer>::value,
|
||||
"DynamicBuffer requirements not met");
|
||||
BOOST_ASSERT(! parser.is_complete());
|
||||
beast::async_completion<ReadHandler,
|
||||
void(error_code)> completion{handler};
|
||||
detail::parse_op<AsyncReadStream, DynamicBuffer,
|
||||
isRequest, isDirect, Derived, decltype(completion.handler)>{
|
||||
completion.handler, stream, dynabuf, parser};
|
||||
return completion.result.get();
|
||||
}
|
||||
|
||||
template<
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, class Body, class Fields,
|
||||
class ReadHandler>
|
||||
typename async_completion<
|
||||
ReadHandler, void(error_code)>::result_type
|
||||
async_read(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
message<isRequest, Body, Fields>& msg,
|
||||
ReadHandler&& handler)
|
||||
{
|
||||
static_assert(is_AsyncReadStream<AsyncReadStream>::value,
|
||||
"AsyncReadStream requirements not met");
|
||||
static_assert(is_DynamicBuffer<DynamicBuffer>::value,
|
||||
"DynamicBuffer requirements not met");
|
||||
static_assert(is_Body<Body>::value,
|
||||
"Body requirements not met");
|
||||
static_assert(has_reader<Body>::value,
|
||||
"Body has no reader");
|
||||
static_assert(is_Reader<typename Body::reader,
|
||||
message<isRequest, Body, Fields>>::value,
|
||||
"Reader requirements not met");
|
||||
beast::async_completion<ReadHandler,
|
||||
void(error_code)> completion{handler};
|
||||
detail::read_message_op<AsyncReadStream, DynamicBuffer,
|
||||
isRequest, Body, Fields, decltype(
|
||||
completion.handler)>{completion.handler,
|
||||
stream, dynabuf, msg};
|
||||
return completion.result.get();
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
1062
include/beast/http/impl/basic_parser.ipp
Normal file
1062
include/beast/http/impl/basic_parser.ipp
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
106
include/beast/http/impl/error.ipp
Normal file
106
include/beast/http/impl/error.ipp
Normal file
@ -0,0 +1,106 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_IMPL_ERROR_IPP
|
||||
#define BEAST_HTTP_IMPL_ERROR_IPP
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace boost {
|
||||
namespace system {
|
||||
template<>
|
||||
struct is_error_code_enum<beast::http::error>
|
||||
{
|
||||
static bool const value = true;
|
||||
};
|
||||
} // system
|
||||
} // boost
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
namespace detail {
|
||||
|
||||
class http_error_category : public error_category
|
||||
{
|
||||
public:
|
||||
const char*
|
||||
name() const noexcept override
|
||||
{
|
||||
return "http";
|
||||
}
|
||||
|
||||
std::string
|
||||
message(int ev) const override
|
||||
{
|
||||
switch(static_cast<error>(ev))
|
||||
{
|
||||
default:
|
||||
case error::end_of_stream: return "end of stream";
|
||||
case error::partial_message: return "partial message";
|
||||
case error::buffer_overflow: return "buffer overflow";
|
||||
case error::bad_line_ending: return "bad line ending";
|
||||
case error::bad_method: return "bad method";
|
||||
case error::bad_path: return "bad path";
|
||||
case error::bad_version: return "bad version";
|
||||
case error::bad_status: return "bad status";
|
||||
case error::bad_reason: return "bad reason";
|
||||
case error::bad_field: return "bad field";
|
||||
case error::bad_value: return "bad value";
|
||||
case error::bad_content_length: return "bad Content-Length";
|
||||
case error::bad_transfer_encoding: return "bad Transfer-Encoding";
|
||||
case error::bad_chunk: return "bad chunk";
|
||||
}
|
||||
}
|
||||
|
||||
error_condition
|
||||
default_error_condition(
|
||||
int ev) const noexcept override
|
||||
{
|
||||
return error_condition{ev, *this};
|
||||
}
|
||||
|
||||
bool
|
||||
equivalent(int ev,
|
||||
error_condition const& condition
|
||||
) const noexcept override
|
||||
{
|
||||
return condition.value() == ev &&
|
||||
&condition.category() == this;
|
||||
}
|
||||
|
||||
bool
|
||||
equivalent(error_code const& error,
|
||||
int ev) const noexcept override
|
||||
{
|
||||
return error.value() == ev &&
|
||||
&error.category() == this;
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
error_category const&
|
||||
get_http_error_category()
|
||||
{
|
||||
static http_error_category const cat{};
|
||||
return cat;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
inline
|
||||
error_code
|
||||
make_error_code(error ev)
|
||||
{
|
||||
return error_code{
|
||||
static_cast<std::underlying_type<error>::type>(ev),
|
||||
detail::get_http_error_category()};
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
25
include/beast/http/impl/header_parser.ipp
Normal file
25
include/beast/http/impl/header_parser.ipp
Normal file
@ -0,0 +1,25 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_IMPL_HEADER_PARSER_IPP
|
||||
#define BEAST_HTTP_IMPL_HEADER_PARSER_IPP
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
template<bool isRequest, class Fields>
|
||||
template<class... Args>
|
||||
header_parser<isRequest, Fields>::
|
||||
header_parser(Args&&... args)
|
||||
: h_(std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
38
include/beast/http/impl/message_parser.ipp
Normal file
38
include/beast/http/impl/message_parser.ipp
Normal file
@ -0,0 +1,38 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_IMPL_MESSAGE_PARSER_IPP
|
||||
#define BEAST_HTTP_IMPL_MESSAGE_PARSER_IPP
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
template<bool isRequest, class Body, class Fields>
|
||||
template<class Arg1, class... ArgN, class>
|
||||
message_parser<isRequest, Body, Fields>::
|
||||
message_parser(Arg1&& arg1, ArgN&&... argn)
|
||||
: m_(std::forward<Arg1>(arg1),
|
||||
std::forward<ArgN>(argn)...)
|
||||
{
|
||||
}
|
||||
|
||||
template<bool isRequest, class Body, class Fields>
|
||||
template<class... Args>
|
||||
message_parser<isRequest, Body, Fields>::
|
||||
message_parser(header_parser<
|
||||
isRequest, Fields>&& parser, Args&&... args)
|
||||
: base_type(std::move(static_cast<basic_parser<
|
||||
isRequest, false, header_parser<
|
||||
isRequest, Fields>>&>(parser)))
|
||||
, m_(parser.release(), std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
@ -1,302 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_IMPL_PARSE_IPP_HPP
|
||||
#define BEAST_HTTP_IMPL_PARSE_IPP_HPP
|
||||
|
||||
#include <beast/http/concepts.hpp>
|
||||
#include <beast/core/bind_handler.hpp>
|
||||
#include <beast/core/handler_helpers.hpp>
|
||||
#include <beast/core/handler_ptr.hpp>
|
||||
#include <beast/core/stream_concepts.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<class Stream,
|
||||
class DynamicBuffer, class Parser, class Handler>
|
||||
class parse_op
|
||||
{
|
||||
struct data
|
||||
{
|
||||
bool cont;
|
||||
Stream& s;
|
||||
DynamicBuffer& db;
|
||||
Parser& p;
|
||||
bool got_some = false;
|
||||
int state = 0;
|
||||
|
||||
data(Handler& handler, Stream& s_,
|
||||
DynamicBuffer& sb_, Parser& p_)
|
||||
: cont(beast_asio_helpers::
|
||||
is_continuation(handler))
|
||||
, s(s_)
|
||||
, db(sb_)
|
||||
, p(p_)
|
||||
{
|
||||
BOOST_ASSERT(! p.complete());
|
||||
}
|
||||
};
|
||||
|
||||
handler_ptr<data, Handler> 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::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 beast_asio_helpers::
|
||||
allocate(size, op->d_.handler());
|
||||
}
|
||||
|
||||
friend
|
||||
void asio_handler_deallocate(
|
||||
void* p, std::size_t size, parse_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
deallocate(p, size, op->d_.handler());
|
||||
}
|
||||
|
||||
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 beast_asio_helpers::
|
||||
invoke(f, op->d_.handler());
|
||||
}
|
||||
};
|
||||
|
||||
template<class Stream,
|
||||
class DynamicBuffer, class Parser, class Handler>
|
||||
void
|
||||
parse_op<Stream, DynamicBuffer, 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:
|
||||
{
|
||||
// Parse any bytes left over in the buffer
|
||||
auto const used =
|
||||
d.p.write(d.db.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.got_some = true;
|
||||
d.db.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;
|
||||
}
|
||||
// Buffer must be empty,
|
||||
// otherwise parse should be complete
|
||||
BOOST_ASSERT(d.db.size() == 0);
|
||||
d.state = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
case 1:
|
||||
{
|
||||
// read
|
||||
d.state = 2;
|
||||
auto const size =
|
||||
read_size_helper(d.db, 65536);
|
||||
BOOST_ASSERT(size > 0);
|
||||
d.s.async_read_some(
|
||||
d.db.prepare(size), std::move(*this));
|
||||
return;
|
||||
}
|
||||
|
||||
// got data
|
||||
case 2:
|
||||
{
|
||||
if(ec == boost::asio::error::eof)
|
||||
{
|
||||
// If we haven't processed any bytes,
|
||||
// give the eof to the handler immediately.
|
||||
if(! d.got_some)
|
||||
{
|
||||
// call handler
|
||||
d.state = 99;
|
||||
break;
|
||||
}
|
||||
// Feed the eof to the parser to complete
|
||||
// the parse, and call the handler. The
|
||||
// next call to parse will deliver the eof.
|
||||
ec = {};
|
||||
d.p.write_eof(ec);
|
||||
BOOST_ASSERT(ec || d.p.complete());
|
||||
// call handler
|
||||
d.state = 99;
|
||||
break;
|
||||
}
|
||||
if(ec)
|
||||
{
|
||||
// call handler
|
||||
d.state = 99;
|
||||
break;
|
||||
}
|
||||
BOOST_ASSERT(bytes_transferred > 0);
|
||||
d.db.commit(bytes_transferred);
|
||||
auto const used = d.p.write(d.db.data(), ec);
|
||||
if(ec)
|
||||
{
|
||||
// call handler
|
||||
d.state = 99;
|
||||
break;
|
||||
}
|
||||
// The parser must either consume
|
||||
// bytes or generate an error.
|
||||
BOOST_ASSERT(used > 0);
|
||||
d.got_some = true;
|
||||
d.db.consume(used);
|
||||
if(d.p.complete())
|
||||
{
|
||||
// call handler
|
||||
d.state = 99;
|
||||
break;
|
||||
}
|
||||
// If the parse is not complete,
|
||||
// all input must be consumed.
|
||||
BOOST_ASSERT(used == bytes_transferred);
|
||||
d.state = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
d_.invoke(ec);
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class SyncReadStream, class DynamicBuffer, class Parser>
|
||||
void
|
||||
parse(SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf, Parser& parser)
|
||||
{
|
||||
static_assert(is_SyncReadStream<SyncReadStream>::value,
|
||||
"SyncReadStream requirements not met");
|
||||
static_assert(is_DynamicBuffer<DynamicBuffer>::value,
|
||||
"DynamicBuffer requirements not met");
|
||||
static_assert(is_Parser<Parser>::value,
|
||||
"Parser requirements not met");
|
||||
error_code ec;
|
||||
parse(stream, dynabuf, parser, ec);
|
||||
if(ec)
|
||||
throw system_error{ec};
|
||||
}
|
||||
|
||||
template<class SyncReadStream, class DynamicBuffer, class Parser>
|
||||
void
|
||||
parse(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
Parser& parser, error_code& ec)
|
||||
{
|
||||
static_assert(is_SyncReadStream<SyncReadStream>::value,
|
||||
"SyncReadStream requirements not met");
|
||||
static_assert(is_DynamicBuffer<DynamicBuffer>::value,
|
||||
"DynamicBuffer requirements not met");
|
||||
static_assert(is_Parser<Parser>::value,
|
||||
"Parser requirements not met");
|
||||
bool got_some = false;
|
||||
for(;;)
|
||||
{
|
||||
auto used =
|
||||
parser.write(dynabuf.data(), ec);
|
||||
if(ec)
|
||||
return;
|
||||
dynabuf.consume(used);
|
||||
if(used > 0)
|
||||
got_some = true;
|
||||
if(parser.complete())
|
||||
break;
|
||||
dynabuf.commit(stream.read_some(
|
||||
dynabuf.prepare(read_size_helper(
|
||||
dynabuf, 65536)), ec));
|
||||
if(ec && ec != boost::asio::error::eof)
|
||||
return;
|
||||
if(ec == boost::asio::error::eof)
|
||||
{
|
||||
if(! got_some)
|
||||
return;
|
||||
// Caller will see eof on next read.
|
||||
ec = {};
|
||||
parser.write_eof(ec);
|
||||
if(ec)
|
||||
return;
|
||||
BOOST_ASSERT(parser.complete());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class AsyncReadStream,
|
||||
class DynamicBuffer, class Parser, class ReadHandler>
|
||||
typename async_completion<
|
||||
ReadHandler, void(error_code)>::result_type
|
||||
async_parse(AsyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf, Parser& parser, ReadHandler&& handler)
|
||||
{
|
||||
static_assert(is_AsyncReadStream<AsyncReadStream>::value,
|
||||
"AsyncReadStream requirements not met");
|
||||
static_assert(is_DynamicBuffer<DynamicBuffer>::value,
|
||||
"DynamicBuffer 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, DynamicBuffer,
|
||||
Parser, decltype(completion.handler)>{
|
||||
completion.handler, stream, dynabuf, parser};
|
||||
return completion.result.get();
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
@ -1,105 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_IMPL_PARSE_ERROR_IPP
|
||||
#define BEAST_HTTP_IMPL_PARSE_ERROR_IPP
|
||||
|
||||
namespace boost {
|
||||
namespace system {
|
||||
template<>
|
||||
struct is_error_code_enum<beast::http::parse_error>
|
||||
{
|
||||
static bool const value = true;
|
||||
};
|
||||
} // system
|
||||
} // boost
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
namespace detail {
|
||||
|
||||
class parse_error_category : public error_category
|
||||
{
|
||||
public:
|
||||
const char*
|
||||
name() const noexcept override
|
||||
{
|
||||
return "http";
|
||||
}
|
||||
|
||||
std::string
|
||||
message(int ev) const override
|
||||
{
|
||||
switch(static_cast<parse_error>(ev))
|
||||
{
|
||||
case parse_error::connection_closed: return "data after Connection close";
|
||||
case parse_error::bad_method: return "bad method";
|
||||
case parse_error::bad_uri: return "bad request-target";
|
||||
case parse_error::bad_version: return "bad HTTP-Version";
|
||||
case parse_error::bad_crlf: return "missing CRLF";
|
||||
case parse_error::bad_status: return "bad status-code";
|
||||
case parse_error::bad_reason: return "bad reason-phrase";
|
||||
case parse_error::bad_field: return "bad field token";
|
||||
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::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";
|
||||
case parse_error::header_too_big: return "header size limit exceeded";
|
||||
case parse_error::body_too_big: return "body size limit exceeded";
|
||||
default:
|
||||
case parse_error::short_read: return "unexpected end of data";
|
||||
}
|
||||
}
|
||||
|
||||
error_condition
|
||||
default_error_condition(int ev) const noexcept override
|
||||
{
|
||||
return error_condition{ev, *this};
|
||||
}
|
||||
|
||||
bool
|
||||
equivalent(int ev,
|
||||
error_condition const& condition
|
||||
) const noexcept override
|
||||
{
|
||||
return condition.value() == ev &&
|
||||
&condition.category() == this;
|
||||
}
|
||||
|
||||
bool
|
||||
equivalent(error_code const& error, int ev) const noexcept override
|
||||
{
|
||||
return error.value() == ev &&
|
||||
&error.category() == this;
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
error_category const&
|
||||
get_parse_error_category()
|
||||
{
|
||||
static parse_error_category const cat{};
|
||||
return cat;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
inline
|
||||
error_code
|
||||
make_error_code(parse_error ev)
|
||||
{
|
||||
return error_code{
|
||||
static_cast<std::underlying_type<parse_error>::type>(ev),
|
||||
detail::get_parse_error_category()};
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
@ -9,306 +9,275 @@
|
||||
#define BEAST_HTTP_IMPL_READ_IPP_HPP
|
||||
|
||||
#include <beast/http/concepts.hpp>
|
||||
#include <beast/http/header_parser_v1.hpp>
|
||||
#include <beast/http/parse.hpp>
|
||||
#include <beast/http/parser_v1.hpp>
|
||||
#include <beast/http/error.hpp>
|
||||
#include <beast/http/message_parser.hpp>
|
||||
#include <beast/http/read.hpp>
|
||||
#include <beast/core/bind_handler.hpp>
|
||||
#include <beast/core/handler_helpers.hpp>
|
||||
#include <beast/core/handler_ptr.hpp>
|
||||
#include <beast/core/stream_concepts.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<class Stream, class DynamicBuffer,
|
||||
bool isRequest, class Fields,
|
||||
class Handler>
|
||||
class read_header_op
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived>
|
||||
inline
|
||||
std::size_t
|
||||
read_some_buffer(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser,
|
||||
error_code& ec)
|
||||
{
|
||||
using parser_type =
|
||||
header_parser_v1<isRequest, Fields>;
|
||||
|
||||
using message_type =
|
||||
header<isRequest, Fields>;
|
||||
|
||||
struct data
|
||||
std::size_t bytes_used;
|
||||
if(dynabuf.size() == 0)
|
||||
goto do_read;
|
||||
for(;;)
|
||||
{
|
||||
bool cont;
|
||||
Stream& s;
|
||||
DynamicBuffer& db;
|
||||
message_type& m;
|
||||
parser_type p;
|
||||
bool started = false;
|
||||
int state = 0;
|
||||
|
||||
data(Handler& handler, Stream& s_,
|
||||
DynamicBuffer& sb_, message_type& m_)
|
||||
: cont(beast_asio_helpers::
|
||||
is_continuation(handler))
|
||||
, s(s_)
|
||||
, db(sb_)
|
||||
, m(m_)
|
||||
bytes_used = parser.write(
|
||||
dynabuf.data(), ec);
|
||||
if(ec)
|
||||
return 0;
|
||||
if(bytes_used > 0)
|
||||
goto do_finish;
|
||||
do_read:
|
||||
boost::optional<typename
|
||||
DynamicBuffer::mutable_buffers_type> mb;
|
||||
auto const size =
|
||||
read_size_helper(dynabuf, 65536);
|
||||
BOOST_ASSERT(size > 0);
|
||||
try
|
||||
{
|
||||
mb.emplace(dynabuf.prepare(size));
|
||||
}
|
||||
};
|
||||
|
||||
handler_ptr<data, Handler> d_;
|
||||
|
||||
public:
|
||||
read_header_op(read_header_op&&) = default;
|
||||
read_header_op(read_header_op const&) = default;
|
||||
|
||||
template<class DeducedHandler, class... Args>
|
||||
read_header_op(
|
||||
DeducedHandler&& h, Stream& s, Args&&... args)
|
||||
: d_(std::forward<DeducedHandler>(h),
|
||||
s, std::forward<Args>(args)...)
|
||||
{
|
||||
(*this)(error_code{}, false);
|
||||
}
|
||||
|
||||
void
|
||||
operator()(error_code ec, bool again = true);
|
||||
|
||||
friend
|
||||
void* asio_handler_allocate(
|
||||
std::size_t size, read_header_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
allocate(size, op->d_.handler());
|
||||
}
|
||||
|
||||
friend
|
||||
void asio_handler_deallocate(
|
||||
void* p, std::size_t size, read_header_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
deallocate(p, size, op->d_.handler());
|
||||
}
|
||||
|
||||
friend
|
||||
bool asio_handler_is_continuation(read_header_op* op)
|
||||
{
|
||||
return op->d_->cont;
|
||||
}
|
||||
|
||||
template<class Function>
|
||||
friend
|
||||
void asio_handler_invoke(Function&& f, read_header_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
invoke(f, op->d_.handler());
|
||||
}
|
||||
};
|
||||
|
||||
template<class Stream, class DynamicBuffer,
|
||||
bool isRequest, class Fields,
|
||||
class Handler>
|
||||
void
|
||||
read_header_op<Stream, DynamicBuffer, isRequest, Fields, Handler>::
|
||||
operator()(error_code ec, bool again)
|
||||
{
|
||||
auto& d = *d_;
|
||||
d.cont = d.cont || again;
|
||||
while(! ec && d.state != 99)
|
||||
{
|
||||
switch(d.state)
|
||||
catch(std::length_error const&)
|
||||
{
|
||||
case 0:
|
||||
d.state = 1;
|
||||
async_parse(d.s, d.db, d.p, std::move(*this));
|
||||
return;
|
||||
|
||||
case 1:
|
||||
// call handler
|
||||
d.state = 99;
|
||||
d.m = d.p.release();
|
||||
ec = error::buffer_overflow;
|
||||
return 0;
|
||||
}
|
||||
auto const bytes_transferred =
|
||||
stream.read_some(*mb, ec);
|
||||
if(ec == boost::asio::error::eof)
|
||||
{
|
||||
BOOST_ASSERT(bytes_transferred == 0);
|
||||
bytes_used = 0;
|
||||
if(parser.got_some())
|
||||
{
|
||||
// caller sees EOF on next read
|
||||
ec = {};
|
||||
parser.write_eof(ec);
|
||||
if(ec)
|
||||
return 0;
|
||||
BOOST_ASSERT(parser.is_complete());
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if(ec)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
BOOST_ASSERT(bytes_transferred > 0);
|
||||
dynabuf.commit(bytes_transferred);
|
||||
}
|
||||
d_.invoke(ec);
|
||||
do_finish:
|
||||
return bytes_used;
|
||||
}
|
||||
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, class Derived>
|
||||
inline
|
||||
std::size_t
|
||||
read_some_body(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, true, Derived>& parser,
|
||||
error_code& ec)
|
||||
{
|
||||
if(dynabuf.size() > 0)
|
||||
return parser.copy_body(dynabuf);
|
||||
boost::optional<typename
|
||||
Derived::mutable_buffers_type> mb;
|
||||
try
|
||||
{
|
||||
parser.prepare_body(mb, 65536);
|
||||
}
|
||||
catch(std::length_error const&)
|
||||
{
|
||||
ec = error::buffer_overflow;
|
||||
return 0;
|
||||
}
|
||||
auto const bytes_transferred =
|
||||
stream.read_some(*mb, ec);
|
||||
if(ec == boost::asio::error::eof)
|
||||
{
|
||||
BOOST_ASSERT(bytes_transferred == 0);
|
||||
// caller sees EOF on next read
|
||||
ec = {};
|
||||
parser.write_eof(ec);
|
||||
if(ec)
|
||||
return 0;
|
||||
BOOST_ASSERT(parser.is_complete());
|
||||
}
|
||||
else if(! ec)
|
||||
{
|
||||
parser.commit_body(bytes_transferred);
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, class Derived>
|
||||
inline
|
||||
std::size_t
|
||||
read_some(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, true, Derived>& parser,
|
||||
error_code& ec)
|
||||
{
|
||||
switch(parser.state())
|
||||
{
|
||||
case parse_state::header:
|
||||
case parse_state::chunk_header:
|
||||
return detail::read_some_buffer(
|
||||
stream, dynabuf, parser, ec);
|
||||
|
||||
default:
|
||||
return detail::read_some_body(
|
||||
stream, dynabuf, parser, ec);
|
||||
}
|
||||
}
|
||||
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, class Derived>
|
||||
inline
|
||||
std::size_t
|
||||
read_some(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, false, Derived>& parser,
|
||||
error_code& ec)
|
||||
{
|
||||
return detail::read_some_buffer(
|
||||
stream, dynabuf, parser, ec);
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
template<class SyncReadStream, class DynamicBuffer,
|
||||
bool isRequest, class Fields>
|
||||
void
|
||||
read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
header<isRequest, Fields>& msg)
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived>
|
||||
std::size_t
|
||||
read_some(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser)
|
||||
{
|
||||
static_assert(is_SyncReadStream<SyncReadStream>::value,
|
||||
"SyncReadStream requirements not met");
|
||||
static_assert(is_DynamicBuffer<DynamicBuffer>::value,
|
||||
"DynamicBuffer requirements not met");
|
||||
BOOST_ASSERT(! parser.is_complete());
|
||||
error_code ec;
|
||||
beast::http::read(stream, dynabuf, msg, ec);
|
||||
auto const bytes_used =
|
||||
read_some(stream, dynabuf, parser, ec);
|
||||
if(ec)
|
||||
throw system_error{ec};
|
||||
return bytes_used;
|
||||
}
|
||||
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived>
|
||||
std::size_t
|
||||
read_some(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser,
|
||||
error_code& ec)
|
||||
{
|
||||
static_assert(is_SyncReadStream<SyncReadStream>::value,
|
||||
"SyncReadStream requirements not met");
|
||||
static_assert(is_DynamicBuffer<DynamicBuffer>::value,
|
||||
"DynamicBuffer requirements not met");
|
||||
BOOST_ASSERT(! parser.is_complete());
|
||||
return detail::read_some(stream, dynabuf, parser, ec);
|
||||
}
|
||||
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived>
|
||||
void
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser)
|
||||
{
|
||||
static_assert(is_SyncReadStream<SyncReadStream>::value,
|
||||
"SyncReadStream requirements not met");
|
||||
static_assert(is_DynamicBuffer<DynamicBuffer>::value,
|
||||
"DynamicBuffer requirements not met");
|
||||
BOOST_ASSERT(! parser.is_complete());
|
||||
error_code ec;
|
||||
read(stream, dynabuf, parser, ec);
|
||||
if(ec)
|
||||
throw system_error{ec};
|
||||
}
|
||||
|
||||
template<class SyncReadStream, class DynamicBuffer,
|
||||
bool isRequest, class Fields>
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived>
|
||||
void
|
||||
read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
header<isRequest, Fields>& m,
|
||||
error_code& ec)
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser,
|
||||
error_code& ec)
|
||||
{
|
||||
static_assert(is_SyncReadStream<SyncReadStream>::value,
|
||||
"SyncReadStream requirements not met");
|
||||
static_assert(is_DynamicBuffer<DynamicBuffer>::value,
|
||||
"DynamicBuffer requirements not met");
|
||||
header_parser_v1<isRequest, Fields> p;
|
||||
beast::http::parse(stream, dynabuf, p, ec);
|
||||
if(ec)
|
||||
return;
|
||||
BOOST_ASSERT(p.complete());
|
||||
m = p.release();
|
||||
}
|
||||
|
||||
template<class AsyncReadStream, class DynamicBuffer,
|
||||
bool isRequest, class Fields,
|
||||
class ReadHandler>
|
||||
typename async_completion<
|
||||
ReadHandler, void(error_code)>::result_type
|
||||
async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
header<isRequest, Fields>& m,
|
||||
ReadHandler&& handler)
|
||||
{
|
||||
static_assert(is_AsyncReadStream<AsyncReadStream>::value,
|
||||
"AsyncReadStream requirements not met");
|
||||
static_assert(is_DynamicBuffer<DynamicBuffer>::value,
|
||||
"DynamicBuffer requirements not met");
|
||||
beast::async_completion<ReadHandler,
|
||||
void(error_code)> completion{handler};
|
||||
detail::read_header_op<AsyncReadStream, DynamicBuffer,
|
||||
isRequest, Fields, decltype(
|
||||
completion.handler)>{completion.handler,
|
||||
stream, dynabuf, m};
|
||||
return completion.result.get();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<class Stream, class DynamicBuffer,
|
||||
bool isRequest, class Body, class Fields,
|
||||
class Handler>
|
||||
class read_op
|
||||
{
|
||||
using parser_type =
|
||||
parser_v1<isRequest, Body, Fields>;
|
||||
|
||||
using message_type =
|
||||
message<isRequest, Body, Fields>;
|
||||
|
||||
struct data
|
||||
BOOST_ASSERT(! parser.is_complete());
|
||||
do
|
||||
{
|
||||
bool cont;
|
||||
Stream& s;
|
||||
DynamicBuffer& db;
|
||||
message_type& m;
|
||||
parser_type p;
|
||||
bool started = false;
|
||||
int state = 0;
|
||||
|
||||
data(Handler& handler, Stream& s_,
|
||||
DynamicBuffer& sb_, message_type& m_)
|
||||
: cont(beast_asio_helpers::
|
||||
is_continuation(handler))
|
||||
, s(s_)
|
||||
, db(sb_)
|
||||
, m(m_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
handler_ptr<data, Handler> d_;
|
||||
|
||||
public:
|
||||
read_op(read_op&&) = default;
|
||||
read_op(read_op const&) = default;
|
||||
|
||||
template<class DeducedHandler, class... Args>
|
||||
read_op(DeducedHandler&& h, Stream& s, Args&&... args)
|
||||
: d_(std::forward<DeducedHandler>(h),
|
||||
s, std::forward<Args>(args)...)
|
||||
{
|
||||
(*this)(error_code{}, false);
|
||||
}
|
||||
|
||||
void
|
||||
operator()(error_code ec, bool again = true);
|
||||
|
||||
friend
|
||||
void* asio_handler_allocate(
|
||||
std::size_t size, read_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
allocate(size, op->d_.handler());
|
||||
}
|
||||
|
||||
friend
|
||||
void asio_handler_deallocate(
|
||||
void* p, std::size_t size, read_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
deallocate(p, size, op->d_.handler());
|
||||
}
|
||||
|
||||
friend
|
||||
bool asio_handler_is_continuation(read_op* op)
|
||||
{
|
||||
return op->d_->cont;
|
||||
}
|
||||
|
||||
template<class Function>
|
||||
friend
|
||||
void asio_handler_invoke(Function&& f, read_op* op)
|
||||
{
|
||||
return beast_asio_helpers::
|
||||
invoke(f, op->d_.handler());
|
||||
}
|
||||
};
|
||||
|
||||
template<class Stream, class DynamicBuffer,
|
||||
bool isRequest, class Body, class Fields,
|
||||
class Handler>
|
||||
void
|
||||
read_op<Stream, DynamicBuffer, isRequest, Body, Fields, Handler>::
|
||||
operator()(error_code ec, bool again)
|
||||
{
|
||||
auto& d = *d_;
|
||||
d.cont = d.cont || again;
|
||||
while(! ec && d.state != 99)
|
||||
{
|
||||
switch(d.state)
|
||||
{
|
||||
case 0:
|
||||
d.state = 1;
|
||||
async_parse(d.s, d.db, d.p, std::move(*this));
|
||||
auto const bytes_used =
|
||||
read_some(stream, dynabuf, parser, ec);
|
||||
if(ec)
|
||||
return;
|
||||
|
||||
case 1:
|
||||
// call handler
|
||||
d.state = 99;
|
||||
d.m = d.p.release();
|
||||
break;
|
||||
}
|
||||
dynabuf.consume(bytes_used);
|
||||
}
|
||||
d_.invoke(ec);
|
||||
while(! parser.is_complete());
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
template<class SyncReadStream, class DynamicBuffer,
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, class Body, class Fields>
|
||||
void
|
||||
read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
message<isRequest, Body, Fields>& msg)
|
||||
{
|
||||
static_assert(is_SyncReadStream<SyncReadStream>::value,
|
||||
@ -328,12 +297,16 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
throw system_error{ec};
|
||||
}
|
||||
|
||||
template<class SyncReadStream, class DynamicBuffer,
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, class Body, class Fields>
|
||||
void
|
||||
read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
message<isRequest, Body, Fields>& m,
|
||||
error_code& ec)
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
message<isRequest, Body, Fields>& msg,
|
||||
error_code& ec)
|
||||
{
|
||||
static_assert(is_SyncReadStream<SyncReadStream>::value,
|
||||
"SyncReadStream requirements not met");
|
||||
@ -346,41 +319,11 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
static_assert(is_Reader<typename Body::reader,
|
||||
message<isRequest, Body, Fields>>::value,
|
||||
"Reader requirements not met");
|
||||
parser_v1<isRequest, Body, Fields> p;
|
||||
beast::http::parse(stream, dynabuf, p, ec);
|
||||
message_parser<isRequest, Body, Fields> p;
|
||||
beast::http::read(stream, dynabuf, p, ec);
|
||||
if(ec)
|
||||
return;
|
||||
BOOST_ASSERT(p.complete());
|
||||
m = p.release();
|
||||
}
|
||||
|
||||
template<class AsyncReadStream, class DynamicBuffer,
|
||||
bool isRequest, class Body, class Fields,
|
||||
class ReadHandler>
|
||||
typename async_completion<
|
||||
ReadHandler, void(error_code)>::result_type
|
||||
async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
message<isRequest, Body, Fields>& m,
|
||||
ReadHandler&& handler)
|
||||
{
|
||||
static_assert(is_AsyncReadStream<AsyncReadStream>::value,
|
||||
"AsyncReadStream requirements not met");
|
||||
static_assert(is_DynamicBuffer<DynamicBuffer>::value,
|
||||
"DynamicBuffer requirements not met");
|
||||
static_assert(is_Body<Body>::value,
|
||||
"Body requirements not met");
|
||||
static_assert(has_reader<Body>::value,
|
||||
"Body has no reader");
|
||||
static_assert(is_Reader<typename Body::reader,
|
||||
message<isRequest, Body, Fields>>::value,
|
||||
"Reader requirements not met");
|
||||
beast::async_completion<ReadHandler,
|
||||
void(error_code)> completion{handler};
|
||||
detail::read_op<AsyncReadStream, DynamicBuffer,
|
||||
isRequest, Body, Fields, decltype(
|
||||
completion.handler)>{completion.handler,
|
||||
stream, dynabuf, m};
|
||||
return completion.result.get();
|
||||
msg = p.release();
|
||||
}
|
||||
|
||||
} // http
|
||||
|
@ -542,6 +542,26 @@ exists(T const& s)
|
||||
) != end();
|
||||
}
|
||||
|
||||
template<class Policy>
|
||||
bool
|
||||
validate_list(detail::basic_parsed_list<
|
||||
Policy> const& list)
|
||||
{
|
||||
auto const last = list.end();
|
||||
auto it = list.begin();
|
||||
if(it.error())
|
||||
return false;
|
||||
while(it != last)
|
||||
{
|
||||
++it;
|
||||
if(it.error())
|
||||
return false;
|
||||
if(it == last)
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
|
286
include/beast/http/message_parser.hpp
Normal file
286
include/beast/http/message_parser.hpp
Normal file
@ -0,0 +1,286 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_MESSAGE_PARSER_HPP
|
||||
#define BEAST_HTTP_MESSAGE_PARSER_HPP
|
||||
|
||||
#include <beast/config.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/http/header_parser.hpp>
|
||||
#include <beast/core/detail/clamp.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
/** A parser for producing HTTP/1 messages.
|
||||
|
||||
This class uses the basic HTTP/1 wire format parser to convert
|
||||
a series of octets into a @ref message.
|
||||
|
||||
@tparam isRequest Indicates whether a request or response
|
||||
will be parsed.
|
||||
|
||||
@tparam Body The type used to represent the body.
|
||||
|
||||
@tparam Fields The type of container used to represent the fields.
|
||||
|
||||
@note A new instance of the parser is required for each message.
|
||||
*/
|
||||
template<bool isRequest, class Body, class Fields>
|
||||
class message_parser
|
||||
: public basic_parser<isRequest,
|
||||
Body::reader::is_direct,
|
||||
message_parser<isRequest, Body, Fields>>
|
||||
{
|
||||
using base_type = basic_parser<isRequest, true,
|
||||
message_parser<isRequest, Body, Fields>>;
|
||||
|
||||
using reader_type = typename Body::reader;
|
||||
|
||||
message<isRequest, Body, Fields> m_;
|
||||
boost::optional<typename Body::reader> r_;
|
||||
|
||||
public:
|
||||
/// The type of message returned by the parser
|
||||
using value_type = message<isRequest, Body, Fields>;
|
||||
|
||||
/// The type of buffer sequence representing the body
|
||||
using mutable_buffers_type =
|
||||
typename reader_type::mutable_buffers_type;
|
||||
|
||||
/// Constructor (default)
|
||||
message_parser() = default;
|
||||
|
||||
/// Copy constructor (disallowed)
|
||||
message_parser(message_parser const&) = delete;
|
||||
|
||||
/// Copy assignment (disallowed)
|
||||
message_parser& operator=(message_parser const&) = delete;
|
||||
|
||||
/** Move constructor.
|
||||
|
||||
After the move, the only valid operation
|
||||
on the moved-from object is destruction.
|
||||
*/
|
||||
message_parser(message_parser&& other);
|
||||
|
||||
/** Constructor
|
||||
|
||||
@param args Optional arguments forwarded to the
|
||||
@ref http::header constructor.
|
||||
|
||||
@note This function participates in overload
|
||||
resolution only if the first argument is not a
|
||||
@ref http::header_parser or @ref message_parser.
|
||||
*/
|
||||
#if GENERATING_DOCS
|
||||
template<class... Args>
|
||||
explicit
|
||||
msesage_parser(Args&&... args);
|
||||
#else
|
||||
template<class Arg1, class... ArgN,
|
||||
class = typename std::enable_if<
|
||||
! std::is_same<typename
|
||||
std::decay<Arg1>::type,
|
||||
header_parser<isRequest, Fields>>::value &&
|
||||
! std::is_same<typename
|
||||
std::decay<Arg1>::type, message_parser>::value
|
||||
>::type>
|
||||
explicit
|
||||
message_parser(Arg1&& arg1, ArgN&&... argn);
|
||||
#endif
|
||||
|
||||
/** Construct a message parser from a @ref header_parser.
|
||||
|
||||
@param parser The header parser to construct from.
|
||||
|
||||
@param args Optional arguments forwarded to the message
|
||||
constructor.
|
||||
*/
|
||||
template<class... Args>
|
||||
explicit
|
||||
message_parser(header_parser<
|
||||
isRequest, Fields>&& parser, Args&&... args);
|
||||
|
||||
/** Returns the parsed message.
|
||||
|
||||
Depending on the progress of the parser, portions
|
||||
of this object may be incomplete.
|
||||
*/
|
||||
value_type const&
|
||||
get() const
|
||||
{
|
||||
return m_;
|
||||
}
|
||||
|
||||
/** Returns the parsed message.
|
||||
|
||||
Depending on the progress of the parser, portions
|
||||
of this object may be incomplete.
|
||||
*/
|
||||
value_type&
|
||||
get()
|
||||
{
|
||||
return m_;
|
||||
}
|
||||
|
||||
/** Returns ownership of the parsed message.
|
||||
|
||||
Ownership is transferred to the caller.
|
||||
Depending on the progress of the parser, portions
|
||||
of this object may be incomplete.
|
||||
|
||||
@par Requires
|
||||
|
||||
@ref value_type is @b MoveConstructible
|
||||
*/
|
||||
value_type
|
||||
release()
|
||||
{
|
||||
static_assert(std::is_move_constructible<decltype(m_)>::value,
|
||||
"MoveConstructible requirements not met");
|
||||
return std::move(m_);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class basic_parser<
|
||||
isRequest, Body::reader::is_direct,
|
||||
message_parser>;
|
||||
|
||||
void
|
||||
on_request(
|
||||
boost::string_ref const& method,
|
||||
boost::string_ref const& path,
|
||||
int version, error_code&)
|
||||
{
|
||||
m_.url = std::string{
|
||||
path.data(), path.size()};
|
||||
m_.method = std::string{
|
||||
method.data(), method.size()};
|
||||
m_.version = version;
|
||||
}
|
||||
|
||||
void
|
||||
on_response(int status,
|
||||
boost::string_ref const& reason,
|
||||
int version, error_code&)
|
||||
{
|
||||
m_.status = status;
|
||||
m_.reason = std::string{
|
||||
reason.data(), reason.size()};
|
||||
m_.version = version;
|
||||
}
|
||||
|
||||
void
|
||||
on_field(boost::string_ref const& name,
|
||||
boost::string_ref const& value,
|
||||
error_code&)
|
||||
{
|
||||
m_.fields.insert(name, value);
|
||||
}
|
||||
|
||||
void
|
||||
on_header(error_code& ec)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_body()
|
||||
{
|
||||
r_.emplace(m_);
|
||||
r_->init();
|
||||
}
|
||||
|
||||
void
|
||||
on_body(std::uint64_t content_length)
|
||||
{
|
||||
r_.emplace(m_);
|
||||
r_->init(content_length);
|
||||
}
|
||||
|
||||
void
|
||||
on_body(error_code& ec)
|
||||
{
|
||||
r_.emplace(m_);
|
||||
r_->init(ec);
|
||||
if(ec)
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
on_body(std::uint64_t content_length,
|
||||
error_code& ec)
|
||||
{
|
||||
r_.emplace(m_);
|
||||
r_->init(content_length, ec);
|
||||
if(ec)
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
on_data(boost::string_ref const& s,
|
||||
error_code& ec)
|
||||
{
|
||||
static_assert(! Body::reader::is_direct, "");
|
||||
r_->write(s, ec);
|
||||
}
|
||||
|
||||
mutable_buffers_type
|
||||
on_prepare(std::size_t n)
|
||||
{
|
||||
return r_->prepare(n);
|
||||
}
|
||||
|
||||
void
|
||||
on_commit(std::size_t n)
|
||||
{
|
||||
r_->commit(n);
|
||||
}
|
||||
|
||||
void
|
||||
on_chunk(std::uint64_t,
|
||||
boost::string_ref const&,
|
||||
error_code&)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_complete(error_code& ec)
|
||||
{
|
||||
if(r_)
|
||||
do_on_complete(ec,
|
||||
std::integral_constant<bool,
|
||||
Body::reader::is_direct>{});
|
||||
}
|
||||
|
||||
void
|
||||
do_on_complete(
|
||||
error_code& ec, std::true_type)
|
||||
{
|
||||
r_->finish();
|
||||
}
|
||||
|
||||
void
|
||||
do_on_complete(
|
||||
error_code& ec, std::false_type)
|
||||
{
|
||||
r_->finish(ec);
|
||||
if(ec)
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#include <beast/http/impl/message_parser.ipp>
|
||||
|
||||
#endif
|
@ -1,155 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_PARSE_HPP
|
||||
#define BEAST_HTTP_PARSE_HPP
|
||||
|
||||
#include <beast/config.hpp>
|
||||
#include <beast/core/error.hpp>
|
||||
#include <beast/core/async_completion.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
/** Parse an object 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 The parser indicates that parsing is complete.
|
||||
|
||||
@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 object
|
||||
being parsed. This additional data is stored in the stream
|
||||
buffer, which may be used in subsequent calls.
|
||||
|
||||
@note This algorithm is generic, and not specific to HTTP
|
||||
messages. It is up to the parser to determine what predicate
|
||||
defines a complete operation.
|
||||
|
||||
@param stream The stream from which the data is to be read.
|
||||
The type must support the @b SyncReadStream concept.
|
||||
|
||||
@param dynabuf A @b DynamicBuffer 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 @b Parser
|
||||
which will receive the data.
|
||||
|
||||
@throws system_error Thrown on failure.
|
||||
*/
|
||||
template<class SyncReadStream, class DynamicBuffer, class Parser>
|
||||
void
|
||||
parse(SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf, Parser& parser);
|
||||
|
||||
/** Parse an object 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 The parser indicates that parsing is complete.
|
||||
|
||||
@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 object
|
||||
being parsed. This additional data is stored in the stream
|
||||
buffer, which may be used in subsequent calls.
|
||||
|
||||
@note This algorithm is generic, and not specific to HTTP
|
||||
messages. It is up to the parser to determine what predicate
|
||||
defines a complete operation.
|
||||
|
||||
@param stream The stream from which the data is to be read.
|
||||
The type must support the @b SyncReadStream concept.
|
||||
|
||||
@param dynabuf A @b DynamicBuffer 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 @b Parser
|
||||
which will receive the data.
|
||||
|
||||
@param ec Set to the error, if any occurred.
|
||||
*/
|
||||
template<class SyncReadStream, class DynamicBuffer, class Parser>
|
||||
void
|
||||
parse(SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf, Parser& parser, error_code& ec);
|
||||
|
||||
/** Start an asynchronous operation to parse an object 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 The parser indicates that parsing is complete.
|
||||
|
||||
@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.
|
||||
The implementation may read additional octets that lie past the
|
||||
end of the object 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 AsyncReadStream concept.
|
||||
|
||||
@param dynabuf A @b DynamicBuffer 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 @b 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 DynamicBuffer, class Parser, class ReadHandler>
|
||||
#if BEAST_DOXYGEN
|
||||
void_or_deduced
|
||||
#else
|
||||
typename async_completion<
|
||||
ReadHandler, void(error_code)>::result_type
|
||||
#endif
|
||||
async_parse(AsyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
Parser& parser, ReadHandler&& handler);
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#include <beast/http/impl/parse.ipp>
|
||||
|
||||
#endif
|
@ -1,48 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_PARSE_ERROR_HPP
|
||||
#define BEAST_HTTP_PARSE_ERROR_HPP
|
||||
|
||||
#include <beast/config.hpp>
|
||||
#include <beast/core/error.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
enum class parse_error
|
||||
{
|
||||
connection_closed = 1,
|
||||
|
||||
bad_method,
|
||||
bad_uri,
|
||||
bad_version,
|
||||
bad_crlf,
|
||||
|
||||
bad_status,
|
||||
bad_reason,
|
||||
|
||||
bad_field,
|
||||
bad_value,
|
||||
bad_content_length,
|
||||
illegal_content_length,
|
||||
|
||||
invalid_chunk_size,
|
||||
invalid_ext_name,
|
||||
invalid_ext_val,
|
||||
|
||||
header_too_big,
|
||||
body_too_big,
|
||||
short_read
|
||||
};
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#include <beast/http/impl/parse_error.ipp>
|
||||
|
||||
#endif
|
@ -1,339 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_PARSER_V1_HPP
|
||||
#define BEAST_HTTP_PARSER_V1_HPP
|
||||
|
||||
#include <beast/config.hpp>
|
||||
#include <beast/http/concepts.hpp>
|
||||
#include <beast/http/header_parser_v1.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/core/error.hpp>
|
||||
#include <beast/core/detail/type_traits.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
/** Skip body option.
|
||||
|
||||
The options controls whether or not the parser expects to see a
|
||||
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 use the @ref skip_body option to
|
||||
inform the parser that no body is expected. The parser will consider
|
||||
the message complete after the header has been received.
|
||||
|
||||
Example:
|
||||
@code
|
||||
parser_v1<true, empty_body, fields> p;
|
||||
p.set_option(skip_body{true});
|
||||
@endcode
|
||||
|
||||
@note Objects of this type are passed to @ref parser_v1::set_option.
|
||||
*/
|
||||
struct skip_body
|
||||
{
|
||||
bool value;
|
||||
|
||||
explicit
|
||||
skip_body(bool v)
|
||||
: value(v)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/** A parser for producing HTTP/1 messages.
|
||||
|
||||
This class uses the basic HTTP/1 wire format parser to convert
|
||||
a series of octets into a `message`.
|
||||
|
||||
@note A new instance of the parser is required for each message.
|
||||
*/
|
||||
template<bool isRequest, class Body, class Fields>
|
||||
class parser_v1
|
||||
: public basic_parser_v1<isRequest,
|
||||
parser_v1<isRequest, Body, Fields>>
|
||||
, private std::conditional<isRequest,
|
||||
detail::request_parser_base,
|
||||
detail::response_parser_base>::type
|
||||
{
|
||||
public:
|
||||
/// The type of message this parser produces.
|
||||
using message_type =
|
||||
message<isRequest, Body, Fields>;
|
||||
|
||||
private:
|
||||
using reader =
|
||||
typename message_type::body_type::reader;
|
||||
|
||||
static_assert(is_Body<Body>::value,
|
||||
"Body requirements not met");
|
||||
static_assert(has_reader<Body>::value,
|
||||
"Body has no reader");
|
||||
static_assert(is_Reader<reader, message_type>::value,
|
||||
"Reader requirements not met");
|
||||
|
||||
std::string field_;
|
||||
std::string value_;
|
||||
message_type m_;
|
||||
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;
|
||||
|
||||
/// Copy constructor (disallowed)
|
||||
parser_v1(parser_v1 const&) = delete;
|
||||
|
||||
/// Move assignment (disallowed)
|
||||
parser_v1& operator=(parser_v1&&) = delete;
|
||||
|
||||
/// Copy assignment (disallowed)
|
||||
parser_v1& operator=(parser_v1 const&) = delete;
|
||||
|
||||
/** Construct the parser.
|
||||
|
||||
@param args Forwarded to the message constructor.
|
||||
|
||||
@note This function participates in overload resolution only
|
||||
if the first argument is not a parser or fields parser.
|
||||
*/
|
||||
#if BEAST_DOXYGEN
|
||||
template<class... Args>
|
||||
explicit
|
||||
parser_v1(Args&&... args);
|
||||
#else
|
||||
template<class Arg1, class... ArgN,
|
||||
class = typename std::enable_if<
|
||||
! std::is_same<typename
|
||||
std::decay<Arg1>::type,
|
||||
header_parser_v1<isRequest, Fields>>::value &&
|
||||
! std::is_same<typename
|
||||
std::decay<Arg1>::type, parser_v1>::value
|
||||
>::type>
|
||||
explicit
|
||||
parser_v1(Arg1&& arg1, ArgN&&... argn)
|
||||
: m_(std::forward<Arg1>(arg1),
|
||||
std::forward<ArgN>(argn)...)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Construct the parser from a fields parser.
|
||||
|
||||
@param parser The fields parser to construct from.
|
||||
@param args Forwarded to the message body constructor.
|
||||
*/
|
||||
template<class... Args>
|
||||
explicit
|
||||
parser_v1(header_parser_v1<isRequest, Fields>& parser,
|
||||
Args&&... args)
|
||||
: m_(parser.release(), std::forward<Args>(args)...)
|
||||
{
|
||||
static_cast<basic_parser_v1<
|
||||
isRequest, parser_v1<
|
||||
isRequest, Body, Fields>>&>(*this) = parser;
|
||||
}
|
||||
|
||||
/// Set the skip body option.
|
||||
void
|
||||
set_option(skip_body const& o)
|
||||
{
|
||||
skip_body_ = o.value ? 1 : 0;
|
||||
}
|
||||
|
||||
/** Returns the parsed message.
|
||||
|
||||
Only valid if @ref complete would return `true`.
|
||||
*/
|
||||
message_type const&
|
||||
get() const
|
||||
{
|
||||
return m_;
|
||||
}
|
||||
|
||||
/** Returns the parsed message.
|
||||
|
||||
Only valid if @ref complete would return `true`.
|
||||
*/
|
||||
message_type&
|
||||
get()
|
||||
{
|
||||
return m_;
|
||||
}
|
||||
|
||||
/** Returns ownership of the parsed message.
|
||||
|
||||
Ownership is transferred to the caller. Only
|
||||
valid if @ref complete would return `true`.
|
||||
|
||||
Requires:
|
||||
`message<isRequest, Body, Fields>` is @b MoveConstructible
|
||||
*/
|
||||
message_type
|
||||
release()
|
||||
{
|
||||
static_assert(std::is_move_constructible<decltype(m_)>::value,
|
||||
"MoveConstructible requirements not met");
|
||||
return std::move(m_);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class basic_parser_v1<isRequest, parser_v1>;
|
||||
|
||||
void flush()
|
||||
{
|
||||
if(! flush_)
|
||||
return;
|
||||
flush_ = false;
|
||||
BOOST_ASSERT(! field_.empty());
|
||||
m_.fields.insert(field_, value_);
|
||||
field_.clear();
|
||||
value_.clear();
|
||||
}
|
||||
|
||||
void on_start(error_code&)
|
||||
{
|
||||
}
|
||||
|
||||
void on_method(boost::string_ref const& s, error_code&)
|
||||
{
|
||||
this->method_.append(s.data(), s.size());
|
||||
}
|
||||
|
||||
void on_uri(boost::string_ref const& s, error_code&)
|
||||
{
|
||||
this->uri_.append(s.data(), s.size());
|
||||
}
|
||||
|
||||
void on_reason(boost::string_ref const& s, error_code&)
|
||||
{
|
||||
this->reason_.append(s.data(), s.size());
|
||||
}
|
||||
|
||||
void on_request_or_response(std::true_type)
|
||||
{
|
||||
m_.method = std::move(this->method_);
|
||||
m_.url = std::move(this->uri_);
|
||||
}
|
||||
|
||||
void on_request_or_response(std::false_type)
|
||||
{
|
||||
m_.status = this->status_code();
|
||||
m_.reason = std::move(this->reason_);
|
||||
}
|
||||
|
||||
void on_request(error_code&)
|
||||
{
|
||||
on_request_or_response(
|
||||
std::integral_constant<bool, isRequest>{});
|
||||
}
|
||||
|
||||
void on_response(error_code&)
|
||||
{
|
||||
on_request_or_response(
|
||||
std::integral_constant<bool, isRequest>{});
|
||||
}
|
||||
|
||||
void on_field(boost::string_ref const& s, error_code&)
|
||||
{
|
||||
flush();
|
||||
field_.append(s.data(), s.size());
|
||||
}
|
||||
|
||||
void on_value(boost::string_ref const& s, error_code&)
|
||||
{
|
||||
value_.append(s.data(), s.size());
|
||||
flush_ = true;
|
||||
}
|
||||
|
||||
void
|
||||
on_header(std::uint64_t, error_code&)
|
||||
{
|
||||
flush();
|
||||
m_.version = 10 * this->http_major() + this->http_minor();
|
||||
}
|
||||
|
||||
body_what
|
||||
on_body_what(std::uint64_t, error_code& ec)
|
||||
{
|
||||
if(skip_body_)
|
||||
return body_what::skip;
|
||||
r_.emplace(m_);
|
||||
r_->init(ec);
|
||||
return body_what::normal;
|
||||
}
|
||||
|
||||
void on_body(boost::string_ref const& s, error_code& ec)
|
||||
{
|
||||
r_->write(s.data(), s.size(), ec);
|
||||
}
|
||||
|
||||
void on_complete(error_code&)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/** Create a new parser from a fields parser.
|
||||
|
||||
Associates a Body type with a fields parser, and returns
|
||||
a new parser which parses a complete message object
|
||||
containing the original message fields and a new body
|
||||
of the specified body type.
|
||||
|
||||
This function allows HTTP messages to be parsed in two stages.
|
||||
First, the fields are parsed and control is returned. Then,
|
||||
the caller can choose at run-time, the type of Body to
|
||||
associate with the message. And finally, complete the parse
|
||||
in a second call.
|
||||
|
||||
@param parser The fields parser to construct from. Ownership
|
||||
of the message fields in the fields parser is transferred
|
||||
as if by call to @ref header_parser_v1::release.
|
||||
|
||||
@param args Forwarded to the body constructor of the message
|
||||
in the new parser.
|
||||
|
||||
@return A parser for a message with the specified @b Body type.
|
||||
|
||||
@par Example
|
||||
@code
|
||||
headers_parser<true, fields> ph;
|
||||
...
|
||||
auto p = with_body<string_body>(ph);
|
||||
...
|
||||
message<true, string_body, fields> m = p.release();
|
||||
@endcode
|
||||
*/
|
||||
template<class Body,
|
||||
bool isRequest, class Fields, class... Args>
|
||||
parser_v1<isRequest, Body, Fields>
|
||||
with_body(header_parser_v1<isRequest, Fields>& parser,
|
||||
Args&&... args)
|
||||
{
|
||||
return parser_v1<isRequest, Body, Fields>(
|
||||
parser, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
@ -11,135 +11,298 @@
|
||||
#include <beast/config.hpp>
|
||||
#include <beast/core/async_completion.hpp>
|
||||
#include <beast/core/error.hpp>
|
||||
#include <beast/http/basic_parser.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
/** Read a HTTP/1 header from a stream.
|
||||
/** Read some HTTP/1 message data from a stream.
|
||||
|
||||
This function is used to synchronously read a header
|
||||
from a stream. The call blocks until one of the following
|
||||
conditions is true:
|
||||
This function synchronously advances the state of the
|
||||
parser using the provided dynamic buffer and reading
|
||||
from the input stream as needed. The call will block
|
||||
until one of the following conditions is true:
|
||||
|
||||
@li An entire header is read in.
|
||||
@li When expecting a message header, and the complete
|
||||
header is received.
|
||||
|
||||
@li When expecting a chunk header, and the complete
|
||||
chunk header is received.
|
||||
|
||||
@li When expecting body octets, one or more body octets
|
||||
are received.
|
||||
|
||||
@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
|
||||
fields being parsed. This additional data is stored in the
|
||||
stream buffer, which may be used in subsequent calls.
|
||||
|
||||
If the message corresponding to the header being received
|
||||
contains a message body, it is the callers responsibility
|
||||
to cause the body to be read in before attempting to read
|
||||
the next message.
|
||||
read additional octets that lie past the end of the object
|
||||
being parsed. This additional data is stored in the dynamic
|
||||
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.
|
||||
The type must support the @b SyncReadStream concept.
|
||||
|
||||
@param dynabuf A @b `DynamicBuffer` holding additional bytes
|
||||
@param dynabuf A @b DynamicBuffer 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
|
||||
dynamic buffer's input sequence will be given to the parser
|
||||
first.
|
||||
|
||||
@param msg An object used to store the header. Any contents
|
||||
will be overwritten. The type must support copy assignment
|
||||
or move assignment.
|
||||
@param parser The parser to use.
|
||||
|
||||
@return The number of bytes processed from the dynamic
|
||||
buffer. The caller should remove these bytes by calling
|
||||
`consume` on the dynamic buffer.
|
||||
|
||||
@throws system_error Thrown on failure.
|
||||
*/
|
||||
template<class SyncReadStream, class DynamicBuffer,
|
||||
bool isRequest, class Fields>
|
||||
void
|
||||
read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
header<isRequest, Fields>& msg);
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived>
|
||||
std::size_t
|
||||
read_some(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser);
|
||||
|
||||
/** Read a HTTP/1 header from a stream.
|
||||
/** Read some HTTP/1 message data from a stream.
|
||||
|
||||
This function is used to synchronously read a header
|
||||
from a stream. The call blocks until one of the following
|
||||
conditions is true:
|
||||
This function synchronously advances the state of the
|
||||
parser using the provided dynamic buffer and reading
|
||||
from the input stream as needed. The call will block
|
||||
until one of the following conditions is true:
|
||||
|
||||
@li An entire header is read in.
|
||||
@li When expecting a message header, and the complete
|
||||
header is received.
|
||||
|
||||
@li When expecting a chunk header, and the complete
|
||||
chunk header is received.
|
||||
|
||||
@li When expecting body octets, one or more body octets
|
||||
are received.
|
||||
|
||||
@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
|
||||
fields being parsed. This additional data is stored in the
|
||||
stream buffer, which may be used in subsequent calls.
|
||||
|
||||
If the message corresponding to the header being received
|
||||
contains a message body, it is the callers responsibility
|
||||
to cause the body to be read in before attempting to read
|
||||
the next message.
|
||||
read additional octets that lie past the end of the object
|
||||
being parsed. This additional data is stored in the dynamic
|
||||
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.
|
||||
The type must support the @b SyncReadStream concept.
|
||||
|
||||
@param dynabuf A @b `DynamicBuffer` holding additional bytes
|
||||
@param dynabuf A @b DynamicBuffer 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
|
||||
dynamic buffer's input sequence will be given to the parser
|
||||
first.
|
||||
|
||||
@param msg An object used to store the header. Any contents
|
||||
will be overwritten. The type must support copy assignment
|
||||
or move assignment.
|
||||
@param parser The parser to use.
|
||||
|
||||
@param ec Set to the error, if any occurred.
|
||||
|
||||
@return The number of bytes processed from the dynamic
|
||||
buffer. The caller should remove these bytes by calling
|
||||
`consume` on the dynamic buffer.
|
||||
*/
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived>
|
||||
std::size_t
|
||||
read_some(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser,
|
||||
error_code& ec);
|
||||
|
||||
/** Start an asynchronous operation to read some HTTP/1 message data from a stream.
|
||||
|
||||
This function asynchronously advances the state of the
|
||||
parser using the provided dynamic buffer and reading from
|
||||
the input stream as needed. The function call always
|
||||
returns immediately. The asynchronous operation will
|
||||
continue until one of the following conditions is true:
|
||||
|
||||
@li When expecting a message header, and the complete
|
||||
header is received.
|
||||
|
||||
@li When expecting a chunk header, and the complete
|
||||
chunk header is received.
|
||||
|
||||
@li When expecting body octets, one or more body octets
|
||||
are received.
|
||||
|
||||
@li An error occurs in the stream or parser.
|
||||
|
||||
This operation is implemented in terms of zero 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.
|
||||
The implementation may read additional octets that lie past the
|
||||
end of the object being parsed. This additional data is stored
|
||||
in the stream buffer, which may be used in subsequent calls.
|
||||
|
||||
The completion handler will be called with the number of bytes
|
||||
processed from the dynamic buffer. The caller should remove
|
||||
these bytes by calling `consume` on the dynamic buffer.
|
||||
|
||||
@param stream The stream from which the data is to be read.
|
||||
The type must support the @b AsyncReadStream concept.
|
||||
|
||||
@param dynabuf A @b DynamicBuffer 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
|
||||
dynamic buffer's input sequence will be given to the parser
|
||||
first.
|
||||
|
||||
@param parser The parser to use.
|
||||
|
||||
@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
|
||||
std::size_t bytes_used // the number of bytes to consume
|
||||
); @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 DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived,
|
||||
class ReadHandler>
|
||||
#if GENERATING_DOCS
|
||||
void_or_deduced
|
||||
#else
|
||||
typename async_completion<
|
||||
ReadHandler, void(error_code, std::size_t)>::result_type
|
||||
#endif
|
||||
async_read_some(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser,
|
||||
ReadHandler&& handler);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Read an 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 is true:
|
||||
|
||||
@li The parser indicates no more additional data is needed.
|
||||
|
||||
@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 object
|
||||
being parsed. This additional data is stored in the dynamic
|
||||
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 dynabuf A @b DynamicBuffer 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
|
||||
dynamic buffer's input sequence will be given to the parser
|
||||
first.
|
||||
|
||||
@param parser The parser to use.
|
||||
|
||||
@throws system_error Thrown on failure.
|
||||
*/
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived>
|
||||
void
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser);
|
||||
|
||||
/** Read an 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 is true:
|
||||
|
||||
@li The parser indicates that no more data is needed.
|
||||
|
||||
@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 object
|
||||
being parsed. This additional data is stored in the dynamic
|
||||
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 dynabuf A @b DynamicBuffer 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
|
||||
dynamic buffer's input sequence will be given to the parser
|
||||
first.
|
||||
|
||||
@param parser The parser to use.
|
||||
|
||||
@param ec Set to the error, if any occurred.
|
||||
*/
|
||||
template<class SyncReadStream, class DynamicBuffer,
|
||||
bool isRequest, class Fields>
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived>
|
||||
void
|
||||
read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
header<isRequest, Fields>& msg,
|
||||
error_code& ec);
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser,
|
||||
error_code& ec);
|
||||
|
||||
/** Read a HTTP/1 header asynchronously from a stream.
|
||||
/** Start an asynchronous operation to read an HTTP/1 message from a stream.
|
||||
|
||||
This function is used to asynchronously read a header from
|
||||
a stream. The function call always returns immediately. The
|
||||
asynchronous operation will continue until one of the following
|
||||
conditions is true:
|
||||
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 An entire header is read in.
|
||||
@li The parser indicates that no more data is needed.
|
||||
|
||||
@li An error occurs in the stream or parser.
|
||||
|
||||
This operation is implemented in terms of one or more calls to
|
||||
the stream's `async_read_some` function, and is known as a
|
||||
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.
|
||||
The implementation may read additional octets that lie past the
|
||||
end of the message fields being parsed. This additional data is
|
||||
stored in the stream buffer, which may be used in subsequent calls.
|
||||
end of the object being parsed. This additional data is stored
|
||||
in the stream buffer, which may be used in subsequent calls.
|
||||
|
||||
If the message corresponding to the header being received
|
||||
contains a message body, it is the callers responsibility
|
||||
to cause the body to be read in before attempting to read
|
||||
the next message.
|
||||
@param stream The stream from which the data is to be read.
|
||||
The type must support the @b AsyncReadStream concept.
|
||||
|
||||
@param stream The stream to read the message from.
|
||||
The type must support the @b `AsyncReadStream` concept.
|
||||
|
||||
@param dynabuf A @b `DynamicBuffer` holding additional bytes
|
||||
@param dynabuf A @b DynamicBuffer 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
|
||||
dynamic buffer's input sequence will be given to the parser
|
||||
first.
|
||||
|
||||
@param msg An object used to store the header. Any contents
|
||||
will be overwritten. The type must support copy assignment or
|
||||
move assignment. The object must remain valid at least until
|
||||
the completion handler is called; ownership is not transferred.
|
||||
@param parser The parser to use.
|
||||
|
||||
@param handler The handler to be called when the operation
|
||||
@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(
|
||||
@ -150,20 +313,24 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
this function. Invocation of the handler will be performed in a
|
||||
manner equivalent to using `boost::asio::io_service::post`.
|
||||
*/
|
||||
template<class AsyncReadStream, class DynamicBuffer,
|
||||
bool isRequest, class Body, class Fields,
|
||||
class ReadHandler>
|
||||
template<
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, bool isDirect, class Derived,
|
||||
class ReadHandler>
|
||||
#if BEAST_DOXYGEN
|
||||
void_or_deduced
|
||||
#else
|
||||
typename async_completion<
|
||||
ReadHandler, void(error_code)>::result_type
|
||||
#endif
|
||||
async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
header<isRequest, Fields>& msg,
|
||||
ReadHandler&& handler);
|
||||
async_read(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser,
|
||||
ReadHandler&& handler);
|
||||
|
||||
/** Read a HTTP/1 message from a stream.
|
||||
/** Read an HTTP/1 message from a stream.
|
||||
|
||||
This function is used to synchronously read a message from
|
||||
a stream. The call blocks until one of the following conditions
|
||||
@ -176,7 +343,7 @@ async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
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
|
||||
being parsed. This additional data is stored in the dynamic
|
||||
buffer, which may be used in subsequent calls.
|
||||
|
||||
@param stream The stream from which the data is to be read.
|
||||
@ -185,7 +352,7 @@ async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
@param dynabuf A @b `DynamicBuffer` 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
|
||||
dynamic buffer's input sequence will be given to the parser
|
||||
first.
|
||||
|
||||
@param msg An object used to store the message. Any
|
||||
@ -194,10 +361,14 @@ async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
|
||||
@throws system_error Thrown on failure.
|
||||
*/
|
||||
template<class SyncReadStream, class DynamicBuffer,
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, class Body, class Fields>
|
||||
void
|
||||
read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
message<isRequest, Body, Fields>& msg);
|
||||
|
||||
/** Read a HTTP/1 message from a stream.
|
||||
@ -213,7 +384,7 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
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
|
||||
being parsed. This additional data is stored in the dynamic
|
||||
buffer, which may be used in subsequent calls.
|
||||
|
||||
@param stream The stream from which the data is to be read.
|
||||
@ -222,7 +393,7 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
@param dynabuf A @b `DynamicBuffer` 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
|
||||
dynamic buffer's input sequence will be given to the parser
|
||||
first.
|
||||
|
||||
@param msg An object used to store the message. Any
|
||||
@ -231,12 +402,16 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
|
||||
@param ec Set to the error, if any occurred.
|
||||
*/
|
||||
template<class SyncReadStream, class DynamicBuffer,
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, class Body, class Fields>
|
||||
void
|
||||
read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
message<isRequest, Body, Fields>& msg,
|
||||
error_code& ec);
|
||||
error_code& ec);
|
||||
|
||||
/** Read a HTTP/1 message asynchronously from a stream.
|
||||
|
||||
@ -255,7 +430,7 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
stream performs no other operations until this operation completes.
|
||||
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.
|
||||
in the dynamic buffer, which may be used in subsequent calls.
|
||||
|
||||
@param stream The stream to read the message from.
|
||||
The type must support the @b `AsyncReadStream` concept.
|
||||
@ -263,7 +438,7 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
@param dynabuf A @b `DynamicBuffer` 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
|
||||
dynamic buffer's input sequence will be given to the parser
|
||||
first.
|
||||
|
||||
@param msg An object used to store the header. Any contents
|
||||
@ -282,22 +457,27 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
this function. Invocation of the handler will be performed in a
|
||||
manner equivalent to using `boost::asio::io_service::post`.
|
||||
*/
|
||||
template<class AsyncReadStream, class DynamicBuffer,
|
||||
template<
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
bool isRequest, class Body, class Fields,
|
||||
class ReadHandler>
|
||||
class ReadHandler>
|
||||
#if BEAST_DOXYGEN
|
||||
void_or_deduced
|
||||
#else
|
||||
typename async_completion<
|
||||
ReadHandler, void(error_code)>::result_type
|
||||
#endif
|
||||
async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf,
|
||||
async_read(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer& dynabuf,
|
||||
message<isRequest, Body, Fields>& msg,
|
||||
ReadHandler&& handler);
|
||||
ReadHandler&& handler);
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#include <beast/http/impl/async_read.ipp>
|
||||
#include <beast/http/impl/read.ipp>
|
||||
|
||||
#endif
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include <beast/config.hpp>
|
||||
#include <beast/http/detail/rfc7230.hpp>
|
||||
#include <beast/http/detail/basic_parsed_list.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
@ -276,6 +277,46 @@ public:
|
||||
exists(T const& s);
|
||||
};
|
||||
|
||||
/** A list of tokens in a comma separated HTTP field value.
|
||||
|
||||
This container allows iteration of a list of items in a
|
||||
header field value. The input is a comma separated list of
|
||||
tokens.
|
||||
|
||||
If a parsing error is encountered while iterating the string,
|
||||
the behavior of the container will be as if a string containing
|
||||
only characters up to but excluding the first invalid character
|
||||
was used to construct the list.
|
||||
|
||||
@par BNF
|
||||
@code
|
||||
token-list = *( "," OWS ) token *( OWS "," [ OWS token ] )
|
||||
@endcode
|
||||
|
||||
To use this class, construct with the string to be parsed and
|
||||
then use `begin` and `end`, or range-for to iterate each item:
|
||||
|
||||
@par Example
|
||||
@code
|
||||
for(auto const& token : token_list{"apple, pear, banana"})
|
||||
std::cout << token << "\n";
|
||||
@endcode
|
||||
*/
|
||||
using opt_token_list =
|
||||
detail::basic_parsed_list<
|
||||
detail::opt_token_list_policy>;
|
||||
|
||||
/** Returns `true` if a parsed list is parsed without errors.
|
||||
|
||||
This function iterates a single pass through a parsed list
|
||||
and returns `true` if there were no parsing errors, else
|
||||
returns `false`.
|
||||
*/
|
||||
template<class Policy>
|
||||
bool
|
||||
validate_list(detail::basic_parsed_list<
|
||||
Policy> const& list);
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
|
@ -34,29 +34,58 @@ private:
|
||||
|
||||
class reader
|
||||
{
|
||||
value_type& s_;
|
||||
value_type& body_;
|
||||
std::size_t len_ = 0;
|
||||
|
||||
public:
|
||||
static bool constexpr is_direct = true;
|
||||
|
||||
using mutable_buffers_type =
|
||||
boost::asio::mutable_buffers_1;
|
||||
|
||||
template<bool isRequest, class Fields>
|
||||
explicit
|
||||
reader(message<isRequest,
|
||||
string_body, Fields>& m) noexcept
|
||||
: s_(m.body)
|
||||
string_body, Fields>& m)
|
||||
: body_(m.body)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
init(error_code&) noexcept
|
||||
init()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
write(void const* data,
|
||||
std::size_t size, error_code&) noexcept
|
||||
init(std::uint64_t content_length)
|
||||
{
|
||||
auto const n = s_.size();
|
||||
s_.resize(n + size);
|
||||
std::memcpy(&s_[n], data, size);
|
||||
if(content_length >
|
||||
(std::numeric_limits<std::size_t>::max)())
|
||||
throw std::length_error{
|
||||
"Content-Length overflow"};
|
||||
body_.reserve(static_cast<
|
||||
std::size_t>(content_length));
|
||||
}
|
||||
|
||||
mutable_buffers_type
|
||||
prepare(std::size_t n)
|
||||
{
|
||||
body_.resize(len_ + n);
|
||||
return {&body_[len_], n};
|
||||
}
|
||||
|
||||
void
|
||||
commit(std::size_t n)
|
||||
{
|
||||
if(body_.size() > len_ + n)
|
||||
body_.resize(len_ + n);
|
||||
len_ = body_.size();
|
||||
}
|
||||
|
||||
void
|
||||
finish()
|
||||
{
|
||||
body_.resize(len_);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
#ifndef BEAST_WEBSOCKET_DETAIL_DECORATOR_HPP
|
||||
#define BEAST_WEBSOCKET_DETAIL_DECORATOR_HPP
|
||||
|
||||
#include <beast/http/empty_body.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/http/string_body.hpp>
|
||||
#include <beast/version.hpp>
|
||||
@ -19,9 +18,9 @@ namespace beast {
|
||||
namespace websocket {
|
||||
namespace detail {
|
||||
|
||||
using request_type = http::request<http::empty_body>;
|
||||
using request_type = http::request_header;
|
||||
|
||||
using response_type = http::response<http::string_body>;
|
||||
using response_type = http::response_header;
|
||||
|
||||
struct abstract_decorator
|
||||
{
|
||||
|
@ -17,7 +17,6 @@
|
||||
#include <beast/websocket/detail/mask.hpp>
|
||||
#include <beast/websocket/detail/pmd_extension.hpp>
|
||||
#include <beast/websocket/detail/utf8_checker.hpp>
|
||||
#include <beast/http/empty_body.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/http/string_body.hpp>
|
||||
#include <beast/zlib/deflate_stream.hpp>
|
||||
|
@ -9,7 +9,7 @@
|
||||
#define BEAST_WEBSOCKET_IMPL_ACCEPT_IPP
|
||||
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/http/parser_v1.hpp>
|
||||
#include <beast/http/header_parser.hpp>
|
||||
#include <beast/http/read.hpp>
|
||||
#include <beast/http/string_body.hpp>
|
||||
#include <beast/http/write.hpp>
|
||||
@ -35,13 +35,13 @@ class stream<NextLayer>::response_op
|
||||
{
|
||||
bool cont;
|
||||
stream<NextLayer>& ws;
|
||||
http::response<http::string_body> res;
|
||||
http::response_header res;
|
||||
error_code final_ec;
|
||||
int state = 0;
|
||||
|
||||
template<class Body, class Fields>
|
||||
template<class Fields>
|
||||
data(Handler&, stream<NextLayer>& ws_,
|
||||
http::request<Body, Fields> const& req,
|
||||
http::header<true, Fields> const& req,
|
||||
bool cont_)
|
||||
: cont(cont_)
|
||||
, ws(ws_)
|
||||
@ -151,7 +151,7 @@ class stream<NextLayer>::accept_op
|
||||
{
|
||||
bool cont;
|
||||
stream<NextLayer>& ws;
|
||||
http::request<http::string_body> req;
|
||||
http::header_parser<true, http::fields> p;
|
||||
int state = 0;
|
||||
|
||||
template<class Buffers>
|
||||
@ -190,8 +190,8 @@ public:
|
||||
(*this)(ec, 0);
|
||||
}
|
||||
|
||||
void operator()(error_code const& ec,
|
||||
std::size_t bytes_transferred, bool again = true);
|
||||
void operator()(error_code ec,
|
||||
std::size_t bytes_used, bool again = true);
|
||||
|
||||
friend
|
||||
void* asio_handler_allocate(
|
||||
@ -228,36 +228,39 @@ template<class NextLayer>
|
||||
template<class Handler>
|
||||
void
|
||||
stream<NextLayer>::accept_op<Handler>::
|
||||
operator()(error_code const& ec,
|
||||
std::size_t bytes_transferred, bool again)
|
||||
operator()(error_code ec,
|
||||
std::size_t bytes_used, bool again)
|
||||
{
|
||||
beast::detail::ignore_unused(bytes_transferred);
|
||||
auto& d = *d_;
|
||||
d.cont = d.cont || again;
|
||||
while(! ec && d.state != 99)
|
||||
if(ec)
|
||||
goto upcall;
|
||||
switch(d.state)
|
||||
{
|
||||
switch(d.state)
|
||||
{
|
||||
case 0:
|
||||
// read message
|
||||
d.state = 1;
|
||||
http::async_read(d.ws.next_layer(),
|
||||
d.ws.stream_.buffer(), d.req,
|
||||
std::move(*this));
|
||||
return;
|
||||
case 0:
|
||||
// read message
|
||||
d.state = 1;
|
||||
http::async_read_some(d.ws.next_layer(),
|
||||
d.ws.stream_.buffer(), d.p,
|
||||
std::move(*this));
|
||||
return;
|
||||
|
||||
// got message
|
||||
case 1:
|
||||
{
|
||||
// respond to request
|
||||
auto& ws = d.ws;
|
||||
auto req = std::move(d.req);
|
||||
response_op<Handler>{
|
||||
d_.release_handler(), ws, req, true};
|
||||
return;
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
BOOST_ASSERT(d.p.got_header());
|
||||
d.ws.stream_.buffer().consume(bytes_used);
|
||||
// Arguments from our state must be
|
||||
// moved to the stack before releasing
|
||||
// the handler.
|
||||
auto& ws = d.ws;
|
||||
auto m = d.p.release();
|
||||
response_op<Handler>{
|
||||
d_.release_handler(),
|
||||
ws, std::move(m), true};
|
||||
return;
|
||||
}
|
||||
}
|
||||
upcall:
|
||||
d_.invoke(ec);
|
||||
}
|
||||
|
||||
@ -295,11 +298,11 @@ async_accept(ConstBufferSequence const& bs, AcceptHandler&& handler)
|
||||
}
|
||||
|
||||
template<class NextLayer>
|
||||
template<class Body, class Fields, class AcceptHandler>
|
||||
template<class Fields, class AcceptHandler>
|
||||
typename async_completion<
|
||||
AcceptHandler, void(error_code)>::result_type
|
||||
stream<NextLayer>::
|
||||
async_accept(http::request<Body, Fields> const& req,
|
||||
async_accept(http::header<true, Fields> const& req,
|
||||
AcceptHandler&& handler)
|
||||
{
|
||||
static_assert(is_AsyncStream<next_layer_type>::value,
|
||||
@ -372,18 +375,21 @@ accept(ConstBufferSequence const& buffers, error_code& ec)
|
||||
stream_.buffer().commit(buffer_copy(
|
||||
stream_.buffer().prepare(
|
||||
buffer_size(buffers)), buffers));
|
||||
http::request<http::string_body> m;
|
||||
http::read(next_layer(), stream_.buffer(), m, ec);
|
||||
http::header_parser<true, http::fields> p;
|
||||
auto const bytes_used = http::read_some(
|
||||
next_layer(), stream_.buffer(), p, ec);
|
||||
if(ec)
|
||||
return;
|
||||
accept(m, ec);
|
||||
BOOST_ASSERT(p.got_header());
|
||||
stream_.buffer().consume(bytes_used);
|
||||
accept(p.get(), ec);
|
||||
}
|
||||
|
||||
template<class NextLayer>
|
||||
template<class Body, class Fields>
|
||||
template<class Fields>
|
||||
void
|
||||
stream<NextLayer>::
|
||||
accept(http::request<Body, Fields> const& request)
|
||||
accept(http::header<true, Fields> const& request)
|
||||
{
|
||||
static_assert(is_SyncStream<next_layer_type>::value,
|
||||
"SyncStream requirements not met");
|
||||
@ -394,10 +400,10 @@ accept(http::request<Body, Fields> const& request)
|
||||
}
|
||||
|
||||
template<class NextLayer>
|
||||
template<class Body, class Fields>
|
||||
template<class Fields>
|
||||
void
|
||||
stream<NextLayer>::
|
||||
accept(http::request<Body, Fields> const& req,
|
||||
accept(http::header<true, Fields> const& req,
|
||||
error_code& ec)
|
||||
{
|
||||
static_assert(is_SyncStream<next_layer_type>::value,
|
||||
@ -418,8 +424,6 @@ accept(http::request<Body, Fields> const& req,
|
||||
open(detail::role_type::server);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
} // websocket
|
||||
} // beast
|
||||
|
||||
|
@ -8,9 +8,9 @@
|
||||
#ifndef BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP
|
||||
#define BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP
|
||||
|
||||
#include <beast/http/empty_body.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/http/read.hpp>
|
||||
#include <beast/http/streambuf_body.hpp>
|
||||
#include <beast/http/write.hpp>
|
||||
#include <beast/core/handler_helpers.hpp>
|
||||
#include <beast/core/handler_ptr.hpp>
|
||||
@ -34,8 +34,8 @@ class stream<NextLayer>::handshake_op
|
||||
bool cont;
|
||||
stream<NextLayer>& ws;
|
||||
std::string key;
|
||||
http::request<http::empty_body> req;
|
||||
http::response<http::string_body> resp;
|
||||
http::request_header req;
|
||||
http::response<http::streambuf_body> res;
|
||||
int state = 0;
|
||||
|
||||
data(Handler& handler, stream<NextLayer>& ws_,
|
||||
@ -129,14 +129,14 @@ operator()(error_code ec, bool again)
|
||||
// read http response
|
||||
d.state = 2;
|
||||
http::async_read(d.ws.next_layer(),
|
||||
d.ws.stream_.buffer(), d.resp,
|
||||
d.ws.stream_.buffer(), d.res,
|
||||
std::move(*this));
|
||||
return;
|
||||
|
||||
// got response
|
||||
case 2:
|
||||
{
|
||||
d.ws.do_response(d.resp, d.key, ec);
|
||||
d.ws.do_response(d.res, d.key, ec);
|
||||
// call handler
|
||||
d.state = 99;
|
||||
break;
|
||||
@ -196,7 +196,7 @@ handshake(boost::string_ref const& host,
|
||||
}
|
||||
if(ec)
|
||||
return;
|
||||
http::response<http::string_body> res;
|
||||
http::response<http::streambuf_body> res;
|
||||
http::read(next_layer(), stream_.buffer(), res, ec);
|
||||
if(ec)
|
||||
return;
|
||||
|
@ -83,17 +83,18 @@ reset()
|
||||
}
|
||||
|
||||
template<class NextLayer>
|
||||
http::request<http::empty_body>
|
||||
http::request_header
|
||||
stream<NextLayer>::
|
||||
build_request(boost::string_ref const& host,
|
||||
boost::string_ref const& resource, std::string& key)
|
||||
{
|
||||
http::request<http::empty_body> req;
|
||||
http::request_header req;
|
||||
req.url = { resource.data(), resource.size() };
|
||||
req.version = 11;
|
||||
req.method = "GET";
|
||||
req.fields.insert("Host", host);
|
||||
req.fields.insert("Upgrade", "websocket");
|
||||
req.fields.insert("Connection", "upgrade");
|
||||
key = detail::make_sec_ws_key(maskgen_);
|
||||
req.fields.insert("Sec-WebSocket-Key", key);
|
||||
req.fields.insert("Sec-WebSocket-Version", "13");
|
||||
@ -113,15 +114,13 @@ build_request(boost::string_ref const& host,
|
||||
req.fields, config);
|
||||
}
|
||||
d_(req);
|
||||
http::prepare(req, http::connection::upgrade);
|
||||
return req;
|
||||
}
|
||||
|
||||
template<class NextLayer>
|
||||
template<class Body, class Fields>
|
||||
http::response<http::string_body>
|
||||
http::response_header
|
||||
stream<NextLayer>::
|
||||
build_response(http::request<Body, Fields> const& req)
|
||||
build_response(http::request_header const& req)
|
||||
{
|
||||
auto err =
|
||||
[&](std::string const& text)
|
||||
@ -170,7 +169,7 @@ build_response(http::request<Body, Fields> const& req)
|
||||
return res;
|
||||
}
|
||||
}
|
||||
http::response<http::string_body> res;
|
||||
http::response_header res;
|
||||
{
|
||||
detail::pmd_offer offer;
|
||||
detail::pmd_offer unused;
|
||||
@ -182,23 +181,22 @@ build_response(http::request<Body, Fields> const& req)
|
||||
res.reason = http::reason_string(res.status);
|
||||
res.version = req.version;
|
||||
res.fields.insert("Upgrade", "websocket");
|
||||
res.fields.insert("Connection", "upgrade");
|
||||
{
|
||||
auto const key =
|
||||
req.fields["Sec-WebSocket-Key"];
|
||||
res.fields.insert("Sec-WebSocket-Accept",
|
||||
detail::make_sec_ws_accept(key));
|
||||
}
|
||||
res.fields.replace("Server", "Beast.WSProto");
|
||||
res.fields.replace("Server", "Beast.WebSocket");
|
||||
d_(res);
|
||||
http::prepare(res, http::connection::upgrade);
|
||||
return res;
|
||||
}
|
||||
|
||||
template<class NextLayer>
|
||||
template<class Body, class Fields>
|
||||
void
|
||||
stream<NextLayer>::
|
||||
do_response(http::response<Body, Fields> const& res,
|
||||
do_response(http::response_header const& res,
|
||||
boost::string_ref const& key, error_code& ec)
|
||||
{
|
||||
// VFALCO Review these error codes
|
||||
|
@ -597,9 +597,9 @@ public:
|
||||
@throws system_error Thrown on failure.
|
||||
*/
|
||||
// VFALCO TODO This should also take a DynamicBuffer with any leftover bytes.
|
||||
template<class Body, class Fields>
|
||||
template<class Fields>
|
||||
void
|
||||
accept(http::request<Body, Fields> const& request);
|
||||
accept(http::header<true, Fields> const& request);
|
||||
|
||||
/** Respond to a WebSocket HTTP Upgrade request
|
||||
|
||||
@ -629,10 +629,9 @@ public:
|
||||
|
||||
@param ec Set to indicate what error occurred, if any.
|
||||
*/
|
||||
template<class Body, class Fields>
|
||||
template<class Fields>
|
||||
void
|
||||
accept(http::request<Body, Fields> const& request,
|
||||
error_code& ec);
|
||||
accept(http::header<true, Fields> const& request, error_code& ec);
|
||||
|
||||
/** Start responding to a WebSocket HTTP Upgrade request.
|
||||
|
||||
@ -675,15 +674,15 @@ public:
|
||||
this function. Invocation of the handler will be performed in a
|
||||
manner equivalent to using `boost::asio::io_service::post`.
|
||||
*/
|
||||
template<class Body, class Fields, class AcceptHandler>
|
||||
template<class Fields, class AcceptHandler>
|
||||
#if BEAST_DOXYGEN
|
||||
void_or_deduced
|
||||
#else
|
||||
typename async_completion<
|
||||
AcceptHandler, void(error_code)>::result_type
|
||||
#endif
|
||||
async_accept(http::request<Body, Fields> const& request,
|
||||
AcceptHandler&& handler);
|
||||
async_accept(http::header<true,
|
||||
Fields> const& request, AcceptHandler&& handler);
|
||||
|
||||
/** Send a HTTP WebSocket Upgrade request and receive the response.
|
||||
|
||||
@ -1674,18 +1673,16 @@ private:
|
||||
void
|
||||
reset();
|
||||
|
||||
http::request<http::empty_body>
|
||||
http::request_header
|
||||
build_request(boost::string_ref const& host,
|
||||
boost::string_ref const& resource,
|
||||
std::string& key);
|
||||
|
||||
template<class Body, class Fields>
|
||||
http::response<http::string_body>
|
||||
build_response(http::request<Body, Fields> const& req);
|
||||
http::response_header
|
||||
build_response(http::request_header const& req);
|
||||
|
||||
template<class Body, class Fields>
|
||||
void
|
||||
do_response(http::response<Body, Fields> const& resp,
|
||||
do_response(http::response_header const& resp,
|
||||
boost::string_ref const& key, error_code& ec);
|
||||
};
|
||||
|
||||
|
@ -136,7 +136,7 @@ public:
|
||||
of bytes needed to store the result of compressing a block of
|
||||
data based on the current compression level and strategy.
|
||||
|
||||
@param bytes The size of the uncompressed data.
|
||||
@param sourceLen The size of the uncompressed data.
|
||||
|
||||
@return The maximum number of resulting compressed bytes.
|
||||
*/
|
||||
|
@ -181,7 +181,7 @@ public:
|
||||
`Flush::trees` is used, and when `write` avoids the allocation of memory for a
|
||||
sliding window when `Flush::finsih` is used.
|
||||
|
||||
If a preset dictionary is needed after this call (see @ref dictionary below),
|
||||
If a preset dictionary is needed after this call,
|
||||
`write` sets `zs.adler` to the Adler-32 checksum of the dictionary chosen by
|
||||
the compressor and returns `error::need_dictionary`; otherwise it sets
|
||||
`zs.adler` to the Adler-32 checksum of all output produced so far (that is,
|
||||
|
11
test/Jamfile
11
test/Jamfile
@ -49,15 +49,14 @@ unit-test http-tests :
|
||||
../extras/beast/unit_test/main.cpp
|
||||
http/basic_dynabuf_body.cpp
|
||||
http/basic_fields.cpp
|
||||
http/basic_parser_v1.cpp
|
||||
http/basic_parser.cpp
|
||||
http/concepts.cpp
|
||||
http/empty_body.cpp
|
||||
http/design.cpp
|
||||
http/error.cpp
|
||||
http/fields.cpp
|
||||
http/header_parser_v1.cpp
|
||||
http/header_parser.cpp
|
||||
http/message.cpp
|
||||
http/parse.cpp
|
||||
http/parse_error.cpp
|
||||
http/parser_v1.cpp
|
||||
http/message_parser.cpp
|
||||
http/read.cpp
|
||||
http/reason.cpp
|
||||
http/rfc7230.cpp
|
||||
|
@ -8,19 +8,18 @@ add_executable (http-tests
|
||||
${BEAST_INCLUDES}
|
||||
${EXTRAS_INCLUDES}
|
||||
message_fuzz.hpp
|
||||
fail_parser.hpp
|
||||
test_parser.hpp
|
||||
../../extras/beast/unit_test/main.cpp
|
||||
basic_dynabuf_body.cpp
|
||||
basic_fields.cpp
|
||||
basic_parser_v1.cpp
|
||||
basic_parser.cpp
|
||||
concepts.cpp
|
||||
empty_body.cpp
|
||||
design.cpp
|
||||
error.cpp
|
||||
fields.cpp
|
||||
header_parser_v1.cpp
|
||||
header_parser.cpp
|
||||
message.cpp
|
||||
parse.cpp
|
||||
parse_error.cpp
|
||||
parser_v1.cpp
|
||||
message_parser.cpp
|
||||
read.cpp
|
||||
reason.cpp
|
||||
rfc7230.cpp
|
||||
|
960
test/http/basic_parser.cpp
Normal file
960
test/http/basic_parser.cpp
Normal file
@ -0,0 +1,960 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include <beast/http/basic_parser.hpp>
|
||||
|
||||
#include "test_parser.hpp"
|
||||
|
||||
#include <beast/core/buffer_cat.hpp>
|
||||
#include <beast/core/consuming_buffers.hpp>
|
||||
#include <beast/core/streambuf.hpp>
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
class basic_parser_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
enum parse_flag
|
||||
{
|
||||
chunked = 1,
|
||||
connection_keep_alive = 2,
|
||||
connection_close = 4,
|
||||
connection_upgrade = 8,
|
||||
upgrade = 16,
|
||||
};
|
||||
|
||||
class expect_version
|
||||
{
|
||||
suite& s_;
|
||||
int version_;
|
||||
|
||||
public:
|
||||
expect_version(suite& s, int version)
|
||||
: s_(s)
|
||||
, version_(version)
|
||||
{
|
||||
}
|
||||
|
||||
template<class Parser>
|
||||
void
|
||||
operator()(Parser const& p) const
|
||||
{
|
||||
s_.BEAST_EXPECT(p.version == version_);
|
||||
}
|
||||
};
|
||||
|
||||
class expect_status
|
||||
{
|
||||
suite& s_;
|
||||
int status_;
|
||||
|
||||
public:
|
||||
expect_status(suite& s, int status)
|
||||
: s_(s)
|
||||
, status_(status)
|
||||
{
|
||||
}
|
||||
|
||||
template<class Parser>
|
||||
void
|
||||
operator()(Parser const& p) const
|
||||
{
|
||||
s_.BEAST_EXPECT(p.status == status_);
|
||||
}
|
||||
};
|
||||
|
||||
class expect_flags
|
||||
{
|
||||
suite& s_;
|
||||
unsigned flags_;
|
||||
|
||||
public:
|
||||
expect_flags(suite& s, unsigned flags)
|
||||
: s_(s)
|
||||
, flags_(flags)
|
||||
{
|
||||
}
|
||||
|
||||
template<class Parser>
|
||||
void
|
||||
operator()(Parser const& p) const
|
||||
{
|
||||
if(flags_ & parse_flag::chunked)
|
||||
s_.BEAST_EXPECT(p.is_chunked());
|
||||
if(flags_ & parse_flag::connection_keep_alive)
|
||||
s_.BEAST_EXPECT(p.is_keep_alive());
|
||||
if(flags_ & parse_flag::connection_close)
|
||||
s_.BEAST_EXPECT(! p.is_keep_alive());
|
||||
if(flags_ & parse_flag::upgrade)
|
||||
s_.BEAST_EXPECT(! p.is_upgrade());
|
||||
}
|
||||
};
|
||||
|
||||
class expect_keepalive
|
||||
{
|
||||
suite& s_;
|
||||
bool v_;
|
||||
|
||||
public:
|
||||
expect_keepalive(suite& s, bool v)
|
||||
: s_(s)
|
||||
, v_(v)
|
||||
{
|
||||
}
|
||||
|
||||
template<class Parser>
|
||||
void
|
||||
operator()(Parser const& p) const
|
||||
{
|
||||
s_.BEAST_EXPECT(p.is_keep_alive() == v_);
|
||||
}
|
||||
};
|
||||
|
||||
class expect_body
|
||||
{
|
||||
suite& s_;
|
||||
std::string const& body_;
|
||||
|
||||
public:
|
||||
expect_body(expect_body&&) = default;
|
||||
|
||||
expect_body(suite& s, std::string const& v)
|
||||
: s_(s)
|
||||
, body_(v)
|
||||
{
|
||||
}
|
||||
|
||||
template<class Parser>
|
||||
void
|
||||
operator()(Parser const& p) const
|
||||
{
|
||||
s_.BEAST_EXPECT(p.body == body_);
|
||||
}
|
||||
};
|
||||
|
||||
template<std::size_t N>
|
||||
static
|
||||
boost::asio::const_buffers_1
|
||||
buf(char const (&s)[N])
|
||||
{
|
||||
return {s, N-1};
|
||||
}
|
||||
|
||||
template<
|
||||
bool isRequest, bool isDirect, class Derived>
|
||||
static
|
||||
std::size_t
|
||||
feed(boost::asio::const_buffer buffer,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser,
|
||||
error_code& ec)
|
||||
{
|
||||
using boost::asio::const_buffers_1;
|
||||
std::size_t used = 0;
|
||||
for(;;)
|
||||
{
|
||||
auto const n = parser.write(
|
||||
const_buffers_1{buffer}, ec);
|
||||
if(ec)
|
||||
return 0;
|
||||
if(n == 0)
|
||||
break;
|
||||
buffer = buffer + n;
|
||||
used += n;
|
||||
if(parser.is_complete())
|
||||
break;
|
||||
if(buffer_size(buffer) == 0)
|
||||
break;
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
template<class ConstBufferSequence,
|
||||
bool isRequest, bool isDirect, class Derived>
|
||||
static
|
||||
std::size_t
|
||||
feed(ConstBufferSequence const& buffers,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser,
|
||||
error_code& ec)
|
||||
{
|
||||
using boost::asio::buffer_size;
|
||||
consuming_buffers<
|
||||
ConstBufferSequence> cb{buffers};
|
||||
std::size_t used = 0;
|
||||
for(;;)
|
||||
{
|
||||
auto const n =
|
||||
parser.write(cb, ec);
|
||||
if(ec)
|
||||
return 0;
|
||||
if(n == 0)
|
||||
break;
|
||||
cb.consume(n);
|
||||
used += n;
|
||||
if(parser.is_complete())
|
||||
break;
|
||||
if(buffer_size(cb) == 0)
|
||||
break;
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
template<
|
||||
bool isRequest, bool isDirect, class Derived>
|
||||
static
|
||||
std::size_t
|
||||
feed(boost::asio::const_buffers_1 buffers,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser,
|
||||
error_code& ec)
|
||||
{
|
||||
return feed(*buffers.begin(), parser, ec);
|
||||
}
|
||||
|
||||
template<bool isRequest, class Pred>
|
||||
void
|
||||
good(boost::string_ref const& s,
|
||||
Pred const& pred, bool skipBody = false)
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
test_parser<isRequest> p;
|
||||
if(skipBody)
|
||||
p.skip_body();
|
||||
error_code ec;
|
||||
auto const n = feed(buffer(
|
||||
s.data(), s.size()), p, ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
return;
|
||||
if(! BEAST_EXPECT(n == s.size()))
|
||||
return;
|
||||
if(p.state() == parse_state::body_to_eof)
|
||||
p.write_eof(ec);
|
||||
if(BEAST_EXPECTS(! ec, ec.message()))
|
||||
pred(p);
|
||||
}
|
||||
|
||||
template<bool isRequest>
|
||||
void
|
||||
good(boost::string_ref const& s)
|
||||
{
|
||||
good<isRequest>(s,
|
||||
[](test_parser<isRequest> const&)
|
||||
{
|
||||
});
|
||||
}
|
||||
|
||||
template<bool isRequest>
|
||||
void
|
||||
bad(boost::string_ref const& s,
|
||||
error_code const& ev, bool skipBody = false)
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
test_parser<isRequest> p;
|
||||
if(skipBody)
|
||||
p.skip_body();
|
||||
error_code ec;
|
||||
feed(buffer(
|
||||
s.data(), s.size()), p, ec);
|
||||
if(! ec && ev)
|
||||
p.write_eof(ec);
|
||||
BEAST_EXPECTS(ec == ev, ec.message());
|
||||
}
|
||||
|
||||
void
|
||||
testFlatten()
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
{
|
||||
std::string const s =
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*";
|
||||
for(std::size_t i = 1;
|
||||
i < s.size() - 1; ++i)
|
||||
{
|
||||
auto const b1 =
|
||||
buffer(s.data(), i);
|
||||
auto const b2 = buffer(
|
||||
s.data() + i, s.size() - i);
|
||||
test_parser<true> p;
|
||||
error_code ec;
|
||||
feed(b1, p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
feed(buffer_cat(b1, b2), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
}
|
||||
}
|
||||
{
|
||||
std::string const s =
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"\r\n";
|
||||
for(std::size_t i = 1;
|
||||
i < s.size() - 1; ++i)
|
||||
{
|
||||
auto const b1 =
|
||||
buffer(s.data(), i);
|
||||
auto const b2 = buffer(
|
||||
s.data() + i, s.size() - i);
|
||||
test_parser<false> p;
|
||||
error_code ec;
|
||||
feed(b1, p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
ec = {};
|
||||
feed(buffer_cat(b1, b2), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
p.write_eof(ec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all callbacks are invoked
|
||||
void
|
||||
testCallbacks()
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
{
|
||||
test_parser<true> p;
|
||||
error_code ec;
|
||||
std::string const s =
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*";
|
||||
feed(buffer(s), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
BEAST_EXPECT(p.got_on_begin);
|
||||
BEAST_EXPECT(p.got_on_field);
|
||||
BEAST_EXPECT(p.got_on_header);
|
||||
BEAST_EXPECT(p.got_on_body);
|
||||
BEAST_EXPECT(! p.got_on_chunk);
|
||||
BEAST_EXPECT(p.got_on_complete);
|
||||
}
|
||||
{
|
||||
test_parser<false> p;
|
||||
error_code ec;
|
||||
std::string const s =
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Server: test\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*";
|
||||
feed(buffer(s), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
BEAST_EXPECT(p.got_on_begin);
|
||||
BEAST_EXPECT(p.got_on_field);
|
||||
BEAST_EXPECT(p.got_on_header);
|
||||
BEAST_EXPECT(p.got_on_body);
|
||||
BEAST_EXPECT(! p.got_on_chunk);
|
||||
BEAST_EXPECT(p.got_on_complete);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testRequestLine()
|
||||
{
|
||||
good<true>("GET /x HTTP/1.0\r\n\r\n");
|
||||
good<true>("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz / HTTP/1.0\r\n\r\n");
|
||||
good<true>("GET / HTTP/1.0\r\n\r\n", expect_version{*this, 10});
|
||||
good<true>("G / HTTP/1.1\r\n\r\n", expect_version{*this, 11});
|
||||
// VFALCO TODO various forms of good request-target (uri)
|
||||
good<true>("GET / HTTP/0.1\r\n\r\n", expect_version{*this, 1});
|
||||
good<true>("GET / HTTP/2.3\r\n\r\n", expect_version{*this, 23});
|
||||
good<true>("GET / HTTP/4.5\r\n\r\n", expect_version{*this, 45});
|
||||
good<true>("GET / HTTP/6.7\r\n\r\n", expect_version{*this, 67});
|
||||
good<true>("GET / HTTP/8.9\r\n\r\n", expect_version{*this, 89});
|
||||
|
||||
bad<true>("\tGET / HTTP/1.0\r\n" "\r\n", error::bad_method);
|
||||
bad<true>("GET\x01 / HTTP/1.0\r\n" "\r\n", error::bad_method);
|
||||
bad<true>("GET / HTTP/1.0\r\n" "\r\n", error::bad_path);
|
||||
bad<true>("GET \x01 HTTP/1.0\r\n" "\r\n", error::bad_path);
|
||||
bad<true>("GET /\x01 HTTP/1.0\r\n" "\r\n", error::bad_path);
|
||||
// VFALCO TODO various forms of bad request-target (uri)
|
||||
bad<true>("GET / HTTP/1.0\r\n" "\r\n", error::bad_version);
|
||||
bad<true>("GET / _TTP/1.0\r\n" "\r\n", error::bad_version);
|
||||
bad<true>("GET / H_TP/1.0\r\n" "\r\n", error::bad_version);
|
||||
bad<true>("GET / HT_P/1.0\r\n" "\r\n", error::bad_version);
|
||||
bad<true>("GET / HTT_/1.0\r\n" "\r\n", error::bad_version);
|
||||
bad<true>("GET / HTTP_1.0\r\n" "\r\n", error::bad_version);
|
||||
bad<true>("GET / HTTP/01.2\r\n" "\r\n", error::bad_version);
|
||||
bad<true>("GET / HTTP/3.45\r\n" "\r\n", error::bad_version);
|
||||
bad<true>("GET / HTTP/67.89\r\n" "\r\n", error::bad_version);
|
||||
bad<true>("GET / HTTP/x.0\r\n" "\r\n", error::bad_version);
|
||||
bad<true>("GET / HTTP/1.x\r\n" "\r\n", error::bad_version);
|
||||
bad<true>("GET / HTTP/1.0 \r\n" "\r\n", error::bad_version);
|
||||
bad<true>("GET / HTTP/1_0\r\n" "\r\n", error::bad_version);
|
||||
bad<true>("GET / HTTP/1.0\n\r\n" "\r\n", error::bad_version);
|
||||
bad<true>("GET / HTTP/1.0\n\r\r\n" "\r\n", error::bad_line_ending);
|
||||
bad<true>("GET / HTTP/1.0\r\r\n" "\r\n", error::bad_line_ending);
|
||||
}
|
||||
|
||||
void
|
||||
testStatusLine()
|
||||
{
|
||||
good<false>("HTTP/0.1 200 OK\r\n" "\r\n", expect_version{*this, 1});
|
||||
good<false>("HTTP/2.3 200 OK\r\n" "\r\n", expect_version{*this, 23});
|
||||
good<false>("HTTP/4.5 200 OK\r\n" "\r\n", expect_version{*this, 45});
|
||||
good<false>("HTTP/6.7 200 OK\r\n" "\r\n", expect_version{*this, 67});
|
||||
good<false>("HTTP/8.9 200 OK\r\n" "\r\n", expect_version{*this, 89});
|
||||
good<false>("HTTP/1.0 000 OK\r\n" "\r\n", expect_status{*this, 0});
|
||||
good<false>("HTTP/1.1 012 OK\r\n" "\r\n", expect_status{*this, 12});
|
||||
good<false>("HTTP/1.0 345 OK\r\n" "\r\n", expect_status{*this, 345});
|
||||
good<false>("HTTP/1.0 678 OK\r\n" "\r\n", expect_status{*this, 678});
|
||||
good<false>("HTTP/1.0 999 OK\r\n" "\r\n", expect_status{*this, 999});
|
||||
good<false>("HTTP/1.0 200 \tX\r\n" "\r\n", expect_version{*this, 10});
|
||||
good<false>("HTTP/1.1 200 X\r\n" "\r\n", expect_version{*this, 11});
|
||||
good<false>("HTTP/1.0 200 \r\n" "\r\n");
|
||||
good<false>("HTTP/1.1 200 X \r\n" "\r\n");
|
||||
good<false>("HTTP/1.1 200 X\t\r\n" "\r\n");
|
||||
good<false>("HTTP/1.1 200 \x80\x81...\xfe\xff\r\n\r\n");
|
||||
good<false>("HTTP/1.1 200 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\r\n\r\n");
|
||||
|
||||
bad<false>("\rHTTP/1.0 200 OK\r\n" "\r\n", error::bad_line_ending);
|
||||
bad<false>("\nHTTP/1.0 200 OK\r\n" "\r\n", error::bad_version);
|
||||
bad<false>(" HTTP/1.0 200 OK\r\n" "\r\n", error::bad_version);
|
||||
bad<false>("_TTP/1.0 200 OK\r\n" "\r\n", error::bad_version);
|
||||
bad<false>("H_TP/1.0 200 OK\r\n" "\r\n", error::bad_version);
|
||||
bad<false>("HT_P/1.0 200 OK\r\n" "\r\n", error::bad_version);
|
||||
bad<false>("HTT_/1.0 200 OK\r\n" "\r\n", error::bad_version);
|
||||
bad<false>("HTTP_1.0 200 OK\r\n" "\r\n", error::bad_version);
|
||||
bad<false>("HTTP/01.2 200 OK\r\n" "\r\n", error::bad_version);
|
||||
bad<false>("HTTP/3.45 200 OK\r\n" "\r\n", error::bad_version);
|
||||
bad<false>("HTTP/67.89 200 OK\r\n" "\r\n", error::bad_version);
|
||||
bad<false>("HTTP/x.0 200 OK\r\n" "\r\n", error::bad_version);
|
||||
bad<false>("HTTP/1.x 200 OK\r\n" "\r\n", error::bad_version);
|
||||
bad<false>("HTTP/1_0 200 OK\r\n" "\r\n", error::bad_version);
|
||||
bad<false>("HTTP/1.0 200 OK\r\n" "\r\n", error::bad_status);
|
||||
bad<false>("HTTP/1.0 0 OK\r\n" "\r\n", error::bad_status);
|
||||
bad<false>("HTTP/1.0 12 OK\r\n" "\r\n", error::bad_status);
|
||||
bad<false>("HTTP/1.0 3456 OK\r\n" "\r\n", error::bad_status);
|
||||
bad<false>("HTTP/1.0 200\r\n" "\r\n", error::bad_status);
|
||||
bad<false>("HTTP/1.0 200 \n\r\n" "\r\n", error::bad_reason);
|
||||
bad<false>("HTTP/1.0 200 \x01\r\n" "\r\n", error::bad_reason);
|
||||
bad<false>("HTTP/1.0 200 \x7f\r\n" "\r\n", error::bad_reason);
|
||||
bad<false>("HTTP/1.0 200 OK\n\r\n" "\r\n", error::bad_reason);
|
||||
bad<false>("HTTP/1.0 200 OK\r\r\n" "\r\n", error::bad_line_ending);
|
||||
}
|
||||
|
||||
void
|
||||
testFields()
|
||||
{
|
||||
auto const m =
|
||||
[](std::string const& s)
|
||||
{
|
||||
return "GET / HTTP/1.1\r\n" + s + "\r\n";
|
||||
};
|
||||
good<true>(m("f:\r\n"));
|
||||
good<true>(m("f: \r\n"));
|
||||
good<true>(m("f:\t\r\n"));
|
||||
good<true>(m("f: \t\r\n"));
|
||||
good<true>(m("f: v\r\n"));
|
||||
good<true>(m("f:\tv\r\n"));
|
||||
good<true>(m("f:\tv \r\n"));
|
||||
good<true>(m("f:\tv\t\r\n"));
|
||||
good<true>(m("f:\tv\t \r\n"));
|
||||
good<true>(m("f:\r\n \r\n"));
|
||||
good<true>(m("f:v\r\n"));
|
||||
good<true>(m("f: v\r\n u\r\n"));
|
||||
good<true>(m("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz: v\r\n"));
|
||||
good<true>(m("f: !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x80\x81...\xfe\xff\r\n"));
|
||||
|
||||
bad<true>(m(" f: v\r\n"), error::bad_field);
|
||||
bad<true>(m("\tf: v\r\n"), error::bad_field);
|
||||
bad<true>(m("f : v\r\n"), error::bad_field);
|
||||
bad<true>(m("f\t: v\r\n"), error::bad_field);
|
||||
bad<true>(m("f: \n\r\n"), error::bad_value);
|
||||
bad<true>(m("f: v\r \r\n"), error::bad_line_ending);
|
||||
bad<true>(m("f: \r v\r\n"), error::bad_line_ending);
|
||||
bad<true>("GET / HTTP/1.1\r\n\r \n\r\n\r\n",error::bad_line_ending);
|
||||
}
|
||||
|
||||
void
|
||||
testConnectionField()
|
||||
{
|
||||
auto const m =
|
||||
[](std::string const& s)
|
||||
{
|
||||
return "GET / HTTP/1.1\r\n" + s + "\r\n";
|
||||
};
|
||||
auto const cn =
|
||||
[](std::string const& s)
|
||||
{
|
||||
return "GET / HTTP/1.1\r\nConnection: " + s + "\r\n";
|
||||
};
|
||||
#if 0
|
||||
auto const keepalive =
|
||||
[&](bool v)
|
||||
{
|
||||
//return keepalive_f{*this, v};
|
||||
return true;
|
||||
};
|
||||
#endif
|
||||
|
||||
good<true>(cn("close\r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(cn(",close\r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(cn(" close\r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(cn("\tclose\r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(cn("close,\r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(cn("close\t\r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(cn("close\r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(cn(" ,\t,,close,, ,\t,,\r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(cn("\r\n close\r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(cn("close\r\n \r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(cn("any,close\r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(cn("close,any\r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(cn("any\r\n ,close\r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(cn("close\r\n ,any\r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(cn("close,close\r\n"), expect_flags{*this, parse_flag::connection_close}); // weird but allowed
|
||||
|
||||
good<true>(cn("keep-alive\r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
|
||||
good<true>(cn("keep-alive \r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
|
||||
good<true>(cn("keep-alive\t \r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
|
||||
good<true>(cn("keep-alive\t ,x\r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
|
||||
good<true>(cn("\r\n keep-alive \t\r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
|
||||
good<true>(cn("keep-alive \r\n \t \r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
|
||||
good<true>(cn("keep-alive\r\n \r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
|
||||
|
||||
good<true>(cn("upgrade\r\n"), expect_flags{*this, parse_flag::connection_upgrade});
|
||||
good<true>(cn("upgrade \r\n"), expect_flags{*this, parse_flag::connection_upgrade});
|
||||
good<true>(cn("upgrade\t \r\n"), expect_flags{*this, parse_flag::connection_upgrade});
|
||||
good<true>(cn("upgrade\t ,x\r\n"), expect_flags{*this, parse_flag::connection_upgrade});
|
||||
good<true>(cn("\r\n upgrade \t\r\n"), expect_flags{*this, parse_flag::connection_upgrade});
|
||||
good<true>(cn("upgrade \r\n \t \r\n"), expect_flags{*this, parse_flag::connection_upgrade});
|
||||
good<true>(cn("upgrade\r\n \r\n"), expect_flags{*this, parse_flag::connection_upgrade});
|
||||
|
||||
// VFALCO What's up with these?
|
||||
//good<true>(cn("close,keep-alive\r\n"), expect_flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive});
|
||||
good<true>(cn("upgrade,keep-alive\r\n"), expect_flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive});
|
||||
good<true>(cn("upgrade,\r\n keep-alive\r\n"), expect_flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive});
|
||||
//good<true>(cn("close,keep-alive,upgrade\r\n"), expect_flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive | parse_flag::connection_upgrade});
|
||||
|
||||
good<true>("GET / HTTP/1.1\r\n\r\n", expect_keepalive(*this, true));
|
||||
good<true>("GET / HTTP/1.0\r\n\r\n", expect_keepalive(*this, false));
|
||||
good<true>("GET / HTTP/1.0\r\n"
|
||||
"Connection: keep-alive\r\n\r\n", expect_keepalive(*this, true));
|
||||
good<true>("GET / HTTP/1.1\r\n"
|
||||
"Connection: close\r\n\r\n", expect_keepalive(*this, false));
|
||||
|
||||
good<true>(cn("x\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("x,y\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("x ,y\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("x\t,y\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("keep\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn(",keep\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn(" keep\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("\tnone\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("keep,\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("keep\t\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("keep\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn(" ,\t,,keep,, ,\t,,\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("\r\n keep\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("keep\r\n \r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("closet\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn(",closet\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn(" closet\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("\tcloset\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("closet,\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("closet\t\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("closet\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn(" ,\t,,closet,, ,\t,,\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("\r\n closet\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("closet\r\n \r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("clog\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("key\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("uptown\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("keeper\r\n \r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("keep-alively\r\n \r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("up\r\n \r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("upgrader\r\n \r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("none\r\n"), expect_flags{*this, 0});
|
||||
good<true>(cn("\r\n none\r\n"), expect_flags{*this, 0});
|
||||
|
||||
good<true>(m("ConnectioX: close\r\n"), expect_flags{*this, 0});
|
||||
good<true>(m("Condor: close\r\n"), expect_flags{*this, 0});
|
||||
good<true>(m("Connect: close\r\n"), expect_flags{*this, 0});
|
||||
good<true>(m("Connections: close\r\n"), expect_flags{*this, 0});
|
||||
|
||||
good<true>(m("Proxy-Connection: close\r\n"), expect_flags{*this, parse_flag::connection_close});
|
||||
good<true>(m("Proxy-Connection: keep-alive\r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
|
||||
good<true>(m("Proxy-Connection: upgrade\r\n"), expect_flags{*this, parse_flag::connection_upgrade});
|
||||
good<true>(m("Proxy-ConnectioX: none\r\n"), expect_flags{*this, 0});
|
||||
good<true>(m("Proxy-Connections: 1\r\n"), expect_flags{*this, 0});
|
||||
good<true>(m("Proxy-Connotes: see-also\r\n"), expect_flags{*this, 0});
|
||||
|
||||
bad<true>(cn("[\r\n"), error::bad_value);
|
||||
bad<true>(cn("close[\r\n"), error::bad_value);
|
||||
bad<true>(cn("close [\r\n"), error::bad_value);
|
||||
bad<true>(cn("close, upgrade [\r\n"), error::bad_value);
|
||||
bad<true>(cn("upgrade[]\r\n"), error::bad_value);
|
||||
bad<true>(cn("keep\r\n -alive\r\n"), error::bad_value);
|
||||
bad<true>(cn("keep-alive[\r\n"), error::bad_value);
|
||||
bad<true>(cn("keep-alive []\r\n"), error::bad_value);
|
||||
bad<true>(cn("no[ne]\r\n"), error::bad_value);
|
||||
}
|
||||
|
||||
void
|
||||
testContentLengthField()
|
||||
{
|
||||
auto const length =
|
||||
[&](std::string const& s, std::uint64_t v)
|
||||
{
|
||||
good<true>(s,
|
||||
[&](test_parser<true> const& p)
|
||||
{
|
||||
BEAST_EXPECT(p.content_length());
|
||||
BEAST_EXPECT(p.content_length() && *p.content_length() == v);
|
||||
}, true);
|
||||
};
|
||||
auto const c =
|
||||
[](std::string const& s)
|
||||
{
|
||||
return "GET / HTTP/1.1\r\nContent-Length: " + s + "\r\n";
|
||||
};
|
||||
auto const m =
|
||||
[](std::string const& s)
|
||||
{
|
||||
return "GET / HTTP/1.1\r\n" + s + "\r\n";
|
||||
};
|
||||
|
||||
length(c("0\r\n"), 0);
|
||||
length(c("00\r\n"), 0);
|
||||
length(c("1\r\n"), 1);
|
||||
length(c("01\r\n"), 1);
|
||||
length(c("9\r\n"), 9);
|
||||
length(c("123456789\r\n"), 123456789);
|
||||
length(c("42 \r\n"), 42);
|
||||
length(c("42\t\r\n"), 42);
|
||||
length(c("42 \t \r\n"), 42);
|
||||
|
||||
// VFALCO Investigate this failure
|
||||
//length(c("42\r\n \t \r\n"), 42);
|
||||
|
||||
good<true>(m("Content-LengtX: 0\r\n"), expect_flags{*this, 0});
|
||||
good<true>(m("Content-Lengths: many\r\n"), expect_flags{*this, 0});
|
||||
good<true>(m("Content: full\r\n"), expect_flags{*this, 0});
|
||||
|
||||
bad<true>(c("\r\n"), error::bad_content_length);
|
||||
bad<true>(c("18446744073709551616\r\n"), error::bad_content_length);
|
||||
bad<true>(c("0 0\r\n"), error::bad_content_length);
|
||||
bad<true>(c("0 1\r\n"), error::bad_content_length);
|
||||
bad<true>(c(",\r\n"), error::bad_content_length);
|
||||
bad<true>(c("0,\r\n"), error::bad_content_length);
|
||||
bad<true>(m(
|
||||
"Content-Length: 0\r\nContent-Length: 0\r\n"), error::bad_content_length);
|
||||
}
|
||||
|
||||
void
|
||||
testTransferEncodingField()
|
||||
{
|
||||
auto const m =
|
||||
[](std::string const& s)
|
||||
{
|
||||
return "GET / HTTP/1.1\r\n" + s + "\r\n";
|
||||
};
|
||||
auto const ce =
|
||||
[](std::string const& s)
|
||||
{
|
||||
return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n0\r\n\r\n";
|
||||
};
|
||||
auto const te =
|
||||
[](std::string const& s)
|
||||
{
|
||||
return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n";
|
||||
};
|
||||
good<true>(ce("chunked\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce("chunked \r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce("chunked\t\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce("chunked \t\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce(" chunked\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce("\tchunked\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce("chunked,\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce("chunked ,\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce("chunked, \r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce(",chunked\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce(", chunked\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce(" ,chunked\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce("chunked\r\n \r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce("\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce("\r\n ,chunked\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce("gzip, chunked\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce("gzip, chunked \r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
good<true>(ce("gzip, \r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
|
||||
// Technically invalid but beyond the parser's scope to detect
|
||||
// VFALCO Look into this
|
||||
//good<true>(ce("custom;key=\",chunked\r\n"), expect_flags{*this, parse_flag::chunked});
|
||||
|
||||
good<true>(te("gzip\r\n"), expect_flags{*this, 0});
|
||||
good<true>(te("chunked, gzip\r\n"), expect_flags{*this, 0});
|
||||
good<true>(te("chunked\r\n , gzip\r\n"), expect_flags{*this, 0});
|
||||
good<true>(te("chunked,\r\n gzip\r\n"), expect_flags{*this, 0});
|
||||
good<true>(te("chunked,\r\n ,gzip\r\n"), expect_flags{*this, 0});
|
||||
good<true>(te("bigchunked\r\n"), expect_flags{*this, 0});
|
||||
good<true>(te("chunk\r\n ked\r\n"), expect_flags{*this, 0});
|
||||
good<true>(te("bar\r\n ley chunked\r\n"), expect_flags{*this, 0});
|
||||
good<true>(te("barley\r\n chunked\r\n"), expect_flags{*this, 0});
|
||||
|
||||
good<true>(m("Transfer-EncodinX: none\r\n"), expect_flags{*this, 0});
|
||||
good<true>(m("Transfer-Encodings: 2\r\n"), expect_flags{*this, 0});
|
||||
good<true>(m("Transfer-Encoded: false\r\n"), expect_flags{*this, 0});
|
||||
|
||||
bad<false>(
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n", error::bad_transfer_encoding, true);
|
||||
}
|
||||
|
||||
void
|
||||
testUpgradeField()
|
||||
{
|
||||
auto const m =
|
||||
[](std::string const& s)
|
||||
{
|
||||
return "GET / HTTP/1.1\r\n" + s + "\r\n";
|
||||
};
|
||||
good<true>(m("Upgrade:\r\n"), expect_flags{*this, parse_flag::upgrade});
|
||||
good<true>(m("Upgrade: \r\n"), expect_flags{*this, parse_flag::upgrade});
|
||||
good<true>(m("Upgrade: yes\r\n"), expect_flags{*this, parse_flag::upgrade});
|
||||
|
||||
good<true>(m("Up: yes\r\n"), expect_flags{*this, 0});
|
||||
good<true>(m("UpgradX: none\r\n"), expect_flags{*this, 0});
|
||||
good<true>(m("Upgrades: 2\r\n"), expect_flags{*this, 0});
|
||||
good<true>(m("Upsample: 4x\r\n"), expect_flags{*this, 0});
|
||||
|
||||
good<true>(
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Connection: upgrade\r\n"
|
||||
"Upgrade: WebSocket\r\n"
|
||||
"\r\n",
|
||||
[&](test_parser<true> const& p)
|
||||
{
|
||||
BEAST_EXPECT(p.is_upgrade());
|
||||
});
|
||||
}
|
||||
|
||||
void testBody()
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
good<true>(
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"1",
|
||||
expect_body(*this, "1"));
|
||||
|
||||
good<false>(
|
||||
"HTTP/1.0 200 OK\r\n"
|
||||
"\r\n"
|
||||
"hello",
|
||||
expect_body(*this, "hello"));
|
||||
|
||||
// write the body in 3 pieces
|
||||
{
|
||||
error_code ec;
|
||||
test_parser<true> p;
|
||||
feed(buffer_cat(
|
||||
buf("GET / HTTP/1.1\r\n"
|
||||
"Content-Length: 10\r\n"
|
||||
"\r\n"),
|
||||
buf("12"),
|
||||
buf("345"),
|
||||
buf("67890")),
|
||||
p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
}
|
||||
|
||||
// request without Content-Length or
|
||||
// Transfer-Encoding: chunked has no body.
|
||||
{
|
||||
error_code ec;
|
||||
test_parser<true> p;
|
||||
feed(buf(
|
||||
"GET / HTTP/1.0\r\n"
|
||||
"\r\n"
|
||||
), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
}
|
||||
{
|
||||
error_code ec;
|
||||
test_parser<true> p;
|
||||
feed(buf(
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"\r\n"
|
||||
), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
}
|
||||
|
||||
// response without Content-Length or
|
||||
// Transfer-Encoding: chunked requires eof.
|
||||
{
|
||||
error_code ec;
|
||||
test_parser<false> p;
|
||||
feed(buf(
|
||||
"HTTP/1.0 200 OK\r\n"
|
||||
"\r\n"
|
||||
), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(! p.is_complete());
|
||||
BEAST_EXPECT(p.state() == parse_state::body_to_eof);
|
||||
feed(buf(
|
||||
"hello"
|
||||
), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(! p.is_complete());
|
||||
BEAST_EXPECT(p.state() == parse_state::body_to_eof);
|
||||
p.write_eof(ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
}
|
||||
|
||||
// 304 "Not Modified" response does not require eof
|
||||
{
|
||||
error_code ec;
|
||||
test_parser<false> p;
|
||||
feed(buf(
|
||||
"HTTP/1.0 304 Not Modified\r\n"
|
||||
"\r\n"
|
||||
), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
}
|
||||
|
||||
// Chunked response does not require eof
|
||||
{
|
||||
error_code ec;
|
||||
test_parser<false> p;
|
||||
feed(buf(
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(! p.is_complete());
|
||||
feed(buf(
|
||||
"0\r\n\r\n"
|
||||
), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
}
|
||||
|
||||
// restart: 1.0 assumes Connection: close
|
||||
{
|
||||
error_code ec;
|
||||
test_parser<true> p;
|
||||
feed(buf(
|
||||
"GET / HTTP/1.0\r\n"
|
||||
"\r\n"
|
||||
), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
}
|
||||
|
||||
// restart: 1.1 assumes Connection: keep-alive
|
||||
{
|
||||
error_code ec;
|
||||
test_parser<true> p;
|
||||
feed(buf(
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"\r\n"
|
||||
), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
}
|
||||
|
||||
bad<true>(
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n",
|
||||
error::partial_message);
|
||||
}
|
||||
|
||||
template<bool isRequest>
|
||||
void
|
||||
check_header(
|
||||
test_parser<isRequest> const& p)
|
||||
{
|
||||
BEAST_EXPECT(p.got_on_begin);
|
||||
BEAST_EXPECT(p.got_on_field);
|
||||
BEAST_EXPECT(p.got_on_header);
|
||||
BEAST_EXPECT(! p.got_on_body);
|
||||
BEAST_EXPECT(! p.got_on_chunk);
|
||||
BEAST_EXPECT(! p.got_on_end_body);
|
||||
BEAST_EXPECT(! p.got_on_complete);
|
||||
BEAST_EXPECT(p.state() != parse_state::header);
|
||||
}
|
||||
|
||||
void
|
||||
testSplit()
|
||||
{
|
||||
#if 0
|
||||
streambuf sb;
|
||||
sb <<
|
||||
"POST / HTTP/1.1\r\n"
|
||||
"Content-Length: 5\r\n"
|
||||
"\r\n"
|
||||
"*****";
|
||||
error_code ec;
|
||||
test_parser<true> p;
|
||||
p.pause();
|
||||
auto n = feed(sb.data(), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.got_on_begin);
|
||||
BEAST_EXPECT(p.got_on_field);
|
||||
BEAST_EXPECT(p.got_on_header);
|
||||
BEAST_EXPECT(! p.got_on_body);
|
||||
BEAST_EXPECT(! p.got_on_chunk);
|
||||
BEAST_EXPECT(! p.got_on_complete);
|
||||
BEAST_EXPECT(p.state() != parse_state::header);
|
||||
BEAST_EXPECT(! p.is_complete());
|
||||
BEAST_EXPECT(p.body.empty());
|
||||
sb.consume(n);
|
||||
p.resume();
|
||||
n = feed(sb.data(), p, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.got_on_begin);
|
||||
BEAST_EXPECT(p.got_on_field);
|
||||
BEAST_EXPECT(p.got_on_header);
|
||||
BEAST_EXPECT(p.got_on_body);
|
||||
BEAST_EXPECT(! p.got_on_chunk);
|
||||
BEAST_EXPECT(p.got_on_complete);
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
BEAST_EXPECT(p.body == "*****");
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testFlatten();
|
||||
testCallbacks();
|
||||
testRequestLine();
|
||||
testStatusLine();
|
||||
testFields();
|
||||
testConnectionField();
|
||||
testContentLengthField();
|
||||
testTransferEncodingField();
|
||||
testUpgradeField();
|
||||
testBody();
|
||||
testSplit();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(basic_parser,http,beast);
|
||||
|
||||
} // http
|
||||
} // beast
|
File diff suppressed because it is too large
Load Diff
534
test/http/design.cpp
Normal file
534
test/http/design.cpp
Normal file
@ -0,0 +1,534 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include <beast/core/flat_streambuf.hpp>
|
||||
#include <beast/core/prepare_buffers.hpp>
|
||||
#include <beast/http/chunk_encode.hpp>
|
||||
#include <beast/http/read.hpp>
|
||||
#include <beast/http/write.hpp>
|
||||
#include <beast/http/string_body.hpp>
|
||||
#include <beast/core/detail/clamp.hpp>
|
||||
#include <beast/test/string_istream.hpp>
|
||||
#include <beast/test/string_ostream.hpp>
|
||||
#include <beast/test/yield_to.hpp>
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
class design_test
|
||||
: public beast::unit_test::suite
|
||||
, public beast::test::enable_yield_to
|
||||
{
|
||||
public:
|
||||
//--------------------------------------------------------------------------
|
||||
/*
|
||||
Read a message with a direct Reader Body.
|
||||
*/
|
||||
struct direct_body
|
||||
{
|
||||
using value_type = std::string;
|
||||
|
||||
class reader
|
||||
{
|
||||
value_type& body_;
|
||||
std::size_t len_ = 0;
|
||||
|
||||
public:
|
||||
static bool constexpr is_direct = true;
|
||||
|
||||
using mutable_buffers_type =
|
||||
boost::asio::mutable_buffers_1;
|
||||
|
||||
template<bool isRequest, class Fields>
|
||||
explicit
|
||||
reader(message<isRequest, direct_body, Fields>& m)
|
||||
: body_(m.body)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
init()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
init(std::uint64_t content_length)
|
||||
{
|
||||
if(content_length >
|
||||
(std::numeric_limits<std::size_t>::max)())
|
||||
throw std::length_error(
|
||||
"Content-Length max exceeded");
|
||||
body_.reserve(static_cast<
|
||||
std::size_t>(content_length));
|
||||
}
|
||||
|
||||
mutable_buffers_type
|
||||
prepare(std::size_t n)
|
||||
{
|
||||
body_.resize(len_ + n);
|
||||
return {&body_[len_], n};
|
||||
}
|
||||
|
||||
void
|
||||
commit(std::size_t n)
|
||||
{
|
||||
if(body_.size() > len_ + n)
|
||||
body_.resize(len_ + n);
|
||||
len_ = body_.size();
|
||||
}
|
||||
|
||||
void
|
||||
finish()
|
||||
{
|
||||
body_.resize(len_);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
void
|
||||
testDirectBody()
|
||||
{
|
||||
// Content-Length
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*"
|
||||
};
|
||||
message<true, direct_body, fields> m;
|
||||
flat_streambuf sb{1024};
|
||||
read(is, sb, m);
|
||||
BEAST_EXPECT(m.body == "*");
|
||||
}
|
||||
|
||||
// end of file
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"\r\n" // 19 byte header
|
||||
"*"
|
||||
};
|
||||
message<false, direct_body, fields> m;
|
||||
flat_streambuf sb{20};
|
||||
read(is, sb, m);
|
||||
BEAST_EXPECT(m.body == "*");
|
||||
}
|
||||
|
||||
// chunked
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"1\r\n"
|
||||
"*\r\n"
|
||||
"0\r\n\r\n"
|
||||
};
|
||||
message<true, direct_body, fields> m;
|
||||
flat_streambuf sb{100};
|
||||
read(is, sb, m);
|
||||
BEAST_EXPECT(m.body == "*");
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/*
|
||||
Read a message with an indirect Reader Body.
|
||||
*/
|
||||
struct indirect_body
|
||||
{
|
||||
using value_type = std::string;
|
||||
|
||||
class reader
|
||||
{
|
||||
value_type& body_;
|
||||
|
||||
public:
|
||||
static bool constexpr is_direct = false;
|
||||
|
||||
using mutable_buffers_type =
|
||||
boost::asio::null_buffers;
|
||||
|
||||
template<bool isRequest, class Fields>
|
||||
explicit
|
||||
reader(message<isRequest, indirect_body, Fields>& m)
|
||||
: body_(m.body)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
init(error_code& ec)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
init(std::uint64_t content_length,
|
||||
error_code& ec)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
write(boost::string_ref const& s,
|
||||
error_code& ec)
|
||||
{
|
||||
body_.append(s.data(), s.size());
|
||||
}
|
||||
|
||||
void
|
||||
finish(error_code& ec)
|
||||
{
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
void
|
||||
testIndirectBody()
|
||||
{
|
||||
// Content-Length
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*"
|
||||
};
|
||||
message<true, indirect_body, fields> m;
|
||||
flat_streambuf sb{1024};
|
||||
read(is, sb, m);
|
||||
BEAST_EXPECT(m.body == "*");
|
||||
}
|
||||
|
||||
// end of file
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"\r\n" // 19 byte header
|
||||
"*"
|
||||
};
|
||||
message<false, indirect_body, fields> m;
|
||||
flat_streambuf sb{20};
|
||||
read(is, sb, m);
|
||||
BEAST_EXPECT(m.body == "*");
|
||||
}
|
||||
|
||||
|
||||
// chunked
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"1\r\n"
|
||||
"*\r\n"
|
||||
"0\r\n\r\n"
|
||||
};
|
||||
message<true, indirect_body, fields> m;
|
||||
flat_streambuf sb{1024};
|
||||
read(is, sb, m);
|
||||
BEAST_EXPECT(m.body == "*");
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/*
|
||||
Read a message header and manually read the body.
|
||||
*/
|
||||
void
|
||||
testManualBody()
|
||||
{
|
||||
// Content-Length
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Content-Length: 5\r\n"
|
||||
"\r\n" // 37 byte header
|
||||
"*****"
|
||||
};
|
||||
header_parser<true, fields> p;
|
||||
flat_streambuf sb{38};
|
||||
auto const bytes_used =
|
||||
read_some(is, sb, p);
|
||||
sb.consume(bytes_used);
|
||||
BEAST_EXPECT(p.size() == 5);
|
||||
BEAST_EXPECT(sb.size() < 5);
|
||||
sb.commit(boost::asio::read(
|
||||
is, sb.prepare(5 - sb.size())));
|
||||
BEAST_EXPECT(sb.size() == 5);
|
||||
}
|
||||
|
||||
// end of file
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"\r\n" // 19 byte header
|
||||
"*****"
|
||||
};
|
||||
header_parser<false, fields> p;
|
||||
flat_streambuf sb{20};
|
||||
auto const bytes_used =
|
||||
read_some(is, sb, p);
|
||||
sb.consume(bytes_used);
|
||||
BEAST_EXPECT(p.state() ==
|
||||
parse_state::body_to_eof);
|
||||
BEAST_EXPECT(sb.size() < 5);
|
||||
sb.commit(boost::asio::read(
|
||||
is, sb.prepare(5 - sb.size())));
|
||||
BEAST_EXPECT(sb.size() == 5);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/*
|
||||
Read a header, check for Expect: 100-continue,
|
||||
then conditionally read the body.
|
||||
*/
|
||||
void
|
||||
testExpect100Continue()
|
||||
{
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Expect: 100-continue\r\n"
|
||||
"Content-Length: 5\r\n"
|
||||
"\r\n"
|
||||
"*****"
|
||||
};
|
||||
|
||||
header_parser<true, fields> p;
|
||||
flat_streambuf sb{128};
|
||||
auto const bytes_used =
|
||||
read_some(is, sb, p);
|
||||
sb.consume(bytes_used);
|
||||
BEAST_EXPECT(p.got_header());
|
||||
BEAST_EXPECT(
|
||||
p.get().fields["Expect"] ==
|
||||
"100-continue");
|
||||
message_parser<
|
||||
true, string_body, fields> p1{
|
||||
std::move(p)};
|
||||
read(is, sb, p1);
|
||||
BEAST_EXPECT(
|
||||
p1.get().body == "*****");
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/*
|
||||
Efficiently relay a message from one stream to another
|
||||
*/
|
||||
template<
|
||||
bool isRequest,
|
||||
class SyncWriteStream,
|
||||
class DynamicBuffer,
|
||||
class SyncReadStream>
|
||||
void
|
||||
relay(
|
||||
SyncWriteStream& out,
|
||||
DynamicBuffer& sb,
|
||||
SyncReadStream& in)
|
||||
{
|
||||
flat_streambuf buffer{4096}; // 4K limit
|
||||
header_parser<isRequest, fields> parser;
|
||||
error_code ec;
|
||||
do
|
||||
{
|
||||
auto const state0 = parser.state();
|
||||
auto const bytes_used =
|
||||
read_some(in, buffer, parser, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
switch(state0)
|
||||
{
|
||||
case parse_state::header:
|
||||
{
|
||||
BEAST_EXPECT(parser.got_header());
|
||||
write(out, parser.get());
|
||||
break;
|
||||
}
|
||||
|
||||
case parse_state::chunk_header:
|
||||
{
|
||||
// inspect parser.chunk_extension() here
|
||||
if(parser.is_complete())
|
||||
boost::asio::write(out,
|
||||
chunk_encode_final());
|
||||
break;
|
||||
}
|
||||
|
||||
case parse_state::body:
|
||||
case parse_state::body_to_eof:
|
||||
case parse_state::chunk_body:
|
||||
{
|
||||
if(! parser.is_complete())
|
||||
{
|
||||
auto const body = parser.body();
|
||||
boost::asio::write(out, chunk_encode(
|
||||
false, boost::asio::buffer(
|
||||
body.data(), body.size())));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case parse_state::complete:
|
||||
break;
|
||||
}
|
||||
buffer.consume(bytes_used);
|
||||
}
|
||||
while(! parser.is_complete());
|
||||
}
|
||||
|
||||
void
|
||||
testRelay()
|
||||
{
|
||||
// Content-Length
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Content-Length: 5\r\n"
|
||||
"\r\n" // 37 byte header
|
||||
"*****",
|
||||
3 // max_read
|
||||
};
|
||||
test::string_ostream os{ios_};
|
||||
flat_streambuf sb{16};
|
||||
relay<true>(os, sb, is);
|
||||
}
|
||||
|
||||
// end of file
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"\r\n" // 19 byte header
|
||||
"*****",
|
||||
3 // max_read
|
||||
};
|
||||
test::string_ostream os{ios_};
|
||||
flat_streambuf sb{16};
|
||||
relay<false>(os, sb, is);
|
||||
}
|
||||
|
||||
// chunked
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"5;x;y=1;z=\"-\"\r\n*****\r\n"
|
||||
"3\r\n---\r\n"
|
||||
"1\r\n+\r\n"
|
||||
"0\r\n\r\n",
|
||||
2 // max_read
|
||||
};
|
||||
test::string_ostream os{ios_};
|
||||
flat_streambuf sb{16};
|
||||
relay<true>(os, sb, is);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/*
|
||||
Read the request header, then read the request body content using
|
||||
a fixed-size buffer, i.e. read the body in chunks of 4k for instance.
|
||||
The end of the body should be indicated somehow and chunk-encoding
|
||||
should be decoded by beast.
|
||||
*/
|
||||
template<bool isRequest,
|
||||
class SyncReadStream, class BodyCallback>
|
||||
void
|
||||
doFixedRead(SyncReadStream& stream, BodyCallback const& cb)
|
||||
{
|
||||
flat_streambuf buffer{4096}; // 4K limit
|
||||
header_parser<isRequest, fields> parser;
|
||||
std::size_t bytes_used;
|
||||
bytes_used = read_some(stream, buffer, parser);
|
||||
BEAST_EXPECT(parser.got_header());
|
||||
buffer.consume(bytes_used);
|
||||
do
|
||||
{
|
||||
bytes_used =
|
||||
read_some(stream, buffer, parser);
|
||||
if(! parser.body().empty())
|
||||
cb(parser.body());
|
||||
buffer.consume(bytes_used);
|
||||
}
|
||||
while(! parser.is_complete());
|
||||
}
|
||||
|
||||
struct bodyHandler
|
||||
{
|
||||
void
|
||||
operator()(boost::string_ref const& body) const
|
||||
{
|
||||
// called for each piece of the body,
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
testFixedRead()
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
using boost::asio::buffer_cast;
|
||||
using boost::asio::buffer_size;
|
||||
|
||||
// Content-Length
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*"
|
||||
};
|
||||
doFixedRead<true>(is, bodyHandler{});
|
||||
}
|
||||
|
||||
// end of file
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"\r\n" // 19 byte header
|
||||
"*****"
|
||||
};
|
||||
doFixedRead<false>(is, bodyHandler{});
|
||||
}
|
||||
|
||||
// chunked
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"5;x;y=1;z=\"-\"\r\n*****\r\n"
|
||||
"3\r\n---\r\n"
|
||||
"1\r\n+\r\n"
|
||||
"0\r\n\r\n",
|
||||
2 // max_read
|
||||
};
|
||||
doFixedRead<true>(is, bodyHandler{});
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
run()
|
||||
{
|
||||
testDirectBody();
|
||||
testIndirectBody();
|
||||
testManualBody();
|
||||
testExpect100Continue();
|
||||
testRelay();
|
||||
testFixedRead();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(design,http,beast);
|
||||
|
||||
} // http
|
||||
} // beast
|
@ -1,9 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include <beast/http/empty_body.hpp>
|
59
test/http/error.cpp
Normal file
59
test/http/error.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include <beast/http/error.hpp>
|
||||
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
class error_test : public unit_test::suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
check(char const* name, error ev)
|
||||
{
|
||||
auto const ec = make_error_code(ev);
|
||||
BEAST_EXPECT(std::string(ec.category().name()) == name);
|
||||
BEAST_EXPECT(! ec.message().empty());
|
||||
BEAST_EXPECT(std::addressof(ec.category()) ==
|
||||
std::addressof(detail::get_http_error_category()));
|
||||
BEAST_EXPECT(detail::get_http_error_category().equivalent(
|
||||
static_cast<std::underlying_type<error>::type>(ev),
|
||||
ec.category().default_error_condition(
|
||||
static_cast<std::underlying_type<error>::type>(ev))));
|
||||
BEAST_EXPECT(detail::get_http_error_category().equivalent(
|
||||
ec, static_cast<std::underlying_type<error>::type>(ev)));
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
check("http", error::end_of_stream);
|
||||
check("http", error::partial_message);
|
||||
check("http", error::buffer_overflow);
|
||||
check("http", error::bad_line_ending);
|
||||
check("http", error::bad_method);
|
||||
check("http", error::bad_path);
|
||||
check("http", error::bad_version);
|
||||
check("http", error::bad_status);
|
||||
check("http", error::bad_reason);
|
||||
check("http", error::bad_field);
|
||||
check("http", error::bad_value);
|
||||
check("http", error::bad_content_length);
|
||||
check("http", error::bad_transfer_encoding);
|
||||
check("http", error::bad_chunk);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(error,http,beast);
|
||||
|
||||
} // http
|
||||
} // beast
|
@ -1,120 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_TEST_FAIL_PARSER_HPP
|
||||
#define BEAST_HTTP_TEST_FAIL_PARSER_HPP
|
||||
|
||||
#include <beast/http/basic_parser_v1.hpp>
|
||||
#include <beast/test/fail_counter.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
template<bool isRequest>
|
||||
class fail_parser
|
||||
: public basic_parser_v1<isRequest, fail_parser<isRequest>>
|
||||
{
|
||||
test::fail_counter& fc_;
|
||||
std::uint64_t content_length_ = no_content_length;
|
||||
body_what body_rv_ = body_what::normal;
|
||||
|
||||
public:
|
||||
std::string body;
|
||||
|
||||
template<class... Args>
|
||||
explicit
|
||||
fail_parser(test::fail_counter& fc, Args&&... args)
|
||||
: fc_(fc)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_body_rv(body_what rv)
|
||||
{
|
||||
body_rv_ = rv;
|
||||
}
|
||||
|
||||
// valid on successful parse
|
||||
std::uint64_t
|
||||
content_length() const
|
||||
{
|
||||
return content_length_;
|
||||
}
|
||||
|
||||
void on_start(error_code& ec)
|
||||
{
|
||||
fc_.fail(ec);
|
||||
}
|
||||
|
||||
void on_method(boost::string_ref const&, error_code& ec)
|
||||
{
|
||||
fc_.fail(ec);
|
||||
}
|
||||
|
||||
void on_uri(boost::string_ref const&, error_code& ec)
|
||||
{
|
||||
fc_.fail(ec);
|
||||
}
|
||||
|
||||
void on_reason(boost::string_ref const&, error_code& ec)
|
||||
{
|
||||
fc_.fail(ec);
|
||||
}
|
||||
|
||||
void on_request(error_code& ec)
|
||||
{
|
||||
fc_.fail(ec);
|
||||
}
|
||||
|
||||
void on_response(error_code& ec)
|
||||
{
|
||||
fc_.fail(ec);
|
||||
}
|
||||
|
||||
void on_field(boost::string_ref const&, error_code& ec)
|
||||
{
|
||||
fc_.fail(ec);
|
||||
}
|
||||
|
||||
void on_value(boost::string_ref const&, error_code& ec)
|
||||
{
|
||||
fc_.fail(ec);
|
||||
}
|
||||
|
||||
void
|
||||
on_header(std::uint64_t content_length, error_code& ec)
|
||||
{
|
||||
if(fc_.fail(ec))
|
||||
return;
|
||||
}
|
||||
|
||||
body_what
|
||||
on_body_what(std::uint64_t content_length, error_code& ec)
|
||||
{
|
||||
if(fc_.fail(ec))
|
||||
return body_what::normal;
|
||||
content_length_ = content_length;
|
||||
return body_rv_;
|
||||
}
|
||||
|
||||
void on_body(boost::string_ref const& s, error_code& ec)
|
||||
{
|
||||
if(fc_.fail(ec))
|
||||
return;
|
||||
body.append(s.data(), s.size());
|
||||
}
|
||||
|
||||
void on_complete(error_code& ec)
|
||||
{
|
||||
fc_.fail(ec);
|
||||
}
|
||||
};
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
66
test/http/header_parser.cpp
Normal file
66
test/http/header_parser.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include <beast/http/header_parser.hpp>
|
||||
|
||||
#include <beast/core/flat_streambuf.hpp>
|
||||
#include <beast/http/read.hpp>
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
#include <beast/test/string_istream.hpp>
|
||||
#include <beast/test/yield_to.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
class header_parser_test
|
||||
: public beast::unit_test::suite
|
||||
, public test::enable_yield_to
|
||||
{
|
||||
public:
|
||||
void
|
||||
testParse()
|
||||
{
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"\r\n"
|
||||
};
|
||||
flat_streambuf db{1024};
|
||||
header_parser<true, fields> p;
|
||||
read_some(is, db, p);
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
}
|
||||
{
|
||||
test::string_istream is{ios_,
|
||||
"POST / HTTP/1.1\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*"
|
||||
};
|
||||
flat_streambuf db{1024};
|
||||
header_parser<true, fields> p;
|
||||
read_some(is, db, p);
|
||||
BEAST_EXPECT(! p.is_complete());
|
||||
BEAST_EXPECT(p.state() == parse_state::body);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testParse();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(header_parser,http,beast);
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
@ -1,90 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include <beast/http/header_parser_v1.hpp>
|
||||
|
||||
#include <beast/http/fields.hpp>
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
class header_parser_v1_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
void testParser()
|
||||
{
|
||||
{
|
||||
error_code ec;
|
||||
header_parser_v1<true, fields> p;
|
||||
BEAST_EXPECT(! p.complete());
|
||||
auto const n = p.write(boost::asio::buffer(
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"\r\n"
|
||||
), ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.complete());
|
||||
BEAST_EXPECT(n == 36);
|
||||
}
|
||||
{
|
||||
error_code ec;
|
||||
header_parser_v1<true, fields> p;
|
||||
BEAST_EXPECT(! p.complete());
|
||||
auto const n = p.write(boost::asio::buffer(
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"Content-Length: 5\r\n"
|
||||
"\r\n"
|
||||
"*****"
|
||||
), ec);
|
||||
BEAST_EXPECT(n == 55);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.complete());
|
||||
}
|
||||
{
|
||||
error_code ec;
|
||||
header_parser_v1<false, fields> p;
|
||||
BEAST_EXPECT(! p.complete());
|
||||
auto const n = p.write(boost::asio::buffer(
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Server: test\r\n"
|
||||
"\r\n"
|
||||
), ec);
|
||||
BEAST_EXPECT(n == 33);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.complete());
|
||||
}
|
||||
{
|
||||
error_code ec;
|
||||
header_parser_v1<false, fields> p;
|
||||
BEAST_EXPECT(! p.complete());
|
||||
auto const n = p.write(boost::asio::buffer(
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Server: test\r\n"
|
||||
"Content-Length: 5\r\n"
|
||||
"\r\n"
|
||||
"*****"
|
||||
), ec);
|
||||
BEAST_EXPECT(n == 52);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.complete());
|
||||
}
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
testParser();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(header_parser_v1,http,beast);
|
||||
|
||||
} // http
|
||||
} // beast
|
@ -8,7 +8,7 @@
|
||||
// Test that header file is self-contained.
|
||||
#include <beast/http/message.hpp>
|
||||
|
||||
#include <beast/http/empty_body.hpp>
|
||||
#include <beast/http/string_body.hpp>
|
||||
#include <beast/http/fields.hpp>
|
||||
#include <beast/http/string_body.hpp>
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
@ -195,7 +195,7 @@ public:
|
||||
void testFreeFunctions()
|
||||
{
|
||||
{
|
||||
request<empty_body> m;
|
||||
request<string_body> m;
|
||||
m.method = "GET";
|
||||
m.url = "/";
|
||||
m.version = 11;
|
||||
@ -213,7 +213,7 @@ public:
|
||||
|
||||
void testPrepare()
|
||||
{
|
||||
request<empty_body> m;
|
||||
request<string_body> m;
|
||||
m.version = 10;
|
||||
BEAST_EXPECT(! is_upgrade(m));
|
||||
m.fields.insert("Transfer-Encoding", "chunked");
|
||||
|
@ -9,7 +9,6 @@
|
||||
#define BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP
|
||||
|
||||
#include <beast/core/write_dynabuf.hpp>
|
||||
#include <beast/http/detail/basic_parser_v1.hpp>
|
||||
#include <beast/http/detail/rfc7230.hpp>
|
||||
#include <cstdint>
|
||||
#include <random>
|
||||
|
244
test/http/message_parser.cpp
Normal file
244
test/http/message_parser.cpp
Normal file
@ -0,0 +1,244 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include <beast/http/message_parser.hpp>
|
||||
|
||||
#include "test_parser.hpp"
|
||||
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
#include <beast/test/string_istream.hpp>
|
||||
#include <beast/test/string_ostream.hpp>
|
||||
#include <beast/test/yield_to.hpp>
|
||||
#include <beast/core/flat_streambuf.hpp>
|
||||
#include <beast/core/streambuf.hpp>
|
||||
#include <beast/http/header_parser.hpp>
|
||||
#include <beast/http/read.hpp>
|
||||
#include <beast/http/read.hpp>
|
||||
#include <beast/http/string_body.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
class message_parser_test
|
||||
: public beast::unit_test::suite
|
||||
, public beast::test::enable_yield_to
|
||||
{
|
||||
public:
|
||||
template<bool isRequest, class Pred>
|
||||
void
|
||||
testMatrix(std::string const& s, Pred const& pred)
|
||||
{
|
||||
beast::test::string_istream ss{get_io_service(), s};
|
||||
error_code ec;
|
||||
#if 0
|
||||
streambuf dynabuf;
|
||||
#else
|
||||
flat_streambuf dynabuf{1024};
|
||||
#endif
|
||||
message<isRequest, string_body, fields> m;
|
||||
read(ss, dynabuf, m, ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
return;
|
||||
pred(m);
|
||||
}
|
||||
|
||||
void
|
||||
testRead()
|
||||
{
|
||||
testMatrix<false>(
|
||||
"HTTP/1.0 200 OK\r\n"
|
||||
"Server: test\r\n"
|
||||
"\r\n"
|
||||
"*******",
|
||||
[&](message<false, string_body, fields> const& m)
|
||||
{
|
||||
BEAST_EXPECTS(m.body == "*******",
|
||||
"body='" + m.body + "'");
|
||||
}
|
||||
);
|
||||
testMatrix<false>(
|
||||
"HTTP/1.0 200 OK\r\n"
|
||||
"Server: test\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"5\r\n"
|
||||
"*****\r\n"
|
||||
"2;a;b=1;c=\"2\"\r\n"
|
||||
"--\r\n"
|
||||
"0;d;e=3;f=\"4\"\r\n"
|
||||
"Expires: never\r\n"
|
||||
"MD5-Fingerprint: -\r\n"
|
||||
"\r\n",
|
||||
[&](message<false, string_body, fields> const& m)
|
||||
{
|
||||
BEAST_EXPECT(m.body == "*****--");
|
||||
}
|
||||
);
|
||||
testMatrix<false>(
|
||||
"HTTP/1.0 200 OK\r\n"
|
||||
"Server: test\r\n"
|
||||
"Content-Length: 5\r\n"
|
||||
"\r\n"
|
||||
"*****",
|
||||
[&](message<false, string_body, fields> const& m)
|
||||
{
|
||||
BEAST_EXPECT(m.body == "*****");
|
||||
}
|
||||
);
|
||||
testMatrix<true>(
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"\r\n",
|
||||
[&](message<true, string_body, fields> const& m)
|
||||
{
|
||||
}
|
||||
);
|
||||
testMatrix<true>(
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"X: \t x \t \r\n"
|
||||
"\r\n",
|
||||
[&](message<true, string_body, fields> const& m)
|
||||
{
|
||||
BEAST_EXPECT(m.fields["X"] == "x");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
testParse()
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
{
|
||||
error_code ec;
|
||||
beast::test::string_istream is{
|
||||
get_io_service(),
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*"};
|
||||
flat_streambuf sb{1024};
|
||||
message_parser<true, string_body, fields> p;
|
||||
read(is, sb, p, ec);
|
||||
auto const& m = p.get();
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
BEAST_EXPECT(m.method == "GET");
|
||||
BEAST_EXPECT(m.url == "/");
|
||||
BEAST_EXPECT(m.version == 11);
|
||||
BEAST_EXPECT(m.fields["User-Agent"] == "test");
|
||||
BEAST_EXPECT(m.body == "*");
|
||||
}
|
||||
#if 0
|
||||
{
|
||||
// test partial parsing of final chunk
|
||||
// parse through the chunk body
|
||||
beast::test::string_istream is{
|
||||
get_io_service(), ""};
|
||||
streambuf sb;
|
||||
sb <<
|
||||
"PUT / HTTP/1.1\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"1\r\n"
|
||||
"*";
|
||||
error_code ec;
|
||||
message_parser<true, string_body, fields> p;
|
||||
read(is, sb, p, ec);
|
||||
BEAST_EXPECT(sb.size() == 0);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(!p.is_complete());
|
||||
BEAST_EXPECT(p.get().body == "*");
|
||||
sb << "\r\n0;d;e=3;f=\"4\"\r\n"
|
||||
"Expires: never\r\n"
|
||||
"MD5-Fingerprint: -\r\n";
|
||||
// incomplete parse, missing the final crlf
|
||||
BEAST_EXPECT(p.write(sb.data(), ec) == 0);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(!p.is_complete());
|
||||
sb << "\r\n"; // final crlf to end message
|
||||
BEAST_EXPECT(p.write(sb.data(), ec) == sb.size());
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
}
|
||||
{
|
||||
error_code ec;
|
||||
message_parser<false, string_body, fields> p;
|
||||
std::string const s =
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Server: test\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*";
|
||||
p.write(buffer(s), ec);
|
||||
auto const& m = p.get();
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
BEAST_EXPECT(m.status == 200);
|
||||
BEAST_EXPECT(m.reason == "OK");
|
||||
BEAST_EXPECT(m.version == 11);
|
||||
BEAST_EXPECT(m.fields["Server"] == "test");
|
||||
BEAST_EXPECT(m.body == "*");
|
||||
}
|
||||
// skip body
|
||||
{
|
||||
error_code ec;
|
||||
message_parser<false, string_body, fields> p;
|
||||
std::string const s =
|
||||
"HTTP/1.1 200 Connection Established\r\n"
|
||||
"Proxy-Agent: Zscaler/5.1\r\n"
|
||||
"\r\n";
|
||||
p.skip_body();
|
||||
p.write(buffer(s), ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p.is_complete());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
testExpect100Continue()
|
||||
{
|
||||
test::string_istream ss{ios_,
|
||||
"POST / HTTP/1.1\r\n"
|
||||
"Expect: 100-continue\r\n"
|
||||
"Content-Length: 5\r\n"
|
||||
"\r\n"
|
||||
"*****"};
|
||||
streambuf sb;
|
||||
error_code ec;
|
||||
header_parser<true, fields> p0;
|
||||
auto const bytes_used =
|
||||
read_some(ss, sb, p0, ec);
|
||||
sb.consume(bytes_used);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p0.state() != parse_state::header);
|
||||
BEAST_EXPECT(! p0.is_complete());
|
||||
message_parser<true,
|
||||
string_body, fields> p1{std::move(p0)};
|
||||
read(ss, sb, p1, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
BEAST_EXPECT(p1.get().body == "*****");
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testRead();
|
||||
testParse();
|
||||
testExpect100Continue();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(message_parser,http,beast);
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
@ -740,11 +740,6 @@ template<bool isRequest, class Body, class Fields>
|
||||
class nodejs_parser
|
||||
: public nodejs_basic_parser<nodejs_parser<isRequest, Body, Fields>>
|
||||
{
|
||||
using message_type =
|
||||
message<isRequest, Body, Fields>;
|
||||
|
||||
message_type m_;
|
||||
typename message_type::body_type::reader r_;
|
||||
bool started_ = false;
|
||||
|
||||
public:
|
||||
@ -752,7 +747,6 @@ public:
|
||||
|
||||
nodejs_parser()
|
||||
: http::nodejs_basic_parser<nodejs_parser>(isRequest)
|
||||
, r_(m_)
|
||||
{
|
||||
}
|
||||
|
||||
@ -763,12 +757,6 @@ public:
|
||||
return started_;
|
||||
}
|
||||
|
||||
message_type
|
||||
release()
|
||||
{
|
||||
return std::move(m_);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class http::nodejs_basic_parser<nodejs_parser>;
|
||||
|
||||
@ -781,7 +769,6 @@ private:
|
||||
void
|
||||
on_field(std::string const& field, std::string const& value)
|
||||
{
|
||||
m_.fields.insert(field, value);
|
||||
}
|
||||
|
||||
void
|
||||
@ -798,9 +785,6 @@ private:
|
||||
int major, int minor, bool /*keep_alive*/, bool /*upgrade*/,
|
||||
std::true_type)
|
||||
{
|
||||
m_.method = detail::method_to_string(method);
|
||||
m_.url = url;
|
||||
m_.version = major * 10 + minor;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -819,7 +803,7 @@ private:
|
||||
return on_request(method, url,
|
||||
major, minor, keep_alive, upgrade,
|
||||
std::integral_constant<
|
||||
bool, message_type::is_request>{});
|
||||
bool, isRequest>{});
|
||||
}
|
||||
|
||||
bool
|
||||
@ -828,10 +812,6 @@ private:
|
||||
std::true_type)
|
||||
{
|
||||
beast::detail::ignore_unused(keep_alive, upgrade);
|
||||
m_.status = status;
|
||||
m_.reason = reason;
|
||||
m_.version = major * 10 + minor;
|
||||
// VFALCO TODO return expect_body_
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -848,14 +828,13 @@ private:
|
||||
{
|
||||
return on_response(
|
||||
status, reason, major, minor, keep_alive, upgrade,
|
||||
std::integral_constant<bool, ! message_type::is_request>{});
|
||||
std::integral_constant<bool, ! isRequest>{});
|
||||
}
|
||||
|
||||
void
|
||||
on_body(void const* data,
|
||||
std::size_t size, error_code& ec)
|
||||
{
|
||||
r_.write(data, size, ec);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1,9 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include <beast/http/parse.hpp>
|
@ -1,60 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include <beast/http/parse_error.hpp>
|
||||
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
class parse_error_test : public unit_test::suite
|
||||
{
|
||||
public:
|
||||
void check(char const* name, parse_error ev)
|
||||
{
|
||||
auto const ec = make_error_code(ev);
|
||||
BEAST_EXPECT(std::string{ec.category().name()} == name);
|
||||
BEAST_EXPECT(! ec.message().empty());
|
||||
BEAST_EXPECT(std::addressof(ec.category()) ==
|
||||
std::addressof(detail::get_parse_error_category()));
|
||||
BEAST_EXPECT(detail::get_parse_error_category().equivalent(
|
||||
static_cast<std::underlying_type<parse_error>::type>(ev),
|
||||
ec.category().default_error_condition(
|
||||
static_cast<std::underlying_type<parse_error>::type>(ev))));
|
||||
BEAST_EXPECT(detail::get_parse_error_category().equivalent(
|
||||
ec, static_cast<std::underlying_type<parse_error>::type>(ev)));
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
check("http", parse_error::connection_closed);
|
||||
check("http", parse_error::bad_method);
|
||||
check("http", parse_error::bad_uri);
|
||||
check("http", parse_error::bad_version);
|
||||
check("http", parse_error::bad_crlf);
|
||||
check("http", parse_error::bad_status);
|
||||
check("http", parse_error::bad_reason);
|
||||
check("http", parse_error::bad_field);
|
||||
check("http", parse_error::bad_value);
|
||||
check("http", parse_error::bad_content_length);
|
||||
check("http", parse_error::illegal_content_length);
|
||||
check("http", parse_error::invalid_chunk_size);
|
||||
check("http", parse_error::invalid_ext_name);
|
||||
check("http", parse_error::invalid_ext_val);
|
||||
check("http", parse_error::header_too_big);
|
||||
check("http", parse_error::body_too_big);
|
||||
check("http", parse_error::short_read);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(parse_error,http,beast);
|
||||
|
||||
} // http
|
||||
} // beast
|
@ -9,6 +9,7 @@
|
||||
#include "message_fuzz.hpp"
|
||||
|
||||
#include <beast/http.hpp>
|
||||
#include <beast/core/consuming_buffers.hpp>
|
||||
#include <beast/core/streambuf.hpp>
|
||||
#include <beast/core/to_string.hpp>
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
@ -64,9 +65,39 @@ public:
|
||||
return v;
|
||||
}
|
||||
|
||||
template<class ConstBufferSequence,
|
||||
bool isRequest, bool isDirect, class Derived>
|
||||
static
|
||||
std::size_t
|
||||
feed(ConstBufferSequence const& buffers,
|
||||
basic_parser<isRequest, isDirect, Derived>& parser,
|
||||
error_code& ec)
|
||||
{
|
||||
using boost::asio::buffer_size;
|
||||
beast::consuming_buffers<
|
||||
ConstBufferSequence> cb{buffers};
|
||||
std::size_t used = 0;
|
||||
for(;;)
|
||||
{
|
||||
auto const n =
|
||||
parser.write(cb, ec);
|
||||
if(ec)
|
||||
return 0;
|
||||
if(n == 0)
|
||||
break;
|
||||
cb.consume(n);
|
||||
used += n;
|
||||
if(parser.is_complete())
|
||||
break;
|
||||
if(buffer_size(cb) == 0)
|
||||
break;
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
template<class Parser>
|
||||
void
|
||||
testParser(std::size_t repeat, corpus const& v)
|
||||
testParser1(std::size_t repeat, corpus const& v)
|
||||
{
|
||||
while(repeat--)
|
||||
for(auto const& sb : v)
|
||||
@ -79,6 +110,21 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
template<class Parser>
|
||||
void
|
||||
testParser2(std::size_t repeat, corpus const& v)
|
||||
{
|
||||
while(repeat--)
|
||||
for(auto const& sb : v)
|
||||
{
|
||||
Parser p;
|
||||
error_code ec;
|
||||
feed(sb.data(), p, ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
log << to_string(sb.data()) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Function>
|
||||
void
|
||||
timedTest(std::size_t repeat, std::string const& name, Function&& f)
|
||||
@ -98,21 +144,92 @@ public:
|
||||
}
|
||||
|
||||
template<bool isRequest>
|
||||
struct null_parser : basic_parser_v1<isRequest, null_parser<isRequest>>
|
||||
struct null_parser :
|
||||
basic_parser<isRequest, true,
|
||||
null_parser<isRequest>>
|
||||
{
|
||||
};
|
||||
|
||||
template<bool isRequest, class Body, class Fields>
|
||||
struct bench_parser : basic_parser<
|
||||
isRequest, false, bench_parser<isRequest, Body, Fields>>
|
||||
{
|
||||
using mutable_buffers_type =
|
||||
boost::asio::mutable_buffers_1;
|
||||
|
||||
void
|
||||
on_request(boost::string_ref const&,
|
||||
boost::string_ref const&,
|
||||
int, error_code&)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_response(int,
|
||||
boost::string_ref const&,
|
||||
int, error_code&)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_field(boost::string_ref const&,
|
||||
boost::string_ref const&,
|
||||
error_code&)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_header(error_code& ec)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_body(error_code& ec)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_body(std::uint64_t content_length,
|
||||
error_code& ec)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_data(boost::string_ref const&,
|
||||
error_code& ec)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_chunk(std::uint64_t,
|
||||
boost::string_ref const&,
|
||||
error_code&)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_body(boost::string_ref const&,
|
||||
error_code&)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_complete(error_code&)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
testSpeed()
|
||||
{
|
||||
static std::size_t constexpr Trials = 3;
|
||||
static std::size_t constexpr Repeat = 50;
|
||||
static std::size_t constexpr Repeat = 500;
|
||||
|
||||
log << "sizeof(request parser) == " <<
|
||||
sizeof(basic_parser_v1<true, null_parser<true>>) << '\n';
|
||||
sizeof(null_parser<true>) << '\n';
|
||||
|
||||
log << "sizeof(response parser) == " <<
|
||||
sizeof(basic_parser_v1<false, null_parser<true>>)<< '\n';
|
||||
sizeof(null_parser<false>)<< '\n';
|
||||
|
||||
testcase << "Parser speed test, " <<
|
||||
((Repeat * size_ + 512) / 1024) << "KB in " <<
|
||||
@ -121,20 +238,20 @@ public:
|
||||
timedTest(Trials, "nodejs_parser",
|
||||
[&]
|
||||
{
|
||||
testParser<nodejs_parser<
|
||||
testParser1<nodejs_parser<
|
||||
true, streambuf_body, fields>>(
|
||||
Repeat, creq_);
|
||||
testParser<nodejs_parser<
|
||||
testParser1<nodejs_parser<
|
||||
false, streambuf_body, fields>>(
|
||||
Repeat, cres_);
|
||||
});
|
||||
timedTest(Trials, "http::basic_parser_v1",
|
||||
timedTest(Trials, "http::basic_parser",
|
||||
[&]
|
||||
{
|
||||
testParser<parser_v1<
|
||||
true, streambuf_body, fields>>(
|
||||
testParser2<bench_parser<
|
||||
true, streambuf_body, fields> >(
|
||||
Repeat, creq_);
|
||||
testParser<parser_v1<
|
||||
testParser2<bench_parser<
|
||||
false, streambuf_body, fields>>(
|
||||
Repeat, cres_);
|
||||
});
|
||||
|
@ -1,160 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include <beast/http/parser_v1.hpp>
|
||||
|
||||
#include <beast/core/streambuf.hpp>
|
||||
#include <beast/http/fields.hpp>
|
||||
#include <beast/http/header_parser_v1.hpp>
|
||||
#include <beast/http/parse.hpp>
|
||||
#include <beast/http/string_body.hpp>
|
||||
#include <beast/test/string_istream.hpp>
|
||||
#include <beast/test/yield_to.hpp>
|
||||
#include <beast/unit_test/suite.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
class parser_v1_test
|
||||
: public beast::unit_test::suite
|
||||
, public test::enable_yield_to
|
||||
{
|
||||
public:
|
||||
void
|
||||
testParse()
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
{
|
||||
error_code ec;
|
||||
parser_v1<true, string_body,
|
||||
basic_fields<std::allocator<char>>> p;
|
||||
std::string const s =
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*";
|
||||
p.write(buffer(s), ec);
|
||||
BEAST_EXPECT(! ec);
|
||||
BEAST_EXPECT(p.complete());
|
||||
auto m = p.release();
|
||||
BEAST_EXPECT(m.method == "GET");
|
||||
BEAST_EXPECT(m.url == "/");
|
||||
BEAST_EXPECT(m.version == 11);
|
||||
BEAST_EXPECT(m.fields["User-Agent"] == "test");
|
||||
BEAST_EXPECT(m.body == "*");
|
||||
}
|
||||
{
|
||||
error_code ec;
|
||||
parser_v1<false, string_body,
|
||||
basic_fields<std::allocator<char>>> p;
|
||||
std::string const s =
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Server: test\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*";
|
||||
p.write(buffer(s), ec);
|
||||
BEAST_EXPECT(! ec);
|
||||
BEAST_EXPECT(p.complete());
|
||||
auto m = p.release();
|
||||
BEAST_EXPECT(m.status == 200);
|
||||
BEAST_EXPECT(m.reason == "OK");
|
||||
BEAST_EXPECT(m.version == 11);
|
||||
BEAST_EXPECT(m.fields["Server"] == "test");
|
||||
BEAST_EXPECT(m.body == "*");
|
||||
}
|
||||
// skip body
|
||||
{
|
||||
error_code ec;
|
||||
parser_v1<false, string_body, fields> p;
|
||||
std::string const s =
|
||||
"HTTP/1.1 200 Connection Established\r\n"
|
||||
"Proxy-Agent: Zscaler/5.1\r\n"
|
||||
"\r\n";
|
||||
p.set_option(skip_body{true});
|
||||
p.write(buffer(s), ec);
|
||||
BEAST_EXPECT(! ec);
|
||||
BEAST_EXPECT(p.complete());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithBody()
|
||||
{
|
||||
std::string const raw =
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*";
|
||||
test::string_istream ss{
|
||||
ios_, raw, raw.size() - 1};
|
||||
|
||||
streambuf rb;
|
||||
header_parser_v1<true, fields> p0;
|
||||
parse(ss, rb, p0);
|
||||
request_header const& reqh = p0.get();
|
||||
BEAST_EXPECT(reqh.method == "GET");
|
||||
BEAST_EXPECT(reqh.url == "/");
|
||||
BEAST_EXPECT(reqh.version == 11);
|
||||
BEAST_EXPECT(reqh.fields["User-Agent"] == "test");
|
||||
BEAST_EXPECT(reqh.fields["Content-Length"] == "1");
|
||||
parser_v1<true, string_body, fields> p =
|
||||
with_body<string_body>(p0);
|
||||
BEAST_EXPECT(p.get().method == "GET");
|
||||
BEAST_EXPECT(p.get().url == "/");
|
||||
BEAST_EXPECT(p.get().version == 11);
|
||||
BEAST_EXPECT(p.get().fields["User-Agent"] == "test");
|
||||
BEAST_EXPECT(p.get().fields["Content-Length"] == "1");
|
||||
parse(ss, rb, p);
|
||||
request<string_body, fields> req = p.release();
|
||||
BEAST_EXPECT(req.body == "*");
|
||||
}
|
||||
|
||||
void
|
||||
testRegressions()
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
|
||||
// consecutive empty header values
|
||||
{
|
||||
error_code ec;
|
||||
parser_v1<true, string_body, fields> p;
|
||||
std::string const s =
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"X1:\r\n"
|
||||
"X2:\r\n"
|
||||
"X3:x\r\n"
|
||||
"\r\n";
|
||||
p.write(buffer(s), ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
return;
|
||||
BEAST_EXPECT(p.complete());
|
||||
auto const msg = p.release();
|
||||
BEAST_EXPECT(msg.fields.exists("X1"));
|
||||
BEAST_EXPECT(msg.fields["X1"] == "");
|
||||
BEAST_EXPECT(msg.fields.exists("X2"));
|
||||
BEAST_EXPECT(msg.fields["X2"] == "");
|
||||
BEAST_EXPECT(msg.fields.exists("X3"));
|
||||
BEAST_EXPECT(msg.fields["X3"] == "x");
|
||||
}
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
testParse();
|
||||
testWithBody();
|
||||
testRegressions();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(parser_v1,http,beast);
|
||||
|
||||
} // http
|
||||
} // beast
|
@ -8,10 +8,12 @@
|
||||
// Test that header file is self-contained.
|
||||
#include <beast/http/read.hpp>
|
||||
|
||||
#include "fail_parser.hpp"
|
||||
#include "test_parser.hpp"
|
||||
|
||||
#include <beast/http/fields.hpp>
|
||||
#include <beast/http/header_parser.hpp>
|
||||
#include <beast/http/streambuf_body.hpp>
|
||||
#include <beast/http/string_body.hpp>
|
||||
#include <beast/test/fail_stream.hpp>
|
||||
#include <beast/test/string_istream.hpp>
|
||||
#include <beast/test/yield_to.hpp>
|
||||
@ -27,62 +29,9 @@ 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
|
||||
{
|
||||
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(char const* s, yield_context do_yield)
|
||||
void
|
||||
failMatrix(char const* s, yield_context do_yield)
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
using boost::asio::buffer_copy;
|
||||
@ -97,9 +46,9 @@ public:
|
||||
test::fail_counter fc(n);
|
||||
test::fail_stream<
|
||||
test::string_istream> fs{fc, ios_, ""};
|
||||
fail_parser<isRequest> p(fc);
|
||||
test_parser<isRequest> p(fc);
|
||||
error_code ec;
|
||||
parse(fs, sb, p, ec);
|
||||
read(fs, sb, p, ec);
|
||||
if(! ec)
|
||||
break;
|
||||
}
|
||||
@ -113,9 +62,9 @@ public:
|
||||
test::fail_counter fc(n);
|
||||
test::fail_stream<test::string_istream> fs{
|
||||
fc, ios_, std::string{s + pre, len - pre}};
|
||||
fail_parser<isRequest> p(fc);
|
||||
test_parser<isRequest> p(fc);
|
||||
error_code ec;
|
||||
parse(fs, sb, p, ec);
|
||||
read(fs, sb, p, ec);
|
||||
if(! ec)
|
||||
break;
|
||||
}
|
||||
@ -128,9 +77,9 @@ public:
|
||||
test::fail_counter fc(n);
|
||||
test::fail_stream<
|
||||
test::string_istream> fs{fc, ios_, ""};
|
||||
fail_parser<isRequest> p(fc);
|
||||
test_parser<isRequest> p(fc);
|
||||
error_code ec;
|
||||
async_parse(fs, sb, p, do_yield[ec]);
|
||||
async_read(fs, sb, p, do_yield[ec]);
|
||||
if(! ec)
|
||||
break;
|
||||
}
|
||||
@ -144,23 +93,9 @@ public:
|
||||
test::fail_counter fc(n);
|
||||
test::fail_stream<test::string_istream> fs{
|
||||
fc, ios_, std::string{s + pre, len - pre}};
|
||||
fail_parser<isRequest> p(fc);
|
||||
test_parser<isRequest> p(fc);
|
||||
error_code ec;
|
||||
async_parse(fs, sb, p, do_yield[ec]);
|
||||
if(! ec)
|
||||
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_istream ss{ios_, s};
|
||||
parser_v1<isRequest, fail_body, fields> p{fc};
|
||||
error_code ec;
|
||||
parse(ss, sb, p, ec);
|
||||
async_read(fs, sb, p, do_yield[ec]);
|
||||
if(! ec)
|
||||
break;
|
||||
}
|
||||
@ -173,8 +108,8 @@ public:
|
||||
{
|
||||
streambuf sb;
|
||||
test::string_istream ss(ios_, "GET / X");
|
||||
parser_v1<true, streambuf_body, fields> p;
|
||||
parse(ss, sb, p);
|
||||
message_parser<true, streambuf_body, fields> p;
|
||||
read(ss, sb, p);
|
||||
fail();
|
||||
}
|
||||
catch(std::exception const&)
|
||||
@ -245,52 +180,6 @@ public:
|
||||
failMatrix<false>(res[i], do_yield);
|
||||
}
|
||||
|
||||
void testReadHeaders(yield_context do_yield)
|
||||
{
|
||||
static std::size_t constexpr limit = 100;
|
||||
std::size_t n;
|
||||
|
||||
for(n = 0; n < limit; ++n)
|
||||
{
|
||||
test::fail_stream<test::string_istream> fs{n, ios_,
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Host: localhost\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"Content-Length: 5\r\n"
|
||||
"\r\n"
|
||||
};
|
||||
request_header m;
|
||||
try
|
||||
{
|
||||
streambuf sb;
|
||||
read(fs, sb, m);
|
||||
break;
|
||||
}
|
||||
catch(std::exception const&)
|
||||
{
|
||||
}
|
||||
}
|
||||
BEAST_EXPECT(n < limit);
|
||||
|
||||
for(n = 0; n < limit; ++n)
|
||||
{
|
||||
test::fail_stream<test::string_istream> fs(n, ios_,
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Host: localhost\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"Content-Length: 0\r\n"
|
||||
"\r\n"
|
||||
);
|
||||
request_header m;
|
||||
error_code ec;
|
||||
streambuf sb;
|
||||
async_read(fs, sb, m, do_yield[ec]);
|
||||
if(! ec)
|
||||
break;
|
||||
}
|
||||
BEAST_EXPECT(n < limit);
|
||||
}
|
||||
|
||||
void testRead(yield_context do_yield)
|
||||
{
|
||||
static std::size_t constexpr limit = 100;
|
||||
@ -355,22 +244,23 @@ public:
|
||||
BEAST_EXPECT(n < limit);
|
||||
}
|
||||
|
||||
void testEof(yield_context do_yield)
|
||||
void
|
||||
testEof(yield_context do_yield)
|
||||
{
|
||||
{
|
||||
streambuf sb;
|
||||
test::string_istream ss(ios_, "");
|
||||
parser_v1<true, streambuf_body, fields> p;
|
||||
message_parser<true, streambuf_body, fields> p;
|
||||
error_code ec;
|
||||
parse(ss, sb, p, ec);
|
||||
read(ss, sb, p, ec);
|
||||
BEAST_EXPECT(ec == boost::asio::error::eof);
|
||||
}
|
||||
{
|
||||
streambuf sb;
|
||||
test::string_istream ss(ios_, "");
|
||||
parser_v1<true, streambuf_body, fields> p;
|
||||
message_parser<true, streambuf_body, fields> p;
|
||||
error_code ec;
|
||||
async_parse(ss, sb, p, do_yield[ec]);
|
||||
async_read(ss, sb, p, do_yield[ec]);
|
||||
BEAST_EXPECT(ec == boost::asio::error::eof);
|
||||
}
|
||||
}
|
||||
@ -424,12 +314,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void run() override
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testThrow();
|
||||
|
||||
yield_to(&read_test::testFailures, this);
|
||||
yield_to(&read_test::testReadHeaders, this);
|
||||
yield_to(&read_test::testRead, this);
|
||||
yield_to(&read_test::testEof, this);
|
||||
|
||||
|
@ -13,9 +13,10 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <beast/core/detail/empty_base_optimization.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
namespace test {
|
||||
|
||||
class rfc7230_test : public beast::unit_test::suite
|
||||
{
|
||||
@ -135,9 +136,9 @@ public:
|
||||
BEAST_EXPECTS(got == good, fmt(got));
|
||||
};
|
||||
/*
|
||||
ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] )
|
||||
ext = token param-list
|
||||
param-list = *( OWS ";" OWS param )
|
||||
ext-basic_parsed_list = *( "," OWS ) ext *( OWS "," [ OWS ext ] )
|
||||
ext = token param-basic_parsed_list
|
||||
param-basic_parsed_list = *( OWS ";" OWS param )
|
||||
param = token OWS "=" OWS ( token / quoted-string )
|
||||
*/
|
||||
cs(",", "");
|
||||
@ -237,17 +238,120 @@ public:
|
||||
cs("x y", "x");
|
||||
}
|
||||
|
||||
template<class Policy>
|
||||
static
|
||||
std::vector<std::string>
|
||||
to_vector(boost::string_ref const& in)
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
detail::basic_parsed_list<Policy> list{in};
|
||||
for(auto const& s :
|
||||
detail::basic_parsed_list<Policy>{in})
|
||||
v.emplace_back(s.data(), s.size());
|
||||
return v;
|
||||
}
|
||||
|
||||
template<class Policy>
|
||||
void
|
||||
validate(boost::string_ref const& in,
|
||||
std::vector<std::string> const& v)
|
||||
{
|
||||
BEAST_EXPECT(to_vector<Policy>(in) == v);
|
||||
}
|
||||
|
||||
template<class Policy>
|
||||
void
|
||||
good(boost::string_ref const& in)
|
||||
{
|
||||
BEAST_EXPECT(validate_list(
|
||||
detail::basic_parsed_list<Policy>{in}));
|
||||
}
|
||||
|
||||
template<class Policy>
|
||||
void
|
||||
good(boost::string_ref const& in,
|
||||
std::vector<std::string> const& v)
|
||||
{
|
||||
BEAST_EXPECT(validate_list(
|
||||
detail::basic_parsed_list<Policy>{in}));
|
||||
validate<Policy>(in, v);
|
||||
}
|
||||
|
||||
template<class Policy>
|
||||
void
|
||||
bad(boost::string_ref const& in)
|
||||
{
|
||||
BEAST_EXPECT(! validate_list(
|
||||
detail::basic_parsed_list<Policy>{in}));
|
||||
}
|
||||
|
||||
void
|
||||
testOptTokenList()
|
||||
{
|
||||
/*
|
||||
#token = [ ( "," / token ) *( OWS "," [ OWS token ] ) ]
|
||||
*/
|
||||
using type = detail::opt_token_list_policy;
|
||||
|
||||
good<type>("", {});
|
||||
good<type>(" ", {});
|
||||
good<type>("\t", {});
|
||||
good<type>(" \t", {});
|
||||
good<type>(",", {});
|
||||
good<type>(",,", {});
|
||||
good<type>(", ,", {});
|
||||
good<type>(",\t,", {});
|
||||
good<type>(", \t,", {});
|
||||
good<type>(", \t, ", {});
|
||||
good<type>(", \t,\t", {});
|
||||
good<type>(", \t, \t", {});
|
||||
|
||||
good<type>("x", {"x"});
|
||||
good<type>(" x", {"x"});
|
||||
good<type>("x,,", {"x"});
|
||||
good<type>("x, ,", {"x"});
|
||||
good<type>("x,, ", {"x"});
|
||||
good<type>("x,,,", {"x"});
|
||||
|
||||
good<type>("x,y", {"x","y"});
|
||||
good<type>("x ,y", {"x","y"});
|
||||
good<type>("x\t,y", {"x","y"});
|
||||
good<type>("x \t,y", {"x","y"});
|
||||
good<type>(" x,y", {"x","y"});
|
||||
good<type>(" x,y ", {"x","y"});
|
||||
good<type>(",x,y", {"x","y"});
|
||||
good<type>("x,y,", {"x","y"});
|
||||
good<type>(",,x,y", {"x","y"});
|
||||
good<type>(",x,,y", {"x","y"});
|
||||
good<type>(",x,y,", {"x","y"});
|
||||
good<type>("x ,, y", {"x","y"});
|
||||
good<type>("x , ,y", {"x","y"});
|
||||
|
||||
good<type>("x,y,z", {"x","y","z"});
|
||||
|
||||
bad<type>("(");
|
||||
bad<type>("x(");
|
||||
bad<type>("(x");
|
||||
bad<type>(",(");
|
||||
bad<type>("(,");
|
||||
bad<type>("x,(");
|
||||
bad<type>("(,x");
|
||||
bad<type>("x y");
|
||||
}
|
||||
|
||||
void
|
||||
run()
|
||||
{
|
||||
testOptTokenList();
|
||||
#if 0
|
||||
testParamList();
|
||||
testExtList();
|
||||
testTokenList();
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(rfc7230,http,beast);
|
||||
|
||||
} // test
|
||||
} // http
|
||||
} // beast
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
#include <beast/core/to_string.hpp>
|
||||
#include <beast/http/fields.hpp>
|
||||
#include <beast/http/parser_v1.hpp>
|
||||
#include <beast/http/message_parser.hpp>
|
||||
#include <beast/http/read.hpp>
|
||||
#include <beast/http/write.hpp>
|
||||
#include <beast/test/string_istream.hpp>
|
||||
@ -34,11 +34,12 @@ public:
|
||||
"\r\n"
|
||||
"xyz";
|
||||
test::string_istream ss(ios_, s);
|
||||
parser_v1<false, streambuf_body, fields> p;
|
||||
message_parser<false, streambuf_body, fields> p;
|
||||
streambuf sb;
|
||||
parse(ss, sb, p);
|
||||
BEAST_EXPECT(to_string(p.get().body.data()) == "xyz");
|
||||
BEAST_EXPECT(boost::lexical_cast<std::string>(p.get()) == s);
|
||||
read(ss, sb, p);
|
||||
auto const& m = p.get();
|
||||
BEAST_EXPECT(to_string(m.body.data()) == "xyz");
|
||||
BEAST_EXPECT(boost::lexical_cast<std::string>(m) == s);
|
||||
}
|
||||
};
|
||||
|
||||
|
147
test/http/test_parser.hpp
Normal file
147
test/http/test_parser.hpp
Normal file
@ -0,0 +1,147 @@
|
||||
//
|
||||
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_TEST_PARSER_HPP
|
||||
#define BEAST_HTTP_TEST_PARSER_HPP
|
||||
|
||||
#include <beast/http/basic_parser.hpp>
|
||||
#include <beast/test/fail_counter.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
template<bool isRequest>
|
||||
class test_parser
|
||||
: public basic_parser<isRequest, false,
|
||||
test_parser<isRequest>>
|
||||
{
|
||||
test::fail_counter* fc_ = nullptr;
|
||||
|
||||
public:
|
||||
using mutable_buffers_type =
|
||||
boost::asio::mutable_buffers_1;
|
||||
|
||||
int status = 0;
|
||||
int version = 0;
|
||||
std::string method;
|
||||
std::string path;
|
||||
std::string reason;
|
||||
std::string body;
|
||||
bool got_on_begin = false;
|
||||
bool got_on_field = false;
|
||||
bool got_on_header = false;
|
||||
bool got_on_body = false;
|
||||
bool got_content_length = false;
|
||||
bool got_on_prepare = false;
|
||||
bool got_on_commit = false;
|
||||
bool got_on_chunk = false;
|
||||
bool got_on_complete = false;
|
||||
|
||||
test_parser() = default;
|
||||
|
||||
explicit
|
||||
test_parser(test::fail_counter& fc)
|
||||
: fc_(&fc)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
on_request(
|
||||
boost::string_ref const& method_,
|
||||
boost::string_ref const& path_,
|
||||
int version_, error_code& ec)
|
||||
{
|
||||
method = std::string(
|
||||
method_.data(), method_.size());
|
||||
path = std::string(
|
||||
path_.data(), path_.size());
|
||||
version = version_;
|
||||
got_on_begin = true;
|
||||
if(fc_)
|
||||
fc_->fail(ec);
|
||||
}
|
||||
|
||||
void
|
||||
on_response(int status_,
|
||||
boost::string_ref const& reason_,
|
||||
int version_, error_code& ec)
|
||||
{
|
||||
status = status_;
|
||||
reason = std::string(
|
||||
reason_.data(), reason_.size());
|
||||
version = version_;
|
||||
got_on_begin = true;
|
||||
if(fc_)
|
||||
fc_->fail(ec);
|
||||
}
|
||||
|
||||
void
|
||||
on_field(boost::string_ref const&,
|
||||
boost::string_ref const&,
|
||||
error_code& ec)
|
||||
{
|
||||
got_on_field = true;
|
||||
if(fc_)
|
||||
fc_->fail(ec);
|
||||
}
|
||||
|
||||
void
|
||||
on_header(error_code& ec)
|
||||
{
|
||||
got_on_header = true;
|
||||
if(fc_)
|
||||
fc_->fail(ec);
|
||||
}
|
||||
|
||||
void
|
||||
on_body(error_code& ec)
|
||||
{
|
||||
got_on_body = true;
|
||||
if(fc_)
|
||||
fc_->fail(ec);
|
||||
}
|
||||
|
||||
void
|
||||
on_body(std::uint64_t content_length,
|
||||
error_code& ec)
|
||||
{
|
||||
got_on_body = true;
|
||||
got_content_length = true;
|
||||
if(fc_)
|
||||
fc_->fail(ec);
|
||||
}
|
||||
|
||||
void
|
||||
on_data(boost::string_ref const& s,
|
||||
error_code& ec)
|
||||
{
|
||||
body.append(s.data(), s.size());
|
||||
}
|
||||
|
||||
void
|
||||
on_chunk(std::uint64_t,
|
||||
boost::string_ref const&,
|
||||
error_code& ec)
|
||||
{
|
||||
got_on_chunk = true;
|
||||
if(fc_)
|
||||
fc_->fail(ec);
|
||||
}
|
||||
|
||||
void
|
||||
on_complete(error_code& ec)
|
||||
{
|
||||
got_on_complete = true;
|
||||
if(fc_)
|
||||
fc_->fail(ec);
|
||||
}
|
||||
};
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
@ -10,7 +10,6 @@
|
||||
|
||||
#include <beast/http/fields.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/http/empty_body.hpp>
|
||||
#include <beast/http/string_body.hpp>
|
||||
#include <beast/http/write.hpp>
|
||||
#include <beast/core/error.hpp>
|
||||
@ -510,7 +509,7 @@ public:
|
||||
}
|
||||
// upgrade HTTP/1.1
|
||||
{
|
||||
message<true, empty_body, fields> m;
|
||||
message<true, string_body, fields> m;
|
||||
m.method = "GET";
|
||||
m.url = "/";
|
||||
m.version = 11;
|
||||
|
@ -174,7 +174,7 @@ public:
|
||||
for(n = 0; n < limit; ++n)
|
||||
{
|
||||
// valid
|
||||
http::request<http::empty_body> req;
|
||||
http::request_header req;
|
||||
req.method = "GET";
|
||||
req.url = "/";
|
||||
req.version = 11;
|
||||
|
Reference in New Issue
Block a user