diff --git a/CHANGELOG.md b/CHANGELOG.md
index 38121340..f81b9769 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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:
diff --git a/doc/quickref.xml b/doc/quickref.xml
index 20feb6ab..38dadda9 100644
--- a/doc/quickref.xml
+++ b/doc/quickref.xml
@@ -34,6 +34,7 @@
basic_parser_v1
empty_body
headers
+ headers_parser
message
message_headers
parser_v1
@@ -65,6 +66,7 @@
prepare
read
swap
+ with_body
write
Type Traits
diff --git a/include/beast/http/basic_parser_v1.hpp b/include/beast/http/basic_parser_v1.hpp
index 039a42c1..0a5290a5 100644
--- a/include/beast/http/basic_parser_v1.hpp
+++ b/include/beast/http/basic_parser_v1.hpp
@@ -226,6 +226,9 @@ template
class basic_parser_v1 : public detail::parser_base
{
private:
+ template
+ 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
+ basic_parser_v1(basic_parser_v1<
+ isRequest, OtherDerived> const& other);
+
+ /// Copy assignment.
+ template
+ 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.
diff --git a/include/beast/http/headers_parser_v1.hpp b/include/beast/http/headers_parser_v1.hpp
new file mode 100644
index 00000000..799d148d
--- /dev/null
+++ b/include/beast/http/headers_parser_v1.hpp
@@ -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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+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
+class headers_parser_v1
+ : public basic_parser_v1>
+ , private std::conditional::type
+{
+public:
+ /// The type of message this parser produces.
+ using headers_type =
+ message_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
+ explicit
+ headers_parser_v1(Args&&... args);
+#else
+ template::type, headers_parser_v1>::value>>
+ explicit
+ headers_parser_v1(Arg1&& arg1, ArgN&&... argn)
+ : h_(std::forward(arg1),
+ std::forward(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` is @b MoveConstructible
+ */
+ headers_type
+ release()
+ {
+ static_assert(std::is_move_constructible::value,
+ "MoveConstructible requirements not met");
+ return std::move(h_);
+ }
+
+private:
+ friend class basic_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{});
+ }
+
+ void on_response(error_code& ec)
+ {
+ on_request_or_response(
+ std::integral_constant{});
+ }
+
+ 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
diff --git a/include/beast/http/impl/basic_parser_v1.ipp b/include/beast/http/impl/basic_parser_v1.ipp
index a610a20b..069656b7 100644
--- a/include/beast/http/impl/basic_parser_v1.ipp
+++ b/include/beast/http/impl/basic_parser_v1.ipp
@@ -50,6 +50,55 @@ basic_parser_v1()
init();
}
+template
+template
+basic_parser_v1::
+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
+template
+auto
+basic_parser_v1::
+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
basic_parser_v1::
@@ -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::
reset()
{
+ cb_ = nullptr;
h_left_ = h_max_;
b_left_ = b_max_;
reset(std::integral_constant{});
diff --git a/include/beast/http/parser_v1.hpp b/include/beast/http/parser_v1.hpp
index 7bbbc846..703326b2 100644
--- a/include/beast/http/parser_v1.hpp
+++ b/include/beast/http/parser_v1.hpp
@@ -8,8 +8,8 @@
#ifndef BEAST_HTTP_PARSER_V1_HPP
#define BEAST_HTTP_PARSER_V1_HPP
-#include
#include
+#include
#include
#include
#include
@@ -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>
, private std::conditional::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
@@ -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
+ explicit
+ parser_v1(headers_parser_v1&& parser,
+ Args&&... args)
+ : m_(parser.release(), std::forward(args)...)
+ {
+ static_cast>&>(*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` is MoveConstructible
+ `message` 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{});
+ }
+
+ void on_response(error_code& ec)
+ {
+ on_request_or_response(
+ std::integral_constant{});
+ }
+
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 ph;
+ ...
+ auto p = with_body(std::move(ph));
+ ...
+ message m = p.release();
+ @endcode
+*/
+template
+parser_v1
+with_body(headers_parser_v1& parser,
+ Args&&... args)
+{
+ return parser_v1(
+ std::move(parser), std::forward(args)...);
+}
+
} // http
} // beast
diff --git a/test/Jamfile b/test/Jamfile
index 41b94eac..a01bda0f 100644
--- a/test/Jamfile
+++ b/test/Jamfile
@@ -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
diff --git a/test/http/CMakeLists.txt b/test/http/CMakeLists.txt
index eb47c628..03647196 100644
--- a/test/http/CMakeLists.txt
+++ b/test/http/CMakeLists.txt
@@ -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
diff --git a/test/http/headers_parser_v1.cpp b/test/http/headers_parser_v1.cpp
new file mode 100644
index 00000000..e15997dd
--- /dev/null
+++ b/test/http/headers_parser_v1.cpp
@@ -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
+
+#include
+#include
+#include
+
+namespace beast {
+namespace http {
+
+class headers_parser_v1_test : public beast::unit_test::suite
+{
+public:
+ void testParser()
+ {
+ {
+ error_code ec;
+ headers_parser_v1 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 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 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 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
diff --git a/test/http/parser_v1.cpp b/test/http/parser_v1.cpp
index f684238e..7717d8ce 100644
--- a/test/http/parser_v1.cpp
+++ b/test/http/parser_v1.cpp
@@ -8,14 +8,21 @@
// Test that header file is self-contained.
#include
+#include
#include
+#include
+#include
#include
+#include
+#include
#include
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 p0;
+ parse(ss, rb, p0);
+ request_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 p =
+ with_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 req = p.release();
+ BEAST_EXPECT(req.body == "*");
+ }
+
void run() override
{
using boost::asio::buffer;
@@ -104,6 +140,7 @@ public:
}
testRegressions();
+ testWithBody();
}
};