New header-only basic_parser:

The basic_parser is rewritten to be header-only. The nodejs parser is
removed from the include subtree and placed into the test directory.
Other changes:

* Parser specific error codes in parse_error.hpp
* Add parser-bench performance testing, nodejs vs beast
* New random message generator for fuzz tests
* Test for header-only parser using random message generator
* Augmented some existing message tests to check more cases
This commit is contained in:
Vinnie Falco
2016-04-25 05:27:34 -04:00
parent 4cfa1d5cd3
commit 61a8f7f078
32 changed files with 4380 additions and 870 deletions

View File

@@ -60,8 +60,11 @@ project beast
<threading>multi <threading>multi
<link>static <link>static
<runtime-link>shared <runtime-link>shared
<debug-symbols>on
<toolset>gcc:<cxxflags>-std=c++11 <toolset>gcc:<cxxflags>-std=c++11
<toolset>clang:<cxxflags>-std=c++11 <toolset>clang:<cxxflags>-std=c++11
<toolset>msvc:<define>_SCL_SECURE_NO_WARNINGS=1
<toolset>msvc:<define>_CRT_SECURE_NO_WARNINGS=1
<os>LINUX:<define>_XOPEN_SOURCE=600 <os>LINUX:<define>_XOPEN_SOURCE=600
<os>LINUX:<define>_GNU_SOURCE=1 <os>LINUX:<define>_GNU_SOURCE=1
<os>SOLARIS:<define>_XOPEN_SOURCE=500 <os>SOLARIS:<define>_XOPEN_SOURCE=500
@@ -78,8 +81,6 @@ project beast
<os>HPUX:<library>ipv6 <os>HPUX:<library>ipv6
<os>QNXNTO:<library>socket <os>QNXNTO:<library>socket
<os>HAIKU:<library>network <os>HAIKU:<library>network
<toolset>msvc:<define>_SCL_SECURE_NO_WARNINGS=1
<toolset>msvc:<define>_CRT_SECURE_NO_WARNINGS=1
: usage-requirements : usage-requirements
<include>. <include>.
: :

View File

@@ -22,3 +22,10 @@
* Figure out why namespace rfc2616 is included in docs * Figure out why namespace rfc2616 is included in docs
(currently disabled via GENERATING_DOCS macro) (currently disabled via GENERATING_DOCS macro)
* Include Example program listings in the docs * Include Example program listings in the docs
* Update for rfc7230
* HTTP parser size limit with test (configurable?)
* HTTP parser trailers with test
* URL parser, strong URL checking in HTTP parser
* Fix method, use string instead of enum
* More fine grained parser errors
* Fix all the warnings in all projects/build configs

View File

@@ -119,6 +119,7 @@ INPUT = \
../include/beast/streambuf_readstream.hpp \ ../include/beast/streambuf_readstream.hpp \
../include/beast/type_check.hpp \ ../include/beast/type_check.hpp \
../include/beast/websocket.hpp \ ../include/beast/websocket.hpp \
../include/beast/write_streambuf.hpp \
../include/beast/http/basic_headers.hpp \ ../include/beast/http/basic_headers.hpp \
../include/beast/http/basic_parser.hpp \ ../include/beast/http/basic_parser.hpp \
../include/beast/http/chunk_encode.hpp \ ../include/beast/http/chunk_encode.hpp \
@@ -128,6 +129,7 @@ INPUT = \
../include/beast/http/headers.hpp \ ../include/beast/http/headers.hpp \
../include/beast/http/message.hpp \ ../include/beast/http/message.hpp \
../include/beast/http/method.hpp \ ../include/beast/http/method.hpp \
../include/beast/http/parse_error.hpp \
../include/beast/http/parser.hpp \ ../include/beast/http/parser.hpp \
../include/beast/http/read.hpp \ ../include/beast/http/read.hpp \
../include/beast/http/resume_context.hpp \ ../include/beast/http/resume_context.hpp \

View File

@@ -80,9 +80,9 @@ Beast requires:
[note Tested compilers: msvc-14+, gcc 5+, clang 3.6+] [note Tested compilers: msvc-14+, gcc 5+, clang 3.6+]
Most of the library is header-only; however, the HTTP parser used is written The library is [*header-only]. It is not necessary to add any .cpp files,
in C. To link an application that uses Beast, it is necessary to add a single or to edit your existing build script or project file except to provide
.cpp file from beast into your project's build script. that the include/ directory for beast is searched for include files.
[endsect] [endsect]
@@ -95,11 +95,6 @@ flavor of the library. They are complete programs which may be built
and run. Source code and build scripts for these programs may be found and run. Source code and build scripts for these programs may be found
in the examples directory. in the examples directory.
[note
To link these programs, please add the file
`src/beast_http_nodejs_parser.cpp` to your build script or Makefile
]
Use HTTP to request the root page from a website and print the response: Use HTTP to request the root page from a website and print the response:
``` ```
#include <beast/http.hpp> #include <beast/http.hpp>

View File

@@ -33,9 +33,16 @@ interface for the C++ standard library will likely closely resemble the current
interface of Boost.Asio, it is logical for Beast.HTTP to use Boost.Asio as its interface of Boost.Asio, it is logical for Beast.HTTP to use Boost.Asio as its
network transport. network transport.
[heading Scope]
The scope of this library is meant to include only the functionality of
modeling the HTTP message, serializing and deserializing the message, and
sending and receiving messages on sockets or streams. It is designed to
be a building block for creating higher level abstractions.
[note The documentation which follows assumes familiarity with [note The documentation which follows assumes familiarity with
both Boost.Asio and the HTTP protocol specification described in both Boost.Asio and the HTTP protocol specification described in
[@https://tools.ietf.org/html/rfc2616 rfc2616] ] [@https://tools.ietf.org/html/rfc7230 rfc7230] ]
[endsect] [endsect]

View File

@@ -8,28 +8,23 @@
import os ; import os ;
exe http_crawl : exe http_crawl :
../src/beast_http_nodejs_parser.cpp
http_crawl.cpp http_crawl.cpp
urls_large_data.cpp urls_large_data.cpp
; ;
exe http_server : exe http_server :
../src/beast_http_nodejs_parser.cpp
http_server.cpp http_server.cpp
; ;
exe wsproto_echo : exe websocket_echo :
../src/beast_http_nodejs_parser.cpp websocket_echo.cpp
wsproto_echo.cpp
; ;
exe http_example : exe http_example :
../src/beast_http_nodejs_parser.cpp
http_example.cpp http_example.cpp
; ;
exe websocket_example : exe websocket_example :
../src/beast_http_nodejs_parser.cpp
websocket_example.cpp websocket_example.cpp
; ;

View File

@@ -17,8 +17,8 @@
*/ */
//============================================================================== //==============================================================================
#ifndef BEAST_WSPROTO_ASYNC_ECHO_PEER_H_INCLUDED #ifndef BEAST_WEBSOCKET_ASYNC_ECHO_PEER_H_INCLUDED
#define BEAST_WSPROTO_ASYNC_ECHO_PEER_H_INCLUDED #define BEAST_WEBSOCKET_ASYNC_ECHO_PEER_H_INCLUDED
#include <beast/placeholders.hpp> #include <beast/placeholders.hpp>
#include <beast/streambuf.hpp> #include <beast/streambuf.hpp>

View File

@@ -17,8 +17,8 @@
*/ */
//============================================================================== //==============================================================================
#include "wsproto_async_echo_peer.h" #include "websocket_async_echo_peer.h"
#include "wsproto_sync_echo_peer.h" #include "websocket_sync_echo_peer.h"
#include "sig_wait.h" #include "sig_wait.h"
int main() int main()

View File

@@ -15,6 +15,7 @@
#include <beast/http/error.hpp> #include <beast/http/error.hpp>
#include <beast/http/headers.hpp> #include <beast/http/headers.hpp>
#include <beast/http/message.hpp> #include <beast/http/message.hpp>
#include <beast/http/parse_error.hpp>
#include <beast/http/parser.hpp> #include <beast/http/parser.hpp>
#include <beast/http/read.hpp> #include <beast/http/read.hpp>
#include <beast/http/reason.hpp> #include <beast/http/reason.hpp>

View File

@@ -8,150 +8,355 @@
#ifndef BEAST_HTTP_BASIC_PARSER_HPP #ifndef BEAST_HTTP_BASIC_PARSER_HPP
#define BEAST_HTTP_BASIC_PARSER_HPP #define BEAST_HTTP_BASIC_PARSER_HPP
#include <beast/http/method.hpp> #include <beast/http/message.hpp>
#include <beast/http/impl/http_parser.h> #include <beast/http/parse_error.hpp>
#include <beast/http/rfc7230.hpp>
#include <beast/http/detail/basic_parser.hpp>
#include <beast/type_check.hpp> #include <beast/type_check.hpp>
#include <boost/asio/buffer.hpp> #include <beast/detail/ci_char_traits.hpp>
#include <boost/system/error_code.hpp> #include <array>
#include <cassert>
#include <climits>
#include <cstdint> #include <cstdint>
#include <string>
#include <type_traits> #include <type_traits>
namespace beast { namespace beast {
namespace http { namespace http {
namespace parse_flag {
enum values
{
chunked = 1 << 0,
connection_keep_alive = 1 << 1,
connection_close = 1 << 2,
connection_upgrade = 1 << 3,
trailing = 1 << 4,
upgrade = 1 << 5,
skipbody = 1 << 6,
contentlength = 1 << 7
};
} // parse_flag
/** Parser for producing HTTP requests and responses. /** Parser for producing HTTP requests and responses.
Callbacks: During parsing, callbacks will be made to the derived class
if those members are present (detected through SFINAE). The
signatures which can be present in the derived class are:<br>
If a is an object of type Derived, and the call expression is @li `void on_method(boost::string_ref const&, error_code& ec)`
valid then the stated effects will take place:
a.on_start() Called for each piece of the Request-Method
Called once when a new message begins. @li `void on_uri(boost::string_ref const&, error_code& ec)`
a.on_field(std::string field, std::string value) Called for each piece of the Request-URI
Called for each field @li `void on_reason(boost::string_ref const&, error_code& ec)`
a.on_headers_complete(error_code&) Called for each piece of the reason-phrase
Called when all the header fields have been received, but @li `void on_request(error_code& ec)`
before any part of the body if any is received.
a.on_request(method_t method, std::string url, Called after the entire Request-Line has been parsed successfully.
int major, int minor, bool keep_alive, bool upgrade)
Called for requests when all the headers have been received. @li `void on_response(error_code& ec)`
This will precede any content body.
When keep_alive is false: Called after the entire Response-Line has been parsed successfully.
* Server roles respond with a "Connection: close" header.
* Client roles close the connection.
a.on_response(int status, std::string text, @li `void on_field(boost::string_ref const&, error_code& ec)`
int major, int minor, bool keep_alive,
bool upgrade)
Called for responses when all the headers have been received. Called for each piece of the current header field.
This will precede any content body.
When keep_alive is `false`: @li `void on_value(boost::string_ref const&, error_code& ec)`
* Client roles close the connection.
* Server roles respond with a "Connection: close" header.
This function should return `true` if upgrade is false and Called for each piece of the current header value.
a content body is expected. When upgrade is true, no
content-body is expected, and the return value is ignored.
a.on_body(void const* data, std::size_t bytes, error_code&) @li `int on_headers(error_code& ec)`
Called zero or more times for the content body. Any transfer Called when all the headers have been parsed successfully.
encoding is already decoded in the memory pointed to by data.
a.on_complete() @li `void on_body(boost::string_ref const&, error_code& ec)`
Called when parsing completes successfully. Called for each piece of the body. If the headers indicated
chunked encoding, the chunk encoding is removed from the
buffer before being passed to the callback.
@li `void on_complete(error_code& ec)`
Called when the entire message has been parsed successfully.
At this point, basic_parser::complete() returns `true`, and
the parser is ready to parse another message if keep_alive()
would return `true`.
The return value of `on_headers` is special, it controls whether
or not the parser should expect a body. These are the return values:
@li *0* The parser should expect a body
@li *1* The parser should skip the body. For example, this is
used when sending a response to a HEAD request.
@li *2* The parser should skip ths body, this is an
upgrade to a different protocol.
The parser uses traits to determine if the callback is possible. The parser uses traits to determine if the callback is possible.
If the Derived type omits the callbacks, they are simply skipped If the Derived type omits one or more callbacks, they are simply
with no compilation error. skipped with no compilation error. The default behavior of on_body
when the derived class does not provide the member, is to specify that
the body should not be skipped.
If a callback sets an error, parsing stops at the current octet
and the error is returned to the caller.
*/ */
/* template<bool isRequest, class Derived>
VFALCO TODO is_call_possible, enable_if_t on Derived calls
use boost::string_ref instead of std::string
*/
template<class Derived>
class basic_parser class basic_parser
{ {
http_parser state_; private:
boost::system::error_code* ec_; using self = basic_parser;
bool complete_ = false; typedef void(self::*pmf_t)(error_code&, boost::string_ref const&);
std::string url_;
std::string status_; static std::uint64_t constexpr no_content_length =
std::string field_; std::numeric_limits<std::uint64_t>::max();
std::string value_;
enum state : std::uint8_t
{
s_closed = 1,
s_req_start,
s_req_method_start,
s_req_method,
s_req_space_before_url,
s_req_url_start,
s_req_url,
s_req_http_start,
s_req_http_H,
s_req_http_HT,
s_req_http_HTT,
s_req_http_HTTP,
s_req_major_start,
s_req_major,
s_req_minor_start,
s_req_minor,
s_req_line_end,
s_res_start,
s_res_H,
s_res_HT,
s_res_HTT,
s_res_HTTP,
s_res_major_start,
s_res_major,
s_res_minor_start,
s_res_minor,
s_res_status_code_start,
s_res_status_code,
s_res_status_start,
s_res_status,
s_res_line_almost_done,
s_res_line_done,
s_header_field_start,
s_header_field,
s_header_value_start,
s_header_value_discard_lWs0,
s_header_value_discard_ws0,
s_header_value_almost_done0,
s_header_value_text_start,
s_header_value_discard_lWs,
s_header_value_discard_ws,
s_header_value_text,
s_header_value_almost_done,
s_headers_almost_done,
s_headers_done,
s_chunk_size_start,
s_chunk_size,
s_chunk_parameters,
s_chunk_size_almost_done,
// states below do not count towards
// the limit on the size of the message
s_body_identity0,
s_body_identity,
s_body_identity_eof0,
s_body_identity_eof,
s_chunk_data_start,
s_chunk_data,
s_chunk_data_almost_done,
s_chunk_data_done,
s_complete,
s_restart
};
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_length,
h_transfer_encoding,
h_upgrade,
h_matching_transfer_encoding_chunked,
h_matching_connection_token_start,
h_matching_connection_keep_alive,
h_matching_connection_close,
h_matching_connection_upgrade,
h_matching_connection_token,
h_transfer_encoding_chunked,
h_connection_keep_alive,
h_connection_close,
h_connection_upgrade,
};
std::uint64_t content_length_;
std::uint64_t nread_;
pmf_t cb_;
state s_ : 8;
unsigned flags_ : 8;
unsigned fs_ : 8;
unsigned pos_ : 8; // position in field state
unsigned http_major_ : 16;
unsigned http_minor_ : 16;
unsigned status_code_ : 16;
bool upgrade_ : 1; // true if parser exited for upgrade
public: public:
using error_code = boost::system::error_code; /// Copy constructor.
basic_parser(basic_parser const&) = default;
/** Move constructor. /// Copy assignment.
basic_parser& operator=(basic_parser const&) = default;
The state of the moved-from object is undefined, /// Constructor
but safe to destroy. basic_parser()
*/ {
basic_parser(basic_parser&& other); init(std::integral_constant<bool, isRequest>{});
}
/** Move assignment. /// Returns internal flags associated with the parser.
unsigned
flags() const
{
return flags_;
}
The state of the moved-from object is undefined, /** Returns `true` if the message end is indicated by eof.
but safe to destroy.
*/
basic_parser&
operator=(basic_parser&& other);
/** Copy constructor. */ This function returns true if the semantics of the message require
basic_parser(basic_parser const& other); 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.
/** Copy assignment. */ @return `true` if write_eof must be used to indicate the message end.
basic_parser& operator=(basic_parser const& other);
/** Construct the parser.
@param request If `true`, the parser is setup for a request.
*/
explicit
basic_parser(bool request) noexcept;
/** Returns `true` if parsing is complete.
This is only defined when no errors have been returned.
*/ */
bool bool
complete() const noexcept needs_eof() const
{ {
return complete_; 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;
} }
/** Write data to the parser. /** Write data to the parser.
@param data A pointer to a buffer representing the input sequence. @param buffers An object meeting the requirements of
@param size The number of bytes in the buffer pointed to by data. ConstBufferSequence that represents the input sequence.
@throws boost::system::system_error Thrown on failure. @param ec Set to the error, if any error occurred.
@return The number of bytes consumed in the input sequence. @return The number of bytes consumed in the input sequence.
*/ */
template<class ConstBufferSequence>
std::size_t std::size_t
write(void const* data, std::size_t size) write(ConstBufferSequence const& buffers, error_code& ec);
{
error_code ec;
auto const used = write(data, size, ec);
if(ec)
throw boost::system::system_error{ec};
return used;
}
/** Write data to the parser. /** Write data to the parser.
@@ -162,41 +367,7 @@ public:
@return The number of bytes consumed in the input sequence. @return The number of bytes consumed in the input sequence.
*/ */
std::size_t std::size_t
write(void const* data, std::size_t size, write(void const* data, std::size_t size, error_code& ec);
error_code& ec);
/** Write data to the parser.
@param buffers An object meeting the requirements of
ConstBufferSequence that represents the input sequence.
@throws boost::system::system_error Thrown on failure.
@return The number of bytes consumed in the input sequence.
*/
template<class ConstBufferSequence>
std::size_t
write(ConstBufferSequence const& buffers)
{
error_code ec;
auto const used = write(buffers, ec);
if(ec)
throw boost::system::system_error{ec};
return used;
}
/** Write data 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>
std::size_t
write(ConstBufferSequence const& buffers,
error_code& ec);
/** Called to indicate the end of file. /** Called to indicate the end of file.
@@ -210,26 +381,6 @@ public:
@throws boost::system::system_error Thrown on failure. @throws boost::system::system_error Thrown on failure.
*/ */
void void
write_eof()
{
error_code ec;
write_eof(ec);
if(ec)
throw boost::system::system_error{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.
@param ec Set to the error, if any error occurred.
*/
void
write_eof(error_code& ec); write_eof(error_code& ec);
private: private:
@@ -239,298 +390,181 @@ private:
return *static_cast<Derived*>(this); return *static_cast<Derived*>(this);
} }
template<class C>
class has_on_start_t
{
template<class T, class R =
decltype(std::declval<T>().on_start(), std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_start =
std::integral_constant<bool, has_on_start_t<C>::value>;
void void
call_on_start(std::true_type) init(std::true_type)
{ {
impl().on_start(); s_ = s_req_start;
} }
void void
call_on_start(std::false_type) init(std::false_type)
{ {
} s_ = s_res_start;
template<class C>
class has_on_field_t
{
template<class T, class R =
decltype(std::declval<T>().on_field(
std::declval<std::string const&>(),
std::declval<std::string const&>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_field =
std::integral_constant<bool, has_on_field_t<C>::value>;
void
call_on_field(std::string const& field,
std::string const& value, std::true_type)
{
impl().on_field(field, value);
}
void
call_on_field(std::string const&, std::string const&,
std::false_type)
{
}
template<class C>
class has_on_headers_complete_t
{
template<class T, class R =
decltype(std::declval<T>().on_headers_complete(
std::declval<error_code&>()), std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_headers_complete =
std::integral_constant<bool, has_on_headers_complete_t<C>::value>;
void
call_on_headers_complete(error_code& ec, std::true_type)
{
impl().on_headers_complete(ec);
}
void
call_on_headers_complete(error_code&, std::false_type)
{
}
template<class C>
class has_on_request_t
{
template<class T, class R =
decltype(std::declval<T>().on_request(
std::declval<method_t>(), std::declval<std::string>(),
std::declval<int>(), std::declval<int>(),
std::declval<bool>(), std::declval<bool>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_request =
std::integral_constant<bool, has_on_request_t<C>::value>;
void
call_on_request(method_t method, std::string url,
int major, int minor, bool keep_alive, bool upgrade,
std::true_type)
{
impl().on_request(
method, url, major, minor, keep_alive, upgrade);
}
void
call_on_request(method_t, std::string, int, int, bool, bool,
std::false_type)
{
}
template<class C>
class has_on_response_t
{
template<class T, class R =
decltype(std::declval<T>().on_response(
std::declval<int>(), std::declval<std::string>,
std::declval<int>(), std::declval<int>(),
std::declval<bool>(), std::declval<bool>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
#if 0
using type = decltype(check<C>(0));
#else
// VFALCO Trait seems broken for http::parser
using type = std::true_type;
#endif
public:
static bool const value = type::value;
};
template<class C>
using has_on_response =
std::integral_constant<bool, has_on_response_t<C>::value>;
bool
call_on_response(int status, std::string text,
int major, int minor, bool keep_alive, bool upgrade,
std::true_type)
{
return impl().on_response(
status, text, major, minor, keep_alive, upgrade);
} }
bool bool
call_on_response(int, std::string, int, int, bool, bool, needs_eof(std::true_type) const;
std::false_type)
bool
needs_eof(std::false_type) const;
void call_on_method(error_code& ec,
boost::string_ref const& s, std::true_type)
{ {
// VFALCO Certainly incorrect impl().on_method(s, ec);
return true;
} }
template<class C> void call_on_method(error_code&,
class has_on_body_t boost::string_ref const&, std::false_type)
{
template<class T, class R =
decltype(std::declval<T>().on_body(
std::declval<void const*>(), std::declval<std::size_t>(),
std::declval<error_code&>()), std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_body =
std::integral_constant<bool, has_on_body_t<C>::value>;
void
call_on_body(void const* data, std::size_t bytes,
error_code& ec, std::true_type)
{
impl().on_body(data, bytes, ec);
}
void
call_on_body(void const*, std::size_t,
error_code&, std::false_type)
{ {
} }
template<class C> void call_on_method(error_code& ec,
class has_on_complete_t boost::string_ref const& s)
{ {
template<class T, class R = call_on_method(ec, s, std::integral_constant<bool,
decltype(std::declval<T>().on_complete(), std::true_type{})> isRequest && detail::has_on_method<Derived>::value>{});
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_complete =
std::integral_constant<bool, has_on_complete_t<C>::value>;
void
call_on_complete(std::true_type)
{
impl().on_complete();
} }
void void call_on_uri(error_code& ec,
call_on_complete(std::false_type) boost::string_ref const& s, std::true_type)
{
impl().on_uri(s, ec);
}
void call_on_uri(error_code&,
boost::string_ref const&, std::false_type)
{ {
} }
void void call_on_uri(error_code& ec, boost::string_ref const& s)
check_header();
static int cb_message_start(http_parser*);
static int cb_url(http_parser*, char const*, std::size_t);
static int cb_status(http_parser*, char const*, std::size_t);
static int cb_header_field(http_parser*, char const*, std::size_t);
static int cb_header_value(http_parser*, char const*, std::size_t);
static int cb_headers_complete(http_parser*);
static int cb_body(http_parser*, char const*, std::size_t);
static int cb_message_complete(http_parser*);
static int cb_chunk_header(http_parser*);
static int cb_chunk_complete(http_parser*);
struct hooks_t : http_parser_settings
{ {
hooks_t() call_on_uri(ec, s, std::integral_constant<bool,
{ isRequest && detail::has_on_uri<Derived>::value>{});
http_parser_settings_init(this);
on_message_begin = &basic_parser::cb_message_start;
on_url = &basic_parser::cb_url;
on_status = &basic_parser::cb_status;
on_header_field = &basic_parser::cb_header_field;
on_header_value = &basic_parser::cb_header_value;
on_headers_complete = &basic_parser::cb_headers_complete;
on_body = &basic_parser::cb_body;
on_message_complete = &basic_parser::cb_message_complete;
on_chunk_header = &basic_parser::cb_chunk_header;
on_chunk_complete = &basic_parser::cb_chunk_complete;
} }
};
static void call_on_reason(error_code& ec,
http_parser_settings const* boost::string_ref const& s, std::true_type)
hooks(); {
}; impl().on_reason(s, ec);
}
template<class Derived>
template<class ConstBufferSequence> void call_on_reason(error_code&,
std::size_t boost::string_ref const&, std::false_type)
basic_parser<Derived>::write( {
ConstBufferSequence const& buffers, error_code& ec) }
{
static_assert(beast::is_ConstBufferSequence< void call_on_reason(error_code& ec, boost::string_ref const& s)
ConstBufferSequence>::value, {
"ConstBufferSequence requirements not met"); call_on_reason(ec, s, std::integral_constant<bool,
using boost::asio::buffer_cast; ! isRequest && detail::has_on_reason<Derived>::value>{});
using boost::asio::buffer_size; }
std::size_t bytes_used = 0;
for (auto const& buffer : buffers) void call_on_request(error_code& ec, std::true_type)
{
impl().on_request(ec);
}
void call_on_request(error_code&, std::false_type)
{
}
void call_on_request(error_code& ec)
{
call_on_request(ec, detail::has_on_request<Derived>{});
}
void call_on_response(error_code& ec, std::true_type)
{
impl().on_response(ec);
}
void call_on_response(error_code&, std::false_type)
{
}
void call_on_response(error_code& ec)
{
call_on_response(ec, detail::has_on_response<Derived>{});
}
void call_on_field(error_code& ec,
boost::string_ref const& s, std::true_type)
{
impl().on_field(s, ec);
}
void call_on_field(error_code&,
boost::string_ref const&, std::false_type)
{
}
void call_on_field(error_code& ec, boost::string_ref const& s)
{
call_on_field(ec, s, detail::has_on_field<Derived>{});
}
void call_on_value(error_code& ec,
boost::string_ref const& s, std::true_type)
{
impl().on_value(s, ec);
}
void call_on_value(error_code&,
boost::string_ref const&, std::false_type)
{
}
void call_on_value(error_code& ec, boost::string_ref const& s)
{
call_on_value(ec, s, detail::has_on_value<Derived>{});
}
int call_on_headers(error_code& ec, std::true_type)
{
return impl().on_headers(ec);
}
int call_on_headers(error_code& ec, std::false_type)
{ {
auto const n = write(
buffer_cast<void const*>(buffer),
buffer_size(buffer), ec);
if(ec)
return 0; return 0;
bytes_used += n;
if(complete())
break;
} }
return bytes_used;
}
template<class Derived> int call_on_headers(error_code& ec)
http_parser_settings const* {
basic_parser<Derived>::hooks() return call_on_headers(ec, detail::has_on_headers<Derived>{});
{ }
static hooks_t const h;
return &h; void call_on_body(error_code& ec,
} boost::string_ref const& s, std::true_type)
{
impl().on_body(s, ec);
}
void call_on_body(error_code&,
boost::string_ref const&, std::false_type)
{
}
void call_on_body(error_code& ec, boost::string_ref const& s)
{
call_on_body(ec, s, detail::has_on_body<Derived>{});
}
void call_on_complete(error_code& ec, std::true_type)
{
impl().on_complete(ec);
}
void call_on_complete(error_code&, std::false_type)
{
}
void call_on_complete(error_code& ec)
{
call_on_complete(ec, detail::has_on_complete<Derived>{});
}
};
} // http } // http
} // beast } // beast

View File

@@ -0,0 +1,388 @@
//
// Copyright (c) 2013-2016 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 <boost/system/error_code.hpp>
#include <boost/utility/string_ref.hpp>
#include <array>
#include <cstdint>
namespace beast {
namespace http {
namespace detail {
// '0'...'9'
inline
bool
is_digit(char c)
{
return c >= '0' && c <= '9';
}
inline
bool
is_token(char c)
{
/* token = 1*<any CHAR except CTLs or separators>
CHAR = <any US-ASCII character (octets 0 - 127)>
sep = "(" | ")" | "<" | ">" | "@"
| "," | ";" | ":" | "\" | <">
| "/" | "[" | "]" | "?" | "="
| "{" | "}" | SP | HT
*/
static std::array<char, 256> constexpr tab = {{
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, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48
0, 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, 0, 0, 0, 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, 0, 1, 0, 1, 0, // 112
}};
return tab[static_cast<std::uint8_t>(c)] != 0;
}
inline
bool
is_text(char c)
{
// TEXT = <any OCTET except CTLs, but including LWS>
static std::array<char, 256> constexpr tab = {{
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<std::uint8_t>(c)] != 0;
}
// converts to lower case,
// returns 0 if not a valid token char
//
inline
char
to_field_char(char c)
{
/* token = 1*<any CHAR except CTLs or separators>
CHAR = <any US-ASCII character (octets 0 - 127)>
sep = "(" | ")" | "<" | ">" | "@"
| "," | ";" | ":" | "\" | <">
| "/" | "[" | "]" | "?" | "="
| "{" | "}" | SP | HT
*/
static std::array<char, 256> constexpr tab = {{
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, '!', 0, '#', '$', '%', '&', '\'', 0, 0, '*', '+', 0, '-', '.', 0,
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0,
0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, '^', '_',
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, '|', 0, '~', 0
}};
return tab[static_cast<std::uint8_t>(c)];
}
// converts to lower case,
// returns 0 if not a valid text char
//
inline
char
to_value_char(char c)
{
// TEXT = <any OCTET except CTLs, but including LWS>
static std::array<std::uint8_t, 256> constexpr tab = {{
0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 32
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // 48
64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 64
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, // 80
96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 96
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 0, // 112
128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, // 128
144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, // 144
160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, // 160
176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, // 176
192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, // 192
208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, // 208
224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, // 224
240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 // 240
}};
return static_cast<char>(tab[static_cast<std::uint8_t>(c)]);
}
inline
std::uint8_t
unhex(char c)
{
static std::array<std::int8_t, 256> constexpr tab = {{
-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
}};
return tab[static_cast<std::uint8_t>(c)];
};
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<>;
template<class C>
class has_on_method_t
{
template<class T, class R =
decltype(std::declval<T>().on_method(
std::declval<boost::string_ref const&>(),
std::declval<error_code&>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_method =
std::integral_constant<bool, has_on_method_t<C>::value>;
template<class C>
class has_on_uri_t
{
template<class T, class R =
decltype(std::declval<T>().on_uri(
std::declval<boost::string_ref const&>(),
std::declval<error_code&>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_uri =
std::integral_constant<bool, has_on_uri_t<C>::value>;
template<class C>
class has_on_reason_t
{
template<class T, class R =
decltype(std::declval<T>().on_reason(
std::declval<boost::string_ref const&>(),
std::declval<error_code&>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_reason =
std::integral_constant<bool, has_on_reason_t<C>::value>;
template<class C>
class has_on_request_t
{
template<class T, class R =
decltype(std::declval<T>().on_request(
std::declval<error_code&>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_request =
std::integral_constant<bool, has_on_request_t<C>::value>;
template<class C>
class has_on_response_t
{
template<class T, class R =
decltype(std::declval<T>().on_response(
std::declval<error_code&>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_response =
std::integral_constant<bool, has_on_response_t<C>::value>;
template<class C>
class has_on_field_t
{
template<class T, class R =
decltype(std::declval<T>().on_uri(
std::declval<boost::string_ref const&>(),
std::declval<error_code&>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_field =
std::integral_constant<bool, has_on_field_t<C>::value>;
template<class C>
class has_on_value_t
{
template<class T, class R =
decltype(std::declval<T>().on_uri(
std::declval<boost::string_ref const&>(),
std::declval<error_code&>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_value =
std::integral_constant<bool, has_on_value_t<C>::value>;
template<class C>
class has_on_headers_t
{
template<class T, class R = std::is_same<int,
decltype(std::declval<T>().on_headers(
std::declval<error_code&>()))>>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_headers =
std::integral_constant<bool, has_on_headers_t<C>::value>;
template<class C>
class has_on_body_t
{
template<class T, class R =
decltype(std::declval<T>().on_body(
std::declval<boost::string_ref const&>(),
std::declval<error_code&>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_body =
std::integral_constant<bool, has_on_body_t<C>::value>;
template<class C>
class has_on_complete_t
{
template<class T, class R =
decltype(std::declval<T>().on_complete(
std::declval<error_code&>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_complete =
std::integral_constant<bool, has_on_complete_t<C>::value>;
} // detail
} // http
} // beast
#endif

View File

@@ -1,71 +0,0 @@
//
// Copyright (c) 2013-2016 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_ERROR_HPP
#define BEAST_HTTP_DETAIL_ERROR_HPP
#include <beast/http/impl/http_parser.h>
#include <boost/system/error_code.hpp>
namespace beast {
namespace http {
namespace detail {
class message_category
: public boost::system::error_category
{
public:
const char*
name() const noexcept override
{
return "http error";
}
std::string
message(int ev) const override
{
return http_errno_description(
static_cast<http_errno>(ev));
}
boost::system::error_condition
default_error_condition(int ev) const noexcept override
{
return boost::system::error_condition{ev, *this};
}
bool
equivalent(int ev,
boost::system::error_condition const& condition
) const noexcept override
{
return condition.value() == ev &&
&condition.category() == this;
}
bool
equivalent(boost::system::error_code const& error,
int ev) const noexcept override
{
return error.value() == ev &&
&error.category() == this;
}
};
template<class = void>
boost::system::error_code
make_error(int http_errno)
{
static message_category const mc{};
return boost::system::error_code{http_errno, mc};
}
} // detail
} // http
} // beast
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -38,6 +38,7 @@ class read_op
message_type& m; message_type& m;
parser_type p; parser_type p;
Handler h; Handler h;
bool started = false;
bool cont; bool cont;
int state = 0; int state = 0;
@@ -129,6 +130,8 @@ operator()(error_code ec, std::size_t bytes_transferred, bool again)
bind_handler(std::move(*this), ec, 0)); bind_handler(std::move(*this), ec, 0));
return; return;
} }
if(used > 0)
d.started = true;
d.sb.consume(used); d.sb.consume(used);
if(d.p.complete()) if(d.p.complete())
{ {
@@ -156,7 +159,7 @@ operator()(error_code ec, std::size_t bytes_transferred, bool again)
{ {
if(ec == boost::asio::error::eof) if(ec == boost::asio::error::eof)
{ {
if(! d.p.started()) if(! d.started)
{ {
// call handler // call handler
d.state = 99; d.state = 99;
@@ -219,6 +222,7 @@ read(SyncReadStream& stream, Streambuf& streambuf,
static_assert(is_Streambuf<Streambuf>::value, static_assert(is_Streambuf<Streambuf>::value,
"Streambuf requirements not met"); "Streambuf requirements not met");
parser<isRequest, Body, Headers> p; parser<isRequest, Body, Headers> p;
bool started = false;
for(;;) for(;;)
{ {
auto used = auto used =
@@ -226,6 +230,8 @@ read(SyncReadStream& stream, Streambuf& streambuf,
if(ec) if(ec)
return; return;
streambuf.consume(used); streambuf.consume(used);
if(used > 0)
started = true;
if(p.complete()) if(p.complete())
{ {
m = p.release(); m = p.release();
@@ -238,7 +244,7 @@ read(SyncReadStream& stream, Streambuf& streambuf,
return; return;
if(ec == boost::asio::error::eof) if(ec == boost::asio::error::eof)
{ {
if(! p.started()) if(! started)
return; return;
// Caller will see eof on next read. // Caller will see eof on next read.
ec = {}; ec = {};

View File

@@ -0,0 +1,157 @@
//
// Copyright (c) 2013-2016 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/http/error.hpp>
#include <boost/system/error_code.hpp>
namespace beast {
namespace http {
enum class parse_error
{
connection_closed,
bad_method,
bad_uri,
bad_version,
bad_crlf,
bad_request,
bad_status_code,
bad_status,
bad_field,
bad_value,
bad_content_length,
illegal_content_length,
bad_on_headers_rv,
invalid_chunk_size,
short_read
};
class parse_error_category : public boost::system::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-URI";
case parse_error::bad_version:
return "bad HTTP-Version";
case parse_error::bad_crlf:
return "missing CRLF";
case parse_error::bad_request:
return "bad Request-Line";
case parse_error::bad_status_code:
return "bad Status-Code";
case parse_error::bad_status:
return "bad Status-Line";
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::bad_on_headers_rv:
return "on_headers returned an unknown value";
case parse_error::invalid_chunk_size:
return "invalid chunk size";
case parse_error::short_read:
return "unexpected end of data";
default:
return "beast::http::parser error";
}
}
boost::system::error_condition
default_error_condition(int ev) const noexcept override
{
return boost::system::error_condition(ev, *this);
}
bool
equivalent(int ev,
boost::system::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
boost::system::error_category const&
get_parse_error_category()
{
static parse_error_category const cat{};
return cat;
}
inline
boost::system::error_code
make_error_code(parse_error ev)
{
return error_code(static_cast<int>(ev),
get_parse_error_category());
}
} // http
} // beast
namespace boost {
namespace system {
template<>
struct is_error_code_enum<beast::http::parse_error>
{
static bool const value = true;
};
} // system
} // boost
#endif

View File

@@ -13,43 +13,51 @@
#include <beast/http/message.hpp> #include <beast/http/message.hpp>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <functional> #include <functional>
#include <string>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
namespace beast { namespace beast {
namespace http { namespace http {
/** A HTTP parser. namespace detail {
struct parser_request
{
std::string method_;
std::string uri_;
};
struct parser_response
{
std::string reason_;
};
} // detail
The parser may only be used once.
*/
template<bool isRequest, class Body, class Headers> template<bool isRequest, class Body, class Headers>
class parser class parser
: public basic_parser<parser<isRequest, Body, Headers>> : public basic_parser<isRequest,
parser<isRequest, Body, Headers>>
, private std::conditional<isRequest,
detail::parser_request, detail::parser_response>::type
{ {
using message_type = using message_type =
message<isRequest, Body, Headers>; message<isRequest, Body, Headers>;
std::string field_;
std::string value_;
message_type m_; message_type m_;
typename message_type::body_type::reader r_; typename message_type::body_type::reader r_;
bool started_ = false;
public: public:
parser(parser&&) = default; parser(parser&&) = default;
parser() parser()
: http::basic_parser<parser>(isRequest) : r_(m_)
, r_(m_)
{ {
} }
/// Returns `true` if at least one byte has been processed
bool
started()
{
return started_;
}
message_type message_type
release() release()
{ {
@@ -57,94 +65,164 @@ public:
} }
private: private:
friend class http::basic_parser<parser>; friend class basic_parser<isRequest, parser>;
void void flush()
on_start()
{ {
started_ = true; if(! value_.empty())
{
rfc2616::trim_right_in_place(value_);
// VFALCO could std::move
m_.headers.insert(field_, value_);
field_.clear();
value_.clear();
}
} }
void void on_method(boost::string_ref const& s, error_code&)
on_field(std::string const& field, std::string const& value)
{ {
m_.headers.insert(field, value); this->method_.append(s.data(), s.size());
} }
void void on_uri(boost::string_ref const& s, error_code&)
on_headers_complete(error_code&)
{ {
// vFALCO TODO Decode the Content-Length and this->uri_.append(s.data(), s.size());
// Transfer-Encoding, see if we can reserve the buffer.
//
// r_.reserve(content_length)
} }
bool void on_reason(boost::string_ref const& s, error_code&)
on_request(http::method_t method, std::string const& url,
int major, int minor, bool keep_alive, bool upgrade,
std::true_type)
{ {
m_.method = method; this->reason_.append(s.data(), s.size());
m_.url = url; }
m_.version = major * 10 + minor;
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());
}
void set(std::true_type)
{
// VFALCO This is terrible for setting method
auto m =
[&](char const* s, method_t m)
{
if(this->method_ == s)
{
m_.method = m;
return true; return true;
} }
return false;
bool };
on_request(http::method_t, std::string const&, while(false)
int, int, bool, bool,
std::false_type)
{ {
return true; if(m("DELETE", method_t::http_delete))
break;
if(m("GET", method_t::http_get))
break;
if(m("HEAD", method_t::http_head))
break;
if(m("POST", method_t::http_post))
break;
if(m("PUT", method_t::http_put))
break;
if(m("CONNECT", method_t::http_connect))
break;
if(m("OPTIONS", method_t::http_options))
break;
if(m("TRACE", method_t::http_trace))
break;
if(m("COPY", method_t::http_copy))
break;
if(m("LOCK", method_t::http_lock))
break;
if(m("MKCOL", method_t::http_mkcol))
break;
if(m("MOVE", method_t::http_move))
break;
if(m("PROPFIND", method_t::http_propfind))
break;
if(m("PROPPATCH", method_t::http_proppatch))
break;
if(m("SEARCH", method_t::http_search))
break;
if(m("UNLOCK", method_t::http_unlock))
break;
if(m("BIND", method_t::http_bind))
break;
if(m("REBID", method_t::http_rebind))
break;
if(m("UNBIND", method_t::http_unbind))
break;
if(m("ACL", method_t::http_acl))
break;
if(m("REPORT", method_t::http_report))
break;
if(m("MKACTIVITY", method_t::http_mkactivity))
break;
if(m("CHECKOUT", method_t::http_checkout))
break;
if(m("MERGE", method_t::http_merge))
break;
if(m("MSEARCH", method_t::http_msearch))
break;
if(m("NOTIFY", method_t::http_notify))
break;
if(m("SUBSCRIBE", method_t::http_subscribe))
break;
if(m("UNSUBSCRIBE",method_t::http_unsubscribe))
break;
if(m("PATCH", method_t::http_patch))
break;
if(m("PURGE", method_t::http_purge))
break;
if(m("MKCALENDAR", method_t::http_mkcalendar))
break;
if(m("LINK", method_t::http_link))
break;
if(m("UNLINK", method_t::http_unlink))
break;
} }
bool m_.url = std::move(this->uri_);
on_request(http::method_t method, std::string const& url,
int major, int minor, bool keep_alive, bool upgrade)
{
return on_request(method, url,
major, minor, keep_alive, upgrade,
typename message_type::is_request{});
} }
bool void set(std::false_type)
on_response(int status, std::string const& reason,
int major, int minor, bool keep_alive, bool upgrade,
std::true_type)
{ {
m_.status = status; m_.status = this->status_code();
m_.reason = reason; m_.reason = this->reason_;
m_.version = major * 10 + minor;
// VFALCO TODO return expect_body_
return true;
} }
bool int on_headers(error_code&)
on_response(int, std::string const&, int, int, bool, bool,
std::false_type)
{ {
return true; flush();
m_.version = 10 * this->http_major() + this->http_minor();
return 0;
} }
bool void on_request(error_code& ec)
on_response(int status, std::string const& reason,
int major, int minor, bool keep_alive, bool upgrade)
{ {
return on_response( set(std::integral_constant<
status, reason, major, minor, keep_alive, upgrade, bool, isRequest>{});
std::integral_constant<bool, ! message_type::is_request::value>{});
} }
void void on_response(error_code& ec)
on_body(void const* data,
std::size_t size, error_code& ec)
{ {
r_.write(data, size, ec); set(std::integral_constant<
bool, isRequest>{});
} }
void void on_body(boost::string_ref const& s, error_code& ec)
on_complete() {
r_.write(s.data(), s.size(), ec);
}
void on_complete(error_code&)
{ {
} }
}; };

View File

@@ -0,0 +1,23 @@
//
// Copyright (c) 2013-2016 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_RFC7230_HPP
#define BEAST_HTTP_RFC7230_HPP
#include <array>
#include <cstdint>
namespace beast {
namespace rfc7230 {
} // rfc7230
} // beast
#endif

View File

@@ -21,11 +21,15 @@ namespace beast {
argument into the stream buffer. It is capable of converting the argument into the stream buffer. It is capable of converting the
following types of arguments: following types of arguments:
* `boost::asio::const_buffer` @li `boost::asio::const_buffer`
* `boost::asio::mutable_buffer`
* A type for which the call to `boost::asio::buffer()` is defined @li `boost::asio::mutable_buffer`
* A type meeting the requirements of `ConstBufferSequence`
* A type meeting the requirements of `MutableBufferSequence` @li A type for which the call to `boost::asio::buffer()` is defined
@li A type meeting the requirements of `ConstBufferSequence`
@li A type meeting the requirements of `MutableBufferSequence`
For all types not listed above, the function will invoke For all types not listed above, the function will invoke
`boost::lexical_cast` on the argument in an attempt to convert to `boost::lexical_cast` on the argument in an attempt to convert to
@@ -38,7 +42,7 @@ namespace beast {
@param args A list of one or more arguments to write. @param args A list of one or more arguments to write.
@throws Any exceptions thrown by `boost::lexical_cast`. @throws unspecified Any exceptions thrown by `boost::lexical_cast`.
@note This function participates in overload resolution only if @note This function participates in overload resolution only if
the `streambuf` parameter meets the requirements of Streambuf. the `streambuf` parameter meets the requirements of Streambuf.

View File

@@ -7,7 +7,7 @@
import os ; import os ;
unit-test core_tests : unit-test core-tests :
main.cpp main.cpp
async_completion.cpp async_completion.cpp
basic_streambuf.cpp basic_streambuf.cpp
@@ -27,9 +27,8 @@ unit-test core_tests :
detail/empty_base_optimization.cpp detail/empty_base_optimization.cpp
; ;
unit-test http_tests : unit-test http-tests :
main.cpp main.cpp
../src/beast_http_nodejs_parser.cpp
http/basic_headers.cpp http/basic_headers.cpp
http/basic_parser.cpp http/basic_parser.cpp
http/chunk_encode.cpp http/chunk_encode.cpp
@@ -38,17 +37,19 @@ unit-test http_tests :
http/headers.cpp http/headers.cpp
http/message.cpp http/message.cpp
http/method.cpp http/method.cpp
http/parse_error.cpp
http/parser.cpp http/parser.cpp
http/read.cpp http/read.cpp
http/reason.cpp http/reason.cpp
http/resume_context.cpp http/resume_context.cpp
http/rfc2616.cpp http/rfc2616.cpp
http/rfc7230.cpp
http/streambuf_body.cpp http/streambuf_body.cpp
http/string_body.cpp http/string_body.cpp
http/write.cpp http/write.cpp
; ;
unit-test websocket_tests : unit-test websocket-tests :
main.cpp main.cpp
websocket/error.cpp websocket/error.cpp
websocket/option.cpp websocket/option.cpp
@@ -56,3 +57,9 @@ unit-test websocket_tests :
websocket/static_string.cpp websocket/static_string.cpp
websocket/teardown.cpp websocket/teardown.cpp
; ;
exe parser-bench :
main.cpp
http/nodejs_parser.cpp
http/parser_bench.cpp
;

View File

@@ -7,3 +7,507 @@
// Test that header file is self-contained. // Test that header file is self-contained.
#include <beast/http/basic_parser.hpp> #include <beast/http/basic_parser.hpp>
#include "message_fuzz.hpp"
#include <beast/streambuf.hpp>
#include <beast/buffers_debug.hpp>
#include <beast/write_streambuf.hpp>
#include <beast/http/error.hpp>
#include <beast/http/rfc2616.hpp>
#include <beast/detail/ci_char_traits.hpp>
#include <beast/detail/unit_test/suite.hpp>
#include <boost/utility/string_ref.hpp>
#include <cassert>
#include <climits>
#include <map>
#include <new>
#include <random>
#include <type_traits>
namespace beast {
namespace http {
class basic_parser_test : public beast::detail::unit_test::suite
{
std::mt19937 rng_;
public:
static
std::string
escaped_string(boost::string_ref const& s)
{
std::string out;
out.reserve(s.size());
char const* p = s.data();
while(p != s.end())
{
if(*p == '\r')
out.append("\\r");
else if(*p == '\n')
out.append("\\n");
else if (*p == '\t')
out.append("\\t");
else
out.append(p, 1);
++p;
}
return out;
}
template<bool isRequest>
struct null_parser : basic_parser<isRequest, null_parser<isRequest>>
{
};
template<bool isRequest>
class test_parser :
public basic_parser<isRequest, test_parser<isRequest>>
{
std::string field_;
std::string value_;
void check()
{
if(! value_.empty())
{
rfc2616::trim_right_in_place(value_);
fields.emplace(field_, value_);
field_.clear();
value_.clear();
}
}
public:
std::map<std::string, std::string,
beast::detail::ci_less> fields;
std::string body;
void on_field(boost::string_ref const& s, error_code&)
{
check();
field_.append(s.data(), s.size());
}
void on_value(boost::string_ref const& s, error_code&)
{
value_.append(s.data(), s.size());
}
int on_headers(error_code&)
{
check();
return 0;
}
void on_body(boost::string_ref const& s, error_code&)
{
body.append(s.data(), s.size());
}
};
// Parse the entire input buffer as a valid message,
// then parse in two pieces of all possible lengths.
//
template<class Parser, class F>
void
parse(boost::string_ref const& m, F&& f)
{
{
error_code ec;
Parser p;
p.write(m.data(), m.size(), ec);
if(expect(p.complete()))
if(expect(! ec, ec.message()))
f(p);
}
for(std::size_t i = 1; i < m.size() - 1; ++i)
{
error_code ec;
Parser p;
p.write(&m[0], i, ec);
if(! expect(! ec, ec.message()))
continue;
if(p.complete())
{
f(p);
}
else
{
p.write(&m[i], m.size() - i, ec);
if(! expect(! ec, ec.message()))
continue;
expect(p.complete());
f(p);
}
}
}
// Parse with an expected error code
//
template<bool isRequest>
void
parse_ev(boost::string_ref const& m, parse_error ev)
{
{
error_code ec;
null_parser<isRequest> p;
p.write(m.data(), m.size(), ec);
if(expect(! p.complete()))
expect(ec == ev, ec.message());
}
for(std::size_t i = 1; i < m.size() - 1; ++i)
{
error_code ec;
null_parser<isRequest> p;
p.write(&m[0], i, ec);
if(! expect(! p.complete()))
continue;
if(ec)
{
expect(ec == ev, ec.message());
continue;
}
p.write(&m[i], m.size() - i, ec);
if(! expect(! p.complete()))
continue;
if(! expect(ec == ev, ec.message()))
continue;
}
}
//--------------------------------------------------------------------------
template<class UInt = std::size_t>
UInt
rand(std::size_t n)
{
return static_cast<UInt>(
std::uniform_int_distribution<
std::size_t>{0, n-1}(rng_));
}
//--------------------------------------------------------------------------
// Parse a valid message with expected version
//
template<bool isRequest>
void
version(boost::string_ref const& m,
unsigned major, unsigned minor)
{
parse<null_parser<isRequest>>(m,
[&](null_parser<isRequest> const& p)
{
expect(p.http_major() == major);
expect(p.http_minor() == minor);
});
}
// Parse a valid message with expected flags mask
//
void
checkf(boost::string_ref const& m, std::uint8_t mask)
{
parse<null_parser<true>>(m,
[&](null_parser<true> const& p)
{
expect(p.flags() & mask);
});
}
void
testVersion()
{
version<true>("GET / HTTP/0.0\r\n\r\n", 0, 0);
version<true>("GET / HTTP/0.1\r\n\r\n", 0, 1);
version<true>("GET / HTTP/0.9\r\n\r\n", 0, 9);
version<true>("GET / HTTP/1.0\r\n\r\n", 1, 0);
version<true>("GET / HTTP/1.1\r\n\r\n", 1, 1);
version<true>("GET / HTTP/9.9\r\n\r\n", 9, 9);
version<true>("GET / HTTP/999.999\r\n\r\n", 999, 999);
parse_ev<true>("GET / HTTP/1000.0\r\n\r\n", parse_error::bad_version);
parse_ev<true>("GET / HTTP/0.1000\r\n\r\n", parse_error::bad_version);
parse_ev<true>("GET / HTTP/99999999999999999999.0\r\n\r\n", parse_error::bad_version);
parse_ev<true>("GET / HTTP/0.99999999999999999999\r\n\r\n", parse_error::bad_version);
}
void
testConnection(std::string const& token,
std::uint8_t flag)
{
checkf("GET / HTTP/1.1\r\nConnection:" + token + "\r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection: " + token + "\r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection:\t" + token + "\r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection: \t" + token + "\r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection: " + token + " \r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection: " + token + "\t\r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection: " + token + " \t\r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection: " + token + "\t \r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection: \r\n" " " + token + "\r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection:\t\r\n" " " + token + "\r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection: \r\n" "\t" + token + "\r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection:\t\r\n" "\t" + token + "\r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection: X," + token + "\r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection: X, " + token + "\r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection: X,\t" + token + "\r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection: X,\t " + token + "\r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection: X," + token + " \r\n\r\n", flag);
checkf("GET / HTTP/1.1\r\nConnection: X," + token + "\t\r\n\r\n", flag);
}
void
testContentLength()
{
std::size_t const length = 0;
std::string const length_s =
std::to_string(length);
checkf("GET / HTTP/1.1\r\nContent-Length:"+ length_s + "\r\n\r\n", parse_flag::contentlength);
checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\r\n\r\n", parse_flag::contentlength);
checkf("GET / HTTP/1.1\r\nContent-Length:\t"+ length_s + "\r\n\r\n", parse_flag::contentlength);
checkf("GET / HTTP/1.1\r\nContent-Length: \t"+ length_s + "\r\n\r\n", parse_flag::contentlength);
checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + " \r\n\r\n", parse_flag::contentlength);
checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\t\r\n\r\n", parse_flag::contentlength);
checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + " \t\r\n\r\n", parse_flag::contentlength);
checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\t \r\n\r\n", parse_flag::contentlength);
checkf("GET / HTTP/1.1\r\nContent-Length: \r\n" " "+ length_s + "\r\n\r\n", parse_flag::contentlength);
checkf("GET / HTTP/1.1\r\nContent-Length:\t\r\n" " "+ length_s + "\r\n\r\n", parse_flag::contentlength);
checkf("GET / HTTP/1.1\r\nContent-Length: \r\n" "\t"+ length_s + "\r\n\r\n", parse_flag::contentlength);
checkf("GET / HTTP/1.1\r\nContent-Length:\t\r\n" "\t"+ length_s + "\r\n\r\n", parse_flag::contentlength);
}
void
testTransferEncoding()
{
checkf("GET / HTTP/1.1\r\nTransfer-Encoding:chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked);
checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked);
checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked);
checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked);
checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked \r\n\r\n0\r\n\r\n", parse_flag::chunked);
checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\t\r\n\r\n0\r\n\r\n", parse_flag::chunked);
checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked \t\r\n\r\n0\r\n\r\n", parse_flag::chunked);
checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\t \r\n\r\n0\r\n\r\n", parse_flag::chunked);
checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \r\n" " chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked);
checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\t\r\n" " chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked);
checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \r\n" "\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked);
checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\t\r\n" "\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked );
}
void
testFlags()
{
testConnection("keep-alive",
parse_flag::connection_keep_alive);
testConnection("close",
parse_flag::connection_close);
testConnection("upgrade",
parse_flag::connection_upgrade);
testContentLength();
testTransferEncoding();
checkf(
"GET / HTTP/1.1\r\n"
"Upgrade: x\r\n"
"\r\n",
parse_flag::upgrade
);
parse_ev<true>(
"GET / HTTP/1.1\r\n"
"Transfer-Encoding:chunked\r\n"
"Content-Length: 0\r\n"
"\r\n", parse_error::illegal_content_length);
}
void
testUpgrade()
{
null_parser<true> p;
boost::string_ref s =
"GET / HTTP/1.1\r\nConnection: upgrade\r\nUpgrade: WebSocket\r\n\r\n";
error_code ec;
p.write(s.data(), s.size(), ec);
if(! expect(! ec, ec.message()))
return;
expect(p.complete());
expect(p.upgrade());
}
void testBad()
{
parse_ev<true>(" ", parse_error::bad_method);
parse_ev<true>(" G", parse_error::bad_method);
parse_ev<true>("G:", parse_error::bad_request);
parse_ev<true>("GET /", parse_error::bad_uri);
parse_ev<true>("GET / X", parse_error::bad_version);
parse_ev<true>("GET / HX", parse_error::bad_version);
parse_ev<true>("GET / HTTX", parse_error::bad_version);
parse_ev<true>("GET / HTTPX", parse_error::bad_version);
parse_ev<true>("GET / HTTP/.", parse_error::bad_version);
parse_ev<true>("GET / HTTP/1000", parse_error::bad_version);
parse_ev<true>("GET / HTTP/1. ", parse_error::bad_version);
parse_ev<true>("GET / HTTP/1.1000", parse_error::bad_version);
parse_ev<true>("GET / HTTP/1.1\r ", parse_error::bad_crlf);
parse_ev<true>("GET / HTTP/1.1\r\nf :", parse_error::bad_field);
}
void
testRandomReq(std::size_t N)
{
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
message_fuzz mg;
for(std::size_t i = 0; i < N; ++i)
{
std::string s;
{
streambuf sb;
mg.request(sb);
s.reserve(buffer_size(sb.data()));
for(auto const& b : sb.data())
s.append(buffer_cast<char const*>(b),
buffer_size(b));
}
null_parser<true> p;
for(std::size_t j = 1; j < s.size() - 1; ++j)
{
error_code ec;
p.write(&s[0], j, ec);
if(! expect(! ec, ec.message()))
{
log << escaped_string(s);
break;
}
if(! p.complete())
{
p.write(&s[j], s.size() - j, ec);
if(! expect(! ec, ec.message()))
{
log << escaped_string(s);
break;
}
}
if(! expect(p.complete()))
break;
if(! p.keep_alive())
{
p.~null_parser();
new(&p) null_parser<true>{};
}
}
}
}
void
testRandomResp(std::size_t N)
{
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
message_fuzz mg;
for(std::size_t i = 0; i < N; ++i)
{
std::string s;
{
streambuf sb;
mg.response(sb);
s.reserve(buffer_size(sb.data()));
for(auto const& b : sb.data())
s.append(buffer_cast<char const*>(b),
buffer_size(b));
}
null_parser<false> p;
for(std::size_t j = 1; j < s.size() - 1; ++j)
{
error_code ec;
p.write(&s[0], j, ec);
if(! expect(! ec, ec.message()))
{
log << escaped_string(s);
break;
}
if(! p.complete())
{
p.write(&s[j], s.size() - j, ec);
if(! expect(! ec, ec.message()))
{
log << escaped_string(s);
break;
}
}
if(! expect(p.complete()))
break;
if(! p.keep_alive())
{
p.~null_parser();
new(&p) null_parser<false>{};
}
}
}
}
void testBody()
{
auto match =
[&](std::string const& body)
{
return
[&](test_parser<true> const& p)
{
expect(p.body == body);
};
};
parse<test_parser<true>>(
"GET / HTTP/1.1\r\nContent-Length: 1\r\n\r\n123", match("1"));
parse<test_parser<true>>(
"GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\n123", match("123"));
parse<test_parser<true>>(
"GET / HTTP/1.1\r\nContent-Length: 0\r\n\r\n", match(""));
parse<test_parser<true>>(
"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
"1\r\n"
"a\r\n"
"0\r\n"
"\r\n", match("a"));
parse<test_parser<true>>(
"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
"2\r\n"
"ab\r\n"
"0\r\n"
"\r\n", match("ab"));
parse<test_parser<true>>(
"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
"2\r\n"
"ab\r\n"
"1\r\n"
"c\r\n"
"0\r\n"
"\r\n", match("abc"));
parse<test_parser<true>>(
"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
"10\r\n"
"1234567890123456\r\n"
"0\r\n"
"\r\n", match("1234567890123456"));
}
void run() override
{
testVersion();
testFlags();
testUpgrade();
testBad();
testRandomReq(100);
testRandomResp(100);
testBody();
}
};
BEAST_DEFINE_TESTSUITE(basic_parser,http,beast);
} // http
} // beast

562
test/http/message_fuzz.hpp Normal file
View File

@@ -0,0 +1,562 @@
//
// Copyright (c) 2013-2016 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_MESSAGE_FUZZ_HPP
#define BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP
#include <beast/http/detail/basic_parser.hpp>
#include <beast/write_streambuf.hpp>
#include <cstdint>
#include <random>
#include <string>
namespace beast {
namespace http {
template<class = void>
std::string
escaped_string(boost::string_ref const& s)
{
std::string out;
out.reserve(s.size());
char const* p = s.data();
while(p != s.end())
{
if(*p == '\r')
out.append("\\r");
else if(*p == '\n')
out.append("\\n");
else if (*p == '\t')
out.append("\\t");
else
out.append(p, 1);
++p;
}
return out;
}
// Produces random HTTP messages
//
template<class = void>
class message_fuzz_t
{
std::mt19937 rng_;
static
std::string
to_hex(std::size_t v)
{
if(! v)
return "0";
std::string s;
while(v > 0)
{
s.insert(s.begin(),
"0123456789abcdef"[v&0xf]);
v >>= 4;
}
return s;
}
public:
template<class UInt = std::size_t>
UInt
rand(std::size_t n)
{
return static_cast<UInt>(
std::uniform_int_distribution<
std::size_t>{0, n-1}(rng_));
}
std::string
method()
{
#if 0
// All IANA registered methods
static char const* const list[39] = {
"ACL", "BASELINE-CONTROL", "BIND", "CHECKIN", "CHECKOUT", "CONNECT",
"COPY", "DELETE", "GET", "HEAD", "LABEL", "LINK", "LOCK", "MERGE",
"MKACTIVITY", "MKCALENDAR", "MKCOL", "MKREDIRECTREF", "MKWORKSPACE",
"MOVE", "OPTIONS", "ORDERPATCH", "PATCH", "POST", "PRI", "PROPFIND",
"PROPPATCH", "PUT", "REBIND", "REPORT", "SEARCH", "TRACE", "UNBIND",
"UNCHECKOUT", "UNLINK", "UNLOCK", "UPDATE", "UPDATEREDIRECTREF",
"VERSION-CONTROL"
};
return list[rand(39)];
#else
// methods parsed by nodejs-http-parser
static char const* const list[33] = {
"ACL", "BIND", "CHECKOUT", "CONNECT", "COPY", "DELETE", "HEAD", "GET",
"LINK", "LOCK", "MERGE", "MKCOL", "MKCALENDAR", "MKACTIVITY", "M-SEARCH",
"MOVE", "NOTIFY", "OPTIONS", "PATCH", "POST", "PROPFIND", "PROPPATCH",
"PURGE", "PUT", "REBIND", "REPORT", "SEARCH", "SUBSCRIBE", "TRACE",
"UNBIND", "UNLINK", "UNLOCK", "UNSUBSCRIBE"
};
return list[rand(33)];
#endif
}
std::string
scheme()
{
static char const* const list[241] = {
"aaa", "aaas", "about", "acap", "acct", "acr", "adiumxtra", "afp", "afs",
"aim", "appdata", "apt", "attachment", "aw", "barion", "beshare", "bitcoin",
"blob", "bolo", "callto", "cap", "chrome", "chrome-extension", "cid",
"coap", "coaps", "com-eventbrite-attendee", "content", "crid", "cvs",
"data", "dav", "dict", "dis", "dlna-playcontainer", "dlna-playsingle",
"dns", "dntp", "dtn", "dvb", "ed2k", "example", "facetime", "fax", "feed",
"feedready", "file", "filesystem", "finger", "fish", "ftp", "geo", "gg",
"git", "gizmoproject", "go", "gopher", "gtalk", "h323", "ham", "hcp",
"http", "https", "iax", "icap", "icon", "im", "imap", "info", "iotdisco",
"ipn", "ipp", "ipps", "irc", "irc6", "ircs", "iris", "iris.beep",
"iris.lwz", "iris.xpc", "iris.xpcs", "isostore", "itms", "jabber", "jar",
"jms", "keyparc", "lastfm", "ldap", "ldaps", "magnet", "mailserver",
"mailto", "maps", "market", "message", "mid", "mms",
"modem", "ms-access", "ms-drive-to", "ms-enrollment", "ms-excel",
"ms-getoffice", "ms-help", "ms-infopath", "ms-media-stream-id", "ms-project",
"ms-powerpoint", "ms-publisher", "ms-search-repair",
"ms-secondary-screen-controller", "ms-secondary-screen-setup",
"ms-settings", "ms-settings-airplanemode", "ms-settings-bluetooth",
"ms-settings-camera", "ms-settings-cellular", "ms-settings-cloudstorage",
"ms-settings-emailandaccounts", "ms-settings-language", "ms-settings-location",
"ms-settings-lock", "ms-settings-nfctransactions", "ms-settings-notifications",
"ms-settings-power", "ms-settings-privacy", "ms-settings-proximity",
"ms-settings-screenrotation", "ms-settings-wifi", "ms-settings-workplace",
"ms-spd", "ms-transit-to", "ms-visio", "ms-walk-to", "ms-word", "msnim",
"msrp", "msrps", "mtqp", "mumble", "mupdate", "mvn", "news", "nfs", "ni",
"nih", "nntp", "notes", "oid", "opaquelocktoken", "pack", "palm", "paparazzi",
"pkcs11", "platform", "pop", "pres", "prospero", "proxy", "psyc", "query",
"redis", "rediss", "reload", "res", "resource", "rmi", "rsync", "rtmfp",
"rtmp", "rtsp", "rtsps", "rtspu", "secondlife", "service", "session", "sftp",
"sgn", "shttp", "sieve", "sip", "sips", "skype", "smb", "sms", "smtp",
"snews", "snmp", "soap.beep", "soap.beeps", "soldat", "spotify", "ssh",
"steam", "stun", "stuns", "submit", "svn", "tag", "teamspeak", "tel",
"teliaeid", "telnet", "tftp", "things", "thismessage", "tip", "tn3270",
"tool", "turn", "turns", "tv", "udp", "unreal", "urn", "ut2004", "v-event",
"vemmi", "ventrilo", "videotex", "vnc", "view-source", "wais", "webcal",
"wpid", "ws", "wss", "wtai", "wyciwyg", "xcon", "xcon-userid", "xfire",
"xmlrpc.beep", "xmlrpc.beeps", "xmpp", "xri", "ymsgr", "z39.50", "z39.50r",
"z39.50s:"
};
return list[rand(241)];
}
std::string
pchar()
{
if(rand(4))
return std::string(1,
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
":@&=+$,"[rand(69)]);
std::string s = "%";
s += "0123456789abcdef"[rand(16)];
s += "0123456789abcdef"[rand(16)];
return s;
}
char
uric()
{
return 'a';
}
char
uric_no_slash()
{
return 'a';
}
std::string
param()
{
std::string s;
while(rand(2))
s += pchar();
return s;
}
std::string
query()
{
std::string s;
while(rand(2))
s += uric();
return s;
}
std::string
userinfo()
{
std::string s;
while(rand(2))
s += "a";
return s;
}
/* authority = server | reg_name
reg_name = 1*( unreserved | escaped | "$" | "," |
";" | ":" | "@" | "&" | "=" | "+" )
server = [ [ userinfo "@" ] hostport ]
userinfo = *( unreserved | escaped |
";" | ":" | "&" | "=" | "+" | "$" | "," )
hostport = host [ ":" port ]
host = hostname | IPv4address
hostname = *( domainlabel "." ) toplabel [ "." ]
domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
toplabel = alpha | alpha *( alphanum | "-" ) alphanum
IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
port = *digit
*/
std::string
server()
{
std::string s;
if(rand(2))
s += userinfo() + "@";
return s;
}
std::string
reg_name()
{
std::string s;
s = "a";
while(rand(2))
s += "a";
return s;
}
std::string
authority()
{
if(rand(2))
return server();
return reg_name();
}
std::string
opaque_part()
{
std::string s;
s += uric_no_slash();
while(rand(2))
s += uric();
return s;
}
/* abs_path = "/" path_segments
path_segments = segment *( "/" segment )
segment = *pchar *( ";" param )
param = *pchar
pchar = unreserved | escaped |
":" | "@" | "&" | "=" | "+" | "$" | ","
*/
std::string
abs_path()
{
std::string s = "/";
for(;;)
{
while(rand(2))
s += pchar();
while(rand(2))
s += ";" + param();
if(rand(2))
break;
s.append("/");
}
return s;
}
/* net_path = "//" authority [ abs_path ]
*/
std::string
net_path()
{
std::string s = "//";
s += authority();
if(rand(2))
s += abs_path();
return s;
}
/* absoluteURI = scheme ":" ( hier_part | opaque_part )
scheme = alpha *( alpha | digit | "+" | "-" | "." )
hier_part = ( net_path | abs_path ) [ "?" query ]
abs_path = "/" path_segments
query = *uric
opaque_part = uric_no_slash *uric
*/
std::string
abs_uri()
{
std::string s;
s = scheme() + ":";
if(rand(2))
{
if(rand(2))
s += net_path();
else
s += abs_path();
if(rand(2))
s += "?" + query();
}
else
{
s += opaque_part();
}
return s;
}
std::string
uri()
{
//switch(rand(4))
switch(1)
{
case 0: return abs_uri();
case 1: return abs_path();
case 2: return authority();
default:
case 3: break;
}
return "*";
}
std::string
token()
{
static char constexpr valid[78] =
"!#$%&\'*+-." "0123456789" "ABCDEFGHIJ" "KLMNOPQRST"
"UVWXYZ^_`a" "bcdefghijk" "lmnopqrstu" "vwxyz|~";
std::string s;
s.append(1, valid[rand(77)]);
while(rand(4))
s.append(1, valid[rand(77)]);
return s;
}
#if 0
std::string
uri()
{
static char constexpr alpha[63] =
"0123456789" "ABCDEFGHIJ" "KLMNOPQRST"
"UVWXYZabcd" "efghijklmn" "opqrstuvwx" "yz";
std::string s;
s = "/";
while(rand(4))
s.append(1, alpha[rand(62)]);
return s;
}
#endif
std::string
field()
{ static char const* const list[289] =
{
"A-IM",
"Accept", "Accept-Additions", "Accept-Charset", "Accept-Datetime", "Accept-Encoding",
"Accept-Features", "Accept-Language", "Accept-Patch", "Accept-Ranges", "Age", "Allow",
"ALPN", "Also-Control", "Alt-Svc", "Alt-Used", "Alternate-Recipient", "Alternates",
"Apply-To-Redirect-Ref", "Approved", "Archive", "Archived-At", "Article-Names",
"Article-Updates", "Authentication-Info", "Authentication-Results", "Authorization",
"Auto-Submitted", "Autoforwarded", "Autosubmitted", "Base", "Bcc", "Body", "C-Ext",
"C-Man", "C-Opt", "C-PEP", "C-PEP-Info", "Cache-Control",
"CalDAV-Timezones", "Cc", "Close", "Comments", /*"Connection",*/ "Content-Alternative",
"Content-Base", "Content-Description", "Content-Disposition", "Content-Duration",
"Content-Encoding", "Content-features", "Content-ID", "Content-Identifier",
"Content-Language", /*"Content-Length",*/ "Content-Location", "Content-MD5",
"Content-Range", "Content-Return", "Content-Script-Type", "Content-Style-Type",
"Content-Transfer-Encoding", "Content-Type", "Content-Version", "Control", "Conversion",
"Conversion-With-Loss", "Cookie", "Cookie2", "DASL", "DAV", "DL-Expansion-History", "Date",
"Date-Received", "Default-Style", "Deferred-Delivery", "Delivery-Date", "Delta-Base",
"Depth", "Derived-From", "Destination", "Differential-ID", "Digest",
"Discarded-X400-IPMS-Extensions", "Discarded-X400-MTS-Extensions", "Disclose-Recipients",
"Disposition-Notification-Options", "Disposition-Notification-To", "Distribution",
"DKIM-Signature", "Downgraded-Bcc", "Downgraded-Cc", "Downgraded-Disposition-Notification-To",
"Downgraded-Final-Recipient", "Downgraded-From", "Downgraded-In-Reply-To",
"Downgraded-Mail-From", "Downgraded-Message-Id", "Downgraded-Original-Recipient",
"Downgraded-Rcpt-To", "Downgraded-References", "Downgraded-Reply-To", "Downgraded-Resent-Bcc",
"Downgraded-Resent-Cc", "Downgraded-Resent-From", "Downgraded-Resent-Reply-To",
"Downgraded-Resent-Sender", "Downgraded-Resent-To", "Downgraded-Return-Path",
"Downgraded-Sender", "Downgraded-To", "Encoding", "Encrypted", "ETag", "Expect",
"Expires", "Expiry-Date", "Ext", "Followup-To", "Forwarded", "From",
"Generate-Delivery-Report", "GetProfile", "Hobareg", "Host", "HTTP2-Settings", "IM", "If",
"If-Match", "If-Modified-Since", "If-None-Match", "If-Range", "If-Schedule-Tag-Match",
"If-Unmodified-Since", "Importance", "In-Reply-To", "Incomplete-Copy", "Injection-Date",
"Injection-Info", "Keep-Alive", "Keywords", "Label", "Language", "Last-Modified",
"Latest-Delivery-Time", "Lines", "Link", "List-Archive", "List-Help", "List-ID",
"List-Owner", "List-Post", "List-Subscribe", "List-Unsubscribe", "Location", "Lock-Token",
"Man", "Max-Forwards", "Memento-Datetime", "Message-Context", "Message-ID", "Message-Type",
"Meter", "MIME-Version", "MMHS-Exempted-Address", "MMHS-Extended-Authorisation-Info",
"MMHS-Subject-Indicator-Codes", "MMHS-Handling-Instructions", "MMHS-Message-Instructions",
"MMHS-Codress-Message-Indicator", "MMHS-Originator-Reference", "MMHS-Primary-Precedence",
"MMHS-Copy-Precedence", "MMHS-Message-Type", "MMHS-Other-Recipients-Indicator-To",
"MMHS-Other-Recipients-Indicator-CC", "MMHS-Acp127-Message-Identifier", "MMHS-Originator-PLAD",
"MT-Priority", "Negotiate", "Newsgroups", "NNTP-Posting-Date", "NNTP-Posting-Host",
"Obsoletes", "Opt", "Ordering-Type", "Organization", "Origin",
"Original-Encoded-Information-Types", "Original-From", "Original-Message-ID",
"Original-Recipient", "Original-Sender", "Originator-Return-Address", "Original-Subject",
"Overwrite", "P3P", "Path", "PEP", "PICS-Label", "Pep-Info", "Position", "Posting-Version",
"Pragma", "Prefer", "Preference-Applied", "Prevent-NonDelivery-Report", "Priority",
"ProfileObject", "Protocol", "Protocol-Info", "Protocol-Query", "Protocol-Request",
"Proxy-Authenticate", "Proxy-Authentication-Info", "Proxy-Authorization", "Proxy-Features",
"Proxy-Instruction", "Public", "Public-Key-Pins", "Public-Key-Pins-Report-Only", "Range",
"Received", "Received-SPF", "Redirect-Ref", "References", "Referer", "Relay-Version",
"Reply-By", "Reply-To", "Require-Recipient-Valid-Since", "Resent-Bcc", "Resent-Cc",
"Resent-Date", "Resent-From", "Resent-Message-ID", "Resent-Reply-To", "Resent-Sender",
"Resent-To", "Retry-After", "Return-Path", "Safe", "Schedule-Reply", "Schedule-Tag",
"Sec-WebSocket-Accept", "Sec-WebSocket-Extensions", "Sec-WebSocket-Key",
"Sec-WebSocket-Protocol", "Sec-WebSocket-Version", "Security-Scheme", "See-Also", "Sender",
"Sensitivity", "Server", "Set-Cookie", "Set-Cookie2",
"SetProfile", "SLUG", "SoapAction", "Solicitation", "Status-URI", "Strict-Transport-Security",
"Subject", "Summary", "Supersedes", "Surrogate-Capability", "Surrogate-Control", "TCN",
"TE", "Timeout", "To", "Trailer", /*"Transfer-Encoding",*/ "URI", /*"Upgrade",*/ "User-Agent",
"Variant-Vary", "Vary", "VBR-Info", "Via", "WWW-Authenticate", "Want-Digest", "Warning",
"X400-Content-Identifier", "X400-Content-Return", "X400-Content-Type", "X400-MTS-Identifier",
"X400-Originator", "X400-Received", "X400-Recipients", "X400-Trace", "X-Frame-Options", "Xref"
};
return list[rand(289)];
}
std::string
text()
{
std::string s;
while(rand(3))
{
for(;;)
{
char c = rand<char>(256);
if(detail::is_text(c))
{
s.append(1, c);
break;
}
}
}
return s;
}
std::string
value()
{
std::string s;
while(rand(3))
{
if(rand(5))
{
s.append(text());
}
else
{
// LWS
if(! rand(4))
s.append("\r\n");
s.append(1, rand(2) ? ' ' : '\t');
while(rand(2))
s.append(1, rand(2) ? ' ' : '\t');
}
}
return s;
}
template<class Streambuf>
void
headers(Streambuf& sb)
{
while(rand(6))
{
write(sb, field());
write(sb, rand(4) ? ": " : ":");
write(sb, value());
write(sb, "\r\n");
}
}
template<class Streambuf>
void
body(Streambuf& sb)
{
if(! rand(4))
{
write(sb, "Content-Length: 0\r\n\r\n");
return;
}
if(rand(2))
{
auto const len = rand(500);
write(sb, "Content-Length: ", len, "\r\n\r\n");
for(auto const& b : sb.prepare(len))
{
auto p = boost::asio::buffer_cast<char*>(b);
auto n = boost::asio::buffer_size(b);
while(n--)
*p++ = static_cast<char>(32 + rand(26+26+10+6));
}
sb.commit(len);
}
else
{
auto len = rand(500);
write(sb, "Transfer-Encoding: chunked\r\n\r\n");
while(len > 0)
{
auto n = std::min(1 + rand(300), len);
len -= n;
write(sb, to_hex(n), "\r\n");
for(auto const& b : sb.prepare(n))
{
auto p = boost::asio::buffer_cast<char*>(b);
auto m = boost::asio::buffer_size(b);
while(m--)
*p++ = static_cast<char>(32 + rand(26+26+10+6));
}
sb.commit(n);
write(sb, "\r\n");
}
write(sb, "0\r\n\r\n");
}
}
template<class Streambuf>
void
request(Streambuf& sb)
{
write(sb, method(), " ", uri(), " HTTP/1.1\r\n");
headers(sb);
body(sb);
}
template<class Streambuf>
void
response(Streambuf& sb)
{
write(sb, "HTTP/1.");
write(sb, rand(2) ? "0" : "1");
write(sb, " ", 100 + rand(401), " ");
write(sb, token());
write(sb, "\r\n");
headers(sb);
body(sb);
write(sb, "\r\n");
}
};
using message_fuzz = message_fuzz_t<>;
} // http
} // beast
#endif

View File

@@ -21,7 +21,7 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
#include "../../include/beast/http/impl/http_parser.h" #include "http_parser.h"
#include <assert.h> #include <assert.h>
#include <stddef.h> #include <stddef.h>
#include <ctype.h> #include <ctype.h>

View File

@@ -10,7 +10,7 @@
# pragma warning (disable: 4127) // conditional expression is constant # pragma warning (disable: 4127) // conditional expression is constant
# pragma warning (disable: 4244) // integer conversion, possible loss of data # pragma warning (disable: 4244) // integer conversion, possible loss of data
#endif #endif
#include "http-parser/http_parser.c" #include "nodejs-parser/http_parser.c"
#ifdef _MSC_VER #ifdef _MSC_VER
# pragma warning (pop) # pragma warning (pop)
#endif #endif

881
test/http/nodejs_parser.hpp Normal file
View File

@@ -0,0 +1,881 @@
//
// Copyright (c) 2013-2016 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_NODEJS_PARSER_HPP
#define BEAST_HTTP_NODEJS_PARSER_HPP
#include "nodejs-parser/http_parser.h"
#include <beast/http/method.hpp>
#include <beast/http/basic_parser.hpp>
#include <beast/http/rfc2616.hpp>
#include <beast/type_check.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/system/error_code.hpp>
#include <cstdint>
#include <string>
#include <type_traits>
namespace beast {
namespace http {
namespace detail {
class nodejs_message_category
: public boost::system::error_category
{
public:
const char*
name() const noexcept override
{
return "nodejs-http-error";
}
std::string
message(int ev) const override
{
return http_errno_description(
static_cast<http_errno>(ev));
}
boost::system::error_condition
default_error_condition(int ev) const noexcept override
{
return boost::system::error_condition{ev, *this};
}
bool
equivalent(int ev,
boost::system::error_condition const& condition
) const noexcept override
{
return condition.value() == ev &&
&condition.category() == this;
}
bool
equivalent(boost::system::error_code const& error,
int ev) const noexcept override
{
return error.value() == ev &&
&error.category() == this;
}
};
template<class = void>
boost::system::error_code
make_nodejs_error(int http_errno)
{
static nodejs_message_category const mc{};
return boost::system::error_code{http_errno, mc};
}
inline
beast::http::method_t
convert_http_method(http_method m)
{
using namespace beast;
switch (m)
{
case HTTP_DELETE: return http::method_t::http_delete;
case HTTP_GET: return http::method_t::http_get;
case HTTP_HEAD: return http::method_t::http_head;
case HTTP_POST: return http::method_t::http_post;
case HTTP_PUT: return http::method_t::http_put;
// pathological
case HTTP_CONNECT: return http::method_t::http_connect;
case HTTP_OPTIONS: return http::method_t::http_options;
case HTTP_TRACE: return http::method_t::http_trace;
// webdav
case HTTP_COPY: return http::method_t::http_copy;
case HTTP_LOCK: return http::method_t::http_lock;
case HTTP_MKCOL: return http::method_t::http_mkcol;
case HTTP_MOVE: return http::method_t::http_move;
case HTTP_PROPFIND: return http::method_t::http_propfind;
case HTTP_PROPPATCH: return http::method_t::http_proppatch;
case HTTP_SEARCH: return http::method_t::http_search;
case HTTP_UNLOCK: return http::method_t::http_unlock;
case HTTP_BIND: return http::method_t::http_bind;
case HTTP_REBIND: return http::method_t::http_rebind;
case HTTP_UNBIND: return http::method_t::http_unbind;
case HTTP_ACL: return http::method_t::http_acl;
// subversion
case HTTP_REPORT: return http::method_t::http_report;
case HTTP_MKACTIVITY: return http::method_t::http_mkactivity;
case HTTP_CHECKOUT: return http::method_t::http_checkout;
case HTTP_MERGE: return http::method_t::http_merge;
// upnp
case HTTP_MSEARCH: return http::method_t::http_msearch;
case HTTP_NOTIFY: return http::method_t::http_notify;
case HTTP_SUBSCRIBE: return http::method_t::http_subscribe;
case HTTP_UNSUBSCRIBE: return http::method_t::http_unsubscribe;
// RFC-5789
case HTTP_PATCH: return http::method_t::http_patch;
case HTTP_PURGE: return http::method_t::http_purge;
// CalDav
case HTTP_MKCALENDAR: return http::method_t::http_mkcalendar;
// RFC-2068, section 19.6.1.2
case HTTP_LINK: return http::method_t::http_link;
case HTTP_UNLINK: return http::method_t::http_unlink;
};
return http::method_t::http_get;
}
} // detail
template<class Derived>
class nodejs_basic_parser
{
http_parser state_;
boost::system::error_code* ec_;
bool complete_ = false;
std::string url_;
std::string status_;
std::string field_;
std::string value_;
public:
using error_code = boost::system::error_code;
nodejs_basic_parser(nodejs_basic_parser&& other);
nodejs_basic_parser&
operator=(nodejs_basic_parser&& other);
nodejs_basic_parser(nodejs_basic_parser const& other);
nodejs_basic_parser& operator=(nodejs_basic_parser const& other);
explicit
nodejs_basic_parser(bool request) noexcept;
bool
complete() const noexcept
{
return complete_;
}
std::size_t
write(void const* data, std::size_t size)
{
error_code ec;
auto const used = write(data, size, ec);
if(ec)
throw boost::system::system_error{ec};
return used;
}
std::size_t
write(void const* data, std::size_t size,
error_code& ec);
template<class ConstBufferSequence>
std::size_t
write(ConstBufferSequence const& buffers)
{
error_code ec;
auto const used = write(buffers, ec);
if(ec)
throw boost::system::system_error{ec};
return used;
}
template<class ConstBufferSequence>
std::size_t
write(ConstBufferSequence const& buffers,
error_code& ec);
void
write_eof()
{
error_code ec;
write_eof(ec);
if(ec)
throw boost::system::system_error{ec};
}
void
write_eof(error_code& ec);
private:
Derived&
impl()
{
return *static_cast<Derived*>(this);
}
template<class C>
class has_on_start_t
{
template<class T, class R =
decltype(std::declval<T>().on_start(), std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_start =
std::integral_constant<bool, has_on_start_t<C>::value>;
void
call_on_start(std::true_type)
{
impl().on_start();
}
void
call_on_start(std::false_type)
{
}
template<class C>
class has_on_field_t
{
template<class T, class R =
decltype(std::declval<T>().on_field(
std::declval<std::string const&>(),
std::declval<std::string const&>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_field =
std::integral_constant<bool, has_on_field_t<C>::value>;
void
call_on_field(std::string const& field,
std::string const& value, std::true_type)
{
impl().on_field(field, value);
}
void
call_on_field(std::string const&, std::string const&,
std::false_type)
{
}
template<class C>
class has_on_headers_complete_t
{
template<class T, class R =
decltype(std::declval<T>().on_headers_complete(
std::declval<error_code&>()), std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_headers_complete =
std::integral_constant<bool, has_on_headers_complete_t<C>::value>;
void
call_on_headers_complete(error_code& ec, std::true_type)
{
impl().on_headers_complete(ec);
}
void
call_on_headers_complete(error_code&, std::false_type)
{
}
template<class C>
class has_on_request_t
{
template<class T, class R =
decltype(std::declval<T>().on_request(
std::declval<method_t>(), std::declval<std::string>(),
std::declval<int>(), std::declval<int>(),
std::declval<bool>(), std::declval<bool>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_request =
std::integral_constant<bool, has_on_request_t<C>::value>;
void
call_on_request(method_t method, std::string url,
int major, int minor, bool keep_alive, bool upgrade,
std::true_type)
{
impl().on_request(
method, url, major, minor, keep_alive, upgrade);
}
void
call_on_request(method_t, std::string, int, int, bool, bool,
std::false_type)
{
}
template<class C>
class has_on_response_t
{
template<class T, class R =
decltype(std::declval<T>().on_response(
std::declval<int>(), std::declval<std::string>,
std::declval<int>(), std::declval<int>(),
std::declval<bool>(), std::declval<bool>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
#if 0
using type = decltype(check<C>(0));
#else
// VFALCO Trait seems broken for http::parser
using type = std::true_type;
#endif
public:
static bool const value = type::value;
};
template<class C>
using has_on_response =
std::integral_constant<bool, has_on_response_t<C>::value>;
bool
call_on_response(int status, std::string text,
int major, int minor, bool keep_alive, bool upgrade,
std::true_type)
{
return impl().on_response(
status, text, major, minor, keep_alive, upgrade);
}
bool
call_on_response(int, std::string, int, int, bool, bool,
std::false_type)
{
// VFALCO Certainly incorrect
return true;
}
template<class C>
class has_on_body_t
{
template<class T, class R =
decltype(std::declval<T>().on_body(
std::declval<void const*>(), std::declval<std::size_t>(),
std::declval<error_code&>()), std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_body =
std::integral_constant<bool, has_on_body_t<C>::value>;
void
call_on_body(void const* data, std::size_t bytes,
error_code& ec, std::true_type)
{
impl().on_body(data, bytes, ec);
}
void
call_on_body(void const*, std::size_t,
error_code&, std::false_type)
{
}
template<class C>
class has_on_complete_t
{
template<class T, class R =
decltype(std::declval<T>().on_complete(), std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_complete =
std::integral_constant<bool, has_on_complete_t<C>::value>;
void
call_on_complete(std::true_type)
{
impl().on_complete();
}
void
call_on_complete(std::false_type)
{
}
void
check_header();
static int cb_message_start(http_parser*);
static int cb_url(http_parser*, char const*, std::size_t);
static int cb_status(http_parser*, char const*, std::size_t);
static int cb_header_field(http_parser*, char const*, std::size_t);
static int cb_header_value(http_parser*, char const*, std::size_t);
static int cb_headers_complete(http_parser*);
static int cb_body(http_parser*, char const*, std::size_t);
static int cb_message_complete(http_parser*);
static int cb_chunk_header(http_parser*);
static int cb_chunk_complete(http_parser*);
struct hooks_t : http_parser_settings
{
hooks_t()
{
http_parser_settings_init(this);
on_message_begin = &nodejs_basic_parser::cb_message_start;
on_url = &nodejs_basic_parser::cb_url;
on_status = &nodejs_basic_parser::cb_status;
on_header_field = &nodejs_basic_parser::cb_header_field;
on_header_value = &nodejs_basic_parser::cb_header_value;
on_headers_complete = &nodejs_basic_parser::cb_headers_complete;
on_body = &nodejs_basic_parser::cb_body;
on_message_complete = &nodejs_basic_parser::cb_message_complete;
on_chunk_header = &nodejs_basic_parser::cb_chunk_header;
on_chunk_complete = &nodejs_basic_parser::cb_chunk_complete;
}
};
static
http_parser_settings const*
hooks();
};
template<class Derived>
template<class ConstBufferSequence>
std::size_t
nodejs_basic_parser<Derived>::write(
ConstBufferSequence const& buffers, error_code& ec)
{
static_assert(beast::is_ConstBufferSequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
std::size_t bytes_used = 0;
for (auto const& buffer : buffers)
{
auto const n = write(
buffer_cast<void const*>(buffer),
buffer_size(buffer), ec);
if(ec)
return 0;
bytes_used += n;
if(complete())
break;
}
return bytes_used;
}
template<class Derived>
http_parser_settings const*
nodejs_basic_parser<Derived>::hooks()
{
static hooks_t const h;
return &h;
}
template<class Derived>
nodejs_basic_parser<Derived>::
nodejs_basic_parser(nodejs_basic_parser&& other)
{
state_ = other.state_;
state_.data = this;
complete_ = other.complete_;
url_ = std::move(other.url_);
status_ = std::move(other.status_);
field_ = std::move(other.field_);
value_ = std::move(other.value_);
}
template<class Derived>
auto
nodejs_basic_parser<Derived>::operator=(nodejs_basic_parser&& other) ->
nodejs_basic_parser&
{
state_ = other.state_;
state_.data = this;
complete_ = other.complete_;
url_ = std::move(other.url_);
status_ = std::move(other.status_);
field_ = std::move(other.field_);
value_ = std::move(other.value_);
return *this;
}
template<class Derived>
nodejs_basic_parser<Derived>::
nodejs_basic_parser(nodejs_basic_parser const& other)
{
state_ = other.state_;
state_.data = this;
complete_ = other.complete_;
url_ = other.url_;
status_ = other.status_;
field_ = other.field_;
value_ = other.value_;
}
template<class Derived>
auto
nodejs_basic_parser<Derived>::
operator=(nodejs_basic_parser const& other) ->
nodejs_basic_parser&
{
state_ = other.state_;
state_.data = this;
complete_ = other.complete_;
url_ = other.url_;
status_ = other.status_;
field_ = other.field_;
value_ = other.value_;
return *this;
}
template<class Derived>
nodejs_basic_parser<Derived>::nodejs_basic_parser(bool request) noexcept
{
state_.data = this;
http_parser_init(&state_, request
? http_parser_type::HTTP_REQUEST
: http_parser_type::HTTP_RESPONSE);
}
template<class Derived>
std::size_t
nodejs_basic_parser<Derived>::write(void const* data,
std::size_t size, error_code& ec)
{
ec_ = &ec;
auto const n = http_parser_execute(
&state_, hooks(),
static_cast<const char*>(data), size);
if(! ec)
ec = detail::make_nodejs_error(
static_cast<int>(state_.http_errno));
if(ec)
return 0;
return n;
}
template<class Derived>
void
nodejs_basic_parser<Derived>::write_eof(error_code& ec)
{
ec_ = &ec;
http_parser_execute(
&state_, hooks(), nullptr, 0);
if(! ec)
ec = detail::make_nodejs_error(
static_cast<int>(state_.http_errno));
}
template<class Derived>
void
nodejs_basic_parser<Derived>::check_header()
{
if (! value_.empty())
{
rfc2616::trim_right_in_place(value_);
call_on_field(field_, value_,
has_on_field<Derived>{});
field_.clear();
value_.clear();
}
}
template<class Derived>
int
nodejs_basic_parser<Derived>::cb_message_start(http_parser* p)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.complete_ = false;
t.url_.clear();
t.status_.clear();
t.field_.clear();
t.value_.clear();
t.call_on_start(has_on_start<Derived>{});
return 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::cb_url(http_parser* p,
char const* in, std::size_t bytes)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.url_.append(in, bytes);
return 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::cb_status(http_parser* p,
char const* in, std::size_t bytes)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.status_.append(in, bytes);
return 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::cb_header_field(http_parser* p,
char const* in, std::size_t bytes)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.check_header();
t.field_.append(in, bytes);
return 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::cb_header_value(http_parser* p,
char const* in, std::size_t bytes)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.value_.append(in, bytes);
return 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::cb_headers_complete(http_parser* p)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.check_header();
t.call_on_headers_complete(*t.ec_,
has_on_headers_complete<Derived>{});
if(*t.ec_)
return 1;
bool const keep_alive =
http_should_keep_alive(p) != 0;
if(p->type == http_parser_type::HTTP_REQUEST)
{
t.call_on_request(detail::convert_http_method(
http_method(p->method)), t.url_,
p->http_major, p->http_minor, keep_alive,
p->upgrade, has_on_request<Derived>{});
return 0;
}
return t.call_on_response(p->status_code, t.status_,
p->http_major, p->http_minor, keep_alive,
p->upgrade, has_on_response<Derived>{}) ? 0 : 1;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::cb_body(http_parser* p,
char const* in, std::size_t bytes)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.call_on_body(in, bytes, *t.ec_, has_on_body<Derived>{});
return *t.ec_ ? 1 : 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::cb_message_complete(http_parser* p)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.complete_ = true;
t.call_on_complete(has_on_complete<Derived>{});
return 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::cb_chunk_header(http_parser*)
{
return 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::cb_chunk_complete(http_parser*)
{
return 0;
}
} // http
} // beast
#include <beast/http/error.hpp>
#include <beast/http/message.hpp>
#include <boost/optional.hpp>
#include <functional>
#include <type_traits>
#include <utility>
namespace beast {
namespace http {
/** A HTTP parser.
The parser may only be used once.
*/
template<bool isRequest, class Body, class Headers>
class nodejs_parser
: public nodejs_basic_parser<nodejs_parser<isRequest, Body, Headers>>
{
using message_type =
message<isRequest, Body, Headers>;
message_type m_;
typename message_type::body_type::reader r_;
bool started_ = false;
public:
nodejs_parser(nodejs_parser&&) = default;
nodejs_parser()
: http::nodejs_basic_parser<nodejs_parser>(isRequest)
, r_(m_)
{
}
/// Returns `true` if at least one byte has been processed
bool
started()
{
return started_;
}
message_type
release()
{
return std::move(m_);
}
private:
friend class http::nodejs_basic_parser<nodejs_parser>;
void
on_start()
{
started_ = true;
}
void
on_field(std::string const& field, std::string const& value)
{
m_.headers.insert(field, value);
}
void
on_headers_complete(error_code&)
{
// vFALCO TODO Decode the Content-Length and
// Transfer-Encoding, see if we can reserve the buffer.
//
// r_.reserve(content_length)
}
bool
on_request(http::method_t method, std::string const& url,
int major, int minor, bool keep_alive, bool upgrade,
std::true_type)
{
m_.method = method;
m_.url = url;
m_.version = major * 10 + minor;
return true;
}
bool
on_request(http::method_t, std::string const&,
int, int, bool, bool,
std::false_type)
{
return true;
}
bool
on_request(http::method_t method, std::string const& url,
int major, int minor, bool keep_alive, bool upgrade)
{
return on_request(method, url,
major, minor, keep_alive, upgrade,
typename message_type::is_request{});
}
bool
on_response(int status, std::string const& reason,
int major, int minor, bool keep_alive, bool upgrade,
std::true_type)
{
m_.status = status;
m_.reason = reason;
m_.version = major * 10 + minor;
// VFALCO TODO return expect_body_
return true;
}
bool
on_response(int, std::string const&, int, int, bool, bool,
std::false_type)
{
return true;
}
bool
on_response(int status, std::string const& reason,
int major, int minor, bool keep_alive, bool upgrade)
{
return on_response(
status, reason, major, minor, keep_alive, upgrade,
std::integral_constant<bool, ! message_type::is_request::value>{});
}
void
on_body(void const* data,
std::size_t size, error_code& ec)
{
r_.write(data, size, ec);
}
void
on_complete()
{
}
};
} // http
} // beast
#endif

View File

@@ -0,0 +1,9 @@
//
// Copyright (c) 2013-2016 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>

157
test/http/parser_bench.cpp Normal file
View File

@@ -0,0 +1,157 @@
//
// Copyright (c) 2013-2016 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 "nodejs_parser.hpp"
#include "message_fuzz.hpp"
#include <beast/http.hpp>
#include <beast/streambuf.hpp>
#include <beast/buffers_debug.hpp>
#include <beast/detail/unit_test/suite.hpp>
#include <chrono>
#include <iostream>
#include <vector>
namespace beast {
namespace http {
class parser_bench_test : public beast::detail::unit_test::suite
{
public:
static std::size_t constexpr N = 2000;
using corpus = std::vector<streambuf>;
corpus creq_;
corpus cres_;
std::size_t size_ = 0;
parser_bench_test()
{
creq_ = build_corpus(N/2, std::true_type{});
cres_ = build_corpus(N/2, std::false_type{});
}
corpus
build_corpus(std::size_t N, std::true_type)
{
corpus v;
v.resize(N);
message_fuzz mg;
for(std::size_t i = 0; i < N; ++i)
{
mg.request(v[i]);
//log << debug::buffers_to_string(v[i].data()) << "\r";
size_ += v[i].size();
}
return v;
}
corpus
build_corpus(std::size_t N, std::false_type)
{
corpus v;
v.resize(N);
message_fuzz mg;
for(std::size_t i = 0; i < N; ++i)
{
mg.response(v[i]);
//log << debug::buffers_to_string(v[i].data()) << "\r";
size_ += v[i].size();
}
return v;
}
template<class Parser>
void
testParser(std::size_t repeat, corpus const& v)
{
while(repeat--)
for(auto const& sb : v)
{
Parser p;
error_code ec;
p.write(sb.data(), ec);
if(! expect(! ec, ec.message()))
log << debug::buffers_to_string(
sb.data()) << std::endl;
}
}
template<class Function>
void
timedTest(std::size_t repeat, std::string const& name, Function&& f)
{
using namespace std::chrono;
using clock_type = std::chrono::high_resolution_clock;
log << name;
for(std::size_t trial = 1; trial <= repeat; ++trial)
{
auto const t0 = clock_type::now();
f();
auto const elapsed = clock_type::now() - t0;
log <<
"Trial " << trial << ": " <<
duration_cast<milliseconds>(elapsed).count() << " ms";
}
}
template<bool isRequest>
struct null_parser : basic_parser<isRequest, null_parser<isRequest>>
{
};
void
testSpeed()
{
static std::size_t constexpr Trials = 3;
static std::size_t constexpr Repeat = 50;
log << "sizeof(request parser) == " <<
sizeof(basic_parser<true, null_parser<true>>);
log << "sizeof(response parser) == " <<
sizeof(basic_parser<false, null_parser<true>>);
testcase << "Parser speed test, " <<
((Repeat * size_ + 512) / 1024) << "KB in " <<
(Repeat * (creq_.size() + cres_.size())) << " messages";
timedTest(Trials, "nodejs_parser",
[&]
{
testParser<nodejs_parser<
true, streambuf_body, http_headers>>(
Repeat, creq_);
testParser<nodejs_parser<
false, streambuf_body, http_headers>>(
Repeat, cres_);
});
timedTest(Trials, "http::basic_parser",
[&]
{
testParser<parser<
true, streambuf_body, http_headers>>(
Repeat, creq_);
testParser<parser<
false, streambuf_body, http_headers>>(
Repeat, cres_);
});
pass();
}
void run() override
{
pass();
testSpeed();
}
};
BEAST_DEFINE_TESTSUITE(parser_bench,http,beast);
} // http
} // beast

9
test/http/rfc7230.cpp Normal file
View File

@@ -0,0 +1,9 @@
//
// Copyright (c) 2013-2016 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/rfc7230.hpp>