diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d6f61b..8cb43fff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ HTTP * Make chunk_encode public * Add write, async_write, operator<< for message_headers +* Add read, async_read for message_headers WebSocket diff --git a/include/beast/http/impl/read.ipp b/include/beast/http/impl/read.ipp index 6a07ccfc..50b19867 100644 --- a/include/beast/http/impl/read.ipp +++ b/include/beast/http/impl/read.ipp @@ -9,6 +9,7 @@ #define BEAST_HTTP_IMPL_READ_IPP_HPP #include +#include #include #include #include @@ -21,6 +22,185 @@ namespace http { namespace detail { +template +class read_headers_op +{ + using alloc_type = + handler_alloc; + + using parser_type = + headers_parser_v1; + + using message_type = + message_headers; + + struct data + { + Stream& s; + DynamicBuffer& db; + message_type& m; + parser_type p; + Handler h; + bool started = false; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, Stream& s_, + DynamicBuffer& sb_, message_type& m_) + : s(s_) + , db(sb_) + , m(m_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + read_headers_op(read_headers_op&&) = default; + read_headers_op(read_headers_op const&) = default; + + template + read_headers_op( + DeducedHandler&& h, Stream& s, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + 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_headers_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, read_headers_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(read_headers_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, read_headers_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +void +read_headers_op:: +operator()(error_code ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + d.state = 1; + async_parse(d.s, d.db, d.p, std::move(*this)); + return; + + case 1: + // call handler + d.state = 99; + d.m = d.p.release(); + break; + } + } + d.h(ec); +} + +} // detail + +template +void +read(SyncReadStream& stream, DynamicBuffer& dynabuf, + message_headers& msg) +{ + static_assert(is_SyncReadStream::value, + "SyncReadStream requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); + error_code ec; + beast::http::read(stream, dynabuf, msg, ec); + if(ec) + throw system_error{ec}; +} + +template +void +read(SyncReadStream& stream, DynamicBuffer& dynabuf, + message_headers& m, + error_code& ec) +{ + static_assert(is_SyncReadStream::value, + "SyncReadStream requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); + headers_parser_v1 p; + beast::http::parse(stream, dynabuf, p, ec); + if(ec) + return; + BOOST_ASSERT(p.complete()); + m = p.release(); +} + +template +typename async_completion< + ReadHandler, void(error_code)>::result_type +async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, + message_headers& m, + ReadHandler&& handler) +{ + static_assert(is_AsyncReadStream::value, + "AsyncReadStream requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); + beast::async_completion completion(handler); + detail::read_headers_op{completion.handler, + stream, dynabuf, m}; + return completion.result.get(); +} + +//------------------------------------------------------------------------------ + +namespace detail { + template @@ -138,8 +318,6 @@ operator()(error_code ec, bool again) } // detail -//------------------------------------------------------------------------------ - template void diff --git a/include/beast/http/parse.hpp b/include/beast/http/parse.hpp index 807d995b..ed7f4f44 100644 --- a/include/beast/http/parse.hpp +++ b/include/beast/http/parse.hpp @@ -106,8 +106,10 @@ parse(SyncReadStream& stream, 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. + 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. @param stream The stream from which the data is to be read. The type must support the @b AsyncReadStream concept. diff --git a/include/beast/http/read.hpp b/include/beast/http/read.hpp index 022071c6..e5f0bb70 100644 --- a/include/beast/http/read.hpp +++ b/include/beast/http/read.hpp @@ -15,6 +15,144 @@ namespace beast { namespace http { +/** Read HTTP/1 message headers from a stream. + + This function is used to synchronously read message headers from + the stream. The call blocks until one of the following conditions + is true: + + @li The complete message headers are read in. + + @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 message + headers being parsed. This additional data is stored in the + stream buffer, which may be used in subsequent calls. + + @param stream The stream from which the data is to be read. + The type must support the @b `SyncReadStream` concept. + + @param dynabuf 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 + stream buffer's input sequence will be given to the parser + first. + + @param msg An object used to store the message headers. + Any contents will be overwritten. The type must support + copy assignment or move assignment. + + @throws system_error Thrown on failure. +*/ +template +void +read(SyncReadStream& stream, DynamicBuffer& dynabuf, + message_headers& msg); + +/** Read HTTP/1 message headers from a stream. + + This function is used to synchronously read message headers from + the stream. The call blocks until one of the following conditions + is true: + + @li The complete message headers are read in. + + @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 message + headers being parsed. This additional data is stored in the + stream buffer, which may be used in subsequent calls. + + If the message being received contains a message body, it + is the callers responsibility to cause the body to be read + in before attempting to read the next message. + + @param stream The stream from which the data is to be read. + The type must support the @b `SyncReadStream` concept. + + @param dynabuf 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 + stream buffer's input sequence will be given to the parser + first. + + @param msg An object used to store the message headers. + Any contents will be overwritten. The type must support + copy assignment or move assignment. + + @param ec Set to the error, if any occurred. +*/ +template +void +read(SyncReadStream& stream, DynamicBuffer& dynabuf, + message_headers& msg, + error_code& ec); + +/** Start an asynchronous operation to read HTTP/1 message headers from a stream. + + This function is used to asynchronously read a message from the + stream. The function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is true: + + @li A complete message is read in. + + @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 message headers being parsed. This additional data is + stored in the stream buffer, which may be used in subsequent calls. + + If the message being received contains a message body, it + is the callers responsibility to cause the body to be read + in before attempting to read the next message. + + @param stream The stream to read the message from. + The type must support the @b `AsyncReadStream` concept. + + @param dynabuf 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 + stream buffer's input sequence will be given to the parser + first. + + @param msg An object used to store the message. Any contents + will be overwritten. The type must support copy assignment or + move assignment. + + @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`. +*/ +template +#if GENERATING_DOCS +void_or_deduced +#else +typename async_completion< + ReadHandler, void(error_code)>::result_type +#endif +async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, + message_headers& msg, + ReadHandler&& handler); + /** Read a HTTP/1 message from a stream. This function is used to synchronously read a message from @@ -41,7 +179,8 @@ namespace http { first. @param msg An object used to store the message. Any - contents will be overwritten. + contents will be overwritten. The type must support + copy assignment or move assignment. @throws system_error Thrown on failure. */ @@ -77,7 +216,8 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, first. @param msg An object used to store the message. Any - contents will be overwritten. + contents will be overwritten. The type must support + copy assignment or move assignment. @param ec Set to the error, if any occurred. */ @@ -98,10 +238,13 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, @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. + 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 message being parsed. This additional data is stored + in the stream buffer, which may be used in subsequent calls. @param stream The stream to read the message from. The type must support the @b `AsyncReadStream` concept. @@ -113,7 +256,8 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, first. @param msg An object used to store the message. Any contents - will be overwritten. + will be overwritten. The type must support copy assignment or + move assignment. @param handler The handler to be called when the request completes. Copies will be made of the handler as required. The equivalent diff --git a/test/http/read.cpp b/test/http/read.cpp index 8e91789a..3d4f57b3 100644 --- a/test/http/read.cpp +++ b/test/http/read.cpp @@ -244,6 +244,52 @@ public: failMatrix(res[i], do_yield); } + void testReadHeaders(yield_context do_yield) + { + static std::size_t constexpr limit = 100; + std::size_t n; + + for(n = 0; n < limit; ++n) + { + test::fail_stream fs{n, ios_, + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "User-Agent: test\r\n" + "Content-Length: 5\r\n" + "\r\n" + }; + request_headers m; + try + { + streambuf sb; + read(fs, sb, m); + break; + } + catch(std::exception const&) + { + } + } + BEAST_EXPECT(n < limit); + + for(n = 0; n < limit; ++n) + { + test::fail_stream fs(n, ios_, + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "User-Agent: test\r\n" + "Content-Length: 0\r\n" + "\r\n" + ); + request_headers m; + error_code ec; + streambuf sb; + async_read(fs, sb, m, do_yield[ec]); + if(! ec) + break; + } + BEAST_EXPECT(n < limit); + } + void testRead(yield_context do_yield) { static std::size_t constexpr limit = 100; @@ -335,6 +381,9 @@ public: yield_to(std::bind(&read_test::testFailures, this, std::placeholders::_1)); + yield_to(std::bind(&read_test::testReadHeaders, + this, std::placeholders::_1)); + yield_to(std::bind(&read_test::testRead, this, std::placeholders::_1));