Add headers_parser:

This allows just the HTTP headers to be parsed, and
the choice of body to be deferred to a subsequent
call to parse.
This commit is contained in:
Vinnie Falco
2016-10-16 19:28:35 -04:00
parent 4ded6cff76
commit f3b22f74b2
10 changed files with 522 additions and 56 deletions

View File

@@ -11,6 +11,7 @@
* Tidy up documentation
* Add basic_parser_v1::reset
* Fix handling of body_what::pause in basic_parser_v1
* Add headers_parser
API Changes:

View File

@@ -34,6 +34,7 @@
<member><link linkend="beast.ref.http__basic_parser_v1">basic_parser_v1</link></member>
<member><link linkend="beast.ref.http__empty_body">empty_body</link></member>
<member><link linkend="beast.ref.http__headers">headers</link></member>
<member><link linkend="beast.ref.http__headers_parser">headers_parser</link></member>
<member><link linkend="beast.ref.http__message">message</link></member>
<member><link linkend="beast.ref.http__message_headers">message_headers</link></member>
<member><link linkend="beast.ref.http__parser_v1">parser_v1</link></member>
@@ -65,6 +66,7 @@
<member><link linkend="beast.ref.http__prepare">prepare</link></member>
<member><link linkend="beast.ref.http__read">read</link></member>
<member><link linkend="beast.ref.http__swap">swap</link></member>
<member><link linkend="beast.ref.http__with_body">with_body</link></member>
<member><link linkend="beast.ref.http__write">write</link></member>
</simplelist>
<bridgehead renderas="sect3">Type Traits</bridgehead>

View File

@@ -226,6 +226,9 @@ template<bool isRequest, class Derived>
class basic_parser_v1 : public detail::parser_base
{
private:
template<bool, class>
friend class basic_parser_v1;
using self = basic_parser_v1;
typedef void(self::*pmf_t)(error_code&, boost::string_ref const&);
@@ -284,15 +287,19 @@ private:
bool upgrade_ : 1; // true if parser exited for upgrade
public:
/// Copy constructor.
basic_parser_v1(basic_parser_v1 const&) = default;
/// Copy assignment.
basic_parser_v1& operator=(basic_parser_v1 const&) = default;
/// Default constructor
basic_parser_v1();
/// Copy constructor.
template<class OtherDerived>
basic_parser_v1(basic_parser_v1<
isRequest, OtherDerived> const& other);
/// Copy assignment.
template<class OtherDerived>
basic_parser_v1& operator=(basic_parser_v1<
isRequest, OtherDerived> const& other);
/** Set options on the parser.
@param args One or more parser options to set.

View File

@@ -0,0 +1,234 @@
//
// 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_HEADERS_PARSER_V1_HPP
#define BEAST_HTTP_HEADERS_PARSER_V1_HPP
#include <beast/http/basic_parser_v1.hpp>
#include <beast/http/concepts.hpp>
#include <beast/http/message.hpp>
#include <beast/core/error.hpp>
#include <boost/assert.hpp>
#include <boost/optional.hpp>
#include <string>
#include <type_traits>
#include <utility>
namespace beast {
namespace http {
namespace detail {
struct request_parser_base
{
std::string method_;
std::string uri_;
};
struct response_parser_base
{
std::string reason_;
};
} // detail
/** A parser for HTTP/1 request and response headers.
This class uses the HTTP/1 wire format parser to
convert a series of octets into a @ref request_headers
or @ref @response_headers.
@note A new instance of the parser is required for each message.
*/
template<bool isRequest, class Headers>
class headers_parser_v1
: public basic_parser_v1<isRequest,
headers_parser_v1<isRequest, Headers>>
, private std::conditional<isRequest,
detail::request_parser_base,
detail::response_parser_base>::type
{
public:
/// The type of message this parser produces.
using headers_type =
message_headers<isRequest, Headers>;
private:
// VFALCO Check Headers requirements?
std::string field_;
std::string value_;
headers_type h_;
bool flush_ = false;
public:
/// Default constructor
headers_parser_v1() = default;
/// Move constructor
headers_parser_v1(headers_parser_v1&&) = default;
/// Copy constructor (disallowed)
headers_parser_v1(headers_parser_v1 const&) = delete;
/// Move assignment (disallowed)
headers_parser_v1& operator=(headers_parser_v1&&) = delete;
/// Copy assignment (disallowed)
headers_parser_v1& operator=(headers_parser_v1 const&) = delete;
/** Construct the parser.
@param args Forwarded to the message headers constructor.
*/
#if GENERATING_DOCS
template<class... Args>
explicit
headers_parser_v1(Args&&... args);
#else
template<class Arg1, class... ArgN,
class = typename std::enable_if<! std::is_same<
typename std::decay<Arg1>::type, headers_parser_v1>::value>>
explicit
headers_parser_v1(Arg1&& arg1, ArgN&&... argn)
: h_(std::forward<Arg1>(arg1),
std::forward<ArgN>(argn)...)
{
}
#endif
/** Returns the parsed headers.
Only valid if @ref complete would return `true`.
*/
headers_type const&
get() const
{
return h_;
}
/** Returns the parsed headers.
Only valid if @ref complete would return `true`.
*/
headers_type&
get()
{
return h_;
}
/** Returns ownership of the parsed headers.
Ownership is transferred to the caller. Only
valid if @ref complete would return `true`.
Requires:
`message_headers<isRequest, Headers>` is @b MoveConstructible
*/
headers_type
release()
{
static_assert(std::is_move_constructible<decltype(h_)>::value,
"MoveConstructible requirements not met");
return std::move(h_);
}
private:
friend class basic_parser_v1<isRequest, headers_parser_v1>;
void flush()
{
if(! flush_)
return;
flush_ = false;
BOOST_ASSERT(! field_.empty());
h_.headers.insert(field_, value_);
field_.clear();
value_.clear();
}
void on_start(error_code&)
{
}
void on_method(boost::string_ref const& s, error_code&)
{
this->method_.append(s.data(), s.size());
}
void on_uri(boost::string_ref const& s, error_code&)
{
this->uri_.append(s.data(), s.size());
}
void on_reason(boost::string_ref const& s, error_code&)
{
this->reason_.append(s.data(), s.size());
}
void on_request_or_response(std::true_type)
{
h_.method = std::move(this->method_);
h_.url = std::move(this->uri_);
}
void on_request_or_response(std::false_type)
{
h_.status = this->status_code();
h_.reason = std::move(this->reason_);
}
void on_request(error_code& ec)
{
on_request_or_response(
std::integral_constant<bool, isRequest>{});
}
void on_response(error_code& ec)
{
on_request_or_response(
std::integral_constant<bool, isRequest>{});
}
void on_field(boost::string_ref const& s, error_code&)
{
flush();
field_.append(s.data(), s.size());
}
void on_value(boost::string_ref const& s, error_code&)
{
value_.append(s.data(), s.size());
flush_ = true;
}
void
on_headers(std::uint64_t, error_code&)
{
flush();
h_.version = 10 * this->http_major() + this->http_minor();
}
body_what
on_body_what(std::uint64_t, error_code&)
{
return body_what::pause;
}
void on_body(boost::string_ref const&, error_code&)
{
}
void on_complete(error_code&)
{
}
};
} // http
} // beast
#endif

View File

@@ -50,6 +50,55 @@ basic_parser_v1()
init();
}
template<bool isRequest, class Derived>
template<class OtherDerived>
basic_parser_v1<isRequest, Derived>::
basic_parser_v1(basic_parser_v1<
isRequest, OtherDerived> const& other)
: h_max_(other.h_max_)
, h_left_(other.h_left_)
, b_max_(other.b_max_)
, b_left_(other.b_left_)
, content_length_(other.content_length_)
, cb_(nullptr)
, s_(other.s_)
, flags_(other.flags_)
, fs_(other.fs_)
, pos_(other.pos_)
, http_major_(other.http_major_)
, http_minor_(other.http_minor_)
, status_code_(other.status_code_)
, upgrade_(other.upgrade_)
{
BOOST_ASSERT(! other.cb_);
}
template<bool isRequest, class Derived>
template<class OtherDerived>
auto
basic_parser_v1<isRequest, Derived>::
operator=(basic_parser_v1<
isRequest, OtherDerived> const& other) ->
basic_parser_v1&
{
BOOST_ASSERT(! other.cb_);
h_max_ = other.h_max_;
h_left_ = other.h_left_;
b_max_ = other.b_max_;
b_left_ = other.b_left_;
content_length_ = other.content_length_;
cb_ = nullptr;
s_ = other.s_;
flags_ = other.flags_;
fs_ = other.fs_;
pos_ = other.pos_;
http_major_ = other.http_major_;
http_minor_ = other.http_minor_;
status_code_ = other.status_code_;
upgrade_ = other.upgrade_;
return *this;
}
template<bool isRequest, class Derived>
bool
basic_parser_v1<isRequest, Derived>::
@@ -942,6 +991,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
case body_what::pause:
return used();
}
--p;
s_ = s_headers_done;
// fall through
}
@@ -1198,6 +1248,7 @@ void
basic_parser_v1<isRequest, Derived>::
reset()
{
cb_ = nullptr;
h_left_ = h_max_;
b_left_ = b_max_;
reset(std::integral_constant<bool, isRequest>{});

View File

@@ -8,8 +8,8 @@
#ifndef BEAST_HTTP_PARSER_V1_HPP
#define BEAST_HTTP_PARSER_V1_HPP
#include <beast/http/basic_parser_v1.hpp>
#include <beast/http/concepts.hpp>
#include <beast/http/headers_parser_v1.hpp>
#include <beast/http/message.hpp>
#include <beast/core/error.hpp>
#include <boost/assert.hpp>
@@ -22,21 +22,6 @@
namespace beast {
namespace http {
namespace detail {
struct parser_request
{
std::string method_;
std::string uri_;
};
struct parser_response
{
std::string reason_;
};
} // detail
/** Skip body option.
The options controls whether or not the parser expects to see a
@@ -80,7 +65,8 @@ class parser_v1
: public basic_parser_v1<isRequest,
parser_v1<isRequest, Body, Headers>>
, private std::conditional<isRequest,
detail::parser_request, detail::parser_response>::type
detail::request_parser_base,
detail::response_parser_base>::type
{
public:
/// The type of message this parser produces.
@@ -123,7 +109,7 @@ public:
/** Construct the parser.
@param args A list of arguments forwarded to the message constructor.
@param args Forwarded to the message constructor.
*/
#if GENERATING_DOCS
template<class... Args>
@@ -141,7 +127,23 @@ public:
}
#endif
/// Set the expect body option.
/** Construct the parser from a headers parser.
@param parser The headers parser to construct from.
@param args Forwarded to the message body constructor.
*/
template<class... Args>
explicit
parser_v1(headers_parser_v1<isRequest, Headers>&& parser,
Args&&... args)
: m_(parser.release(), std::forward<Args>(args)...)
{
static_cast<basic_parser_v1<
isRequest, parser_v1<
isRequest, Body, Headers>>&>(*this) = parser;
}
/// Set the skip body option.
void
set_option(skip_body const& o)
{
@@ -150,7 +152,7 @@ public:
/** Returns the parsed message.
Only valid if `complete()` would return `true`.
Only valid if @ref complete would return `true`.
*/
message_type const&
get() const
@@ -160,7 +162,7 @@ public:
/** Returns the parsed message.
Only valid if `complete()` would return `true`.
Only valid if @ref complete would return `true`.
*/
message_type&
get()
@@ -168,13 +170,13 @@ public:
return m_;
}
/** Returns the parsed message.
/** Returns ownership of the parsed message.
Ownership is transferred to the caller.
Only valid if `complete()` would return `true`.
Ownership is transferred to the caller. Only
valid if @ref complete` would return `true`.
Requires:
`message<isRequest, Body, Headers>` is MoveConstructible
`message<isRequest, Body, Headers>` is @b MoveConstructible
*/
message_type
release()
@@ -217,6 +219,30 @@ private:
this->reason_.append(s.data(), s.size());
}
void on_request_or_response(std::true_type)
{
m_.method = std::move(this->method_);
m_.url = std::move(this->uri_);
}
void on_request_or_response(std::false_type)
{
m_.status = this->status_code();
m_.reason = std::move(this->reason_);
}
void on_request(error_code& ec)
{
on_request_or_response(
std::integral_constant<bool, isRequest>{});
}
void on_response(error_code& ec)
{
on_request_or_response(
std::integral_constant<bool, isRequest>{});
}
void on_field(boost::string_ref const& s, error_code&)
{
flush();
@@ -229,18 +255,6 @@ private:
flush_ = true;
}
void set(std::true_type)
{
m_.method = std::move(this->method_);
m_.url = std::move(this->uri_);
}
void set(std::false_type)
{
m_.status = this->status_code();
m_.reason = std::move(this->reason_);
}
void
on_headers(std::uint64_t, error_code& ec)
{
@@ -258,18 +272,6 @@ private:
return body_what::normal;
}
void on_request(error_code& ec)
{
set(std::integral_constant<
bool, isRequest>{});
}
void on_response(error_code& ec)
{
set(std::integral_constant<
bool, isRequest>{});
}
void on_body(boost::string_ref const& s, error_code& ec)
{
r_->write(s.data(), s.size(), ec);
@@ -280,6 +282,46 @@ private:
}
};
/** Create a new parser from a headers parser.
Associates a Body type with a headers parser, and returns
a new parser which parses a complete message object
containing the original message headers and a new body
of the specified body type.
This function allows HTTP messages to be parsed in two stages.
First, the headers are parsed and control is returned. Then,
the caller can choose at run-time, the type of Body to
associate with the message. And finally, complete the parse
in a second call.
@param parser The headers parser to construct from. Ownership
of the message headers in the headers parser is transferred
as if by call to @ref headers_parser_v1::release.
@param args Forwarded to the body constructor of the message
in the new parser.
@return A parser for a message with the specified @ref Body type.
@par Example
@code
headers_parser<true, headers> ph;
...
auto p = with_body<string_body>(std::move(ph));
...
message<true, string_body, headers> m = p.release();
@endcode
*/
template<class Body, bool isRequest, class Headers, class... Args>
parser_v1<isRequest, Body, Headers>
with_body(headers_parser_v1<isRequest, Headers>& parser,
Args&&... args)
{
return parser_v1<isRequest, Body, Headers>(
std::move(parser), std::forward<Args>(args)...);
}
} // http
} // beast

View File

@@ -48,6 +48,7 @@ unit-test http-tests :
http/concepts.cpp
http/empty_body.cpp
http/headers.cpp
http/headers_parser_v1.cpp
http/message.cpp
http/parse.cpp
http/parse_error.cpp

View File

@@ -17,6 +17,7 @@ add_executable (http-tests
concepts.cpp
empty_body.cpp
headers.cpp
headers_parser_v1.cpp
message.cpp
parse.cpp
parse_error.cpp

View File

@@ -0,0 +1,90 @@
//
// 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/headers_parser_v1.hpp>
#include <beast/http/headers.hpp>
#include <beast/unit_test/suite.hpp>
#include <boost/asio/buffer.hpp>
namespace beast {
namespace http {
class headers_parser_v1_test : public beast::unit_test::suite
{
public:
void testParser()
{
{
error_code ec;
headers_parser_v1<true, headers> p;
BEAST_EXPECT(! p.complete());
auto const n = p.write(boost::asio::buffer(
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"\r\n"
), ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.complete());
BEAST_EXPECT(n == 36);
}
{
error_code ec;
headers_parser_v1<true, headers> p;
BEAST_EXPECT(! p.complete());
auto const n = p.write(boost::asio::buffer(
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 5\r\n"
"\r\n"
"*****"
), ec);
BEAST_EXPECT(n == 55);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.complete());
}
{
error_code ec;
headers_parser_v1<false, headers> p;
BEAST_EXPECT(! p.complete());
auto const n = p.write(boost::asio::buffer(
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"\r\n"
), ec);
BEAST_EXPECT(n == 33);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.complete());
}
{
error_code ec;
headers_parser_v1<false, headers> p;
BEAST_EXPECT(! p.complete());
auto const n = p.write(boost::asio::buffer(
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 5\r\n"
"\r\n"
"*****"
), ec);
BEAST_EXPECT(n == 52);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.complete());
}
}
void run() override
{
testParser();
}
};
BEAST_DEFINE_TESTSUITE(headers_parser_v1,http,beast);
} // http
} // beast

View File

@@ -8,14 +8,21 @@
// Test that header file is self-contained.
#include <beast/http/parser_v1.hpp>
#include <beast/core/streambuf.hpp>
#include <beast/http/headers.hpp>
#include <beast/http/headers_parser_v1.hpp>
#include <beast/http/parse.hpp>
#include <beast/http/string_body.hpp>
#include <beast/test/string_stream.hpp>
#include <beast/test/yield_to.hpp>
#include <beast/unit_test/suite.hpp>
namespace beast {
namespace http {
class parser_v1_test : public beast::unit_test::suite
class parser_v1_test
: public beast::unit_test::suite
, public test::enable_yield_to
{
public:
void testRegressions()
@@ -46,6 +53,35 @@ public:
}
}
void testWithBody()
{
test::string_stream ss{ios_,
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*"};
streambuf rb;
headers_parser_v1<true, headers> p0;
parse(ss, rb, p0);
request_headers<headers> const& reqh = p0.get();
BEAST_EXPECT(reqh.method == "GET");
BEAST_EXPECT(reqh.url == "/");
BEAST_EXPECT(reqh.version == 11);
BEAST_EXPECT(reqh.headers["User-Agent"] == "test");
BEAST_EXPECT(reqh.headers["Content-Length"] == "1");
parser_v1<true, string_body, headers> p =
with_body<string_body>(p0);
BEAST_EXPECT(p.get().method == "GET");
BEAST_EXPECT(p.get().url == "/");
BEAST_EXPECT(p.get().version == 11);
BEAST_EXPECT(p.get().headers["User-Agent"] == "test");
BEAST_EXPECT(p.get().headers["Content-Length"] == "1");
parse(ss, rb, p);
request<string_body, headers> req = p.release();
BEAST_EXPECT(req.body == "*");
}
void run() override
{
using boost::asio::buffer;
@@ -104,6 +140,7 @@ public:
}
testRegressions();
testWithBody();
}
};