From 2106f364f6563155a8e66117b0ca4cceecd6336a Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 10 Oct 2016 07:55:39 -0400 Subject: [PATCH] Add pause option to on_headers interface: * on_headers returns enum body_what * body_what::pause lets caller receive headers during the parse --- CHANGELOG.md | 1 + doc/quickref.xml | 1 + include/beast/http/basic_parser_v1.hpp | 74 ++++++++++++++++----- include/beast/http/impl/basic_parser_v1.ipp | 15 +++-- include/beast/http/impl/parse_error.ipp | 1 - include/beast/http/parse_error.hpp | 1 - include/beast/http/parser_v1.hpp | 7 +- test/http/basic_parser_v1.cpp | 28 ++++---- test/http/fail_parser.hpp | 9 +-- test/http/parse_error.cpp | 1 - 10 files changed, 90 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb336e4a..56a73e29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Fix on_headers called twice from basic_parser_v1 * Constrain parser_v1 constructor * Improve first line serialization +* Add pause option to on_headers interface * Refine Parser concept API Changes: diff --git a/doc/quickref.xml b/doc/quickref.xml index 321a34f3..b9c5c647 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -68,6 +68,7 @@ Constants + body_what connection Concepts diff --git a/include/beast/http/basic_parser_v1.hpp b/include/beast/http/basic_parser_v1.hpp index 86b9c2d5..b9425c49 100644 --- a/include/beast/http/basic_parser_v1.hpp +++ b/include/beast/http/basic_parser_v1.hpp @@ -80,6 +80,49 @@ struct body_max_size } }; +/** A value indicating how the parser should treat the body. + + This value is returned from the `on_headers` callback in + the derived class. It controls what the parser does next + in terms of the message body. +*/ +enum class body_what +{ + /** The parser should expect a body, keep reading. + */ + normal, + + /** Skip parsing of the body. + + When returned by `on_headers` this causes parsing to + complete and control to return to the caller. This + could be used when sending a response to a HEAD + request, for example. + */ + skip, + + /** The message represents an UPGRADE request. + + When returned by `on_headers` this causes parsing + to complete and control to return to the caller. + */ + upgrade, + + /** Suspend parsing before reading the body. + + When returned by `on_headers` this causes parsing to + pause. Control is returned to the caller, and the parser + state is preserved such that a subsequent call to the + parser will begin reading the message body. + + This could be used by callers to inspect the HTTP + headers before committing to read the body. For example, + to choose the body type based on the headers. Or to + respond to an Expect: 100-continue request. + */ + pause +}; + /// The value returned when no content length is known or applicable. static std::uint64_t constexpr no_content_length = std::numeric_limits::max(); @@ -133,7 +176,7 @@ static std::uint64_t constexpr no_content_length = Called for each piece of the current header value. - @li `int on_headers(std::uint64_t content_length, error_code&)` + @li `body_what on_headers(std::uint64_t content_length, error_code&)` Called when all the headers have been parsed successfully. @@ -151,18 +194,11 @@ static std::uint64_t constexpr no_content_length = 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. + or not the parser should expect a body. See @ref body_what for + choices of the return value. The parser uses traits to determine if the callback is possible. - If the Derived type omits one or more callbacks, they are simply + If the @b Derived type omits one or more callbacks, they are simply 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. @@ -172,6 +208,9 @@ static std::uint64_t constexpr no_content_length = @tparam isRequest A `bool` indicating whether the parser will be presented with request or response message. + + @tparam Derived The derived class type. This is part of the + Curiously Recurring Template Pattern interface. */ template class basic_parser_v1 : public detail::parser_base @@ -626,7 +665,7 @@ private: template class has_on_headers_t { - template().on_headers( std::declval(), std::declval()))>> static R check(int); @@ -845,18 +884,21 @@ private: } } - int call_on_headers(error_code& ec, + body_what + call_on_headers(error_code& ec, std::uint64_t content_length, std::true_type) { return impl().on_headers(content_length, ec); } - int call_on_headers(error_code& ec, std::uint64_t, std::false_type) + body_what + call_on_headers(error_code& ec, std::uint64_t, std::false_type) { - return 0; + return body_what::normal; } - int call_on_headers(error_code& ec) + body_what + call_on_headers(error_code& ec) { return call_on_headers(ec, content_length_, has_on_headers{}); diff --git a/include/beast/http/impl/basic_parser_v1.ipp b/include/beast/http/impl/basic_parser_v1.ipp index c64799a6..982a988e 100644 --- a/include/beast/http/impl/basic_parser_v1.ipp +++ b/include/beast/http/impl/basic_parser_v1.ipp @@ -899,21 +899,22 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) return err(parse_error::illegal_content_length); upgrade_ = ((flags_ & (parse_flag::upgrade | parse_flag::connection_upgrade)) == (parse_flag::upgrade | parse_flag::connection_upgrade)) /*|| method == "connect"*/; - auto const maybe_skip = call_on_headers(ec); + auto const what = call_on_headers(ec); if(ec) return errc(); - switch(maybe_skip) + switch(what) { - case 0: + case body_what::normal: break; - case 2: + case body_what::upgrade: upgrade_ = true; // fall through - case 1: + case body_what::skip: flags_ |= parse_flag::skipbody; break; - default: - return err(parse_error::bad_on_headers_rv); + case body_what::pause: + s_ = s_headers_done; + return used(); } s_ = s_headers_done; // fall through diff --git a/include/beast/http/impl/parse_error.ipp b/include/beast/http/impl/parse_error.ipp index 7f2d9dc2..155eb6eb 100644 --- a/include/beast/http/impl/parse_error.ipp +++ b/include/beast/http/impl/parse_error.ipp @@ -47,7 +47,6 @@ public: 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::invalid_ext_name: return "invalid ext name"; case parse_error::invalid_ext_val: return "invalid ext val"; diff --git a/include/beast/http/parse_error.hpp b/include/beast/http/parse_error.hpp index 7463fbdb..72025f7d 100644 --- a/include/beast/http/parse_error.hpp +++ b/include/beast/http/parse_error.hpp @@ -29,7 +29,6 @@ enum class parse_error bad_value, bad_content_length, illegal_content_length, - bad_on_headers_rv, invalid_chunk_size, invalid_ext_name, diff --git a/include/beast/http/parser_v1.hpp b/include/beast/http/parser_v1.hpp index 5e1a7629..731d4595 100644 --- a/include/beast/http/parser_v1.hpp +++ b/include/beast/http/parser_v1.hpp @@ -231,11 +231,14 @@ private: m_.reason = std::move(this->reason_); } - int on_headers(std::uint64_t, error_code&) + body_what + on_headers(std::uint64_t, error_code&) { flush(); m_.version = 10 * this->http_major() + this->http_minor(); - return skip_body_; + if(skip_body_) + return body_what::skip; + return body_what::normal; } void on_request(error_code& ec) diff --git a/test/http/basic_parser_v1.cpp b/test/http/basic_parser_v1.cpp index be2f13da..dc4270d4 100644 --- a/test/http/basic_parser_v1.cpp +++ b/test/http/basic_parser_v1.cpp @@ -90,10 +90,11 @@ public: { value = true; } - int on_headers(std::uint64_t, error_code&) + body_what + on_headers(std::uint64_t, error_code&) { headers = true; - return 0; + return body_what::normal; } void on_body(boost::string_ref const&, error_code&) { @@ -194,7 +195,7 @@ public: template void - good(int onBodyRv, std::string const& s, F const& f) + good(body_what onBodyRv, std::string const& s, F const& f) { using boost::asio::buffer; for_split(s, @@ -237,12 +238,12 @@ public: void good(std::string const& s, F const& f = {}) { - return good(0, s, f); + return good(body_what::normal, s, f); } template void - bad(int onBodyRv, std::string const& s, error_code ev) + bad(body_what onBodyRv, std::string const& s, error_code ev) { using boost::asio::buffer; for_split(s, @@ -294,7 +295,7 @@ public: void bad(std::string const& s, error_code ev = {}) { - return bad(0, s, ev); + return bad(body_what::normal, s, ev); } //-------------------------------------------------------------------------- @@ -658,7 +659,7 @@ public: auto const length = [&](std::string const& s, std::uint64_t v) { - good(1, s, + good(body_what::skip, s, [&](fail_parser const& p) { BEAST_EXPECT(p.content_length() == v); @@ -757,7 +758,7 @@ public: good(m("Transfer-Encodings: 2\r\n"), flags{*this, 0}); good(m("Transfer-Encoded: false\r\n"), flags{*this, 0}); - bad(1, + bad(body_what::skip, "HTTP/1.1 200 OK\r\n" "Content-Length: 1\r\n" "Transfer-Encoding: chunked\r\n" @@ -848,8 +849,8 @@ public: { error_code ec; test::fail_counter fc(1000); - fail_parser p(fc); - p.on_body_rv(2); + fail_parser p{fc}; + p.on_body_rv(body_what::upgrade); p.write(buf( "GET / HTTP/1.1\r\n" "Content-Length: 1\r\n" @@ -1007,12 +1008,7 @@ public: BEAST_EXPECT(p.complete()); } - bad(3, - "HTTP/1.1 200 OK\r\n" - "Content-Length: 1\r\n" - "\r\n*", parse_error::bad_on_headers_rv); - - bad(0, + bad(body_what::normal, "GET / HTTP/1.1\r\n" "Content-Length: 1\r\n" "\r\n", diff --git a/test/http/fail_parser.hpp b/test/http/fail_parser.hpp index 003992a2..e55ca1e3 100644 --- a/test/http/fail_parser.hpp +++ b/test/http/fail_parser.hpp @@ -20,7 +20,7 @@ class fail_parser { test::fail_counter& fc_; std::uint64_t content_length_ = no_content_length; - int body_rv_ = 0; + body_what body_rv_ = body_what::normal; public: std::string body; @@ -33,7 +33,7 @@ public: } void - on_body_rv(int rv) + on_body_rv(body_what rv) { body_rv_ = rv; } @@ -85,10 +85,11 @@ public: fc_.fail(ec); } - int on_headers(std::uint64_t content_length, error_code& ec) + body_what + on_headers(std::uint64_t content_length, error_code& ec) { if(fc_.fail(ec)) - return 0; + return body_what::normal; content_length_ = content_length; return body_rv_; } diff --git a/test/http/parse_error.cpp b/test/http/parse_error.cpp index a20e6f4f..1bbb3025 100644 --- a/test/http/parse_error.cpp +++ b/test/http/parse_error.cpp @@ -45,7 +45,6 @@ public: check("http", parse_error::bad_value); check("http", parse_error::bad_content_length); check("http", parse_error::illegal_content_length); - check("http", parse_error::bad_on_headers_rv); check("http", parse_error::invalid_chunk_size); check("http", parse_error::short_read); check("http", parse_error::general);