diff --git a/CHANGELOG.md b/CHANGELOG.md index 558beaed..cb7eb680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ API Changes: * Refactor treatment of request-method * Refactor treatment of status code and obsolete reason +* Refactor HTTP serialization and parsing -------------------------------------------------------------------------------- diff --git a/README.md b/README.md index 90851669..cd798fba 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,7 @@ int main() Example HTTP program: ```C++ +#include #include #include #include @@ -197,7 +198,7 @@ Example HTTP program: int main() { // Normal boost::asio setup - std::string const host = "boost.org"; + std::string const host = "www.example.com"; boost::asio::io_service ios; boost::asio::ip::tcp::resolver r{ios}; boost::asio::ip::tcp::socket sock{ios}; @@ -206,7 +207,7 @@ int main() // Send HTTP request using beast beast::http::request req; - req.method("GET"); + req.method(beast::http::verb::get); req.target("/"); req.version = 11; req.fields.replace("Host", host + ":" + @@ -216,10 +217,10 @@ int main() beast::http::write(sock, req); // Receive and print HTTP response using beast - beast::streambuf sb; - beast::http::response resp; - beast::http::read(sock, sb, resp); - std::cout << resp; + beast::flat_buffer b; + beast::http::response res; + beast::http::read(sock, b, res); + std::cout << res << std::endl; } ``` diff --git a/doc/4_0_adv_http.qbk b/doc/4_0_adv_http.qbk index c8514451..1541b4ab 100644 --- a/doc/4_0_adv_http.qbk +++ b/doc/4_0_adv_http.qbk @@ -144,11 +144,21 @@ and which must remain valid until the operation is complete: ][ Send __serializer__ buffer data to a __SyncWriteStream__. ]] +[[ + [link beast.ref.http__write_header.overload1 [*write_header]] +][ + Send an entire header from a __serializer__ to a __SyncWriteStream__. +]] [[ [link beast.ref.http__async_write_some [*async_write_some]] ][ Send some __serializer__ buffer data to an __AsyncWriteStream__. ]] +[[ + [link beast.ref.http__async_write_header [*async_write_header]] +][ + Send an entire header from a __serializer__ to a __AsyncWriteStream__. +]] ] Here is an example which synchronously sends a message on a stream using diff --git a/doc/6_examples.qbk b/doc/6_examples.qbk index 7656c3f6..6389cc43 100644 --- a/doc/6_examples.qbk +++ b/doc/6_examples.qbk @@ -27,7 +27,7 @@ Use HTTP to request the root page from a website and print the response: int main() { // Normal boost::asio setup - std::string const host = "boost.org"; + std::string const host = "www.example.com"; boost::asio::io_service ios; boost::asio::ip::tcp::resolver r{ios}; boost::asio::ip::tcp::socket sock{ios}; @@ -49,7 +49,7 @@ int main() beast::flat_buffer b; beast::http::response res; beast::http::read(sock, b, res); - std::cout << res; + std::cout << res << std::endl; } ``` [heading WebSocket] diff --git a/doc/quickref.xml b/doc/quickref.xml index b2a15f7f..4a74892e 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -59,8 +59,10 @@ Functions async_read + async_read_header async_read_some async_write + async_write_header async_write_some is_keep_alive is_upgrade @@ -69,19 +71,15 @@ operator<< prepare read + read_header read_some string_to_verb swap to_string write + write_header write_some - Type Traits - - is_body - is_body_writer - is_body_reader - Constants @@ -91,6 +89,12 @@ status verb + Type Traits + + is_body + is_body_writer + is_body_reader + Concepts Body diff --git a/examples/http_example.cpp b/examples/http_example.cpp index abe61000..cc6f7227 100644 --- a/examples/http_example.cpp +++ b/examples/http_example.cpp @@ -15,7 +15,7 @@ int main() { // Normal boost::asio setup - std::string const host = "boost.org"; + std::string const host = "www.example.com"; boost::asio::io_service ios; boost::asio::ip::tcp::resolver r{ios}; boost::asio::ip::tcp::socket sock{ios}; @@ -37,5 +37,5 @@ int main() beast::flat_buffer b; beast::http::response res; beast::http::read(sock, b, res); - std::cout << res; + std::cout << res << std::endl; } diff --git a/extras/beast/test/pipe_stream.hpp b/extras/beast/test/pipe_stream.hpp index 7b75a7ec..eac8549c 100644 --- a/extras/beast/test/pipe_stream.hpp +++ b/extras/beast/test/pipe_stream.hpp @@ -55,6 +55,7 @@ private: buffer_type b; std::condition_variable cv; std::unique_ptr op; + bool eof = false; }; state s_[2]; @@ -151,6 +152,15 @@ public: std::size_t>::max)()); } + /** Close the stream. + + The other end of the pipe will see + `boost::asio::error::eof` on read. + */ + template + void + close(); + template std::size_t read_some(MutableBufferSequence const& buffers); @@ -275,22 +285,49 @@ read_op_impl::operator()() { BOOST_ASSERT(s_.in_.op); std::unique_lock lock{s_.in_.m}; - BOOST_ASSERT(buffer_size(s_.in_.b.data()) > 0); - auto const bytes_transferred = buffer_copy( - b_, s_.in_.b.data(), s_.read_max_); - s_.in_.b.consume(bytes_transferred); - auto& s = s_; - Handler h{std::move(h_)}; - lock.unlock(); - s.in_.op.reset(nullptr); - ++s.nread; - s.ios_.post(bind_handler(std::move(h), - error_code{}, bytes_transferred)); + if(s_.in_.b.size() > 0) + { + auto const bytes_transferred = buffer_copy( + b_, s_.in_.b.data(), s_.read_max_); + s_.in_.b.consume(bytes_transferred); + auto& s = s_; + Handler h{std::move(h_)}; + lock.unlock(); + s.in_.op.reset(nullptr); + ++s.nread; + s.ios_.post(bind_handler(std::move(h), + error_code{}, bytes_transferred)); + } + else + { + BOOST_ASSERT(s_.in_.eof); + auto& s = s_; + Handler h{std::move(h_)}; + lock.unlock(); + s.in_.op.reset(nullptr); + ++s.nread; + s.ios_.post(bind_handler(std::move(h), + boost::asio::error::eof, 0)); + } }); } //------------------------------------------------------------------------------ +template +void +pipe::stream:: +close() +{ + std::lock_guard lock{out_.m}; + out_.eof = true; + if(out_.op) + out_.op.get()->operator()(); + else + out_.cv.notify_all(); +} + + template std::size_t pipe::stream:: @@ -318,19 +355,28 @@ read_some(MutableBufferSequence const& buffers, using boost::asio::buffer_copy; using boost::asio::buffer_size; BOOST_ASSERT(! in_.op); + BOOST_ASSERT(buffer_size(buffers) > 0); if(fc_ && fc_->fail(ec)) return 0; std::unique_lock lock{in_.m}; in_.cv.wait(lock, [&]() { - return - buffer_size(buffers) == 0 || - buffer_size(in_.b.data()) > 0; + return in_.b.size() > 0 || in_.eof; }); - auto const bytes_transferred = buffer_copy( - buffers, in_.b.data(), write_max_); - in_.b.consume(bytes_transferred); + std::size_t bytes_transferred; + if(in_.b.size() > 0) + { + bytes_transferred = buffer_copy( + buffers, in_.b.data(), write_max_); + in_.b.consume(bytes_transferred); + } + else + { + BOOST_ASSERT(in_.eof); + bytes_transferred = 0; + ec = boost::asio::error::eof; + } ++nread; return bytes_transferred; } @@ -347,9 +393,10 @@ async_read_some(MutableBufferSequence const& buffers, "MutableBufferSequence requirements not met"); using boost::asio::buffer_copy; using boost::asio::buffer_size; + BOOST_ASSERT(! in_.op); + BOOST_ASSERT(buffer_size(buffers) > 0); async_completion init{handler}; - BOOST_ASSERT(! in_.op); if(fc_) { error_code ec; @@ -359,7 +406,14 @@ async_read_some(MutableBufferSequence const& buffers, } { std::unique_lock lock{in_.m}; - if(buffer_size(buffers) == 0 || + if(in_.eof) + { + lock.unlock(); + ++nread; + ios_.post(bind_handler(init.completion_handler, + boost::asio::error::eof, 0)); + } + else if(buffer_size(buffers) == 0 || buffer_size(in_.b.data()) > 0) { auto const bytes_transferred = buffer_copy( @@ -389,6 +443,7 @@ write_some(ConstBufferSequence const& buffers) static_assert(is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); + BOOST_ASSERT(! out_.eof); error_code ec; auto const bytes_transferred = write_some(buffers, ec); @@ -408,6 +463,7 @@ write_some( "ConstBufferSequence requirements not met"); using boost::asio::buffer_copy; using boost::asio::buffer_size; + BOOST_ASSERT(! out_.eof); if(fc_ && fc_->fail(ec)) return 0; auto const n = (std::min)( @@ -437,6 +493,7 @@ async_write_some(ConstBufferSequence const& buffers, "ConstBufferSequence requirements not met"); using boost::asio::buffer_copy; using boost::asio::buffer_size; + BOOST_ASSERT(! out_.eof); async_completion init{handler}; if(fc_) diff --git a/extras/beast/test/yield_to.hpp b/extras/beast/test/yield_to.hpp index 689c165f..df5b8a34 100644 --- a/extras/beast/test/yield_to.hpp +++ b/extras/beast/test/yield_to.hpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace beast { namespace test { @@ -32,7 +33,7 @@ protected: private: boost::optional work_; - std::thread thread_; + std::vector threads_; std::mutex m_; std::condition_variable cv_; std::size_t running_ = 0; @@ -42,20 +43,21 @@ public: using yield_context = boost::asio::yield_context; - enable_yield_to() + explicit + enable_yield_to(std::size_t concurrency = 1) : work_(ios_) - , thread_([&] - { - ios_.run(); - } - ) { + threads_.reserve(concurrency); + while(concurrency--) + threads_.emplace_back( + [&]{ ios_.run(); }); } ~enable_yield_to() { work_ = boost::none; - thread_.join(); + for(auto& t : threads_) + t.join(); } /// Return the `io_service` associated with the object diff --git a/include/beast/core/detail/read_size_helper.hpp b/include/beast/core/detail/read_size_helper.hpp index 26fd0957..375c8c26 100644 --- a/include/beast/core/detail/read_size_helper.hpp +++ b/include/beast/core/detail/read_size_helper.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -24,9 +25,13 @@ read_size_helper(DynamicBuffer const& buffer, std::size_t max_size) "DynamicBuffer requirements not met"); BOOST_ASSERT(max_size >= 1); auto const size = buffer.size(); - return std::min( - std::max(512, buffer.capacity() - size), - std::min(max_size, buffer.max_size() - size)); + auto const limit = buffer.max_size() - size; + if(limit > 0) + return std::min( + std::max(512, buffer.capacity() - size), + std::min(max_size, limit)); + BOOST_THROW_EXCEPTION(std::length_error{ + "dynamic buffer overflow"}); } } // detail diff --git a/include/beast/core/detail/sync_ostream.hpp b/include/beast/core/detail/sync_ostream.hpp index 45769ced..44cf74fd 100644 --- a/include/beast/core/detail/sync_ostream.hpp +++ b/include/beast/core/detail/sync_ostream.hpp @@ -80,7 +80,7 @@ write_some(ConstBufferSequence const& buffers, buffer_size(buffer)); if(os_.fail()) { - ec = errc::make_error_code( + ec = make_error_code( errc::no_stream_resources); break; } diff --git a/include/beast/http/basic_parser.hpp b/include/beast/http/basic_parser.hpp index 8d4633a5..d704e1a1 100644 --- a/include/beast/http/basic_parser.hpp +++ b/include/beast/http/basic_parser.hpp @@ -21,41 +21,6 @@ namespace beast { namespace http { -/** Describes the parser's current state. - - The state is expressed as the type of data that - @ref basic_parser is expecting to see in subsequently - provided octets. -*/ -enum class parse_state -{ - /// Expecting one or more header octets - header = 0, - - /// Expecting one or more body octets - body = 1, - - /// Expecting zero or more body octets followed by EOF - body_to_eof = 2, - - /// Expecting additional chunk header octets - chunk_header = 3, - - /// Expecting one or more chunk body octets - chunk_body = 4, - - /** The parsing is complete. - - The parse is considered complete when the full header - is received and either the full body is received, or - the semantics of the message indicate that no body - is expected. This includes the case where the caller - has indicated to the parser that no body is expected, - for example when receiving a response to a HEAD request. - */ - complete = 5 -}; - /** A parser for decoding HTTP/1 wire format messages. This parser is designed to efficiently parse messages in the @@ -83,18 +48,13 @@ enum class parse_state struct derived : basic_parser> { - // The type used when providing a mutable - // buffer sequence in which to store body data. - // - using mutable_buffers_type = ...; - // When isRequest == true, called // after the Request Line is received. // void on_request( - string_view const& method, - string_view const& target, + string_view method, + string_view target, int version, error_code& ec); @@ -104,7 +64,7 @@ enum class parse_state void on_response( int status, - string_view const& reason, + string_view reason, int version, error_code& ec); @@ -112,8 +72,8 @@ enum class parse_state // void on_field( - string_view const& name, - string_view const& value, + string_view name, + string_view value, error_code& ec); // Called after the header is complete. @@ -123,40 +83,19 @@ enum class parse_state error_code& ec); // Called once before the body, if any, is started. - // This will only be called if the semantics of the - // message indicate that a body exists, including - // an indicated body of zero length. // void - on_body(); + on_body( + boost::optional content_length, + error_code& ec); // Called zero or more times to provide body data. // - // Only used if isDirect == false - // void on_data( - string_view const& s, + string_view s, error_code& ec); - // Called zero or more times to retrieve a mutable - // buffer sequence in which to store body data. - // - // Only used if isDirect == true - // - mutable_buffers_type - on_prepare( - std::size_t n); - - // Called after body data has been stored in the - // buffer returned by the previous call to on_prepare. - // - // Only used if isDirect == true - // - void - on_commit( - std::size_t n); - // If the Transfer-Encoding is specified, and the // last item in the list of encodings is "chunked", // called after receiving a chunk header or a final @@ -164,8 +103,8 @@ enum class parse_state // void on_chunk( - std::uint64_t length, // Length of this chunk - string_view const& ext, // The chunk extensions, if any + std::uint64_t length, // Length of this chunk + string_view const& ext, // The chunk extensions, if any error_code& ec); // Called once when the message is complete. @@ -177,16 +116,7 @@ enum class parse_state @endcode If a callback sets the error code, the error will be propagated - to the caller of the parser. Behavior of parsing after an error - is returned is undefined. - - When the parser state is positioned to read bytes belonging to - the body, calling @ref write or @ref write will implicitly - cause a buffer copy (because bytes are first transferred to the - dynamic buffer). To avoid this copy, the additional functions - @ref copy_body, @ref prepare_body, and @ref commit_body are - provided to allow the caller to read bytes directly into buffers - supplied by the parser. + to the caller of the parser. The parser is optimized for the case where the input buffer sequence consists of a single contiguous buffer. The @@ -203,26 +133,21 @@ enum class parse_state @tparam isRequest A `bool` indicating whether the parser will be presented with request or response message. - @tparam isDirect A `bool` indicating whether the parser interface - supports reading body data directly into parser-provided buffers. - @tparam Derived The derived class type. This is part of the Curiously Recurring Template Pattern interface. */ -template +template class basic_parser : private detail::basic_parser_base { - template + template friend class basic_parser; // Message will be complete after reading header static unsigned constexpr flagSkipBody = 1<< 0; - - - static unsigned constexpr flagOnBody = 1<< 1; + // Consume input buffers across semantic boundaries + static unsigned constexpr flagEager = 1<< 1; // The parser has read at least one byte static unsigned constexpr flagGotSome = 1<< 2; @@ -248,10 +173,8 @@ class basic_parser std::size_t buf_len_ = 0; std::size_t skip_ = 0; // search from here std::size_t x_; // scratch variable + state state_ = state::nothing_yet; unsigned f_ = 0; // flags - parse_state state_ = parse_state::header; - string_view ext_; - string_view body_; public: /// Copy constructor (disallowed) @@ -264,7 +187,8 @@ public: basic_parser() = default; /// `true` if this parser parses requests, `false` for responses. - static bool constexpr is_request = isRequest; + using is_request = + std::integral_constant; /// Destructor ~basic_parser() = default; @@ -274,83 +198,70 @@ public: After the move, the only valid operation on the moved-from object is destruction. */ - template - basic_parser(basic_parser< - isRequest, OtherIsDirect, OtherDerived>&&); + template + basic_parser(basic_parser&&); - /** Set the skip body option. + /** Returns a reference to this object as a @ref basic_parser. - The option controls whether or not the parser expects to - see an HTTP body, regardless of the presence or absence of - certain fields such as Content-Length. - - Depending on the request, some responses do not carry a body. - For example, a 200 response to a CONNECT request from a - tunneling proxy. In these cases, callers may use this function - inform the parser that no body is expected. The parser will - consider the message complete after the header has been received. - - @note This function must called before any bytes are processed. + This is used to pass a derived class where a base class is + expected, to choose a correct function overload when the + resolution would be ambiguous. */ - void - skip_body(); - - /** Returns the current parser state. - - The parser state indicates what octets the parser - expects to see next in the input stream. - */ - parse_state - state() const + basic_parser& + base() { - return state_; + return *this; + } + + /** Returns a constant reference to this object as a @ref basic_parser. + + This is used to pass a derived class where a base class is + expected, to choose a correct function overload when the + resolution would be ambiguous. + */ + basic_parser const& + base() const + { + return *this; } /// Returns `true` if the parser has received at least one byte of input. bool got_some() const { - return (f_ & flagGotSome) != 0; - } - - /// Returns `true` if the complete header has been parsed. - bool - got_header() const - { - return state_ != parse_state::header; - } - - /** Returns `true` if a Content-Length is specified. - - @note Only valid after parsing a complete header. - */ - bool - got_content_length() const - { - return (f_ & flagContentLength) != 0; + return state_ != state::nothing_yet; } /** Returns `true` if the message is complete. - The message is complete after a full header is - parsed and one of the following is true: + The message is complete after the full header is prduced + and one of the following is true: - @li @ref skip_body was called + @li The skip body option was set. @li The semantics of the message indicate there is no body. - @li The semantics of the message indicate a body is - expected, and the entire body was received. + @li The semantics of the message indicate a body is expected, + and the entire body was parsed. */ bool - is_complete() const + is_done() const { - return state_ == parse_state::complete; + return state_ == state::complete; + } + + /** Returns `true` if a the parser has produced the full header. + */ + bool + is_header_done() const + { + return state_ > state::header; } /** Returns `true` if the message is an upgrade message. - @note Only valid after parsing a complete header. + @note The return value is undefined unless + @ref is_header_done would return `true`. */ bool is_upgrade() const @@ -358,16 +269,10 @@ public: return (f_ & flagConnectionUpgrade) != 0; } - /** Returns `true` if keep-alive is specified + /** Returns `true` if the last value for Transfer-Encoding is "chunked". - @note Only valid after parsing a complete header. - */ - bool - is_keep_alive() const; - - /** Returns `true` if the chunked Transfer-Encoding is specified. - - @note Only valid after parsing a complete header. + @note The return value is undefined unless + @ref is_header_done would return `true`. */ bool is_chunked() const @@ -375,34 +280,125 @@ public: return (f_ & flagChunked) != 0; } - /** Write part of a buffer sequence to the parser. + /** Returns `true` if the message has keep-alive connection semantics. - This function attempts to parse the HTTP message - stored in the caller provided buffers. Upon success, - a positive return value indicates that the parser + @note The return value is undefined unless + @ref is_header_done would return `true`. + */ + bool + is_keep_alive() const; + + /** Returns the optional value of Content-Length if known. + + @note The return value is undefined unless + @ref is_header_done would return `true`. + */ + boost::optional + content_length() const; + + /** Returns `true` if the message semantics require an end of file. + + Depending on the contents of the header, the parser may + require and end of file notification to know where the end + of the body lies. If this function returns `true` it will be + necessary to call @ref put_eof when there will never be additional + data from the input. + */ + bool + need_eof() const + { + return (f_ & flagNeedEOF) != 0; + } + + /// Returns `true` if the eager parse option is set. + bool + eager() const + { + return (f_ & flagEager) != 0; + } + + /** Set the eager parse option. + + This option controls whether or not the parser will attempt + to consume all of the octets provided during parsing, or + if it will stop when it reaches one of the following positions + within the serialized message: + + @li Immediately after the header + + @li Immediately after any chunk header + + The default is to stop after the header or any chunk header. + */ + void + eager(bool v) + { + if(v) + f_ |= flagEager; + else + f_ &= ~flagEager; + } + + /// Returns `true` if the parser will ignore the message body. + bool + skip() + { + return (f_ & flagSkipBody) != 0; + } + + /** Set the skip body option. + + The option controls whether or not the parser expects to + see an HTTP body, regardless of the presence or absence of + certain fields such as Content-Length. + + Depending on the request, some responses do not carry a + body. For example, a 200 response to a CONNECT request + from a tunneling proxy. In these cases, callers may use + this function inform the parser that no body is expected. + The parser will consider the message complete after the + header has been received. + + @note This function must called before any bytes are processed. + */ + void + skip(bool v); + + /** Write a buffer sequence to the parser. + + This function attempts to incrementally parse the HTTP + message data stored in the caller provided buffers. Upon + success, a positive return value indicates that the parser made forward progress, consuming that number of bytes. - A return value of zero indicates that the parser - requires additional input. In this case the caller - should append additional bytes to the input buffer - sequence and call @ref write again. + In some cases there may be an insufficient number of octets + in the input buffer in order to make forward progress. This + is indicated by the the code @ref error::need_more. When + this happens, the caller should place additional bytes into + the buffer sequence and call @ref put again. + + The error code @ref error::need_more is special. When this + error is returned, a subsequent call to @ref put may succeed + if the buffers have been updated. Otherwise, upon error + the parser may not be restarted. @param buffers An object meeting the requirements of @b ConstBufferSequence that represents the message. @param ec Set to the error, if any occurred. - @return The number of bytes consumed in the buffer - sequence. + @return The number of octets consumed in the buffer + sequence. The caller should remove these octets even if the + error is set. */ template std::size_t - write(ConstBufferSequence const& buffers, error_code& ec); + put(ConstBufferSequence const& buffers, error_code& ec); #if ! BEAST_DOXYGEN std::size_t - write(boost::asio::const_buffers_1 const& buffer, + put(boost::asio::const_buffers_1 const& buffer, error_code& ec); #endif @@ -423,139 +419,7 @@ public: @param ec Set to the error, if any occurred. */ void - write_eof(error_code& ec); - - /** Returns the number of bytes remaining in the body or chunk. - - If a Content-Length is specified and the parser state - is equal to @ref beast::http::parse_state::body, this will return - the number of bytes remaining in the body. If the - chunked Transfer-Encoding is indicated and the parser - state is equal to @ref beast::http::parse_state::chunk_body, this - will return the number of bytes remaining in the chunk. - Otherwise, the function behavior is undefined. - */ - std::uint64_t - size() const - { - BOOST_ASSERT( - state_ == parse_state::body || - state_ == parse_state::chunk_body); - return len_; - } - - /** Returns the body data parsed in the last call to @ref write. - - This buffer is invalidated after any call to @ref write - or @ref write_eof. - - @note If the last call to @ref write came from the input - area of a @b DynamicBuffer object, a call to the dynamic - buffer's `consume` function may invalidate this return - value. - */ - string_view const& - body() const - { - // This function not available when isDirect==true - BOOST_STATIC_ASSERT(! isDirect); - return body_; - } - - /** Returns the chunk extension parsed in the last call to @ref write. - - This buffer is invalidated after any call to @ref write - or @ref write_eof. - - @note If the last call to @ref write came from the input - area of a @b DynamicBuffer object, a call to the dynamic - buffer's `consume` function may invalidate this return - value. - */ - string_view const& - chunk_extension() const - { - // This function not available when isDirect==true - BOOST_STATIC_ASSERT(! isDirect); - return ext_; - } - - /** Returns the optional value of Content-Length if known. - - @note The return value is undefined unless a complete - header has been parsed. - */ - boost::optional - content_length() const - { - BOOST_ASSERT(got_header()); - if(! (f_ & flagContentLength)) - return boost::none; - return len_; - } - - /** Copy leftover body data from the dynamic buffer. - - @note This member function is only available when - `isDirect==true`. - - @return The number of bytes processed from the dynamic - buffer. The caller should remove these bytes by calling - `consume` on the buffer. - */ - template - std::size_t - copy_body(DynamicBuffer& buffer); - - /** Returns a set of buffers for storing body data. - - @param buffers A writable output parameter into which - the function will place the buffers upon success. - - @param limit The maximum number of bytes in the - size of the returned buffer sequence. The actual size - of the buffer sequence may be lower than this number. - - @note This member function is only available when - `isDirect==true`. - */ - template - void - prepare_body(boost::optional< - MutableBufferSequence>& buffers, std::size_t limit); - - /** Commit body data. - - @param n The number of bytes to commit. This must - be less than or equal to the size of the buffer - sequence returned by @ref prepare_body. - - @note This member function is only available when - `isDirect==true`. - */ - void - commit_body(std::size_t n); - - /** Indicate that body octets have been consumed. - - @param n The number of bytes to consume. - */ - void - consume(std::size_t n) - { - BOOST_ASSERT(n <= len_); - BOOST_ASSERT( - state_ == parse_state::body || - state_ == parse_state::chunk_body); - len_ -= n; - if(len_ == 0) - { - if(state_ == parse_state::body) - state_ = parse_state::complete; - else - state_ = parse_state::chunk_header; - } - } + put_eof(error_code& ec); private: inline @@ -570,68 +434,42 @@ private: maybe_flatten( ConstBufferSequence const& buffers); - std::size_t - do_write(boost::asio::const_buffers_1 const& buffer, + void + parse_header(char const*& p, + std::size_t n, error_code& ec); + + void + parse_header(char const*& p, char const* term, error_code& ec, std::true_type); - std::size_t - do_write(boost::asio::const_buffers_1 const& buffer, + void + parse_header(char const*& p, char const* term, error_code& ec, std::false_type); void - parse_startline(char const*& it, - int& version, int& status, - error_code& ec, std::true_type); + parse_body(char const*& p, + std::size_t n, error_code& ec); void - parse_startline(char const*& it, - int& version, int& status, - error_code& ec, std::false_type); + parse_body_to_eof(char const*& p, + std::size_t n, error_code& ec); void - parse_fields(char const*& it, + parse_chunk_header(char const*& p, + std::size_t n, error_code& ec); + + void + parse_chunk_body(char const*& p, + std::size_t n, error_code& ec); + + void + parse_fields(char const*& p, char const* last, error_code& ec); void - do_field( - string_view const& name, - string_view const& value, - error_code& ec); - - std::size_t - parse_header(char const* p, - std::size_t n, error_code& ec); - - void - do_header(int, std::true_type); - - void - do_header(int status, std::false_type); - - void - maybe_do_body_direct(); - - void - maybe_do_body_indirect(error_code& ec); - - std::size_t - parse_chunk_header(char const* p, - std::size_t n, error_code& ec); - - std::size_t - parse_body(char const* p, - std::size_t n, error_code& ec); - - std::size_t - parse_body_to_eof(char const* p, - std::size_t n, error_code& ec); - - std::size_t - parse_chunk_body(char const* p, - std::size_t n, error_code& ec); - - void - do_complete(error_code& ec); + do_field(string_view const& name, + string_view const& value, + error_code& ec); }; } // http diff --git a/include/beast/http/buffer_body.hpp b/include/beast/http/buffer_body.hpp index d8c5be38..c97b343f 100644 --- a/include/beast/http/buffer_body.hpp +++ b/include/beast/http/buffer_body.hpp @@ -8,9 +8,9 @@ #ifndef BEAST_HTTP_BUFFER_BODY_HPP #define BEAST_HTTP_BUFFER_BODY_HPP -#include #include #include +#include #include #include #include @@ -31,41 +31,30 @@ namespace http { ... } @endcode - - @tparam isDeferred A `bool` which, when set to `true`, - indicates to the serialization implementation that it should - send the entire HTTP Header before attempting to acquire - buffers representing the body. - - @tparam ConstBufferSequence The type of buffer sequence - stored in the body by the caller. */ -template< - bool isDeferred, - class ConstBufferSequence> struct buffer_body { - static_assert(is_const_buffer_sequence::value, - "ConstBufferSequence requirements not met"); + /// The type of the body member when used in a message. + struct value_type + { + /** A pointer to a contiguous area of memory of @ref size octets, else `nullptr`. - /** The type of the body member when used in a message. + If this is `nullptr` and @ref more is `true`, the error + @ref error::need_buffer will be returned by a serializer + when attempting to retrieve the next buffer. + */ + void* data; - When engaged, the first element of the pair represents - the current buffer sequence to be written. + /** The number of octets in the buffer pointed to by @ref data - The second element of the pair indicates whether or not - additional buffers will be available. A value of `false` - indicates the end of the message body. + If @ref data is `nullptr` during serialization, this value + is not inspected. + */ + std::size_t size; - If the buffer in the value is disengaged, and the second - element of the pair is `true`, @ref serializer operations - will return @ref http::error::need_more. This signals the - calling code that a new buffer should be placed into the - body, or that the caller should indicate that no more - buffers will be available. - */ - using value_type = - std::pair, bool>; + /// `true` if this is not the last buffer. + bool more; + }; #if BEAST_DOXYGEN /// The algorithm to obtain buffers representing the body @@ -77,10 +66,10 @@ struct buffer_body value_type const& body_; public: - using is_deferred = - std::integral_constant; + using is_deferred = std::false_type; - using const_buffers_type = ConstBufferSequence; + using const_buffers_type = + boost::asio::const_buffers_1; template explicit @@ -95,44 +84,89 @@ struct buffer_body { } - boost::optional> + boost::optional< + std::pair> get(error_code& ec) { if(toggle_) { - if(body_.second) + if(body_.more) { toggle_ = false; - ec = error::need_more; + ec = error::need_buffer; } return boost::none; } - if(body_.first) + if(body_.data) { toggle_ = true; - return {{*body_.first, body_.second}}; + return {{const_buffers_type{ + body_.data, body_.size}, body_.more}}; } - if(body_.second) - ec = error::need_more; + if(body_.more) + ec = error::need_buffer; return boost::none; } }; #endif + +#if BEAST_DOXYGEN + /// The algorithm used store buffers in this body + using writer = implementation_defined; +#else + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& m) + : body_(m.body) + { + } + + void + init(boost::optional, error_code&) + { + } + + template + void + put(ConstBufferSequence const& buffers, + error_code& ec) + { + using boost::asio::buffer_size; + using boost::asio::buffer_copy; + auto const n = buffer_size(buffers); + if(! body_.data || n > body_.size) + { + ec = error::need_buffer; + return; + } + auto const bytes_transferred = + buffer_copy(boost::asio::buffer( + body_.data, body_.size), buffers); + body_.data = reinterpret_cast( + body_.data) + bytes_transferred; + body_.size -= bytes_transferred; + } + + void + finish(error_code&) + { + } + }; +#endif }; #if ! BEAST_DOXYGEN - +// operator<< is not supported for buffer_body" template std::ostream& -operator<<(std::ostream& os, message< - isRequest, buffer_body, Fields> const& msg) -{ - static_assert(sizeof(ConstBufferSequence) == -1, - "operator<< is not supported for buffer_body"); -} - +operator<<(std::ostream& os, message const& msg) = delete; #endif } // http diff --git a/include/beast/http/detail/basic_parser.hpp b/include/beast/http/detail/basic_parser.hpp index 5a78d02c..d51016f4 100644 --- a/include/beast/http/detail/basic_parser.hpp +++ b/include/beast/http/detail/basic_parser.hpp @@ -62,6 +62,20 @@ namespace detail { class basic_parser_base { protected: + enum class state + { + nothing_yet = 0, + header, + body0, + body, + body_to_eof0, + body_to_eof, + chunk_header0, + chunk_header, + chunk_body, + complete + }; + static bool is_pathchar(char c) @@ -263,11 +277,9 @@ protected: bool parse_crlf(char const*& it) { - if(*it != '\r') + if( it[0] != '\r' && it[1] != '\n') return false; - if(*++it != '\n') - return false; - ++it; + it += 2; return true; } diff --git a/include/beast/http/dynamic_body.hpp b/include/beast/http/dynamic_body.hpp index f2ef0833..09f38ab9 100644 --- a/include/beast/http/dynamic_body.hpp +++ b/include/beast/http/dynamic_body.hpp @@ -9,8 +9,8 @@ #define BEAST_HTTP_DYNAMIC_BODY_HPP #include -#include #include +#include #include #include #include @@ -28,57 +28,6 @@ struct basic_dynamic_body /// The type of the body member when used in a message. using value_type = DynamicBuffer; -#if BEAST_DOXYGEN - /// The algorithm used store buffers in this body - using writer = implementation_defined; -#else - class writer - { - value_type& body_; - - public: - static bool constexpr is_direct = true; - - using mutable_buffers_type = - typename DynamicBuffer::mutable_buffers_type; - - template - explicit - writer(message& msg) - : body_(msg.body) - { - } - - void - init() - { - } - - void - init(std::uint64_t content_length) - { - } - - mutable_buffers_type - prepare(std::size_t n) - { - return body_.prepare(n); - } - - void - commit(std::size_t n) - { - body_.commit(n); - } - - void - finish() - { - } - }; -#endif - #if BEAST_DOXYGEN /// The algorithm to obtain buffers representing the body using reader = implementation_defined; @@ -119,6 +68,58 @@ struct basic_dynamic_body } }; #endif + +#if BEAST_DOXYGEN + /// The algorithm used store buffers in this body + using writer = implementation_defined; +#else + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& msg) + : body_(msg.body) + { + } + + void + init(boost::optional< + std::uint64_t> const&, error_code&) + { + } + + template + void + put(ConstBufferSequence const& buffers, + error_code& ec) + { + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + boost::optional b; + try + { + b.emplace(body_.prepare( + buffer_size(buffers))); + } + catch(std::length_error const&) + { + ec = error::buffer_overflow; + return; + } + body_.commit(buffer_copy(*b, buffers)); + } + + void + finish(error_code&) + { + } + }; +#endif }; /** A dynamic message body represented by a @ref multi_buffer diff --git a/include/beast/http/empty_body.hpp b/include/beast/http/empty_body.hpp index 5df0942a..d1b2b83f 100644 --- a/include/beast/http/empty_body.hpp +++ b/include/beast/http/empty_body.hpp @@ -9,6 +9,7 @@ #define BEAST_HTTP_EMPTY_BODY_HPP #include +#include #include #include @@ -26,6 +27,8 @@ struct empty_body /// The type of the body member when used in a message. struct value_type { + // VFALCO We could stash boost::optional + // for the content length here, set on init() }; #if BEAST_DOXYGEN @@ -64,6 +67,39 @@ struct empty_body } }; #endif + +#if BEAST_DOXYGEN + /// The algorithm used store buffers in this body + using writer = implementation_defined; +#else + struct writer + { + template + explicit + writer(message& msg) + { + } + + void + init(boost::optional const&, + error_code&) + { + } + + template + void + put(ConstBufferSequence const&, + error_code& ec) + { + ec = error::missing_body; + } + + void + finish(error_code&) + { + } + }; +#endif }; } // http diff --git a/include/beast/http/error.hpp b/include/beast/http/error.hpp index c231769e..dd5c2af0 100644 --- a/include/beast/http/error.hpp +++ b/include/beast/http/error.hpp @@ -14,36 +14,64 @@ namespace beast { namespace http { -/// Error codes returned from HTTP parsing +/// Error codes returned from HTTP algorithms and operations. enum class error { /** The end of the stream was reached. - This error is returned by @ref basic_parser::write_eof - when the end of stream is reached and there are no - unparsed bytes in the stream buffer. + This error is returned under the following conditions: + + @li When attempting to read HTTP data from a stream and the stream + read returns the error `boost::asio::error::eof` before any new octets + have been received. + + @li When sending a complete HTTP message at once and the semantics of + the message are that the connection should be closed to indicate the + end of the message. */ end_of_stream = 1, /** The incoming message is incomplete. - This happens when the end of stream is reached - and some bytes have been received, but not the + This happens when the end of stream is reached during + parsing and some octets have been received, but not the entire message. */ partial_message, /** Additional buffers are required. - This error is generated during serialization of HTTP - messages using the @ref buffer_body representation. - It indicates to the caller that an additional buffer - sequence should be placed into the body, or that the - caller should indicate that there are no more bytes - remaining in the body to serialize. + This error is returned during parsing when additional + octets are needed. The caller should append more data + to the existing buffer and retry the parse operaetion. */ need_more, + /** The message container has no body. + + This error is returned when attempting to parse body + octets into a message container which has the + @ref empty_body body type. + + @see @ref empty_body + */ + missing_body, + + /** Additional buffers are required. + + This error is returned under the following conditions: + + @li During serialization when using @ref buffer_body. + The caller should update the body to point to a new + buffer or indicate that there are no more octets in + the body. + + @li During parsing when using @ref buffer_body. + The caller should update the body to point to a new + storage area to receive additional body octets. + */ + need_buffer, + /** Buffer maximum exceeded. This error is returned when reading HTTP content @@ -52,6 +80,10 @@ enum class error */ buffer_overflow, + // + // (parser errors) + // + /// The line ending was malformed bad_line_ending, diff --git a/include/beast/http/header_parser.hpp b/include/beast/http/header_parser.hpp index 747f9465..efda061b 100644 --- a/include/beast/http/header_parser.hpp +++ b/include/beast/http/header_parser.hpp @@ -33,18 +33,19 @@ namespace http { */ template class header_parser - : public basic_parser> { header h_; + string_view body_; public: - using mutable_buffers_type = - boost::asio::null_buffers; - /// The type of @ref header this object produces. using value_type = header; + /// Default constructor. + header_parser() = default; + /// Copy constructor. header_parser(header_parser const&) = default; @@ -58,18 +59,49 @@ public: */ header_parser(header_parser&&) = default; + /** Move assignment + + After the move, the only valid operation + on the moved-from object is destruction. + */ + header_parser& operator=(header_parser&&) = default; + /** Constructor - @param args If present, additional arguments to be - forwarded to the @ref beast::http::header constructor. + @param args Optional arguments forwarded + forwarded to the @ref http::header constructor. */ +#if BEAST_DOXYGEN template explicit header_parser(Args&&... args); +#else + template::type, + header_parser>::value>> + explicit + header_parser(Arg0&& arg0, ArgN&&... argn); +#endif + + /** Returns parsed body octets. + + This function will return the most recent buffer + of octets corresponding to the parsed body. This + buffer will become invalidated on any subsequent + call to @ref put or @ref put_eof + */ + string_view + body() const + { + return body_; + } /** Returns the parsed header - Only valid if @ref got_header would return `true`. + @note The return value is undefined unless + @ref is_header_done would return `true`. */ value_type const& get() const @@ -79,7 +111,8 @@ public: /** Returns the parsed header. - Only valid if @ref got_header would return `true`. + @note The return value is undefined unless + @ref is_header_done would return `true`. */ value_type& get() @@ -89,8 +122,10 @@ public: /** Returns ownership of the parsed header. - Ownership is transferred to the caller. Only - valid if @ref got_header would return `true`. + Ownership is transferred to the caller. + + @note The return value is undefined unless + @ref is_header_done would return `true`. Requires: @ref value_type is @b MoveConstructible @@ -98,20 +133,18 @@ public: value_type release() { - static_assert(std::is_move_constructible::value, + static_assert( + std::is_move_constructible::value, "MoveConstructible requirements not met"); return std::move(h_); } private: - friend class basic_parser< - isRequest, false, header_parser>; + friend class basic_parser; void - on_request( - string_view const& method, - string_view const& path, - int version, error_code&) + on_request(string_view method, + string_view path, int version, error_code&) { h_.target(path); h_.method(method); @@ -119,9 +152,8 @@ private: } void - on_response(int status, - string_view const& reason, - int version, error_code&) + on_response(int status, string_view reason, + int version, error_code&) { h_.status = status; h_.version = version; @@ -129,9 +161,8 @@ private: } void - on_field(string_view const& name, - string_view const& value, - error_code&) + on_field(string_view name, + string_view value, error_code&) { h_.fields.insert(name, value); } @@ -142,41 +173,28 @@ private: } void - on_body(error_code& ec) + on_body(boost::optional const&, error_code&) { } void - on_body(std::uint64_t content_length, - error_code& ec) + on_data(string_view s, error_code&) { + body_ = s; } void - on_data(string_view const& s, - error_code& ec) - { - } - - void - on_commit(std::size_t n) - { - // Can't write body data with header-only parser! - BOOST_ASSERT(false); - BOOST_THROW_EXCEPTION(std::logic_error{ - "invalid member function call"}); - } - - void - on_chunk(std::uint64_t n, - string_view const& ext, - error_code& ec) + on_chunk(std::uint64_t, + string_view const&, error_code&) { + body_ = {}; } void on_complete(error_code&) { + body_ = {}; } }; diff --git a/include/beast/http/impl/async_read.ipp b/include/beast/http/impl/async_read.ipp deleted file mode 100644 index de26f494..00000000 --- a/include/beast/http/impl/async_read.ipp +++ /dev/null @@ -1,733 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_IMPL_ASYNC_READ_IPP_HPP -#define BEAST_HTTP_IMPL_ASYNC_READ_IPP_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { -namespace detail { - -template -class read_some_buffer_op -{ - struct data - { - bool cont; - Stream& s; - DynamicBuffer& db; - basic_parser& p; - boost::optional mb; - boost::optional bb; - std::size_t bytes_used; - int state = 0; - - data(Handler& handler, Stream& s_, DynamicBuffer& db_, - basic_parser& p_) - : s(s_) - , db(db_) - , p(p_) - { - using boost::asio::asio_handler_is_continuation; - cont = asio_handler_is_continuation(std::addressof(handler)); - } - }; - - handler_ptr d_; - -public: - read_some_buffer_op(read_some_buffer_op&&) = default; - read_some_buffer_op(read_some_buffer_op const&) = default; - - template - read_some_buffer_op(DeducedHandler&& h, - Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) - { - (*this)(error_code{}, 0, false); - } - - void - operator()(error_code ec, - std::size_t bytes_transferred, bool again = true); - - friend - void* - asio_handler_allocate(std::size_t size, - read_some_buffer_op* op) - { - using boost::asio::asio_handler_allocate; - return asio_handler_allocate( - size, std::addressof(op->d_.handler())); - } - - friend - void - asio_handler_deallocate( - void* p, std::size_t size, - read_some_buffer_op* op) - { - using boost::asio::asio_handler_deallocate; - asio_handler_deallocate( - p, size, std::addressof(op->d_.handler())); - } - - friend - bool - asio_handler_is_continuation( - read_some_buffer_op* op) - { - return op->d_->cont; - } - - template - friend - void - asio_handler_invoke(Function&& f, - read_some_buffer_op* op) - { - using boost::asio::asio_handler_invoke; - asio_handler_invoke( - f, std::addressof(op->d_.handler())); - } -}; - -template -void -read_some_buffer_op:: -operator()(error_code ec, - std::size_t bytes_transferred, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - if(d.state == 99) - goto upcall; - for(;;) - { - switch(d.state) - { - case 0: - if(d.db.size() == 0) - { - d.state = 2; - break; - } - //[[fallthrough]] - - case 1: - { - BOOST_ASSERT(d.db.size() > 0); - d.bytes_used = - d.p.write(d.db.data(), ec); - if(d.bytes_used > 0 || ec) - { - // call handler - if(d.state == 1) - goto upcall; - d.state = 99; - d.s.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); - return; - } - //[[fallthrough]] - } - - case 2: - case 3: - { - using beast::detail::read_size_helper; - auto const size = - read_size_helper(d.db, 65536); - BOOST_ASSERT(size > 0); - try - { - d.mb.emplace(d.db.prepare(size)); - } - catch(std::length_error const&) - { - // call handler - if(d.state == 3) - goto upcall; - d.state = 99; - d.s.get_io_service().post( - bind_handler(std::move(*this), - error::buffer_overflow, 0)); - return; - } - // read - d.state = 4; - d.s.async_read_some(*d.mb, std::move(*this)); - return; - } - - case 4: - if(ec == boost::asio::error::eof) - { - BOOST_ASSERT(bytes_transferred == 0); - d.bytes_used = 0; - if(! d.p.got_some()) - { - ec = error::end_of_stream; - goto upcall; - } - // caller sees end_of_stream on next read. - ec = {}; - d.p.write_eof(ec); - if(ec) - goto upcall; - BOOST_ASSERT(d.p.is_complete()); - goto upcall; - } - if(ec) - { - d.bytes_used = 0; - goto upcall; - } - BOOST_ASSERT(bytes_transferred > 0); - d.db.commit(bytes_transferred); - d.state = 1; - break; - } - } -upcall: - // can't pass any members of `d` otherwise UB - auto const bytes_used = d.bytes_used; - d_.invoke(ec, bytes_used); -} - -//------------------------------------------------------------------------------ - -template -class read_some_body_op -{ - struct data - { - bool cont; - Stream& s; - DynamicBuffer& db; - basic_parser& p; - boost::optional mb; - std::size_t bytes_used; - int state = 0; - - data(Handler& handler, Stream& s_, DynamicBuffer& db_, - basic_parser& p_) - : s(s_) - , db(db_) - , p(p_) - { - using boost::asio::asio_handler_is_continuation; - cont = asio_handler_is_continuation(std::addressof(handler)); - } - }; - - handler_ptr d_; - -public: - read_some_body_op(read_some_body_op&&) = default; - read_some_body_op(read_some_body_op const&) = default; - - template - read_some_body_op(DeducedHandler&& h, - Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) - { - (*this)(error_code{}, 0, false); - } - - void - operator()(error_code ec, - std::size_t bytes_transferred, bool again = true); - - friend - void* - asio_handler_allocate(std::size_t size, - read_some_body_op* op) - { - using boost::asio::asio_handler_allocate; - return asio_handler_allocate( - size, std::addressof(op->d_.handler())); - } - - friend - void - asio_handler_deallocate( - void* p, std::size_t size, - read_some_body_op* op) - { - using boost::asio::asio_handler_deallocate; - asio_handler_deallocate( - p, size, std::addressof(op->d_.handler())); - } - - friend - bool - asio_handler_is_continuation( - read_some_body_op* op) - { - return op->d_->cont; - } - - template - friend - void - asio_handler_invoke(Function&& f, - read_some_body_op* op) - { - using boost::asio::asio_handler_invoke; - asio_handler_invoke( - f, std::addressof(op->d_.handler())); - } -}; - -template -void -read_some_body_op:: -operator()(error_code ec, - std::size_t bytes_transferred, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - if(d.state == 99) - goto upcall; - for(;;) - { - switch(d.state) - { - case 0: - if(d.db.size() > 0) - { - d.bytes_used = d.p.copy_body(d.db); - // call handler - d.state = 99; - d.s.get_io_service().post( - bind_handler(std::move(*this), - ec, 0)); - return; - } - d.p.prepare_body(d.mb, 65536); - // read - d.state = 1; - d.s.async_read_some( - *d.mb, std::move(*this)); - return; - - case 1: - d.bytes_used = 0; - if(ec == boost::asio::error::eof) - { - BOOST_ASSERT(bytes_transferred == 0); - // caller sees EOF on next read - ec = {}; - d.p.write_eof(ec); - if(ec) - goto upcall; - BOOST_ASSERT(d.p.is_complete()); - } - else if(! ec) - { - d.p.commit_body(bytes_transferred); - } - goto upcall; - } - } -upcall: - // can't pass any members of `d` otherwise UB - auto const bytes_used = d.bytes_used; - d_.invoke(ec, bytes_used); -} - -//------------------------------------------------------------------------------ - -template -class parse_op -{ - struct data - { - bool cont; - Stream& s; - DynamicBuffer& db; - basic_parser& p; - - data(Handler& handler, Stream& s_, DynamicBuffer& db_, - basic_parser& p_) - : s(s_) - , db(db_) - , p(p_) - { - using boost::asio::asio_handler_is_continuation; - cont = asio_handler_is_continuation(std::addressof(handler)); - } - }; - - handler_ptr d_; - -public: - parse_op(parse_op&&) = default; - parse_op(parse_op const&) = default; - - template - parse_op(DeducedHandler&& h, - Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) - { - (*this)(error_code{}, 0, false); - } - - void - operator()(error_code const& ec, - std::size_t bytes_used, bool again = true); - - friend - void* - asio_handler_allocate( - std::size_t size, parse_op* op) - { - using boost::asio::asio_handler_allocate; - return asio_handler_allocate( - size, std::addressof(op->d_.handler())); - } - - friend - void - asio_handler_deallocate( - void* p, std::size_t size, - parse_op* op) - { - using boost::asio::asio_handler_deallocate; - asio_handler_deallocate( - p, size, std::addressof(op->d_.handler())); - } - - friend - bool - asio_handler_is_continuation( - parse_op* op) - { - return op->d_->cont; - } - - template - friend - void - asio_handler_invoke( - Function&& f, parse_op* op) - { - using boost::asio::asio_handler_invoke; - asio_handler_invoke( - f, std::addressof(op->d_.handler())); - } -}; - -template -void -parse_op:: -operator()(error_code const& ec, - std::size_t bytes_used, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - if(! ec) - { - d.db.consume(bytes_used); - if(! d.p.is_complete()) - return async_read_some( - d.s, d.db, d.p, std::move(*this)); - } - d_.invoke(ec); -} - -//------------------------------------------------------------------------------ - -template -class read_message_op -{ - using parser_type = - message_parser; - - using message_type = - message; - - struct data - { - bool cont; - Stream& s; - DynamicBuffer& db; - message_type& m; - parser_type p; - bool started = false; - int state = 0; - - data(Handler& handler, Stream& s_, - DynamicBuffer& sb_, message_type& m_) - : s(s_) - , db(sb_) - , m(m_) - { - using boost::asio::asio_handler_is_continuation; - cont = asio_handler_is_continuation(std::addressof(handler)); - } - }; - - handler_ptr d_; - -public: - read_message_op(read_message_op&&) = default; - read_message_op(read_message_op const&) = default; - - template - read_message_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) - { - (*this)(error_code{}, false); - } - - void - operator()(error_code ec, bool again = true); - - friend - void* asio_handler_allocate( - std::size_t size, read_message_op* op) - { - using boost::asio::asio_handler_allocate; - return asio_handler_allocate( - size, std::addressof(op->d_.handler())); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, read_message_op* op) - { - using boost::asio::asio_handler_deallocate; - asio_handler_deallocate( - p, size, std::addressof(op->d_.handler())); - } - - friend - bool asio_handler_is_continuation(read_message_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, read_message_op* op) - { - using boost::asio::asio_handler_invoke; - asio_handler_invoke( - f, std::addressof(op->d_.handler())); - } -}; - -template -void -read_message_op:: -operator()(error_code ec, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - if(ec) - goto upcall; - switch(d.state) - { - case 0: - d.state = 1; - beast::http::async_read( - d.s, d.db, d.p, std::move(*this)); - return; - - case 1: - d.m = d.p.release(); - goto upcall; - } -upcall: - d_.invoke(ec); -} - -template< - class AsyncReadStream, - class DynamicBuffer, - bool isRequest, class Derived, - class ReadHandler> -async_return_type< - ReadHandler, void(error_code, std::size_t)> -async_read_some( - AsyncReadStream& stream, - DynamicBuffer& buffer, - basic_parser& parser, - ReadHandler&& handler) -{ - async_completion init{handler}; - switch(parser.state()) - { - case parse_state::header: - case parse_state::chunk_header: - detail::read_some_buffer_op>{ - init.completion_handler, stream, buffer, parser}; - break; - - default: - detail::read_some_body_op>{ - init.completion_handler, stream, buffer, parser}; - break; - } - return init.result.get(); -} - -template< - class AsyncReadStream, - class DynamicBuffer, - bool isRequest, class Derived, - class ReadHandler> -inline -async_return_type< - ReadHandler, void(error_code, std::size_t)> -async_read_some( - AsyncReadStream& stream, - DynamicBuffer& buffer, - basic_parser& parser, - ReadHandler&& handler) -{ - async_completion init{handler}; - detail::read_some_buffer_op>{ - init.completion_handler, stream, buffer, parser}; - return init.result.get(); -} - -} // detail - -//------------------------------------------------------------------------------ - -template< - class AsyncReadStream, - class DynamicBuffer, - bool isRequest, bool isDirect, class Derived, - class ReadHandler> -async_return_type< - ReadHandler, void(error_code, std::size_t)> -async_read_some( - AsyncReadStream& stream, - DynamicBuffer& buffer, - basic_parser& parser, - ReadHandler&& handler) -{ - static_assert(is_async_read_stream::value, - "AsyncReadStream requirements not met"); - static_assert(is_dynamic_buffer::value, - "DynamicBuffer requirements not met"); - BOOST_ASSERT(! parser.is_complete()); - return detail::async_read_some(stream, buffer, parser, - std::forward(handler)); -} - -template< - class AsyncReadStream, - class DynamicBuffer, - bool isRequest, bool isDirect, class Derived, - class ReadHandler> -async_return_type< - ReadHandler, void(error_code)> -async_read( - AsyncReadStream& stream, - DynamicBuffer& buffer, - basic_parser& parser, - ReadHandler&& handler) -{ - static_assert(is_async_read_stream::value, - "AsyncReadStream requirements not met"); - static_assert(is_dynamic_buffer::value, - "DynamicBuffer requirements not met"); - BOOST_ASSERT(! parser.is_complete()); - async_completion init{handler}; - detail::parse_op>{ - init.completion_handler, stream, buffer, parser}; - return init.result.get(); -} - -template< - class AsyncReadStream, - class DynamicBuffer, - bool isRequest, class Body, class Fields, - class ReadHandler> -async_return_type< - ReadHandler, void(error_code)> -async_read( - AsyncReadStream& stream, - DynamicBuffer& buffer, - message& msg, - ReadHandler&& handler) -{ - static_assert(is_async_read_stream::value, - "AsyncReadStream requirements not met"); - static_assert(is_dynamic_buffer::value, - "DynamicBuffer requirements not met"); - static_assert(is_body::value, - "Body requirements not met"); - static_assert(is_body_writer::value, - "BodyWriter requirements not met"); - async_completion init{handler}; - detail::read_message_op>{ - init.completion_handler, stream, buffer, msg}; - return init.result.get(); -} - -} // http -} // beast - -#endif diff --git a/include/beast/http/impl/basic_parser.ipp b/include/beast/http/impl/basic_parser.ipp index 01bc7535..1ee80a70 100644 --- a/include/beast/http/impl/basic_parser.ipp +++ b/include/beast/http/impl/basic_parser.ipp @@ -20,36 +20,27 @@ namespace beast { namespace http { -template -template -basic_parser:: -basic_parser(basic_parser&& other) +template +template +basic_parser:: +basic_parser(basic_parser< + isRequest, OtherDerived>&& other) : len_(other.len_) , buf_(std::move(other.buf_)) , buf_len_(other.buf_len_) , skip_(other.skip_) , x_(other.x_) - , f_(other.f_) , state_(other.state_) + , f_(other.f_) { } -template -void -basic_parser:: -skip_body() -{ - BOOST_ASSERT(! got_some()); - f_ |= flagSkipBody; -} - -template +template bool -basic_parser:: +basic_parser:: is_keep_alive() const { - BOOST_ASSERT(got_header()); + BOOST_ASSERT(is_header_done()); if(f_ & flagHTTP11) { if(f_ & flagConnectionClose) @@ -63,193 +54,177 @@ is_keep_alive() const return (f_ & flagNeedEOF) == 0; } -template +template +boost::optional +basic_parser:: +content_length() const +{ + BOOST_ASSERT(is_header_done()); + if(! (f_ & flagContentLength)) + return boost::none; + return len_; +} + +template +void +basic_parser:: +skip(bool v) +{ + BOOST_ASSERT(! got_some()); + if(v) + f_ |= flagSkipBody; + else + f_ &= ~flagSkipBody; +} + +template template std::size_t -basic_parser:: -write(ConstBufferSequence const& buffers, +basic_parser:: +put(ConstBufferSequence const& buffers, error_code& ec) { static_assert(is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); auto const buffer = maybe_flatten(buffers); - return write(boost::asio::const_buffers_1{ + return put(boost::asio::const_buffers_1{ buffer.data(), buffer.size()}, ec); } -template +template std::size_t -basic_parser:: -write(boost::asio::const_buffers_1 const& buffer, +basic_parser:: +put(boost::asio::const_buffers_1 const& buffer, error_code& ec) { - return do_write(buffer, ec, - std::integral_constant{}); + BOOST_ASSERT(state_ != state::complete); + using boost::asio::buffer_size; + auto p = boost::asio::buffer_cast< + char const*>(*buffer.begin()); + auto n = buffer_size(*buffer.begin()); + auto const p0 = p; + auto const p1 = p0 + n; +loop: + switch(state_) + { + case state::nothing_yet: + if(n == 0) + { + ec = error::need_more; + return 0; + } + state_ = state::header; + // [[fallthrough]] + + case state::header: + parse_header(p, n, ec); + if(ec) + goto done; + break; + + case state::body0: + impl().on_body(content_length(), ec); + if(ec) + goto done; + state_ = state::body; + // [[fallthrough]] + + case state::body: + parse_body(p, n, ec); + if(ec) + goto done; + break; + + case state::body_to_eof0: + impl().on_body(content_length(), ec); + if(ec) + goto done; + state_ = state::body_to_eof; + // [[fallthrough]] + + case state::body_to_eof: + parse_body_to_eof(p, n, ec); + if(ec) + goto done; + break; + + case state::chunk_header0: + impl().on_body(content_length(), ec); + if(ec) + return 0; + state_ = state::chunk_header; + // [[fallthrough]] + + case state::chunk_header: + parse_chunk_header(p, n, ec); + if(ec) + goto done; + break; + + case state::chunk_body: + parse_chunk_body(p, n, ec); + if(ec) + goto done; + break; + + case state::complete: + break; + } + if(p < p1 && eager()) + { + n = static_cast(p1 - p); + goto loop; + } +done: + return static_cast(p - p0); } -template +template void -basic_parser:: -write_eof(error_code& ec) +basic_parser:: +put_eof(error_code& ec) { BOOST_ASSERT(got_some()); - if(state_ == parse_state::header) + if(state_ == state::header) { ec = error::partial_message; return; } if(f_ & (flagContentLength | flagChunked)) { - if(state_ != parse_state::complete) + if(state_ != state::complete) { ec = error::partial_message; return; } return; } - do_complete(ec); + impl().on_complete(ec); if(ec) return; + state_ = state::complete; } -template -template -std::size_t -basic_parser:: -copy_body(DynamicBuffer& buffer) -{ - // This function not available when isDirect==false - BOOST_STATIC_ASSERT(isDirect); - - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - BOOST_ASSERT(buffer.size() > 0); - BOOST_ASSERT( - state_ == parse_state::body || - state_ == parse_state::body_to_eof || - state_ == parse_state::chunk_body); - maybe_do_body_direct(); - switch(state_) - { - case parse_state::body_to_eof: - { - auto const buffers = - impl().on_prepare(buffer.size()); - BOOST_ASSERT( - buffer_size(buffers) >= 1 && - buffer_size(buffers) <= - buffer.size()); - auto const n = buffer_copy( - buffers, buffer.data()); - buffer.consume(n); - impl().on_commit(n); - return n; - } - - default: - { - BOOST_ASSERT(len_ > 0); - auto const buffers = - impl().on_prepare( - beast::detail::clamp(len_)); - BOOST_ASSERT( - buffer_size(buffers) >= 1 && - buffer_size(buffers) <= - beast::detail::clamp(len_)); - auto const n = buffer_copy( - buffers, buffer.data()); - commit_body(n); - return n; - } - } -} - -template -template -void -basic_parser:: -prepare_body(boost::optional< - MutableBufferSequence>& buffers, std::size_t limit) -{ - // This function not available when isDirect==false - BOOST_STATIC_ASSERT(isDirect); - - BOOST_ASSERT(limit > 0); - BOOST_ASSERT( - state_ == parse_state::body || - state_ == parse_state::body_to_eof || - state_ == parse_state::chunk_body); - maybe_do_body_direct(); - std::size_t n; - switch(state_) - { - case parse_state::body_to_eof: - n = limit; - break; - - default: - BOOST_ASSERT(len_ > 0); - n = beast::detail::clamp(len_, limit); - break; - } - buffers.emplace(impl().on_prepare(n)); -} - -template -void -basic_parser:: -commit_body(std::size_t n) -{ - // This function not available when isDirect==false - BOOST_STATIC_ASSERT(isDirect); - - BOOST_ASSERT(f_ & flagOnBody); - impl().on_commit(n); - switch(state_) - { - case parse_state::body: - len_ -= n; - if(len_ == 0) - { - // VFALCO This is no good, throwing out ec? - error_code ec; - do_complete(ec); - } - break; - - case parse_state::chunk_body: - len_ -= n; - if(len_ == 0) - state_ = parse_state::chunk_header; - break; - - default: - break; - } -} - -template +template template inline string_view -basic_parser:: +basic_parser:: maybe_flatten( ConstBufferSequence const& buffers) { - using boost::asio::buffer; using boost::asio::buffer_cast; using boost::asio::buffer_copy; using boost::asio::buffer_size; - - auto const it = buffers.begin(); + auto const p = buffers.begin(); auto const last = buffers.end(); - if(it == last) + if(p == last) return {nullptr, 0}; - if(std::next(it) == last) + if(std::next(p) == last) { // single buffer - auto const b = *it; + auto const b = *p; return {buffer_cast(b), buffer_size(b)}; } @@ -261,182 +236,407 @@ maybe_flatten( buf_len_ = len; } // flatten - buffer_copy( - buffer(buf_.get(), buf_len_), buffers); + buffer_copy(boost::asio::buffer( + buf_.get(), buf_len_), buffers); return {buf_.get(), buf_len_}; } -template +template inline -std::size_t -basic_parser:: -do_write(boost::asio::const_buffers_1 const& buffer, - error_code& ec, std::true_type) -{ - BOOST_ASSERT( - state_ == parse_state::header || - state_ == parse_state::chunk_header); - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - auto const p = buffer_cast< - char const*>(*buffer.begin()); - auto const n = - buffer_size(*buffer.begin()); - if(state_ == parse_state::header) - { - if(n > 0) - f_ |= flagGotSome; - return parse_header(p, n, ec); - } - else - { - maybe_do_body_direct(); - return parse_chunk_header(p, n, ec); - } -} - -template -inline -std::size_t -basic_parser:: -do_write(boost::asio::const_buffers_1 const& buffer, - error_code& ec, std::false_type) -{ - BOOST_ASSERT(state_ != parse_state::complete); - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - auto const p = buffer_cast< - char const*>(*buffer.begin()); - auto const n = - buffer_size(*buffer.begin()); - switch(state_) - { - case parse_state::header: - if(n > 0) - f_ |= flagGotSome; - return parse_header(p, n, ec); - - case parse_state::body: - maybe_do_body_indirect(ec); - if(ec) - return 0; - return parse_body(p, n, ec); - - case parse_state::body_to_eof: - maybe_do_body_indirect(ec); - if(ec) - return 0; - return parse_body_to_eof(p, n, ec); - - case parse_state::chunk_header: - maybe_do_body_indirect(ec); - if(ec) - return 0; - return parse_chunk_header(p, n, ec); - - case parse_state::chunk_body: - return parse_chunk_body(p, n, ec); - - case parse_state::complete: - break; - } - return 0; -} - - -template void -basic_parser:: -parse_startline(char const*& it, - int& version, int& status, - error_code& ec, std::true_type) +basic_parser:: +parse_header(char const*& p, + std::size_t n, error_code& ec) +{ + if(n < skip_ + 4) + { + ec = http::error::need_more; + return; + } + auto const term = find_eom( + p + skip_, p + n, ec); + if(ec) + return; + if(! term) + { + skip_ = n - 3; + ec = http::error::need_more; + return; + } + skip_ = 0; + + parse_header(p, term, ec, + std::integral_constant{}); + if(ec) + return; + + impl().on_header(ec); + if(ec) + return; + if(state_ == state::complete) + { + impl().on_complete(ec); + if(ec) + return; + } +} + +template +void +basic_parser:: +parse_header(char const*& p, char const* term, + error_code& ec, std::true_type) { /* request-line = method SP request-target SP HTTP-version CRLF method = token */ - auto const method = parse_method(it); + auto const method = parse_method(p); if(method.empty()) { ec = error::bad_method; return; } - if(*it++ != ' ') + if(*p++ != ' ') { ec = error::bad_method; return; } - auto const target = parse_target(it); + auto const target = parse_target(p); if(target.empty()) { ec = error::bad_path; return; } - if(*it++ != ' ') + if(*p++ != ' ') { ec = error::bad_path; return; } - version = parse_version(it); - if(version < 0 || ! parse_crlf(it)) + auto const version = parse_version(p); + if(version < 0 || ! parse_crlf(p)) { ec = error::bad_version; return; } + if(version >= 11) + f_ |= flagHTTP11; + impl().on_request( method, target, version, ec); if(ec) return; + + parse_fields(p, term, ec); + if(ec) + return; + BOOST_ASSERT(p == term); + + // RFC 7230 section 3.3 + // https://tools.ietf.org/html/rfc7230#section-3.3 + + if(f_ & flagSkipBody) + { + state_ = state::complete; + } + else if(f_ & flagContentLength) + { + if(len_ > 0) + { + f_ |= flagHasBody; + state_ = state::body0; + } + else + { + state_ = state::complete; + } + } + else if(f_ & flagChunked) + { + f_ |= flagHasBody; + state_ = state::chunk_header0; + } + else + { + len_ = 0; + state_ = state::complete; + } } -template +template void -basic_parser:: -parse_startline(char const*& it, - int& version, int& status, - error_code& ec, std::false_type) +basic_parser:: +parse_header(char const*& p, char const* term, + error_code& ec, std::false_type) { /* status-line = HTTP-version SP status-code SP reason-phrase CRLF status-code = 3*DIGIT reason-phrase = *( HTAB / SP / VCHAR / obs-text ) */ - version = parse_version(it); - if(version < 0 || *it != ' ') + auto const version = parse_version(p); + if(version < 0 || *p != ' ') { ec = error::bad_version; return; } - ++it; + ++p; - status = parse_status(it); - if(status < 0 || *it != ' ') + auto const status = parse_status(p); + if(status < 0 || *p != ' ') { ec = error::bad_status; return; } - ++it; + ++p; - auto const reason = parse_reason(it); - if(! parse_crlf(it)) + auto const reason = parse_reason(p); + if(! parse_crlf(p)) { ec = error::bad_reason; return; } + if(version >= 11) + f_ |= flagHTTP11; + impl().on_response( status, reason, version, ec); if(ec) return; + + parse_fields(p, term, ec); + if(ec) + return; + BOOST_ASSERT(p == term); + + // RFC 7230 section 3.3 + // https://tools.ietf.org/html/rfc7230#section-3.3 + + if( (f_ & flagSkipBody) || // e.g. response to a HEAD request + status / 100 == 1 || // 1xx e.g. Continue + status == 204 || // No Content + status == 304) // Not Modified + { + state_ = state::complete; + return; + } + + if(f_ & flagContentLength) + { + if(len_ > 0) + { + f_ |= flagHasBody; + state_ = state::body0; + } + else + { + state_ = state::complete; + } + } + else if(f_ & flagChunked) + { + f_ |= flagHasBody; + state_ = state::chunk_header0; + } + else + { + f_ |= flagHasBody; + f_ |= flagNeedEOF; + state_ = state::body_to_eof0; + } } -template +template +inline void -basic_parser:: -parse_fields(char const*& it, +basic_parser:: +parse_body(char const*& p, + std::size_t n, error_code& ec) +{ + n = beast::detail::clamp(len_, n); + impl().on_data(string_view{p, n}, ec); + if(ec) + return; + p += n; + len_ -= n; + if(len_ > 0) + return; + impl().on_complete(ec); + if(ec) + return; + state_ = state::complete; +} + +template +inline +void +basic_parser:: +parse_body_to_eof(char const*& p, + std::size_t n, error_code& ec) +{ + impl().on_data(string_view{p, n}, ec); + if(ec) + return; + p += n; +} + +template +void +basic_parser:: +parse_chunk_header(char const*& p, + std::size_t n, error_code& ec) +{ +/* + chunked-body = *chunk last-chunk trailer-part CRLF + + chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF + last-chunk = 1*("0") [ chunk-ext ] CRLF + trailer-part = *( header-field CRLF ) + + chunk-size = 1*HEXDIG + chunk-data = 1*OCTET ; a sequence of chunk-size octets + chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) + chunk-ext-name = token + chunk-ext-val = token / quoted-string +*/ + + auto const pend = p + n; + auto pbegin = p; + + // Treat the last CRLF in a chunk as + // part of the next chunk, so p can + // be parsed in one call instead of two. + if(f_ & flagExpectCRLF) + { + if(n < 2 || ! parse_crlf(p)) + { + ec = error::need_more; + return; + } + f_ &= ~flagExpectCRLF; + pbegin += 2; + n -= 2; + } + + char const* term; + + if(! (f_ & flagFinalChunk)) + { + if(n < skip_ + 2) + { + ec = error::need_more; + return; + } + term = find_eol(p + skip_, pend, ec); + if(ec) + return; + if(! term) + { + ec = error::need_more; + skip_ = n - 1; + return; + } + std::uint64_t v; + if(! parse_hex(p, v)) + { + ec = error::bad_chunk; + return; + } + if(v != 0) + { + if(*p == ';') + { + // VFALCO We need to parse the chunk + // extension to validate p here. + auto const ext = make_string(p, term - 2); + impl().on_chunk(v, ext, ec); + if(ec) + return; + } + else if(p != term - 2) + { + ec = error::bad_chunk; + return; + } + p = term; + len_ = v; + skip_ = 0; + f_ |= flagExpectCRLF; + state_ = state::chunk_body; + return; + } + + pbegin = p; + n = static_cast(pend - pbegin); + x_ = term - 2 - pbegin; // start of first '\r\n' + skip_ = x_; + + f_ |= flagFinalChunk; + } + + term = find_eom( + pbegin + skip_, pend, ec); + if(ec) + return; + if(! term) + { + // VFALCO Is this right? + if(n > 3) + skip_ = (pend - pbegin) - 3; + ec = error::need_more; + return; + } + + if(*p == ';') + { + auto const ext = make_string(p, pbegin + x_); + impl().on_chunk(0, ext, ec); + if(ec) + return; + p = pbegin + x_; + } + if(! parse_crlf(p)) + { + ec = error::bad_chunk; + return; + } + parse_fields(p, term, ec); + if(ec) + return; + BOOST_ASSERT(p == term); + + impl().on_complete(ec); + if(ec) + return; + state_ = state::complete; +} + +template +inline +void +basic_parser:: +parse_chunk_body(char const*& p, + std::size_t n, error_code& ec) +{ + n = beast::detail::clamp(len_, n); + impl().on_data(string_view{p, n}, ec); + if(ec) + return; + p += n; + len_ -= n; + if(len_ > 0) + return; + state_ = state::chunk_header; +} + +template +void +basic_parser:: +parse_fields(char const*& p, char const* last, error_code& ec) { /* header-field = field-name ":" OWS field-value OWS @@ -452,22 +652,22 @@ parse_fields(char const*& it, */ for(;;) { - auto term = find_eol(it, last, ec); + auto term = find_eol(p, last, ec); if(ec) return; BOOST_ASSERT(term); - if(it == term - 2) + if(p == term - 2) { - it = term; + p = term; break; } - auto const name = parse_name(it); + auto const name = parse_name(p); if(name.empty()) { ec = error::bad_field; return; } - if(*it++ != ':') + if(*p++ != ':') { ec = error::bad_field; return; @@ -476,17 +676,17 @@ parse_fields(char const*& it, *term != '\t') { auto it2 = term - 2; - detail::skip_ows(it, it2); - detail::skip_ows_rev(it2, it); + detail::skip_ows(p, it2); + detail::skip_ows_rev(it2, p); auto const value = - make_string(it, it2); + make_string(p, it2); do_field(name, value, ec); if(ec) return; impl().on_field(name, value, ec); if(ec) return; - it = term; + p = term; } else { @@ -494,33 +694,33 @@ parse_fields(char const*& it, for(;;) { auto const it2 = term - 2; - detail::skip_ows(it, it2); - if(it != it2) + detail::skip_ows(p, it2); + if(p != it2) break; - it = term; - if(*it != ' ' && *it != '\t') + p = term; + if(*p != ' ' && *p != '\t') break; - term = find_eol(it, last, ec); + term = find_eol(p, last, ec); if(ec) return; } std::string s; - if(it != term) + if(p != term) { - s.append(it, term - 2); - it = term; + s.append(p, term - 2); + p = term; for(;;) { - if(*it != ' ' && *it != '\t') + if(*p != ' ' && *p != '\t') break; s.push_back(' '); - detail::skip_ows(it, term - 2); - term = find_eol(it, last, ec); + detail::skip_ows(p, term - 2); + term = find_eol(p, last, ec); if(ec) return; - if(it != term - 2) - s.append(it, term - 2); - it = term; + if(p != term - 2) + s.append(p, term - 2); + p = term; } } string_view value{ @@ -535,9 +735,9 @@ parse_fields(char const*& it, } } -template +template void -basic_parser:: +basic_parser:: do_field( string_view const& name, string_view const& value, @@ -577,10 +777,10 @@ do_field( return; } - for(auto it = value.begin(); - it != value.end(); ++it) + for(auto p = value.begin(); + p != value.end(); ++p) { - if(! is_text(*it)) + if(! is_text(*p)) { ec = error::bad_value; return; @@ -635,14 +835,14 @@ do_field( } auto const v = token_list{value}; - auto const it = std::find_if(v.begin(), v.end(), + auto const p = std::find_if(v.begin(), v.end(), [&](typename token_list::value_type const& s) { return strieq("chunked", s); }); - if(it == v.end()) + if(p == v.end()) return; - if(std::next(it) != v.end()) + if(std::next(p) != v.end()) return; len_ = 0; f_ |= flagChunked; @@ -658,375 +858,6 @@ do_field( } } -template -inline -std::size_t -basic_parser:: -parse_header(char const* p, - std::size_t n, error_code& ec) -{ - if(n < 4) - return 0; - auto const term = find_eom( - p + skip_, p + n, ec); - if(ec) - return 0; - if(! term) - { - skip_ = n - 3; - return 0; - } - - int version; - int status; // ignored for requests - - skip_ = 0; - n = term - p; - parse_startline(p, version, status, ec, - std::integral_constant< - bool, isRequest>{}); - if(ec) - return 0; - if(version >= 11) - f_ |= flagHTTP11; - - parse_fields(p, term, ec); - if(ec) - return 0; - BOOST_ASSERT(p == term); - - do_header(status, - std::integral_constant< - bool, isRequest>{}); - impl().on_header(ec); - if(ec) - return 0; - if(state_ == parse_state::complete) - { - impl().on_complete(ec); - if(ec) - return 0; - } - return n; -} - -template -void -basic_parser:: -do_header(int, std::true_type) -{ - // RFC 7230 section 3.3 - // https://tools.ietf.org/html/rfc7230#section-3.3 - - if(f_ & flagSkipBody) - { - state_ = parse_state::complete; - } - else if(f_ & flagContentLength) - { - if(len_ > 0) - { - f_ |= flagHasBody; - state_ = parse_state::body; - } - else - { - state_ = parse_state::complete; - } - } - else if(f_ & flagChunked) - { - f_ |= flagHasBody; - state_ = parse_state::chunk_header; - } - else - { - len_ = 0; - state_ = parse_state::complete; - } -} - -template -void -basic_parser:: -do_header(int status, std::false_type) -{ - // RFC 7230 section 3.3 - // https://tools.ietf.org/html/rfc7230#section-3.3 - - if( (f_ & flagSkipBody) || // e.g. response to a HEAD request - status / 100 == 1 || // 1xx e.g. Continue - status == 204 || // No Content - status == 304) // Not Modified - { - state_ = parse_state::complete; - return; - } - - if(f_ & flagContentLength) - { - if(len_ > 0) - { - f_ |= flagHasBody; - state_ = parse_state::body; - } - else - { - state_ = parse_state::complete; - } - } - else if(f_ & flagChunked) - { - f_ |= flagHasBody; - state_ = parse_state::chunk_header; - } - else - { - f_ |= flagHasBody; - f_ |= flagNeedEOF; - state_ = parse_state::body_to_eof; - } -} - -template -void -basic_parser:: -maybe_do_body_direct() -{ - if(f_ & flagOnBody) - return; - f_ |= flagOnBody; - if(got_content_length()) - impl().on_body(len_); - else - impl().on_body(); -} - -template -void -basic_parser:: -maybe_do_body_indirect(error_code& ec) -{ - if(f_ & flagOnBody) - return; - f_ |= flagOnBody; - if(got_content_length()) - { - impl().on_body(len_, ec); - if(ec) - return; - } - else - { - impl().on_body(ec); - if(ec) - return; - } -} - -template -std::size_t -basic_parser:: -parse_chunk_header(char const* p, - std::size_t n, error_code& ec) -{ -/* - chunked-body = *chunk last-chunk trailer-part CRLF - - chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF - last-chunk = 1*("0") [ chunk-ext ] CRLF - trailer-part = *( header-field CRLF ) - - chunk-size = 1*HEXDIG - chunk-data = 1*OCTET ; a sequence of chunk-size octets - chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) - chunk-ext-name = token - chunk-ext-val = token / quoted-string -*/ - - auto const first = p; - auto const last = p + n; - - // Treat the last CRLF in a chunk as - // part of the next chunk, so it can - // be parsed in one call instead of two. - if(f_ & flagExpectCRLF) - { - if(n < 2) - return 0; - if(! parse_crlf(p)) - { - ec = error::bad_chunk; - return 0; - } - n -= 2; - } - - char const* term; - - if(! (f_ & flagFinalChunk)) - { - if(n < 2) - return 0; - term = find_eol(p + skip_, last, ec); - if(ec) - return 0; - if(! term) - { - skip_ = n - 1; - return 0; - } - std::uint64_t v; - if(! parse_hex(p, v)) - { - ec = error::bad_chunk; - return 0; - } - if(v != 0) - { - if(*p == ';') - { - // VFALCO We need to parse the chunk - // extension to validate it here. - ext_ = make_string(p, term - 2); - impl().on_chunk(v, ext_, ec); - if(ec) - return 0; - } - else if(p != term - 2) - { - ec = error::bad_chunk; - return 0; - } - p = term; - len_ = v; - skip_ = 0; - f_ |= flagExpectCRLF; - state_ = parse_state::chunk_body; - return p - first; - } - - // This is the offset from the buffer - // to the beginning of the first '\r\n' - x_ = term - 2 - first; - skip_ = x_; - - f_ |= flagFinalChunk; - } - else - { - // We are parsing the value again - // to advance p to the right place. - std::uint64_t v; - auto const result = parse_hex(p, v); - BOOST_ASSERT(result && v == 0); - beast::detail::ignore_unused(result); - beast::detail::ignore_unused(v); - } - - term = find_eom( - first + skip_, last, ec); - if(ec) - return 0; - if(! term) - { - if(n > 3) - skip_ = (last - first) - 3; - return 0; - } - - if(*p == ';') - { - ext_ = make_string(p, first + x_); - impl().on_chunk(0, ext_, ec); - if(ec) - return 0; - p = first + x_; - } - if(! parse_crlf(p)) - { - ec = error::bad_chunk; - return 0; - } - parse_fields(p, term, ec); - if(ec) - return 0; - BOOST_ASSERT(p == term); - - do_complete(ec); - if(ec) - return 0; - return p - first; -} - -template -inline -std::size_t -basic_parser:: -parse_body(char const* p, - std::size_t n, error_code& ec) -{ - n = beast::detail::clamp(len_, n); - body_ = string_view{p, n}; - impl().on_data(body_, ec); - if(ec) - return 0; - len_ -= n; - if(len_ == 0) - { - do_complete(ec); - if(ec) - return 0; - } - return n; -} - -template -inline -std::size_t -basic_parser:: -parse_body_to_eof(char const* p, - std::size_t n, error_code& ec) -{ - body_ = string_view{p, n}; - impl().on_data(body_, ec); - if(ec) - return 0; - return n; -} - -template -inline -std::size_t -basic_parser:: -parse_chunk_body(char const* p, - std::size_t n, error_code& ec) -{ - n = beast::detail::clamp(len_, n); - body_ = string_view{p, n}; - impl().on_data(body_, ec); - if(ec) - return 0; - len_ -= n; - if(len_ == 0) - { - body_ = {}; - state_ = parse_state::chunk_header; - } - return n; -} - -template -void -basic_parser:: -do_complete(error_code& ec) -{ - impl().on_complete(ec); - if(ec) - return; - state_ = parse_state::complete; -} - } // http } // beast diff --git a/include/beast/http/impl/header_parser.ipp b/include/beast/http/impl/header_parser.ipp index 5892564a..9ffb8151 100644 --- a/include/beast/http/impl/header_parser.ipp +++ b/include/beast/http/impl/header_parser.ipp @@ -12,10 +12,11 @@ namespace beast { namespace http { template -template +template header_parser:: -header_parser(Args&&... args) - : h_(std::forward(args)...) +header_parser(Arg0&& arg0, ArgN&&... argn) + : h_(std::forward(arg0), + std::forward(argn)...) { } diff --git a/include/beast/http/impl/message_parser.ipp b/include/beast/http/impl/message_parser.ipp index 276c6a4a..b387755d 100644 --- a/include/beast/http/impl/message_parser.ipp +++ b/include/beast/http/impl/message_parser.ipp @@ -26,8 +26,7 @@ message_parser:: message_parser(header_parser< isRequest, Fields>&& parser, Args&&... args) : base_type(std::move(static_cast>&>(parser))) + isRequest, header_parser>&>(parser))) , m_(parser.release(), std::forward(args)...) { } diff --git a/include/beast/http/impl/read.ipp b/include/beast/http/impl/read.ipp index c24a1b05..bfcb864e 100644 --- a/include/beast/http/impl/read.ipp +++ b/include/beast/http/impl/read.ipp @@ -28,159 +28,396 @@ namespace http { namespace detail { -template< - class SyncReadStream, - class DynamicBuffer, - bool isRequest, bool isDirect, class Derived> -inline -std::size_t -read_some_buffer( - SyncReadStream& stream, - DynamicBuffer& buffer, - basic_parser& parser, - error_code& ec) +//------------------------------------------------------------------------------ + +template +class read_some_op { - std::size_t bytes_used; - if(buffer.size() == 0) - goto do_read; - for(;;) + int state_ = 0; + std::size_t used_ = 0; + Stream& s_; + DynamicBuffer& b_; + basic_parser& p_; + boost::optional mb_; + Handler h_; + +public: + read_some_op(read_some_op&&) = default; + read_some_op(read_some_op const&) = default; + + template + read_some_op(DeducedHandler&& h, Stream& s, + DynamicBuffer& b, basic_parser& p) + : s_(s) + , b_(b) + , p_(p) + , h_(std::forward(h)) { - bytes_used = parser.write( - buffer.data(), ec); + } + + void + operator()(error_code ec, std::size_t bytes_transferred); + + friend + void* asio_handler_allocate( + std::size_t size, read_some_op* op) + { + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, read_some_op* op) + { + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); + } + + friend + bool asio_handler_is_continuation(read_some_op* op) + { + using boost::asio::asio_handler_is_continuation; + return op->state_ >= 2 ? true : + asio_handler_is_continuation( + std::addressof(op->h_)); + } + + template + friend + void asio_handler_invoke(Function&& f, read_some_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->h_)); + } +}; + +template +void +read_some_op:: +operator()(error_code ec, std::size_t bytes_transferred) +{ + switch(state_) + { + case 0: + state_ = 1; + if(b_.size() == 0) + goto do_read; + goto do_parse; + + case 1: + state_ = 2; + case 2: + if(ec == boost::asio::error::eof) + { + BOOST_ASSERT(bytes_transferred == 0); + if(p_.got_some()) + { + // caller sees EOF on next read + ec = {}; + p_.put_eof(ec); + if(ec) + goto upcall; + BOOST_ASSERT(p_.is_done()); + goto upcall; + } + ec = error::end_of_stream; + goto upcall; + } if(ec) - return 0; - if(bytes_used > 0) - goto do_finish; + goto upcall; + b_.commit(bytes_transferred); + + do_parse: + used_ += p_.put(b_.data(), ec); + if(! ec || ec != http::error::need_more) + goto do_upcall; + ec = {}; + // [[fallthrough]] + do_read: - boost::optional mb; - using beast::detail::read_size_helper; - auto const size = - read_size_helper(buffer, 65536); - BOOST_ASSERT(size > 0); try { - mb.emplace(buffer.prepare(size)); + using beast::detail::read_size_helper; + mb_.emplace(b_.prepare( + read_size_helper(b_, 65536))); } catch(std::length_error const&) { ec = error::buffer_overflow; - return 0; + goto do_upcall; } - auto const bytes_transferred = - stream.read_some(*mb, ec); - if(ec == boost::asio::error::eof) - { - BOOST_ASSERT(bytes_transferred == 0); - bytes_used = 0; - if(parser.got_some()) - { - // caller sees EOF on next read - ec = {}; - parser.write_eof(ec); - if(ec) - return 0; - BOOST_ASSERT(parser.is_complete()); - break; - } - ec = error::end_of_stream; - break; - } - else if(ec) - { - return 0; - } - BOOST_ASSERT(bytes_transferred > 0); - buffer.commit(bytes_transferred); + return s_.async_read_some(*mb_, std::move(*this)); + + do_upcall: + if(state_ >= 2) + goto upcall; + state_ = 3; + return s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + + case 3: + break; } -do_finish: - return bytes_used; +upcall: + h_(ec, used_); } -template< - class SyncReadStream, - class DynamicBuffer, - bool isRequest, class Derived> -inline -std::size_t -read_some_body( - SyncReadStream& stream, - DynamicBuffer& buffer, - basic_parser& parser, - error_code& ec) +//------------------------------------------------------------------------------ + +struct parser_is_done { - if(buffer.size() > 0) - return parser.copy_body(buffer); - boost::optional mb; - try + template + bool + operator()(basic_parser< + isRequest, Derived> const& p) const { - parser.prepare_body(mb, 65536); + return p.is_done(); } - catch(std::length_error const&) +}; + +struct parser_is_header_done +{ + template + bool + operator()(basic_parser< + isRequest, Derived> const& p) const { - ec = error::buffer_overflow; - return 0; + return p.is_header_done(); } - auto const bytes_transferred = - stream.read_some(*mb, ec); - if(ec == boost::asio::error::eof) +}; + +template +class read_op +{ + int state_ = 0; + Stream& s_; + DynamicBuffer& b_; + basic_parser& p_; + Handler h_; + +public: + read_op(read_op&&) = default; + read_op(read_op const&) = default; + + template + read_op(DeducedHandler&& h, Stream& s, + DynamicBuffer& b, basic_parser& p) + : s_(s) + , b_(b) + , p_(p) + , h_(std::forward(h)) { - BOOST_ASSERT(bytes_transferred == 0); - // caller sees EOF on next read - ec = {}; - parser.write_eof(ec); + } + + void + operator()(error_code ec, std::size_t bytes_used); + + friend + void* asio_handler_allocate( + std::size_t size, read_op* op) + { + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, read_op* op) + { + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); + } + + friend + bool asio_handler_is_continuation(read_op* op) + { + using boost::asio::asio_handler_is_continuation; + return op->state_ >= 3 ? true : + asio_handler_is_continuation( + std::addressof(op->h_)); + } + + template + friend + void asio_handler_invoke(Function&& f, read_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->h_)); + } +}; + +template +void +read_op:: +operator()(error_code ec, std::size_t bytes_used) +{ + switch(state_) + { + case 0: + if(Condition{}(p_)) + { + state_ = 1; + return s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + } + state_ = 2; + // [[fallthrough]] + + do_read: + return async_read_some( + s_, b_, p_, std::move(*this)); + + case 1: + goto upcall; + + case 2: + case 3: + b_.consume(bytes_used); if(ec) - return 0; - BOOST_ASSERT(parser.is_complete()); + goto upcall; + if(Condition{}(p_)) + goto upcall; + state_ = 3; + goto do_read; } - else if(! ec) - { - parser.commit_body(bytes_transferred); - return 0; - } - return 0; +upcall: + h_(ec); } -template< - class SyncReadStream, - class DynamicBuffer, - bool isRequest, class Derived> -inline -std::size_t -read_some( - SyncReadStream& stream, - DynamicBuffer& buffer, - basic_parser& parser, - error_code& ec) +//------------------------------------------------------------------------------ + +template +class read_msg_op { - switch(parser.state()) + using parser_type = + message_parser; + + using message_type = + message; + + struct data { - case parse_state::header: - case parse_state::chunk_header: - return detail::read_some_buffer( - stream, buffer, parser, ec); + int state = 0; + Stream& s; + DynamicBuffer& b; + message_type& m; + parser_type p; - default: - return detail::read_some_body( - stream, buffer, parser, ec); + data(Handler& handler, Stream& s_, + DynamicBuffer& b_, message_type& m_) + : s(s_) + , b(b_) + , m(m_) + { + p.eager(true); + } + }; + + handler_ptr d_; + +public: + read_msg_op(read_msg_op&&) = default; + read_msg_op(read_msg_op const&) = default; + + template + read_msg_op(DeducedHandler&& h, Stream& s, Args&&... args) + : d_(std::forward(h), + s, std::forward(args)...) + { } -} -template< - class SyncReadStream, - class DynamicBuffer, - bool isRequest, class Derived> -inline -std::size_t -read_some( - SyncReadStream& stream, - DynamicBuffer& buffer, - basic_parser& parser, - error_code& ec) + void + operator()(error_code ec, std::size_t bytes_used); + + friend + void* asio_handler_allocate( + std::size_t size, read_msg_op* op) + { + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->d_.handler())); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, read_msg_op* op) + { + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); + } + + friend + bool asio_handler_is_continuation(read_msg_op* op) + { + using boost::asio::asio_handler_is_continuation; + return op->d_->state >= 2 ? true : + asio_handler_is_continuation( + std::addressof(op->d_.handler())); + } + + template + friend + void asio_handler_invoke(Function&& f, read_msg_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); + } +}; + +template +void +read_msg_op:: +operator()(error_code ec, std::size_t bytes_used) { - return detail::read_some_buffer( - stream, buffer, parser, ec); + auto& d = *d_; + switch(d.state) + { + case 0: + d.state = 1; + + do_read: + return async_read_some( + d.s, d.b, d.p, std::move(*this)); + + case 1: + case 2: + d.b.consume(bytes_used); + if(ec) + goto upcall; + if(d.p.is_done()) + { + d.m = d.p.release(); + goto upcall; + } + d.state = 2; + goto do_read; + } +upcall: + d_.invoke(ec); } } // detail @@ -190,18 +427,18 @@ read_some( template< class SyncReadStream, class DynamicBuffer, - bool isRequest, bool isDirect, class Derived> + bool isRequest, class Derived> std::size_t read_some( SyncReadStream& stream, DynamicBuffer& buffer, - basic_parser& parser) + basic_parser& parser) { static_assert(is_sync_read_stream::value, "SyncReadStream requirements not met"); static_assert(is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - BOOST_ASSERT(! parser.is_complete()); + BOOST_ASSERT(! parser.is_done()); error_code ec; auto const bytes_used = read_some(stream, buffer, parser, ec); @@ -213,37 +450,190 @@ read_some( template< class SyncReadStream, class DynamicBuffer, - bool isRequest, bool isDirect, class Derived> + bool isRequest, class Derived> std::size_t read_some( SyncReadStream& stream, DynamicBuffer& buffer, - basic_parser& parser, + basic_parser& parser, error_code& ec) { static_assert(is_sync_read_stream::value, "SyncReadStream requirements not met"); static_assert(is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - BOOST_ASSERT(! parser.is_complete()); - return detail::read_some(stream, buffer, parser, ec); + BOOST_ASSERT(! parser.is_done()); + std::size_t bytes_used = 0; + if(buffer.size() == 0) + goto do_read; + for(;;) + { + // invoke parser + bytes_used += parser.put(buffer.data(), ec); + if(! ec) + break; + if(ec != http::error::need_more) + break; + ec = {}; + do_read: + boost::optional b; + try + { + using beast::detail::read_size_helper; + b.emplace(buffer.prepare( + read_size_helper(buffer, 65536))); + } + catch(std::length_error const&) + { + ec = error::buffer_overflow; + break; + } + auto const bytes_transferred = + stream.read_some(*b, ec); + if(ec == boost::asio::error::eof) + { + BOOST_ASSERT(bytes_transferred == 0); + if(parser.got_some()) + { + // caller sees EOF on next read + ec = {}; + parser.put_eof(ec); + if(ec) + break; + BOOST_ASSERT(parser.is_done()); + break; + } + ec = error::end_of_stream; + break; + } + if(ec) + break; + buffer.commit(bytes_transferred); + } + return bytes_used; } +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +async_return_type< + ReadHandler, void(error_code, std::size_t)> +async_read_some( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler) +{ + static_assert(is_async_read_stream::value, + "AsyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_done()); + async_completion init{handler}; + detail::read_some_op>{ + init.completion_handler, stream, buffer, parser}( + error_code{}, 0); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + template< class SyncReadStream, class DynamicBuffer, - bool isRequest, bool isDirect, class Derived> + bool isRequest, class Derived> void -read( +read_header( SyncReadStream& stream, DynamicBuffer& buffer, - basic_parser& parser) + basic_parser& parser) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + error_code ec; + read_header(stream, buffer, parser, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read_header( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + error_code& ec) +{ + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + parser.eager(false); + while(! parser.is_header_done()) + { + auto const bytes_used = + read_some(stream, buffer, parser, ec); + buffer.consume(bytes_used); + if(ec) + return; + } +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +async_return_type< + ReadHandler, void(error_code)> +async_read_header( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler) +{ + static_assert(is_async_read_stream::value, + "AsyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + parser.eager(false); + async_completion init{handler}; + detail::read_op>{ + init.completion_handler, stream, buffer, parser}( + error_code{}, 0); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser) { static_assert(is_sync_read_stream::value, "SyncReadStream requirements not met"); static_assert(is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - BOOST_ASSERT(! parser.is_complete()); error_code ec; read(stream, buffer, parser, ec); if(ec) @@ -253,30 +643,59 @@ read( template< class SyncReadStream, class DynamicBuffer, - bool isRequest, bool isDirect, class Derived> + bool isRequest, class Derived> void read( SyncReadStream& stream, DynamicBuffer& buffer, - basic_parser& parser, + basic_parser& parser, error_code& ec) { static_assert(is_sync_read_stream::value, "SyncReadStream requirements not met"); static_assert(is_dynamic_buffer::value, "DynamicBuffer requirements not met"); - BOOST_ASSERT(! parser.is_complete()); - do + parser.eager(true); + while(! parser.is_done()) { - auto const bytes_used = - read_some(stream, buffer, parser, ec); + auto const bytes_used = read_some( + stream, buffer, parser, ec); + buffer.consume(bytes_used); if(ec) return; - buffer.consume(bytes_used); } - while(! parser.is_complete()); } +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +async_return_type< + ReadHandler, void(error_code)> +async_read( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + ReadHandler&& handler) +{ + static_assert(is_async_read_stream::value, + "AsyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + parser.eager(true); + async_completion init{handler}; + detail::read_op>{ + init.completion_handler, stream, buffer, parser}( + error_code{}, 0); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + template< class SyncReadStream, class DynamicBuffer, @@ -296,7 +715,7 @@ read( static_assert(is_body_writer::value, "BodyWriter requirements not met"); error_code ec; - beast::http::read(stream, buffer, msg, ec); + read(stream, buffer, msg, ec); if(ec) BOOST_THROW_EXCEPTION(system_error{ec}); } @@ -321,12 +740,44 @@ read( static_assert(is_body_writer::value, "BodyWriter requirements not met"); message_parser p; - beast::http::read(stream, buffer, p, ec); + p.eager(true); + read(stream, buffer, p.base(), ec); if(ec) return; msg = p.release(); } +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Fields, + class ReadHandler> +async_return_type< + ReadHandler, void(error_code)> +async_read( + AsyncReadStream& stream, + DynamicBuffer& buffer, + message& msg, + ReadHandler&& handler) +{ + static_assert(is_async_read_stream::value, + "AsyncReadStream requirements not met"); + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_writer::value, + "BodyWriter requirements not met"); + async_completion init{handler}; + detail::read_msg_op>{ + init.completion_handler, stream, buffer, msg}( + error_code{}, 0); + return init.result.get(); +} + } // http } // beast diff --git a/include/beast/http/impl/serializer.ipp b/include/beast/http/impl/serializer.ipp index b8f9db29..7768f15f 100644 --- a/include/beast/http/impl/serializer.ipp +++ b/include/beast/http/impl/serializer.ipp @@ -81,19 +81,7 @@ serializer(message const& m, : m_(m) , d_(d) , b_(1024, alloc) - , chunked_(token_list{ - m.fields["Transfer-Encoding"]}.exists("chunked")) - , close_(token_list{ - m.fields["Connection"]}.exists("close") || - (m.version < 11 && ! m.fields.exists( - "Content-Length"))) { - s_ = chunked_ ? do_init_c : do_init; - // VFALCO Move this stuff to the switch? - auto os = ostream(b_); - detail::write_start_line(os, m_); - detail::write_fields(os, m_.fields); - os << "\r\n"; } template #include #include +#include #include #include #include @@ -27,12 +28,14 @@ namespace beast { namespace http { namespace detail { -template< - class Stream, class Serializer, class Handler> +template class write_some_op { Stream& s_; - Serializer& sr_; + serializer& sr_; Handler h_; class lambda @@ -40,7 +43,7 @@ class write_some_op write_some_op& op_; public: - bool empty = true; + bool invoked = false; explicit lambda(write_some_op& op) @@ -53,7 +56,7 @@ class write_some_op operator()(error_code& ec, ConstBufferSequence const& buffer) { - empty = false; + invoked = true; return op_.s_.async_write_some( buffer, std::move(op_)); } @@ -65,7 +68,8 @@ public: template write_some_op(DeducedHandler&& h, - Stream& s, Serializer& sr) + Stream& s, serializer& sr) : s_(s) , sr_(sr) , h_(std::forward(h)) @@ -114,25 +118,40 @@ public: } }; -template< - class Stream, class Serializer, class Handler> +template void -write_some_op:: +write_some_op:: operator()() { - lambda f{*this}; error_code ec; + if(sr_.is_done()) + return s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + lambda f{*this}; sr_.get(ec, f); - if(! ec && ! f.empty) + if(ec) + { + BOOST_ASSERT(! f.invoked); + return s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + } + if(f.invoked) + // *this has been moved from, + // cannot access members here. return; return s_.get_io_service().post( bind_handler(std::move(*this), ec, 0)); } -template< - class Stream, class Serializer, class Handler> +template void -write_some_op:: +write_some_op:: operator()(error_code ec, std::size_t bytes_transferred) { if(! ec) @@ -142,13 +161,199 @@ operator()(error_code ec, std::size_t bytes_transferred) //------------------------------------------------------------------------------ +struct serializer_is_header_done +{ + template + bool + operator()(serializer const& sr) const + { + return sr.is_header_done(); + } +}; + +struct serializer_is_done +{ + template + bool + operator()(serializer const& sr) const + { + return sr.is_done(); + } +}; + +template +class write_op +{ + int state_ = 0; + Stream& s_; + serializer& sr_; + Handler h_; + + class lambda + { + write_op& op_; + + public: + bool invoked = false; + + explicit + lambda(write_op& op) + : op_(op) + { + } + + template + void + operator()(error_code& ec, + ConstBufferSequence const& buffer) + { + invoked = true; + return op_.s_.async_write_some( + buffer, std::move(op_)); + } + }; + +public: + write_op(write_op&&) = default; + write_op(write_op const&) = default; + + template + write_op(DeducedHandler&& h, Stream& s, + serializer& sr) + : s_(s) + , sr_(sr) + , h_(std::forward(h)) + { + } + + void + operator()(error_code ec, + std::size_t bytes_transferred); + + friend + void* asio_handler_allocate( + std::size_t size, write_op* op) + { + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, write_op* op) + { + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); + } + + friend + bool asio_handler_is_continuation(write_op* op) + { + using boost::asio::asio_handler_is_continuation; + return op->state_ >= 3 || + asio_handler_is_continuation( + std::addressof(op->h_)); + } + + template + friend + void asio_handler_invoke(Function&& f, write_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->h_)); + } +}; + +template +void +write_op:: +operator()(error_code ec, + std::size_t bytes_transferred) +{ + if(ec) + goto upcall; + switch(state_) + { + case 0: + { + if(Predicate{}(sr_)) + { + state_ = 1; + return s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + } + lambda f{*this}; + state_ = 2; + sr_.get(ec, f); + if(ec) + { + BOOST_ASSERT(! f.invoked); + return s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + } + if(f.invoked) + // *this has been moved from, + // cannot access members here. + return; + BOOST_ASSERT(Predicate{}(sr_)); + state_ = 1; + return s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + } + + case 1: + goto upcall; + + case 2: + state_ = 3; + // [[fallthrough]] + + case 3: + { + sr_.consume(bytes_transferred); + if(Predicate{}(sr_)) + goto upcall; + lambda f{*this}; + sr_.get(ec, f); + if(ec) + { + BOOST_ASSERT(! f.invoked); + goto upcall; + } + if(f.invoked) + // *this has been moved from, + // cannot access members here. + return; + BOOST_ASSERT(Predicate{}(sr_)); + goto upcall; + } + } +upcall: + h_(ec); +} + +//------------------------------------------------------------------------------ + template -class write_op +class write_msg_op { struct data { - int state = 0; Stream& s; serializer> sr; @@ -165,22 +370,25 @@ class write_op handler_ptr d_; public: - write_op(write_op&&) = default; - write_op(write_op const&) = default; + write_msg_op(write_msg_op&&) = default; + write_msg_op(write_msg_op const&) = default; template - write_op(DeducedHandler&& h, Stream& s, Args&&... args) + write_msg_op(DeducedHandler&& h, Stream& s, Args&&... args) : d_(std::forward(h), s, std::forward(args)...) { } + void + operator()(); + void operator()(error_code ec); friend void* asio_handler_allocate( - std::size_t size, write_op* op) + std::size_t size, write_msg_op* op) { using boost::asio::asio_handler_allocate; return asio_handler_allocate( @@ -189,7 +397,7 @@ public: friend void asio_handler_deallocate( - void* p, std::size_t size, write_op* op) + void* p, std::size_t size, write_msg_op* op) { using boost::asio::asio_handler_deallocate; asio_handler_deallocate( @@ -197,17 +405,16 @@ public: } friend - bool asio_handler_is_continuation(write_op* op) + bool asio_handler_is_continuation(write_msg_op* op) { using boost::asio::asio_handler_is_continuation; - return op->d_->state == 2 || - asio_handler_is_continuation( - std::addressof(op->d_.handler())); + return asio_handler_is_continuation( + std::addressof(op->d_.handler())); } template friend - void asio_handler_invoke(Function&& f, write_op* op) + void asio_handler_invoke(Function&& f, write_msg_op* op) { using boost::asio::asio_handler_invoke; asio_handler_invoke( @@ -218,35 +425,23 @@ public: template void -write_op:: +write_msg_op:: +operator()() +{ + auto& d = *d_; + return async_write(d.s, d.sr, std::move(*this)); +} + +template +void +write_msg_op:: operator()(error_code ec) { auto& d = *d_; - if(ec) - goto upcall; - switch(d.state) - { - case 0: - d.state = 1; - write_some_op{std::move(*this), d.s, d.sr}(); - return; - case 1: - d.state = 2; - // [[fallthrough]] - case 2: - if(d.sr.is_done()) - { - if(d.sr.needs_close()) - // VFALCO Choose an error code - ec = boost::asio::error::eof; - break; - } - write_some_op{std::move(*this), d.s, d.sr}(); - return; - } -upcall: + if(! ec) + if(d.sr.need_close()) + ec = error::end_of_stream; d_.invoke(ec); } @@ -258,7 +453,8 @@ class write_some_lambda Stream& stream_; public: - boost::optional bytes_transferred; + bool invoked = false; + std::size_t bytes_transferred = 0; explicit write_some_lambda(Stream& stream) @@ -271,25 +467,55 @@ public: operator()(error_code& ec, ConstBufferSequence const& buffer) { + invoked = true; bytes_transferred = stream_.write_some(buffer, ec); } }; +template +class write_lambda +{ + Stream& stream_; + +public: + bool invoked = false; + std::size_t bytes_transferred = 0; + + explicit + write_lambda(Stream& stream) + : stream_(stream) + { + } + + template + void + operator()(error_code& ec, + ConstBufferSequence const& buffer) + { + invoked = true; + bytes_transferred = boost::asio::write( + stream_, buffer, ec); + } +}; + } // detail //------------------------------------------------------------------------------ -template< - class SyncWriteStream, - bool isRequest, class Body, class Fields, - class Decorator, class Allocator> +template void write_some(SyncWriteStream& stream, serializer< isRequest, Body, Fields, Decorator, Allocator>& sr) { static_assert(is_sync_write_stream::value, "SyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); error_code ec; write_some(stream, sr, ec); if(ec) @@ -307,15 +533,18 @@ write_some(SyncWriteStream& stream, serializer< { static_assert(is_sync_write_stream::value, "SyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); detail::write_some_lambda f{stream}; - sr.get(ec, f); - if(! ec) + if(! sr.is_done()) { - if(f.bytes_transferred) - sr.consume(*f.bytes_transferred); - if(sr.is_done() && sr.needs_close()) - // VFALCO decide on an error code - ec = boost::asio::error::eof; + sr.get(ec, f); + if(ec) + return; + if(f.invoked) + sr.consume(f.bytes_transferred); } } @@ -336,12 +565,159 @@ async_write_some(AsyncWriteStream& stream, serializer< "BodyReader requirements not met"); async_completion init{handler}; - detail::write_some_op>{ + detail::write_some_op>{ init.completion_handler, stream, sr}(); return init.result.get(); } +//------------------------------------------------------------------------------ + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields, + class Decorator, class Allocator> +void +write_header(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + error_code ec; + write_header(stream, sr, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields, + class Decorator, class Allocator> +void +write_header(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr, + error_code& ec) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + sr.split(true); + detail::write_lambda f{stream}; + while(! sr.is_header_done()) + { + sr.get(ec, f); + if(ec) + return; + BOOST_ASSERT(f.invoked); + sr.consume(f.bytes_transferred); + } +} + +template +async_return_type +async_write_header(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr, + WriteHandler&& handler) +{ + static_assert(is_async_write_stream< + AsyncWriteStream>::value, + "AsyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + sr.split(true); + async_completion init{handler}; + detail::write_op>{ + init.completion_handler, stream, sr}( + error_code{}, 0); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields, + class Decorator, class Allocator> +void +write(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + error_code ec; + write(stream, sr, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields, + class Decorator, class Allocator> +void +write(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr, + error_code& ec) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + sr.split(false); + detail::write_lambda f{stream}; + while(! sr.is_done()) + { + sr.get(ec, f); + if(ec) + return; + if(f.invoked) + sr.consume(f.bytes_transferred); + } + if(sr.need_close()) + ec = error::end_of_stream; +} + +template +async_return_type +async_write(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr, + WriteHandler&& handler) +{ + static_assert(is_async_write_stream< + AsyncWriteStream>::value, + "AsyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + sr.split(false); + async_completion init{handler}; + detail::write_op>{ + init.completion_handler, stream, sr}( + error_code{}, 0); + return init.result.get(); +} + +//------------------------------------------------------------------------------ + template void @@ -374,18 +750,10 @@ write(SyncWriteStream& stream, static_assert(is_body_reader::value, "BodyReader requirements not met"); auto sr = make_serializer(msg); - for(;;) - { -#if 0 - sr.write_some(stream, ec); -#else - write_some(stream, sr, ec); -#endif - if(ec) - return; - if(sr.is_done()) - break; - } + write(stream, sr, ec); + if(! ec) + if(sr.need_close()) + ec = error::end_of_stream; } template init{handler}; - detail::write_op, isRequest, Body, Fields>{ - init.completion_handler, stream, msg}( - error_code{}); + init.completion_handler, stream, msg}(); return init.result.get(); } @@ -439,7 +806,7 @@ operator<<(std::ostream& os, beast::detail::sync_ostream oss{os}; error_code ec; write(oss, msg, ec); - if(ec && ec != boost::asio::error::eof) + if(ec && ec != error::end_of_stream) BOOST_THROW_EXCEPTION(system_error{ec}); return os; } diff --git a/include/beast/http/message_parser.hpp b/include/beast/http/message_parser.hpp index 34cfd6a2..de9a6d4b 100644 --- a/include/beast/http/message_parser.hpp +++ b/include/beast/http/message_parser.hpp @@ -34,13 +34,18 @@ namespace http { @note A new instance of the parser is required for each message. */ -template +template class message_parser : public basic_parser> + message_parser> { - using base_type = basic_parser::value, + "Body requirements not met"); + + static_assert(is_body_writer::value, + "BodyWriter requirements not met"); + + using base_type = basic_parser>; using writer_type = typename Body::writer; @@ -52,10 +57,6 @@ public: /// The type of message returned by the parser using value_type = message; - /// The type of buffer sequence representing the body - using mutable_buffers_type = - typename writer_type::mutable_buffers_type; - /// Constructor (default) message_parser() = default; @@ -99,9 +100,7 @@ public: #endif /** Construct a message parser from a @ref header_parser. - @param parser The header parser to construct from. - @param args Optional arguments forwarded to the message constructor. */ @@ -152,8 +151,7 @@ public: private: friend class basic_parser< - isRequest, Body::writer::is_direct, - message_parser>; + isRequest, message_parser>; void on_request( @@ -190,61 +188,25 @@ private: } void - on_body() - { - wr_.emplace(m_); - wr_->init(); - } - - void - on_body(std::uint64_t content_length) - { - wr_.emplace(m_); - wr_->init(content_length); - } - - void - on_body(error_code& ec) - { - wr_.emplace(m_); - wr_->init(ec); - if(ec) - return; - } - - void - on_body(std::uint64_t content_length, - error_code& ec) + on_body(boost::optional< + std::uint64_t> const& content_length, + error_code& ec) { wr_.emplace(m_); wr_->init(content_length, ec); - if(ec) - return; } void on_data(string_view const& s, error_code& ec) { - BOOST_STATIC_ASSERT(! Body::writer::is_direct); - wr_->write(s, ec); - } - - mutable_buffers_type - on_prepare(std::size_t n) - { - return wr_->prepare(n); + wr_->put(boost::asio::buffer( + s.data(), s.size()), ec); } void - on_commit(std::size_t n) - { - wr_->commit(n); - } - - void - on_chunk(std::uint64_t, - string_view const&, + on_chunk( + std::uint64_t, string_view const&, error_code&) { } @@ -253,28 +215,18 @@ private: on_complete(error_code& ec) { if(wr_) - do_on_complete(ec, - std::integral_constant{}); - } - - void - do_on_complete( - error_code& ec, std::true_type) - { - wr_->finish(); - } - - void - do_on_complete( - error_code& ec, std::false_type) - { - wr_->finish(ec); - if(ec) - return; + wr_->finish(ec); } }; +/// A parser for producing HTTP/1 messages +template +using request_parser = message_parser; + +/// A parser for producing HTTP/1 messages +template +using response_parser = message_parser; + } // http } // beast diff --git a/include/beast/http/read.hpp b/include/beast/http/read.hpp index 56f3e661..b60ee6d4 100644 --- a/include/beast/http/read.hpp +++ b/include/beast/http/read.hpp @@ -66,12 +66,12 @@ namespace http { template< class SyncReadStream, class DynamicBuffer, - bool isRequest, bool isDirect, class Derived> + bool isRequest, class Derived> std::size_t read_some( SyncReadStream& stream, DynamicBuffer& buffer, - basic_parser& parser); + basic_parser& parser); /** Read some HTTP/1 message data from a stream. @@ -122,12 +122,12 @@ read_some( template< class SyncReadStream, class DynamicBuffer, - bool isRequest, bool isDirect, class Derived> + bool isRequest, class Derived> std::size_t read_some( SyncReadStream& stream, DynamicBuffer& buffer, - basic_parser& parser, + basic_parser& parser, error_code& ec); /** Start an asynchronous operation to read some HTTP/1 message data from a stream. @@ -192,7 +192,7 @@ read_some( template< class AsyncReadStream, class DynamicBuffer, - bool isRequest, bool isDirect, class Derived, + bool isRequest, class Derived, class ReadHandler> #if BEAST_DOXYGEN void_or_deduced @@ -203,7 +203,170 @@ async_return_type< async_read_some( AsyncReadStream& stream, DynamicBuffer& buffer, - basic_parser& parser, + basic_parser& parser, + ReadHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Read a header into an HTTP/1 parser from a stream. + + This function synchronously reads from a stream and passes + data to the specified parser. The call will block until one + of the following conditions is true: + + @li The parser indicates that the complete header is received + + @li An error occurs in the stream or parser. + + This function is implemented in terms of one or more calls + to the stream's `read_some` function. The implementation may + read additional octets that lie past the end of the object + being parsed. This additional data is stored in the dynamic + buffer, which may be used in subsequent calls. + + If the end of the stream is reached during the read, the + value @ref error::partial_message is indicated as the + error if bytes have been processed, else the error + @ref error::end_of_stream is indicated. + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @throws system_error Thrown on failure. + + @note The implementation will call @ref basic_parser::eager + with the value `false` on the parser passed in. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read_header( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser); + +/** Read a header into an HTTP/1 parser from a stream. + + This function synchronously reads from a stream and passes + data to the specified parser. The call will block until one + of the following conditions is true: + + @li The parser indicates that the complete header is received + + @li An error occurs in the stream or parser. + + This function is implemented in terms of one or more calls + to the stream's `read_some` function. The implementation may + read additional octets that lie past the end of the object + being parsed. This additional data is stored in the dynamic + buffer, which may be used in subsequent calls. + + If the end of the stream is reached during the read, the + value @ref error::partial_message is indicated as the + error if bytes have been processed, else the error + @ref error::end_of_stream is indicated. + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @param ec Set to the error, if any occurred. + + @note The implementation will call @ref basic_parser::eager + with the value `false` on the parser passed in. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +void +read_header( + SyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, + error_code& ec); + +/** Read a header into an HTTP/1 parser asynchronously from a stream. + + This function is used to asynchronously read from a stream and + pass the data to the specified parser. The function call always + returns immediately. The asynchronous operation will continue + until one of the following conditions is true: + + @li The parser indicates that the complete header is received + + @li An error occurs in the stream or parser. + + This operation is implemented in terms of one or more calls to + the next layer's `async_read_some` function, and is known as a + composed operation. The program must ensure that the + stream performs no other operations until this operation completes. + The implementation may read additional octets that lie past the + end of the object being parsed. This additional data is stored + in the stream buffer, which may be used in subsequent calls. + + If the end of the stream is reached during the read, the + value @ref error::partial_message is indicated as the + error if bytes have been processed, else the error + @ref error::end_of_stream is indicated. + + @param stream The stream from which the data is to be read. + The type must support the @b AsyncReadStream concept. + + @param buffer A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. + The equivalent function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + + @note The implementation will call @ref basic_parser::eager + with the value `false` on the parser passed in. +*/ +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +#if BEAST_DOXYGEN + void_or_deduced +#else +async_return_type< + ReadHandler, void(error_code)> +#endif +async_read_header( + AsyncReadStream& stream, + DynamicBuffer& buffer, + basic_parser& parser, ReadHandler&& handler); //------------------------------------------------------------------------------ @@ -241,16 +404,19 @@ async_read_some( @param parser The parser to use. @throws system_error Thrown on failure. + + @note The implementation will call @ref basic_parser::eager + with the value `true` on the parser passed in. */ template< class SyncReadStream, class DynamicBuffer, - bool isRequest, bool isDirect, class Derived> + bool isRequest, class Derived> void read( SyncReadStream& stream, DynamicBuffer& buffer, - basic_parser& parser); + basic_parser& parser); /** Read into an HTTP/1 parser from a stream. @@ -285,16 +451,19 @@ read( @param parser The parser to use. @param ec Set to the error, if any occurred. + + @note The implementation will call @ref basic_parser::eager + with the value `true` on the parser passed in. */ template< class SyncReadStream, class DynamicBuffer, - bool isRequest, bool isDirect, class Derived> + bool isRequest, class Derived> void read( SyncReadStream& stream, DynamicBuffer& buffer, - basic_parser& parser, + basic_parser& parser, error_code& ec); /** Read into an HTTP/1 parser asynchronously from a stream. @@ -342,11 +511,14 @@ read( immediately or not, the handler will not be invoked from within this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. + + @note The implementation will call @ref basic_parser::eager + with the value `true` on the parser passed in. */ template< class AsyncReadStream, class DynamicBuffer, - bool isRequest, bool isDirect, class Derived, + bool isRequest, class Derived, class ReadHandler> #if BEAST_DOXYGEN void_or_deduced @@ -357,9 +529,11 @@ async_return_type< async_read( AsyncReadStream& stream, DynamicBuffer& buffer, - basic_parser& parser, + basic_parser& parser, ReadHandler&& handler); +//------------------------------------------------------------------------------ + /** Read an HTTP/1 message from a stream. This function is used to synchronously read a message from @@ -522,7 +696,6 @@ async_read( } // http } // beast -#include #include #endif diff --git a/include/beast/http/serializer.hpp b/include/beast/http/serializer.hpp index 8bc65e94..1830c38b 100644 --- a/include/beast/http/serializer.hpp +++ b/include/beast/http/serializer.hpp @@ -65,7 +65,7 @@ struct empty_decorator if the contents of the message indicate that chunk encoding is required. If the semantics of the message indicate that the connection should be closed after the message is sent, the - function @ref needs_close will return `true`. + function @ref need_close will return `true`. Upon construction, an optional chunk decorator may be specified. This decorator is a function object called with @@ -135,9 +135,11 @@ class serializer enum { - do_init = 0, - do_header_only = 10, - do_header = 20, + do_construct = 0, + + do_init = 10, + do_header_only = 20, + do_header = 30, do_body = 40, do_init_c = 50, @@ -191,7 +193,7 @@ class serializer buffer_type b_; boost::variant v_; - int s_; + int s_ = do_construct; bool split_ = is_deferred::value; bool header_done_ = false; bool chunked_; @@ -201,8 +203,13 @@ class serializer public: /** Constructor - @param msg The message to serialize, which must - remain valid for the lifetime of the serializer. + The implementation guarantees that the message passed on + construction will not be accessed until the first call to + @ref get. This allows the message to be lazily created. + For example, if the header is filled in before serialization. + + @param msg The message to serialize, which must remain valid + for the lifetime of the serializer. @param decorator An optional decorator to use. @@ -268,7 +275,7 @@ public: function returns `true`. */ bool - needs_close() const + need_close() const { return close_; } diff --git a/include/beast/http/string_body.hpp b/include/beast/http/string_body.hpp index 7e01aacc..ab8f026b 100644 --- a/include/beast/http/string_body.hpp +++ b/include/beast/http/string_body.hpp @@ -9,7 +9,7 @@ #define BEAST_HTTP_STRING_BODY_HPP #include -#include +#include #include #include #include @@ -30,68 +30,6 @@ struct string_body /// The type of the body member when used in a message. using value_type = std::string; -#if BEAST_DOXYGEN - /// The algorithm used store buffers in this body - using writer = implementation_defined; -#else - class writer - { - value_type& body_; - std::size_t len_ = 0; - - public: - static bool constexpr is_direct = true; - - using mutable_buffers_type = - boost::asio::mutable_buffers_1; - - template - explicit - writer(message& m) - : body_(m.body) - { - } - - void - init() - { - } - - void - init(std::uint64_t content_length) - { - if(content_length > - (std::numeric_limits::max)()) - BOOST_THROW_EXCEPTION(std::length_error{ - "Content-Length overflow"}); - body_.reserve(static_cast< - std::size_t>(content_length)); - } - - mutable_buffers_type - prepare(std::size_t n) - { - body_.resize(len_ + n); - return {&body_[len_], n}; - } - - void - commit(std::size_t n) - { - if(body_.size() > len_ + n) - body_.resize(len_ + n); - len_ = body_.size(); - } - - void - finish() - { - body_.resize(len_); - } - }; -#endif - #if BEAST_DOXYGEN /// The algorithm to obtain buffers representing the body using reader = implementation_defined; @@ -134,6 +72,69 @@ struct string_body } }; #endif + +#if BEAST_DOXYGEN + /// The algorithm used store buffers in this body + using writer = implementation_defined; +#else + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& m) + : body_(m.body) + { + } + + void + init(boost::optional< + std::uint64_t> content_length, error_code& ec) + { + if(content_length) + { + if(*content_length > (std::numeric_limits< + std::size_t>::max)()) + { + ec = make_error_code( + errc::not_enough_memory); + return; + } + body_.reserve(static_cast< + std::size_t>(*content_length)); + } + } + + template + void + put(ConstBufferSequence const& buffers, + error_code& ec) + { + using boost::asio::buffer_size; + using boost::asio::buffer_copy; + auto const n = buffer_size(buffers); + auto const len = body_.size(); + try + { + body_.resize(len + n); + } + catch(std::length_error const&) + { + ec = error::buffer_overflow; + return; + } + buffer_copy(boost::asio::buffer( + &body_[0] + len, n), buffers); + } + + void + finish(error_code&) + { + } + }; +#endif }; } // http diff --git a/include/beast/http/type_traits.hpp b/include/beast/http/type_traits.hpp index 0cc07459..6aa51b2a 100644 --- a/include/beast/http/type_traits.hpp +++ b/include/beast/http/type_traits.hpp @@ -114,28 +114,24 @@ struct is_body_reader struct is_body_writer : std::integral_constant {}; #else -template> -struct is_body_writer : std::true_type {}; +template +struct is_body_writer : std::false_type {}; template struct is_body_writer(), - std::declval().init( - std::declval>()), - std::declval().prepare( - std::declval()), - std::declval().commit( - std::declval()), - std::declval().finish() - )> > : std::integral_constant::value && - std::is_convertible().prepare( - std::declval())), - typename T::writer::mutable_buffers_type - >::value> - + std::declval().init( + std::declval>(), + std::declval()), + std::declval().put( + std::declval(), + std::declval()), + std::declval().finish( + std::declval()), + (void)0)>> : std::integral_constant&>::value + > { }; #endif diff --git a/include/beast/http/write.hpp b/include/beast/http/write.hpp index 169b3886..8ef829c1 100644 --- a/include/beast/http/write.hpp +++ b/include/beast/http/write.hpp @@ -28,27 +28,35 @@ namespace beast { namespace http { -/** Write some serialized message data to a stream. +/** Write part of a message to a stream using a serializer. - This function is used to write serialized message data to the - stream. The function call will block until one of the following - conditions is true: + This function is used to write part of a message to a stream using + a caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: @li One or more bytes have been transferred. + @li The function @ref serializer::is_done returns `true` + @li An error occurs on the stream. - In order to completely serialize a message, this function - should be called until `sr.is_done()` returns `true`. + This operation is implemented in terms of one or more calls + to the stream's `write_some` function. + + The amount of data actually transferred is controlled by the behavior + of the underlying stream, performing bounded work for each call. This + helps applications set reasonable timeouts. It also allows application-level + flow control to function correctly. For example when using a TCP/IP based + stream. - @param stream The stream to write to. This type must - satisfy the requirements of @b SyncWriteStream. + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. @param sr The serializer to use. @throws system_error Thrown on failure. - @see @ref async_write_some, @ref serializer + @see serializer */ template& sr); +/** Write part of a message to a stream using a serializer. -/** Write some serialized message data to a stream. - - This function is used to write serialized message data to the - stream. The function call will block until one of the following - conditions is true: + This function is used to write part of a message to a stream using + a caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: @li One or more bytes have been transferred. + @li The function @ref serializer::is_done returns `true` + @li An error occurs on the stream. - In order to completely serialize a message, this function - should be called until `sr.is_done()` returns `true`. + This operation is implemented in terms of one or more calls + to the stream's `write_some` function. + + The amount of data actually transferred is controlled by the behavior + of the underlying stream, performing bounded work for each call. This + helps applications set reasonable timeouts. It also allows application-level + flow control to function correctly. For example when using a TCP/IP based + stream. - @param stream The stream to write to. This type must - satisfy the requirements of @b SyncWriteStream. + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. @param sr The serializer to use. @@ -88,37 +103,47 @@ write_some(SyncWriteStream& stream, serializer< isRequest, Body, Fields, Decorator, Allocator>& sr, error_code& ec); -/** Start an asynchronous write of some serialized message data to a stream. +/** Write part of a message to a stream asynchronously using a serializer. - This function is used to asynchronously write serialized - message data to the stream. The function call always returns - immediately. The asynchronous operation will continue until - one of the following conditions is true: + This function is used to write part of a message to a stream + asynchronously using a caller-provided HTTP/1 serializer. The function + call always returns immediately. The asynchronous operation will continue + until one of the following conditions is true: @li One or more bytes have been transferred. + @li The function @ref serializer::is_done returns `true` + @li An error occurs on the stream. - In order to completely serialize a message, this function - should be called until `sr.is_done()` returns `true`. + This operation is implemented in terms of zero or more calls to the stream's + `async_write_some` function, and is known as a composed operation. + The program must ensure that the stream performs no other write operations + until this operation completes. - @param stream The stream to write to. This type must - satisfy the requirements of @b SyncWriteStream. + The amount of data actually transferred is controlled by the behavior + of the underlying stream, performing bounded work for each call. This + helps applications set reasonable timeouts. It also allows application-level + flow control to function correctly. For example when using a TCP/IP based + stream. + + @param stream The stream to which the data is to be written. + The type must support the @b AsyncWriteStream concept. - @param sr The serializer to use for writing. + @param sr The serializer to use. - @param handler The handler to be called when the request - completes. Copies will be made of the handler as required. The - equivalent function signature of the handler must be: + @param handler The handler to be called when the operation + completes. Copies will be made of the handler as required. + The equivalent function signature of the handler must be: @code void handler( - error_code const& ec // Result of operation + error_code const& error // result of operation ); @endcode Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. - @see @ref write_some, @ref serializer + @see @ref serializer */ template& sr, WriteHandler&& handler); -/** Write an HTTP/1 message to a stream. +//------------------------------------------------------------------------------ - This function is used to write a message to a stream. The call - will block until one of the following conditions is true: +/** Write a header to a stream using a serializer. - @li The entire message is written. + This function is used to write a header to a stream using a + caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: + + @li The function @ref serializer::is_header_done returns `true` @li An error occurs. This operation is implemented in terms of one or more calls to the stream's `write_some` function. - The implementation will automatically perform chunk encoding if - the contents of the message indicate that chunk encoding is required. - If the semantics of the message indicate that the connection should - be closed after the message is sent, the error thrown from this - function will be `boost::asio::error::eof`. - @param stream The stream to which the data is to be written. The type must support the @b SyncWriteStream concept. - @param msg The message to write. + @param sr The serializer to use. @throws system_error Thrown on failure. + + @note The implementation will call @ref serializer::split with + the value `true` on the serializer passed in. + + @see @ref serializer */ template + bool isRequest, class Body, class Fields, + class Decorator, class Allocator> void -write(SyncWriteStream& stream, - message const& msg); +write_header(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr); -/** Write an HTTP/1 message to a stream. +/** Write a header to a stream using a serializer. - This function is used to write a message to a stream. The call - will block until one of the following conditions is true: + This function is used to write a header to a stream using a + caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: - @li The entire message is written. + @li The function @ref serializer::is_header_done returns `true` @li An error occurs. This operation is implemented in terms of one or more calls to the stream's `write_some` function. - The implementation will automatically perform chunk encoding if - the contents of the message indicate that chunk encoding is required. - If the semantics of the message indicate that the connection should - be closed after the message is sent, the error returned from this - function will be `boost::asio::error::eof`. - @param stream The stream to which the data is to be written. The type must support the @b SyncWriteStream concept. - @param msg The message to write. + @param sr The serializer to use. - @param ec Set to the error, if any occurred. + @param ec Set to indicate what error occurred, if any. + + @note The implementation will call @ref serializer::split with + the value `true` on the serializer passed in. + + @see @ref serializer */ template + bool isRequest, class Body, class Fields, + class Decorator, class Allocator> void -write(SyncWriteStream& stream, - message const& msg, +write_header(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr, error_code& ec); -/** Write an HTTP/1 message asynchronously to a stream. +/** Write a header to a stream asynchronously using a serializer. - This function is used to asynchronously write a message to - a stream. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: + This function is used to write a header to a stream asynchronously + using a caller-provided HTTP/1 serializer. The function call always + returns immediately. The asynchronous operation will continue until + one of the following conditions is true: - @li The entire message is written. + @li The function @ref serializer::is_header_done returns `true` @li An error occurs. - This operation is implemented in terms of one or more calls to - the stream's `async_write_some` functions, and is known as a - composed operation. The program must ensure that the - stream performs no other write operations until this operation - completes. - - The implementation will automatically perform chunk encoding if - the contents of the message indicate that chunk encoding is required. - If the semantics of the message indicate that the connection should - be closed after the message is sent, the operation will complete with - the error set to `boost::asio::error::eof`. + This operation is implemented in terms of zero or more calls to the stream's + `async_write_some` function, and is known as a composed operation. + The program must ensure that the stream performs no other write operations + until this operation completes. @param stream The stream to which the data is to be written. The type must support the @b AsyncWriteStream concept. - @param msg The message to write. The object must remain valid - at least until the completion handler is called; ownership is - not transferred. + @param sr The serializer to use. @param handler The handler to be called when the operation completes. Copies will be made of the handler as required. @@ -235,6 +255,231 @@ write(SyncWriteStream& stream, immediately or not, the handler will not be invoked from within this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. + + @note The implementation will call @ref serializer::split with + the value `true` on the serializer passed in. + + @see @ref serializer +*/ +template +#if BEAST_DOXYGEN + void_or_deduced +#else +async_return_type +#endif +async_write_header(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr, + WriteHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Write a complete message to a stream using a serializer. + + This function is used to write a complete message to a stream using + a caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: + + @li The function @ref serializer::is_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's `write_some` function. + + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. + + @param sr The serializer to use. + + @throws system_error Thrown on failure. + + @see @ref serializer +*/ +template +void +write(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr); + +/** Write a complete message to a stream using a serializer. + + This function is used to write a complete message to a stream using + a caller-provided HTTP/1 serializer. The call will block until one + of the following conditions is true: + + @li The function @ref serializer::is_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's `write_some` function. + + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. + + @param sr The serializer to use. + + @param ec Set to the error, if any occurred. + + @see @ref serializer +*/ +template +void +write(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr, + error_code& ec); + +/** Write a complete message to a stream asynchronously using a serializer. + + This function is used to write a complete message to a stream + asynchronously using a caller-provided HTTP/1 serializer. The + function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is true: + + @li The function @ref serializer::is_done returns `true` + + @li An error occurs. + + This operation is implemented in terms of zero or more calls to the stream's + `async_write_some` function, and is known as a composed operation. + The program must ensure that the stream performs no other write operations + until this operation completes. + + @param stream The stream to which the data is to be written. + The type must support the @b AsyncWriteStream concept. + + @param sr The serializer to use. + + @param handler The handler to be called when the operation + completes. Copies will be made of the handler as required. + The equivalent function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + + @see @ref serializer +*/ +template +#if BEAST_DOXYGEN + void_or_deduced +#else +async_return_type +#endif +async_write(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr, + WriteHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Write a complete message to a stream. + + This function is used to write a complete message to a stream using + HTTP/1. The call will block until one of the following conditions is true: + + @li The entire message is written. + + @li An error occurs. + + This operation is implemented in terms of one or more calls to the stream's + `write_some` function. The algorithm will use a temporary @ref serializer + with an empty chunk decorator to produce buffers. If the semantics of the + message indicate that the connection should be closed after the message is + sent, the error delivered by this function will be @ref error::end_of_stream + + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. + + @param msg The message to write. + + @throws system_error Thrown on failure. + + @see @ref message +*/ +template +void +write(SyncWriteStream& stream, + message const& msg); + +/** Write a complete message to a stream. + + This function is used to write a complete message to a stream using + HTTP/1. The call will block until one of the following conditions is true: + + @li The entire message is written. + + @li An error occurs. + + This operation is implemented in terms of one or more calls to the stream's + `write_some` function. The algorithm will use a temporary @ref serializer + with an empty chunk decorator to produce buffers. If the semantics of the + message indicate that the connection should be closed after the message is + sent, the error delivered by this function will be @ref error::end_of_stream + + @param stream The stream to which the data is to be written. + The type must support the @b SyncWriteStream concept. + + @param msg The message to write. + + @param ec Set to the error, if any occurred. + + @see @ref message +*/ +template +void +write(SyncWriteStream& stream, + message const& msg, + error_code& ec); + +/** Write a complete message to a stream asynchronously. + + This function is used to write a complete message to a stream asynchronously + using HTTP/1. The function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is true: + + @li The entire message is written. + + @li An error occurs. + + This operation is implemented in terms of zero or more calls to the stream's + `async_write_some` function, and is known as a composed operation. + The program must ensure that the stream performs no other write operations + until this operation completes. The algorithm will use a temporary + @ref serializer with an empty chunk decorator to produce buffers. If + the semantics of the message indicate that the connection should be + closed after the message is sent, the error delivered by this function + will be @ref error::end_of_stream + + @param stream The stream to which the data is to be written. + The type must support the @b AsyncWriteStream concept. + + @param msg The message to write. The object must remain valid at least + until the completion handler is called; ownership is not transferred. + + @param handler The handler to be called when the operation + completes. Copies will be made of the handler as required. + The equivalent function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + + @see @ref message */ template +#include #include #include @@ -16,6 +17,21 @@ namespace beast { class bind_handler_test : public unit_test::suite { public: + struct handler + { + void + operator()() const; + }; + +#if 0 + // This function should fail to compile + void + failStdBind() + { + std::bind(bind_handler(handler{})); + } +#endif + void callback(int v) { diff --git a/test/http/CMakeLists.txt b/test/http/CMakeLists.txt index 8e54e9fa..a141e479 100644 --- a/test/http/CMakeLists.txt +++ b/test/http/CMakeLists.txt @@ -40,6 +40,7 @@ add_executable (http-bench ${BEAST_INCLUDES} ${EXTRAS_INCLUDES} nodejs_parser.hpp + message_fuzz.hpp ../../extras/beast/unit_test/main.cpp nodejs_parser.cpp parser_bench.cpp diff --git a/test/http/basic_parser.cpp b/test/http/basic_parser.cpp index 6d58ab05..9ab789af 100644 --- a/test/http/basic_parser.cpp +++ b/test/http/basic_parser.cpp @@ -148,19 +148,18 @@ public: return {s, N-1}; } - template< - bool isRequest, bool isDirect, class Derived> + template static std::size_t feed(boost::asio::const_buffer buffer, - basic_parser& parser, + basic_parser& parser, error_code& ec) { using boost::asio::const_buffers_1; std::size_t used = 0; for(;;) { - auto const n = parser.write( + auto const n = parser.put( const_buffers_1{buffer}, ec); if(ec) return 0; @@ -168,7 +167,7 @@ public: break; buffer = buffer + n; used += n; - if(parser.is_complete()) + if(parser.is_done()) break; if(buffer_size(buffer) == 0) break; @@ -177,11 +176,11 @@ public: } template + bool isRequest, class Derived> static std::size_t feed(ConstBufferSequence const& buffers, - basic_parser& parser, + basic_parser& parser, error_code& ec) { using boost::asio::buffer_size; @@ -191,14 +190,14 @@ public: for(;;) { auto const n = - parser.write(cb, ec); + parser.put(cb, ec); if(ec) return 0; if(n == 0) break; cb.consume(n); used += n; - if(parser.is_complete()) + if(parser.is_done()) break; if(buffer_size(cb) == 0) break; @@ -206,12 +205,11 @@ public: return used; } - template< - bool isRequest, bool isDirect, class Derived> + template static std::size_t feed(boost::asio::const_buffers_1 buffers, - basic_parser& parser, + basic_parser& parser, error_code& ec) { return feed(*buffers.begin(), parser, ec); @@ -225,7 +223,7 @@ public: using boost::asio::buffer; test_parser p; if(skipBody) - p.skip_body(); + p.skip(true); error_code ec; auto const n = feed(buffer( s.data(), s.size()), p, ec); @@ -233,8 +231,8 @@ public: return; if(! BEAST_EXPECT(n == s.size())) return; - if(p.state() == parse_state::body_to_eof) - p.write_eof(ec); + if(p.need_eof()) + p.put_eof(ec); if(BEAST_EXPECTS(! ec, ec.message())) pred(p); } @@ -257,18 +255,19 @@ public: using boost::asio::buffer; test_parser p; if(skipBody) - p.skip_body(); + p.skip(true); error_code ec; feed(buffer( s.data(), s.size()), p, ec); if(! ec && ev) - p.write_eof(ec); + p.put_eof(ec); BEAST_EXPECTS(ec == ev, ec.message()); } void testFlatten() { +#if 0 using boost::asio::buffer; { std::string const s = @@ -289,7 +288,7 @@ public: BEAST_EXPECTS(! ec, ec.message()); feed(buffer_cat(b1, b2), p, ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.is_done()); } } { @@ -310,9 +309,10 @@ public: ec = {}; feed(buffer_cat(b1, b2), p, ec); BEAST_EXPECTS(! ec, ec.message()); - p.write_eof(ec); + p.put_eof(ec); } } +#endif } // Check that all callbacks are invoked @@ -331,7 +331,7 @@ public: "*"; feed(buffer(s), p, ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.is_done()); BEAST_EXPECT(p.got_on_begin); BEAST_EXPECT(p.got_on_field); BEAST_EXPECT(p.got_on_header); @@ -350,7 +350,7 @@ public: "*"; feed(buffer(s), p, ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.is_done()); BEAST_EXPECT(p.got_on_begin); BEAST_EXPECT(p.got_on_field); BEAST_EXPECT(p.got_on_header); @@ -747,6 +747,7 @@ public: void testBody() { +#if 0 using boost::asio::buffer; good( "GET / HTTP/1.1\r\n" @@ -774,7 +775,7 @@ public: buf("67890")), p, ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.is_done()); } // request without Content-Length or @@ -787,7 +788,7 @@ public: "\r\n" ), p, ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.is_done()); } { error_code ec; @@ -797,7 +798,7 @@ public: "\r\n" ), p, ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.is_done()); } // response without Content-Length or @@ -810,17 +811,17 @@ public: "\r\n" ), p, ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(! p.is_complete()); + BEAST_EXPECT(! p.is_done()); BEAST_EXPECT(p.state() == parse_state::body_to_eof); feed(buf( "hello" ), p, ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(! p.is_complete()); + BEAST_EXPECT(! p.is_done()); BEAST_EXPECT(p.state() == parse_state::body_to_eof); - p.write_eof(ec); + p.put_eof(ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.is_done()); } // 304 "Not Modified" response does not require eof @@ -832,7 +833,7 @@ public: "\r\n" ), p, ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.is_done()); } // Chunked response does not require eof @@ -845,12 +846,12 @@ public: "\r\n" ), p, ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(! p.is_complete()); + BEAST_EXPECT(! p.is_done()); feed(buf( "0\r\n\r\n" ), p, ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.is_done()); } // restart: 1.0 assumes Connection: close @@ -862,7 +863,7 @@ public: "\r\n" ), p, ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.is_done()); } // restart: 1.1 assumes Connection: keep-alive @@ -874,7 +875,7 @@ public: "\r\n" ), p, ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.is_done()); } bad( @@ -882,8 +883,10 @@ public: "Content-Length: 1\r\n" "\r\n", error::partial_message); +#endif } +#if 0 template void check_header( @@ -898,6 +901,7 @@ public: BEAST_EXPECT(! p.got_on_complete); BEAST_EXPECT(p.state() != parse_state::header); } +#endif void testSplit() @@ -921,7 +925,7 @@ public: BEAST_EXPECT(! p.got_on_chunk); BEAST_EXPECT(! p.got_on_complete); BEAST_EXPECT(p.state() != parse_state::header); - BEAST_EXPECT(! p.is_complete()); + BEAST_EXPECT(! p.is_done()); BEAST_EXPECT(p.body.empty()); b.consume(n); p.resume(); @@ -933,7 +937,7 @@ public: BEAST_EXPECT(p.got_on_body); BEAST_EXPECT(! p.got_on_chunk); BEAST_EXPECT(p.got_on_complete); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.is_done()); BEAST_EXPECT(p.body == "*****"); #endif } diff --git a/test/http/design.cpp b/test/http/design.cpp index 29145fd7..4eeabd0b 100644 --- a/test/http/design.cpp +++ b/test/http/design.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -24,76 +25,165 @@ class design_test , public beast::test::enable_yield_to { public: - //-------------------------------------------------------------------------- - /* - Expect: 100-continue - - 1. Client sends a header with Expect: 100-continue - - 2. Server reads the request header: - If "Expect: 100-continue" is present, send 100 status response - - 3. Client reads a 100 status response and delivers the body - - 4. Server reads the request body - */ - //-------------------------------------------------------------------------- - - template - void - serverExpect100Continue(Stream& stream, yield_context yield) + // two threads, for some examples using a pipe + design_test() + : enable_yield_to(2) { - flat_buffer buffer; - message_parser parser; - // read the header - async_read_some(stream, buffer, parser, yield); - if(parser.get().fields["Expect"] == - "100-continue") + } + + template + bool + equal_body(string_view sv, string_view body) + { + test::string_istream si{ + get_io_service(), sv.to_string()}; + message m; + multi_buffer b; + try + { + read(si, b, m); + return m.body == body; + } + catch(std::exception const& e) + { + log << "equal_body: " << e.what() << std::endl; + return false; + } + } + + //-------------------------------------------------------------------------- + // + // Example: Expect 100-continue + // + //-------------------------------------------------------------------------- + + /** Send a request with Expect: 100-continue + + This function will send a request with the Expect: 100-continue + field by first sending the header, then waiting for a successful + response from the server before continuing to send the body. If + a non-successful server response is received, the function + returns immediately. + + @param stream The remote HTTP server stream. + + @param buffer The buffer used for reading. + + @param req The request to send. This function modifies the object: + the Expect header field is inserted into the message if it does + not already exist, and set to "100-continue". + + @param ec Set to the error, if any occurred. + */ + template< + class SyncStream, + class DynamicBuffer, + class Body, class Fields> + void + send_expect_100_continue( + SyncStream& stream, + DynamicBuffer& buffer, + request& req, + error_code& ec) + { + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + + // Insert or replace the Expect field + req.fields.replace("Expect", "100-continue"); + + // Create the serializer + auto sr = make_serializer(req); + + // Send just the header + write_header(stream, sr, ec); + if(ec) + return; + + BOOST_ASSERT(sr.is_header_done()); + BOOST_ASSERT(! sr.is_done()); + + // Read the response from the server. + // A robust client could set a timeout here. + { + response res; + read(stream, buffer, res, ec); + if(ec) + return; + if(res.status != 100) + { + // The server indicated that it will not + // accept the request, so skip sending the body. + return; + } + } + + // Server is OK with the request, send the body + write(stream, sr, ec); + } + + /** Receive a request, handling Expect: 100-continue if present. + + This function will read a request from the specified stream. + If the request contains the Expect: 100-continue field, a + status response will be delivered. + + @param stream The remote HTTP client stream. + + @param buffer The buffer used for reading. + + @param ec Set to the error, if any occurred. + */ + template< + class SyncStream, + class DynamicBuffer> + void + receive_expect_100_continue( + SyncStream& stream, + DynamicBuffer& buffer, + error_code& ec) + { + static_assert(is_sync_stream::value, + "SyncStream requirements not met"); + + static_assert(is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + + // Declare a parser for a request with a string body + request_parser parser; + + // Read the header + read_header(stream, buffer, parser, ec); + if(ec) + return; + + // Check for the Expect field value + if(parser.get().fields["Expect"] == "100-continue") { // send 100 response - message res; + response res; res.version = 11; res.status = 100; res.reason("Continue"); res.fields.insert("Server", "test"); - async_write(stream, res, yield); - } - // read the rest of the message - while(! parser.is_complete()) - async_read_some(stream, buffer, parser,yield); - } - - template - void - clientExpect100Continue(Stream& stream, yield_context yield) - { - flat_buffer buffer; - message req; - req.version = 11; - req.method("POST"); - req.target("/"); - req.fields.insert("User-Agent", "test"); - req.fields.insert("Expect", "100-continue"); - req.body = "Hello, world!"; - - // send header - auto sr = make_serializer(req); - for(;;) - { - async_write_some(stream, sr, yield); - if(sr.is_header_done()) - break; - } - - // read response - { - message res; - async_read(stream, buffer, res, yield); + write(stream, res, ec); + if(ec) + return; } - // send body - while(! sr.is_done()) - async_write_some(stream, sr, yield); + BEAST_EXPECT(! parser.is_done()); + BEAST_EXPECT(parser.get().body.empty()); + + // Read the rest of the message. + // + // We use parser.base() to return a basic_parser&, to avoid an + // ambiguous function error (from boost::asio::read). Another + // solution is to qualify the call, e.g. `beast::http::read` + // + read(stream, buffer, parser.base(), ec); } void @@ -101,16 +191,317 @@ public: { test::pipe p{ios_}; yield_to( - [&](yield_context yield) + [&](yield_context) { - serverExpect100Continue(p.server, yield); + error_code ec; + flat_buffer buffer; + receive_expect_100_continue( + p.server, buffer, ec); + BEAST_EXPECTS(! ec, ec.message()); }, - [&](yield_context yield) + [&](yield_context) { - clientExpect100Continue(p.client, yield); + flat_buffer buffer; + request req; + req.version = 11; + req.method("POST"); + req.target("/"); + req.fields.insert("User-Agent", "test"); + req.body = "Hello, world!"; + prepare(req); + + error_code ec; + send_expect_100_continue( + p.client, buffer, req, ec); + BEAST_EXPECTS(! ec, ec.message()); }); } + //-------------------------------------------------------------------------- + // + // Example: CGI child process relay + // + //-------------------------------------------------------------------------- + + /** Send the output of a child process as an HTTP response. + + The output of the child process comes from a @b SyncReadStream. Data + will be sent continuously as it is produced, without the requirement + that the entire process output is buffered before being sent. The + response will use the chunked transfer encoding. + + @param input A stream to read the child process output from. + + @param output A stream to write the HTTP response to. + + @param ec Set to the error, if any occurred. + */ + template< + class SyncReadStream, + class SyncWriteStream> + void + send_cgi_response( + SyncReadStream& input, + SyncWriteStream& output, + error_code& ec) + { + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + + // Set up the response. We use the buffer_body type, + // allowing serialization to use manually provided buffers. + message res; + + res.status = 200; + res.version = 11; + res.fields.insert("Server", "Beast"); + res.fields.insert("Transfer-Encoding", "chunked"); + + // No data yet, but we set more = true to indicate + // that it might be coming later. Otherwise the + // serializer::is_done would return true right after + // sending the header. + res.body.data = nullptr; + res.body.more = true; + + // Create the serializer. We set the split option to + // produce the header immediately without also trying + // to acquire buffers from the body (which would return + // the error http::need_buffer because we set `data` + // to `nullptr` above). + auto sr = make_serializer(res); + + // Send the header immediately. + write_header(output, sr, ec); + if(ec) + return; + + // Alternate between reading from the child process + // and sending all the process output until there + // is no more output. + do + { + // Read a buffer from the child process + char buffer[2048]; + auto bytes_transferred = input.read_some( + boost::asio::buffer(buffer, sizeof(buffer)), ec); + if(ec == boost::asio::error::eof) + { + ec = {}; + + // `nullptr` indicates there is no buffer + res.body.data = nullptr; + + // `false` means no more data is coming + res.body.more = false; + } + else + { + if(ec) + return; + + // Point to our buffer with the bytes that + // we received, and indicate that there may + // be some more data coming + res.body.data = buffer; + res.body.size = bytes_transferred; + res.body.more = true; + } + + // Write everything in the body buffer + write(output, sr, ec); + + // This error is returned by body_buffer during + // serialization when it is done sending the data + // provided and needs another buffer. + if(ec == error::need_buffer) + { + ec = {}; + continue; + } + if(ec) + return; + } + while(! sr.is_done()); + } + + void + doCgiResponse() + { + std::string const s = "Hello, world!"; + test::pipe child{ios_}; + child.server.read_size(3); + ostream(child.server.buffer) << s; + child.client.close(); + test::pipe p{ios_}; + error_code ec; + send_cgi_response(child.server, p.client, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(equal_body(p.server.str(), s)); + } + + //-------------------------------------------------------------------------- + // + // Example: HTTP Relay + // + //-------------------------------------------------------------------------- + + /** Relay an HTTP message. + + This function efficiently relays an HTTP message from a downstream + client to an upstream server, or from an upstream server to a + downstream client. After the message header is read from the input, + a user provided transformation function is invoked which may change + the contents of the header before forwarding to the output. This may + be used to adjust fields such as Server, or proxy fields. + + @param output The stream to write to. + + @param input The stream to read from. + + @param buffer The buffer to use for the input. + + @param transform The header transformation to apply. The function will + be called with this signature: + @code + void transform( + header&, // The header to transform + error_code&); // Set to the error, if any + @endcode + + @param ec Set to the error if any occurred. + + @tparam isRequest `true` to relay a request. + + @tparam Fields The type of fields to use for the message. + */ + template< + bool isRequest, + class Fields = fields, + class SyncWriteStream, + class SyncReadStream, + class DynamicBuffer, + class Transform> + void + relay( + SyncWriteStream& output, + SyncReadStream& input, + DynamicBuffer& buffer, + error_code& ec, + Transform&& transform) + { + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + + static_assert(is_sync_read_stream::value, + "SyncReadStream requirements not met"); + + // A small buffer for relaying the body piece by piece + char buf[2048]; + + // Create a parser with a buffer body to read from the input. + message_parser p; + + // Create a serializer from the message contained in the parser. + serializer sr{p.get()}; + + // Read just the header from the input + read_header(input, buffer, p, ec); + if(ec) + return; + + // Apply the caller's header tranformation + // base() returns a reference to the header portion of the message. + transform(p.get().base(), ec); + if(ec) + return; + + // Send the transformed message to the output + write_header(output, sr, ec); + if(ec) + return; + + // Loop over the input and transfer it to the output + do + { + if(! p.is_done()) + { + // Set up the body for writing into our small buffer + p.get().body.data = buf; + p.get().body.size = sizeof(buf); + + // Read as much as we can + read(input, buffer, p, ec); + + // This error is returned when buffer_body uses up the buffer + if(ec == error::need_buffer) + ec = {}; + if(ec) + return; + + // Set up the body for reading. + // This is how much was parsed: + p.get().body.size = sizeof(buf) - p.get().body.size; + p.get().body.data = buf; + p.get().body.more = ! p.is_done(); + } + else + { + p.get().body.data = nullptr; + p.get().body.size = 0; + } + + // Write everything in the buffer (which might be empty) + write(output, sr, ec); + + // This error is returned when buffer_body uses up the buffer + if(ec == error::need_buffer) + ec = {}; + if(ec) + return; + } + while(! p.is_done() && ! sr.is_done()); + } + + void + doRelay() + { + request req; + req.version = 11; + req.method("POST"); + req.target("/"); + req.fields.insert("User-Agent", "test"); + req.body = "Hello, world!"; + prepare(req); + + test::pipe downstream{ios_}; + downstream.server.read_size(3); + test::pipe upstream{ios_}; + upstream.client.write_size(3); + + error_code ec; + write(downstream.client, req); + BEAST_EXPECTS(! ec, ec.message()); + downstream.client.close(); + + flat_buffer buffer; + relay(upstream.client, downstream.server, buffer, ec, + [&](header& h, error_code& ec) + { + h.fields.erase("Content-Length"); + h.fields.replace("Transfer-Encoding", "chunked"); + }); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(equal_body( + upstream.server.str(), req.body)); + } + //-------------------------------------------------------------------------- // // Deferred Body type commitment @@ -137,357 +528,13 @@ public: message_parser parser2( std::move(parser)); - while(! parser2.is_complete()) + while(! parser2.is_done()) { bytes_used = read_some(p.server, buffer, parser2); buffer.consume(bytes_used); } } - //-------------------------------------------------------------------------- - // - // Write using caller provided buffers - // - //-------------------------------------------------------------------------- - - void - doBufferBody() - { - test::pipe p{ios_}; - message, fields> m; - std::string s = "Hello, world!"; - m.version = 11; - m.method("POST"); - m.target("/"); - m.fields.insert("User-Agent", "test"); - m.fields.insert("Content-Length", s.size()); - auto sr = make_serializer(m); - error_code ec; - for(;;) - { - m.body.first.emplace(s.data(), - std::min(3, s.size())); - m.body.second = s.size() > 3; - for(;;) - { - write_some(p.client, sr, ec); - if(ec == error::need_more) - { - ec = {}; - break; - } - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - if(sr.is_done()) - break; - } - s.erase(s.begin(), s.begin() + - boost::asio::buffer_size(*m.body.first)); - if(sr.is_done()) - break; - } - BEAST_EXPECT(p.server.str() == - "POST / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 13\r\n" - "\r\n" - "Hello, world!"); - } - - //-------------------------------------------------------------------------- - /* - Read a message with a direct Reader Body. - */ - struct direct_body - { - using value_type = std::string; - - class writer - { - value_type& body_; - std::size_t len_ = 0; - - public: - static bool constexpr is_direct = true; - - using mutable_buffers_type = - boost::asio::mutable_buffers_1; - - template - explicit - writer(message& m) - : body_(m.body) - { - } - - void - init() - { - } - - void - init(std::uint64_t content_length) - { - if(content_length > - (std::numeric_limits::max)()) - throw std::length_error( - "Content-Length max exceeded"); - body_.reserve(static_cast< - std::size_t>(content_length)); - } - - mutable_buffers_type - prepare(std::size_t n) - { - body_.resize(len_ + n); - return {&body_[len_], n}; - } - - void - commit(std::size_t n) - { - if(body_.size() > len_ + n) - body_.resize(len_ + n); - len_ = body_.size(); - } - - void - finish() - { - body_.resize(len_); - } - }; - }; - - void - testDirectBody() - { - // Content-Length - { - test::string_istream is{ios_, - "GET / HTTP/1.1\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*" - }; - message m; - flat_buffer b{1024}; - read(is, b, m); - BEAST_EXPECT(m.body == "*"); - } - - // end of file - { - test::string_istream is{ios_, - "HTTP/1.1 200 OK\r\n" - "\r\n" // 19 byte header - "*" - }; - message m; - flat_buffer b{20}; - read(is, b, m); - BEAST_EXPECT(m.body == "*"); - } - - // chunked - { - test::string_istream is{ios_, - "GET / HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "1\r\n" - "*\r\n" - "0\r\n\r\n" - }; - message m; - flat_buffer b{100}; - read(is, b, m); - BEAST_EXPECT(m.body == "*"); - } - } - - //-------------------------------------------------------------------------- - /* - Read a message with an indirect Reader Body. - */ - struct indirect_body - { - using value_type = std::string; - - class writer - { - value_type& body_; - - public: - static bool constexpr is_direct = false; - - using mutable_buffers_type = - boost::asio::null_buffers; - - template - explicit - writer(message& m) - : body_(m.body) - { - } - - void - init(error_code& ec) - { - } - - void - init(std::uint64_t content_length, - error_code& ec) - { - } - - void - write(string_view const& s, - error_code& ec) - { - body_.append(s.data(), s.size()); - } - - void - finish(error_code& ec) - { - } - }; - }; - - void - testIndirectBody() - { - // Content-Length - { - test::string_istream is{ios_, - "GET / HTTP/1.1\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*" - }; - message m; - flat_buffer b{1024}; - read(is, b, m); - BEAST_EXPECT(m.body == "*"); - } - - // end of file - { - test::string_istream is{ios_, - "HTTP/1.1 200 OK\r\n" - "\r\n" // 19 byte header - "*" - }; - message m; - flat_buffer b{20}; - read(is, b, m); - BEAST_EXPECT(m.body == "*"); - } - - - // chunked - { - test::string_istream is{ios_, - "GET / HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "1\r\n" - "*\r\n" - "0\r\n\r\n" - }; - message m; - flat_buffer b{1024}; - read(is, b, m); - BEAST_EXPECT(m.body == "*"); - } - } - - //-------------------------------------------------------------------------- - /* - Read a message header and manually read the body. - */ - void - testManualBody() - { - // Content-Length - { - test::string_istream is{ios_, - "GET / HTTP/1.1\r\n" - "Content-Length: 5\r\n" - "\r\n" // 37 byte header - "*****" - }; - header_parser p; - flat_buffer b{38}; - auto const bytes_used = - read_some(is, b, p); - b.consume(bytes_used); - BEAST_EXPECT(p.size() == 5); - BEAST_EXPECT(b.size() < 5); - b.commit(boost::asio::read( - is, b.prepare(5 - b.size()))); - BEAST_EXPECT(b.size() == 5); - } - - // end of file - { - test::string_istream is{ios_, - "HTTP/1.1 200 OK\r\n" - "\r\n" // 19 byte header - "*****" - }; - header_parser p; - flat_buffer b{20}; - auto const bytes_used = - read_some(is, b, p); - b.consume(bytes_used); - BEAST_EXPECT(p.state() == - parse_state::body_to_eof); - BEAST_EXPECT(b.size() < 5); - b.commit(boost::asio::read( - is, b.prepare(5 - b.size()))); - BEAST_EXPECT(b.size() == 5); - } - } - - //-------------------------------------------------------------------------- - /* - Read a header, check for Expect: 100-continue, - then conditionally read the body. - */ - void - testExpect100Continue() - { - { - test::string_istream is{ios_, - "GET / HTTP/1.1\r\n" - "Expect: 100-continue\r\n" - "Content-Length: 5\r\n" - "\r\n" - "*****" - }; - - header_parser p; - flat_buffer b{128}; - auto const bytes_used = - read_some(is, b, p); - b.consume(bytes_used); - BEAST_EXPECT(p.got_header()); - BEAST_EXPECT( - p.get().fields["Expect"] == - "100-continue"); - message_parser< - true, string_body, fields> p1{ - std::move(p)}; - read(is, b, p1); - BEAST_EXPECT( - p1.get().body == "*****"); - } - } - //-------------------------------------------------------------------------- #if 0 // VFALCO This is broken @@ -521,7 +568,7 @@ public: { case parse_state::header: { - BEAST_EXPECT(parser.got_header()); + BEAST_EXPECT(parser.is_header_done()); for(;;) { ws.write_some(out, ec); @@ -539,7 +586,7 @@ public: case parse_state::chunk_header: { // inspect parser.chunk_extension() here - if(parser.is_complete()) + if(parser.is_done()) boost::asio::write(out, chunk_encode_final()); break; @@ -549,7 +596,7 @@ public: case parse_state::body_to_eof: case parse_state::chunk_body: { - if(! parser.is_complete()) + if(! parser.is_done()) { auto const body = parser.body(); boost::asio::write(out, chunk_encode( @@ -564,7 +611,7 @@ public: } buffer.consume(bytes_used); } - while(! parser.is_complete()); + while(! parser.is_done()); } void @@ -615,104 +662,16 @@ public: } } #endif - - //-------------------------------------------------------------------------- - /* - Read the request header, then read the request body content using - a fixed-size buffer, i.e. read the body in chunks of 4k for instance. - The end of the body should be indicated somehow and chunk-encoding - should be decoded by beast. - */ - template - void - doFixedRead(SyncReadStream& stream, BodyCallback const& cb) - { - flat_buffer buffer{4096}; // 4K limit - header_parser parser; - std::size_t bytes_used; - bytes_used = read_some(stream, buffer, parser); - BEAST_EXPECT(parser.got_header()); - buffer.consume(bytes_used); - do - { - bytes_used = - read_some(stream, buffer, parser); - if(! parser.body().empty()) - cb(parser.body()); - buffer.consume(bytes_used); - } - while(! parser.is_complete()); - } - struct bodyHandler - { - void - operator()(string_view const& body) const - { - // called for each piece of the body, - } - }; - - void - testFixedRead() - { - using boost::asio::buffer; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - - // Content-Length - { - test::string_istream is{ios_, - "GET / HTTP/1.1\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*" - }; - doFixedRead(is, bodyHandler{}); - } - - // end of file - { - test::string_istream is{ios_, - "HTTP/1.1 200 OK\r\n" - "\r\n" // 19 byte header - "*****" - }; - doFixedRead(is, bodyHandler{}); - } - - // chunked - { - test::string_istream is{ios_, - "GET / HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "5;x;y=1;z=\"-\"\r\n*****\r\n" - "3\r\n---\r\n" - "1\r\n+\r\n" - "0\r\n\r\n", - 2 // max_read - }; - doFixedRead(is, bodyHandler{}); - } - } - //-------------------------------------------------------------------------- void run() { doExpect100Continue(); + doCgiResponse(); + doRelay(); doDeferredBody(); - doBufferBody(); - - testDirectBody(); - testIndirectBody(); - testManualBody(); - testExpect100Continue(); - //testRelay(); // VFALCO Broken with serializer changes - testFixedRead(); } }; diff --git a/test/http/header_parser.cpp b/test/http/header_parser.cpp index b54579f4..12689d71 100644 --- a/test/http/header_parser.cpp +++ b/test/http/header_parser.cpp @@ -22,6 +22,13 @@ class header_parser_test , public test::enable_yield_to { public: + static + boost::asio::const_buffers_1 + buf(string_view s) + { + return {s.data(), s.size()}; + } + void testParse() { @@ -34,7 +41,7 @@ public: flat_buffer db{1024}; header_parser p; read_some(is, db, p); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.is_header_done()); } { test::string_istream is{ios_, @@ -47,8 +54,8 @@ public: flat_buffer db{1024}; header_parser p; read_some(is, db, p); - BEAST_EXPECT(! p.is_complete()); - BEAST_EXPECT(p.state() == parse_state::body); + BEAST_EXPECT(p.is_header_done()); + BEAST_EXPECT(! p.is_done()); } } diff --git a/test/http/message_parser.cpp b/test/http/message_parser.cpp index fb60cb8d..43ab78e8 100644 --- a/test/http/message_parser.cpp +++ b/test/http/message_parser.cpp @@ -14,10 +14,10 @@ #include #include #include +#include #include #include -#include -#include +#include #include #include #include @@ -30,41 +30,117 @@ class message_parser_test , public beast::test::enable_yield_to { public: - template - void - testMatrix(std::string const& s, Pred const& pred) + template + using parser_type = + message_parser; + + static + boost::asio::const_buffers_1 + buf(string_view s) { - beast::test::string_istream ss{get_io_service(), s}; - error_code ec; - #if 0 - multi_buffer buffer; - #else - flat_buffer buffer{1024}; - #endif - message m; - read(ss, buffer, m, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - pred(m); + return {s.data(), s.size()}; + } + + template + static + void + put(ConstBufferSequence const& buffers, + basic_parser& p, + error_code& ec) + { + using boost::asio::buffer_size; + consuming_buffers cb{buffers}; + for(;;) + { + auto const used = p.put(cb, ec); + cb.consume(used); + if(ec) + return; + if(p.need_eof() && + buffer_size(cb) == 0) + { + p.put_eof(ec); + if(ec) + return; + } + if(p.is_done()) + break; + } + } + + template + void + doMatrix(string_view s0, F const& f) + { + using boost::asio::buffer; + // parse a single buffer + { + auto s = s0; + error_code ec; + parser_type p; + put(buffer(s.data(), s.size()), p, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + f(p); + } + // parse two buffers + for(auto n = s0.size() - 1; n >= 1; --n) + { + auto s = s0; + error_code ec; + parser_type p; + p.eager(true); + auto used = + p.put(buffer(s.data(), n), ec); + s.remove_prefix(used); + if(ec == error::need_more) + ec = {}; + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + BEAST_EXPECT(! p.is_done()); + used = p.put( + buffer(s.data(), s.size()), ec); + s.remove_prefix(used); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + BEAST_EXPECT(s.empty()); + if(p.need_eof()) + { + p.put_eof(ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + } + if(BEAST_EXPECT(p.is_done())) + f(p); + } } void - testRead() + testParse() { - testMatrix( + doMatrix( "HTTP/1.0 200 OK\r\n" "Server: test\r\n" "\r\n" - "*******", - [&](message const& m) + "Hello, world!", + [&](parser_type const& p) { - BEAST_EXPECTS(m.body == "*******", - "body='" + m.body + "'"); + auto const& m = p.get(); + BEAST_EXPECT(! p.is_chunked()); + BEAST_EXPECT(p.need_eof()); + BEAST_EXPECT(p.content_length() == boost::none); + BEAST_EXPECT(m.version == 10); + BEAST_EXPECT(m.status == 200); + BEAST_EXPECT(m.reason() == "OK"); + BEAST_EXPECT(m.fields["Server"] == "test"); + BEAST_EXPECT(m.body == "Hello, world!"); } ); - testMatrix( - "HTTP/1.0 200 OK\r\n" + doMatrix( + "HTTP/1.1 200 OK\r\n" "Server: test\r\n" + "Expect: Expires, MD5-Fingerprint\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "5\r\n" @@ -75,132 +151,137 @@ public: "Expires: never\r\n" "MD5-Fingerprint: -\r\n" "\r\n", - [&](message const& m) + [&](parser_type const& p) { + auto const& m = p.get(); + BEAST_EXPECT(! p.need_eof()); + BEAST_EXPECT(p.is_chunked()); + BEAST_EXPECT(p.content_length() == boost::none); + BEAST_EXPECT(m.version == 11); + BEAST_EXPECT(m.status == 200); + BEAST_EXPECT(m.reason() == "OK"); + BEAST_EXPECT(m.fields["Server"] == "test"); + BEAST_EXPECT(m.fields["Transfer-Encoding"] == "chunked"); + BEAST_EXPECT(m.fields["Expires"] == "never"); + BEAST_EXPECT(m.fields["MD5-Fingerprint"] == "-"); BEAST_EXPECT(m.body == "*****--"); } ); - testMatrix( + doMatrix( "HTTP/1.0 200 OK\r\n" "Server: test\r\n" "Content-Length: 5\r\n" "\r\n" "*****", - [&](message const& m) + [&](parser_type const& p) { + auto const& m = p.get(); BEAST_EXPECT(m.body == "*****"); } ); - testMatrix( + doMatrix( "GET / HTTP/1.1\r\n" "User-Agent: test\r\n" "\r\n", - [&](message const& m) + [&](parser_type const& p) { + auto const& m = p.get(); + BEAST_EXPECT(m.method() == "GET"); + BEAST_EXPECT(m.target() == "/"); + BEAST_EXPECT(m.version == 11); + BEAST_EXPECT(! p.need_eof()); + BEAST_EXPECT(! p.is_chunked()); + BEAST_EXPECT(p.content_length() == boost::none); } ); - testMatrix( + doMatrix( "GET / HTTP/1.1\r\n" "User-Agent: test\r\n" "X: \t x \t \r\n" "\r\n", - [&](message const& m) + [&](parser_type const& p) { + auto const& m = p.get(); BEAST_EXPECT(m.fields["X"] == "x"); } ); - } - void - testParse() - { - using boost::asio::buffer; + // test eager(true) { error_code ec; - beast::test::string_istream is{ - get_io_service(), + parser_type p; + p.eager(true); + p.put(buf( "GET / HTTP/1.1\r\n" "User-Agent: test\r\n" "Content-Length: 1\r\n" "\r\n" - "*"}; - flat_buffer b{1024}; - message_parser p; - read(is, b, p, ec); + "*") + , ec); auto const& m = p.get(); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(! ec); + BEAST_EXPECT(p.is_done()); + BEAST_EXPECT(p.is_header_done()); + BEAST_EXPECT(! p.need_eof()); BEAST_EXPECT(m.method() == "GET"); BEAST_EXPECT(m.target() == "/"); BEAST_EXPECT(m.version == 11); BEAST_EXPECT(m.fields["User-Agent"] == "test"); BEAST_EXPECT(m.body == "*"); } -#if 0 { // test partial parsing of final chunk // parse through the chunk body - beast::test::string_istream is{ - get_io_service(), ""}; - multi_buffer b; - b << + error_code ec; + flat_buffer b; + parser_type p; + p.eager(true); + ostream(b) << "PUT / HTTP/1.1\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "1\r\n" "*"; - error_code ec; - message_parser p; - read(is, b, p, ec); - BEAST_EXPECT(b.size() == 0); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(!p.is_complete()); + auto used = p.put(b.data(), ec); + b.consume(used); + BEAST_EXPECT(! ec); + BEAST_EXPECT(! p.is_done()); BEAST_EXPECT(p.get().body == "*"); - b << "\r\n0;d;e=3;f=\"4\"\r\n" + ostream(b) << + "\r\n" + "0;d;e=3;f=\"4\"\r\n" "Expires: never\r\n" "MD5-Fingerprint: -\r\n"; // incomplete parse, missing the final crlf - BEAST_EXPECT(p.write(b.data(), ec) == 0); + used = p.put(b.data(), ec); + b.consume(used); + BEAST_EXPECT(ec == error::need_more); + ec = {}; + BEAST_EXPECT(! p.is_done()); + ostream(b) << + "\r\n"; // final crlf to end message + used = p.put(b.data(), ec); + b.consume(used); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(!p.is_complete()); - b << "\r\n"; // final crlf to end message - BEAST_EXPECT(p.write(b.data(), ec) == b.size()); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); - } - { - error_code ec; - message_parser p; - std::string const s = - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - p.write(buffer(s), ec); - auto const& m = p.get(); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); - BEAST_EXPECT(m.status == 200); - BEAST_EXPECT(m.reason() == "OK"); - BEAST_EXPECT(m.version == 11); - BEAST_EXPECT(m.fields["Server"] == "test"); - BEAST_EXPECT(m.body == "*"); + BEAST_EXPECT(p.is_done()); } // skip body { error_code ec; message_parser p; - std::string const s = - "HTTP/1.1 200 Connection Established\r\n" - "Proxy-Agent: Zscaler/5.1\r\n" - "\r\n"; - p.skip_body(); - p.write(buffer(s), ec); + p.skip(true); + p.put(buf( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****") + , ec); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.is_done()); + BEAST_EXPECT(p.is_header_done()); + BEAST_EXPECT(p.content_length() && + *p.content_length() == 5); } -#endif } void @@ -219,8 +300,8 @@ public: read_some(ss, b, p0, ec); b.consume(bytes_used); BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p0.state() != parse_state::header); - BEAST_EXPECT(! p0.is_complete()); + BEAST_EXPECT(p0.is_header_done()); + BEAST_EXPECT(! p0.is_done()); message_parser p1{std::move(p0)}; read(ss, b, p1, ec); @@ -228,12 +309,57 @@ public: BEAST_EXPECT(p1.get().body == "*****"); } + //-------------------------------------------------------------------------- + + template + void + testNeedMore() + { + error_code ec; + std::size_t used; + { + DynamicBuffer b; + parser_type p; + ostream(b) << + "GET / HTTP/1.1\r\n"; + used = p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::need_more, ec.message()); + b.consume(used); + ec = {}; + ostream(b) << + "User-Agent: test\r\n" + "\r\n"; + used = p.put(b.data(), ec); + BEAST_EXPECTS(! ec, ec.message()); + b.consume(used); + BEAST_EXPECT(p.is_done()); + BEAST_EXPECT(p.is_header_done()); + } + } + + void + testGotSome() + { + error_code ec; + parser_type p; + auto used = p.put(buf(""), ec); + BEAST_EXPECT(ec == error::need_more); + BEAST_EXPECT(! p.got_some()); + BEAST_EXPECT(used == 0); + ec = {}; + used = p.put(buf("G"), ec); + BEAST_EXPECT(ec == error::need_more); + BEAST_EXPECT(p.got_some()); + BEAST_EXPECT(used == 0); + } + void run() override { - testRead(); testParse(); - testExpect100Continue(); + testNeedMore(); + testNeedMore(); + testGotSome(); } }; diff --git a/test/http/parser_bench.cpp b/test/http/parser_bench.cpp index 3af58262..77062982 100644 --- a/test/http/parser_bench.cpp +++ b/test/http/parser_bench.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -26,7 +27,8 @@ class parser_bench_test : public beast::unit_test::suite public: static std::size_t constexpr N = 2000; - using corpus = std::vector; + //using corpus = std::vector; + using corpus = std::vector; corpus creq_; corpus cres_; @@ -41,22 +43,17 @@ public: std::string>(buffers(bs)); } - 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); + v.resize(n); message_fuzz mg; for(std::size_t i = 0; i < n; ++i) { mg.request(v[i]); size_ += v[i].size(); + BEAST_EXPECT(v[i].size() > 0); } return v; } @@ -65,22 +62,23 @@ public: build_corpus(std::size_t n, std::false_type) { corpus v; - v.resize(N); + v.resize(n); message_fuzz mg; for(std::size_t i = 0; i < n; ++i) { mg.response(v[i]); size_ += v[i].size(); + BEAST_EXPECT(v[i].size() > 0); } return v; } template + bool isRequest, class Derived> static std::size_t feed(ConstBufferSequence const& buffers, - basic_parser& parser, + basic_parser& parser, error_code& ec) { using boost::asio::buffer_size; @@ -90,14 +88,14 @@ public: for(;;) { auto const n = - parser.write(cb, ec); + parser.put(cb, ec); if(ec) return 0; if(n == 0) break; cb.consume(n); used += n; - if(parser.is_complete()) + if(parser.is_done()) break; if(buffer_size(cb) == 0) break; @@ -155,14 +153,13 @@ public: template struct null_parser : - basic_parser> + basic_parser> { }; template struct bench_parser : basic_parser< - isRequest, false, bench_parser> + isRequest, bench_parser> { using mutable_buffers_type = boost::asio::mutable_buffers_1; @@ -194,13 +191,8 @@ public: } void - on_body(error_code& ec) - { - } - - void - on_body(std::uint64_t content_length, - error_code& ec) + on_body(boost::optional const&, + error_code&) { } @@ -217,12 +209,6 @@ public: { } - void - on_body(string_view const&, - error_code&) - { - } - void on_complete(error_code&) { @@ -235,6 +221,9 @@ public: static std::size_t constexpr Trials = 3; static std::size_t constexpr Repeat = 500; + creq_ = build_corpus(N/2, std::true_type{}); + cres_ = build_corpus(N/2, std::false_type{}); + log << "sizeof(request parser) == " << sizeof(null_parser) << '\n'; @@ -245,16 +234,6 @@ public: ((Repeat * size_ + 512) / 1024) << "KB in " << (Repeat * (creq_.size() + cres_.size())) << " messages"; - timedTest(Trials, "nodejs_parser", - [&] - { - testParser1>( - Repeat, creq_); - testParser1>( - Repeat, cres_); - }); timedTest(Trials, "http::basic_parser", [&] { @@ -265,6 +244,16 @@ public: false, dynamic_body, fields>>( Repeat, cres_); }); + timedTest(Trials, "nodejs_parser", + [&] + { + testParser1>( + Repeat, creq_); + testParser1>( + Repeat, cres_); + }); pass(); } diff --git a/test/http/read.cpp b/test/http/read.cpp index a90f33b7..a972bc42 100644 --- a/test/http/read.cpp +++ b/test/http/read.cpp @@ -10,11 +10,14 @@ #include "test_parser.hpp" +#include +#include #include #include #include #include #include +#include #include #include #include @@ -108,7 +111,7 @@ public: { multi_buffer b; test::string_istream ss(ios_, "GET / X"); - message_parser p; + request_parser p; read(ss, b, p); fail(); } @@ -118,6 +121,52 @@ public: } } + void + testBufferOverflow() + { + { + test::pipe p{ios_}; + ostream(p.server.buffer) << + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "User-Agent: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "10\r\n" + "****************\r\n" + "0\r\n\r\n"; + static_buffer_n<1024> b; + request req; + try + { + read(p.server, b, req); + pass(); + } + catch(std::exception const& e) + { + fail(e.what(), __FILE__, __LINE__); + } + } + { + test::pipe p{ios_}; + ostream(p.server.buffer) << + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "User-Agent: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "10\r\n" + "****************\r\n" + "0\r\n\r\n"; + error_code ec; + static_buffer_n<10> b; + request req; + read(p.server, b, req, ec); + BEAST_EXPECTS(ec == error::buffer_overflow, + ec.message()); + } + } + void testFailures(yield_context do_yield) { char const* req[] = { @@ -318,6 +367,7 @@ public: run() override { testThrow(); + testBufferOverflow(); yield_to([&](yield_context yield){ testFailures(yield); }); diff --git a/test/http/test_parser.hpp b/test/http/test_parser.hpp index 94e3a9ed..148509a8 100644 --- a/test/http/test_parser.hpp +++ b/test/http/test_parser.hpp @@ -16,8 +16,7 @@ namespace http { template class test_parser - : public basic_parser> + : public basic_parser> { test::fail_counter* fc_ = nullptr; @@ -36,8 +35,6 @@ public: bool got_on_header = false; bool got_on_body = false; bool got_content_length = false; - bool got_on_prepare = false; - bool got_on_commit = false; bool got_on_chunk = false; bool got_on_complete = false; @@ -50,10 +47,9 @@ public: } void - on_request( - string_view const& method_, - string_view const& path_, - int version_, error_code& ec) + on_request(string_view const& method_, + string_view const& path_, + int version_, error_code& ec) { method = std::string( method_.data(), method_.size()); @@ -98,19 +94,13 @@ public: } void - on_body(error_code& ec) + on_body(boost::optional< + std::uint64_t> const& content_length_, + error_code& ec) { got_on_body = true; - if(fc_) - fc_->fail(ec); - } - - void - on_body(std::uint64_t content_length, - error_code& ec) - { - got_on_body = true; - got_content_length = true; + got_content_length = + static_cast(content_length_); if(fc_) fc_->fail(ec); } @@ -120,12 +110,13 @@ public: error_code& ec) { body.append(s.data(), s.size()); + if(fc_) + fc_->fail(ec); } void on_chunk(std::uint64_t, - string_view const&, - error_code& ec) + string_view const&, error_code& ec) { got_on_chunk = true; if(fc_) diff --git a/test/http/type_traits.cpp b/test/http/type_traits.cpp index 6527e377..978d5a7e 100644 --- a/test/http/type_traits.cpp +++ b/test/http/type_traits.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace beast { namespace http { @@ -17,5 +18,7 @@ BOOST_STATIC_ASSERT(! is_body_reader::value); BOOST_STATIC_ASSERT(is_body_reader::value); +BOOST_STATIC_ASSERT(! is_body_writer::value); + } // http } // beast diff --git a/test/http/write.cpp b/test/http/write.cpp index 098c64bf..dd9dbeac 100644 --- a/test/http/write.cpp +++ b/test/http/write.cpp @@ -382,7 +382,7 @@ public: m.body = "*****"; error_code ec; write(fs, m, ec); - if(ec == boost::asio::error::eof) + if(ec == error::end_of_stream) { BEAST_EXPECT(fs.next_layer().str == "GET / HTTP/1.0\r\n" @@ -415,7 +415,7 @@ public: m.body = "*****"; error_code ec; async_write(fs, m, do_yield[ec]); - if(ec == boost::asio::error::eof) + if(ec == error::end_of_stream) { BEAST_EXPECT(fs.next_layer().str == "GET / HTTP/1.0\r\n" @@ -559,7 +559,7 @@ public: test::string_ostream ss(ios_); error_code ec; write(ss, m, ec); - BEAST_EXPECT(ec == boost::asio::error::eof); + BEAST_EXPECT(ec == error::end_of_stream); BEAST_EXPECT(ss.str == "GET / HTTP/1.0\r\n" "User-Agent: test\r\n" @@ -596,7 +596,7 @@ public: test::string_ostream ss(ios_); error_code ec; write(ss, m, ec); - BEAST_EXPECT(ec == boost::asio::error::eof); + BEAST_EXPECT(ec == error::end_of_stream); BEAST_EXPECT(ss.str == "GET / HTTP/1.1\r\n" "User-Agent: test\r\n" @@ -949,93 +949,6 @@ public: } } - /** Execute a child process and return the output as an HTTP response. - - @param input A stream to read the child process output from. - - @param output A stream to write the HTTP response to. - */ - template - void - cgi_process(SyncReadStream& input, SyncWriteStream& output, error_code& ec) - { - multi_buffer b; - message, fields> m; - m.status = 200; - m.version = 11; - m.fields.insert("Server", "cgi-process"); - m.fields.insert("Transfer-Encoding", "chunked"); - m.body.first = boost::none; - m.body.second = true; - - auto sr = make_serializer(m); - - // send the header first, so the - // other end gets it right away - for(;;) - { - write_some(output, sr, ec); - if(ec == error::need_more) - { - ec = {}; - break; - } - if(ec) - return; - } - - // send the body - for(;;) - { - // read from input - auto bytes_transferred = - input.read_some(b.prepare(1024), ec); - if(ec == boost::asio::error::eof) - { - BOOST_ASSERT(bytes_transferred == 0); - ec = {}; - m.body = {boost::none, false}; - } - else - { - if(ec) - return; - b.commit(bytes_transferred); - m.body = {b.data(), true}; - } - - // write to output - for(;;) - { - write_some(output, sr, ec); - if(ec == error::need_more) - { - ec = {}; - break; - } - if(ec) - return; - if(sr.is_done()) - goto is_done; - } - b.consume(b.size()); - } - is_done: - ; - } - - void - testCgiRelay() - { - error_code ec; - std::string const body = "Hello, world!\n"; - test::string_ostream so{get_io_service(), 3}; - test::string_istream si{get_io_service(), body, 6}; - cgi_process(si, so, ec); - BEAST_EXPECT(equal_body(so.str, body)); - } - void run() override { yield_to([&](yield_context yield){ @@ -1046,7 +959,6 @@ public: test_std_ostream(); testOstream(); testIoService(); - testCgiRelay(); yield_to( [&](yield_context yield) {