diff --git a/CHANGELOG.md b/CHANGELOG.md index a941e7ef..ea925859 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ WebSocket: * Fix async_read_some handler signature +API Changes: + +* websocket accept refactoring + +Actions Required: + +* Do not call websocket accept overloads which take + both a message and a buffer sequence, as it is + illegal per rfc6455. + -------------------------------------------------------------------------------- Version 108: diff --git a/include/boost/beast/websocket/impl/accept.ipp b/include/boost/beast/websocket/impl/accept.ipp index 39013a81..517d2888 100644 --- a/include/boost/beast/websocket/impl/accept.ipp +++ b/include/boost/beast/websocket/impl/accept.ipp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -35,12 +36,12 @@ namespace websocket { template template class stream::response_op + : public boost::asio::coroutine { struct data { stream& ws; response_type res; - int step = 0; template data(Handler&, stream& ws_, http::request< @@ -66,10 +67,7 @@ public: { } - template - void operator()(Buffers const& buffers); - - void operator()(error_code ec); + void operator()(error_code ec = {}); friend void* asio_handler_allocate( @@ -90,10 +88,11 @@ public: } friend - bool asio_handler_is_continuation(response_op*) + bool asio_handler_is_continuation(response_op* op) { - // VFALCO This will go away in Net-TS - return false; + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->d_.handler())); } template @@ -106,37 +105,6 @@ public: } }; -template -template -template -void -stream:: -response_op:: -operator()(Buffers const& buffers) -{ - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - auto& d = *d_; - error_code ec; - boost::optional mb; - auto const len = buffer_size(buffers); - try - { - mb.emplace(d.ws.rd_.buf.prepare(len)); - } - catch(std::length_error const&) - { - d.step = 2; - ec = error::buffer_overflow; - return d.ws.get_io_service().post( - bind_handler(std::move(*this), ec)); - } - d.ws.rd_.buf.commit( - buffer_copy(*mb, buffers)); - (*this)(ec); -} - template template void @@ -145,18 +113,13 @@ response_op:: operator()(error_code ec) { auto& d = *d_; - switch(d.step) + BOOST_ASIO_CORO_REENTER(*this) { - case 0: - // send response - d.step = 1; + // Send response + BOOST_ASIO_CORO_YIELD http::async_write(d.ws.next_layer(), d.res, std::move(*this)); - return; - - // sent response - case 1: - if(d.res.result() != + if(! ec && d.res.result() != http::status::switching_protocols) ec = error::handshake_failed; if(! ec) @@ -164,13 +127,8 @@ operator()(error_code ec) pmd_read(d.ws.pmd_config_, d.res); d.ws.open(role_type::server); } - break; - - // call handler - case 2: - break; + d_.invoke(ec); } - d_.invoke(ec); } //------------------------------------------------------------------------------ @@ -180,13 +138,13 @@ operator()(error_code ec) template template class stream::accept_op + : public boost::asio::coroutine { struct data { stream& ws; Decorator decorator; http::request_parser p; - int step = 0; data(Handler&, stream& ws_, Decorator const& decorator_) : ws(ws_) @@ -210,9 +168,9 @@ public: } template - void operator()(Buffers const& buffers); + void run(Buffers const& buffers); - void operator()(error_code ec); + void operator()(error_code ec = {}); friend void* asio_handler_allocate( @@ -256,7 +214,7 @@ template void stream:: accept_op:: -operator()(Buffers const& buffers) +run(Buffers const& buffers) { using boost::asio::buffer_copy; using boost::asio::buffer_size; @@ -271,10 +229,8 @@ operator()(Buffers const& buffers) } catch(std::length_error const&) { - d.step = 2; ec = error::buffer_overflow; - return d.ws.get_io_service().post( - bind_handler(std::move(*this), ec)); + return (*this)(ec); } d.ws.rd_.buf.commit( buffer_copy(*mb, buffers)); @@ -289,40 +245,45 @@ accept_op:: operator()(error_code ec) { auto& d = *d_; - switch(d.step) + BOOST_ASIO_CORO_REENTER(*this) { - case 0: - d.step = 1; - return http::async_read( - d.ws.next_layer(), d.ws.rd_.buf, - d.p, std::move(*this)); - - case 1: - { - if(ec == http::error::end_of_stream) - ec = error::closed; if(ec) - break; - // Arguments from our step must be - // moved to the stack before releasing - // the handler. - auto& ws = d.ws; - auto const req = d.p.release(); - auto const decorator = d.decorator; - #if 1 - return response_op{ - d_.release_handler(), - ws, req, decorator}(ec); - #else - // VFALCO This *should* work but breaks - // coroutine invariants in the unit test. - // Also it calls reset() when it shouldn't. - return ws.async_accept_ex( - req, decorator, d_.release_handler()); - #endif + { + BOOST_ASIO_CORO_YIELD + d.ws.get_io_service().post( + bind_handler(std::move(*this), ec)); + } + else + { + BOOST_ASIO_CORO_YIELD + http::async_read( + d.ws.next_layer(), d.ws.rd_.buf, + d.p, std::move(*this)); + if(ec == http::error::end_of_stream) + ec = error::closed; + if(! ec) + { + // Arguments from our step must be + // moved to the stack before releasing + // the handler. + auto& ws = d.ws; + auto const req = d.p.release(); + auto const decorator = d.decorator; + #if 1 + return response_op{ + d_.release_handler(), + ws, req, decorator}(ec); + #else + // VFALCO This *should* work but breaks + // coroutine invariants in the unit test. + // Also it calls reset() when it shouldn't. + return ws.async_accept_ex( + req, decorator, d_.release_handler()); + #endif + } + } + d_.invoke(ec); } - } - d_.invoke(ec); } //------------------------------------------------------------------------------ @@ -403,11 +364,13 @@ accept(ConstBufferSequence const& buffers) template template< - class ConstBufferSequence, class ResponseDecorator> + class ConstBufferSequence, + class ResponseDecorator> typename std::enable_if::value>::type stream:: -accept_ex(ConstBufferSequence const& buffers, +accept_ex( + ConstBufferSequence const& buffers, ResponseDecorator const &decorator) { static_assert(is_sync_stream::value, @@ -429,30 +392,45 @@ template typename std::enable_if::value>::type stream:: -accept(ConstBufferSequence const& buffers, error_code& ec) +accept( + ConstBufferSequence const& buffers, error_code& ec) { static_assert(is_sync_stream::value, "SyncStream requirements not met"); static_assert(is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); - reset(); using boost::asio::buffer_copy; using boost::asio::buffer_size; - rd_.buf.commit(buffer_copy( - rd_.buf.prepare( - buffer_size(buffers)), buffers)); + reset(); + boost::optional mb; + try + { + mb.emplace(rd_.buf.prepare( + buffer_size(buffers))); + } + catch(std::length_error const&) + { + ec = error::buffer_overflow; + return; + } + rd_.buf.commit( + buffer_copy(*mb, buffers)); do_accept(&default_decorate_res, ec); } template template< - class ConstBufferSequence, class ResponseDecorator> + class ConstBufferSequence, + class ResponseDecorator> typename std::enable_if::value>::type stream:: -accept_ex(ConstBufferSequence const& buffers, - ResponseDecorator const& decorator, error_code& ec) +accept_ex( + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + error_code& ec) { static_assert(is_sync_stream::value, "SyncStream requirements not met"); @@ -462,12 +440,22 @@ accept_ex(ConstBufferSequence const& buffers, static_assert(is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); - reset(); using boost::asio::buffer_copy; using boost::asio::buffer_size; - rd_.buf.commit(buffer_copy( - rd_.buf.prepare( - buffer_size(buffers)), buffers)); + reset(); + boost::optional mb; + try + { + mb.emplace(rd_.buf.prepare( + buffer_size(buffers))); + } + catch(std::length_error const&) + { + ec = error::buffer_overflow; + return; + } + rd_.buf.commit(buffer_copy(*mb, buffers)); do_accept(decorator, ec); } @@ -475,8 +463,9 @@ template template void stream:: -accept(http::request> const& req) +accept( + http::request> const& req) { static_assert(is_sync_stream::value, "SyncStream requirements not met"); @@ -487,12 +476,14 @@ accept(http::request -template +template< + class Body, class Allocator, + class ResponseDecorator> void stream:: -accept_ex(http::request> const& req, +accept_ex( + http::request> const& req, ResponseDecorator const& decorator) { static_assert(is_sync_stream::value, @@ -510,9 +501,10 @@ template template void stream:: -accept(http::request> const& req, - error_code& ec) +accept( + http::request> const& req, + error_code& ec) { static_assert(is_sync_stream::value, "SyncStream requirements not met"); @@ -521,13 +513,16 @@ accept(http::request -template void stream:: -accept_ex(http::request> const& req, - ResponseDecorator const& decorator, error_code& ec) +accept_ex( + http::request> const& req, + ResponseDecorator const& decorator, + error_code& ec) { static_assert(is_sync_stream::value, "SyncStream requirements not met"); @@ -538,101 +533,6 @@ accept_ex(http::request -template -void -stream:: -accept(http::request> const& req, - ConstBufferSequence const& buffers) -{ - static_assert(is_sync_stream::value, - "SyncStream requirements not met"); - static_assert(is_const_buffer_sequence< - ConstBufferSequence>::value, - "ConstBufferSequence requirements not met"); - error_code ec; - accept(req, buffers, ec); - if(ec) - BOOST_THROW_EXCEPTION(system_error{ec}); -} - -template -template -void -stream:: -accept_ex(http::request> const& req, - ConstBufferSequence const& buffers, - ResponseDecorator const& decorator) -{ - static_assert(is_sync_stream::value, - "SyncStream requirements not met"); - static_assert(is_const_buffer_sequence< - ConstBufferSequence>::value, - "ConstBufferSequence requirements not met"); - static_assert(detail::is_ResponseDecorator< - ResponseDecorator>::value, - "ResponseDecorator requirements not met"); - error_code ec; - accept_ex(req, buffers, decorator, ec); - if(ec) - BOOST_THROW_EXCEPTION(system_error{ec}); -} - -template -template -void -stream:: -accept(http::request> const& req, - ConstBufferSequence const& buffers, error_code& ec) -{ - static_assert(is_sync_stream::value, - "SyncStream requirements not met"); - static_assert(is_const_buffer_sequence< - ConstBufferSequence>::value, - "ConstBufferSequence requirements not met"); - reset(); - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - rd_.buf.commit(buffer_copy( - rd_.buf.prepare( - buffer_size(buffers)), buffers)); - do_accept(req, &default_decorate_res, ec); -} - -template -template -void -stream:: -accept_ex(http::request> const& req, - ConstBufferSequence const& buffers, - ResponseDecorator const& decorator, - error_code& ec) -{ - static_assert(is_sync_stream::value, - "SyncStream requirements not met"); - static_assert(is_const_buffer_sequence< - ConstBufferSequence>::value, - "ConstBufferSequence requirements not met"); - static_assert(detail::is_ResponseDecorator< - ResponseDecorator>::value, - "ResponseDecorator requirements not met"); - reset(); - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - rd_.buf.commit(buffer_copy( - rd_.buf.prepare( - buffer_size(buffers)), buffers)); - do_accept(req, decorator, ec); -} - //------------------------------------------------------------------------------ template @@ -709,7 +609,7 @@ async_accept( handler_type>{ init.completion_handler, *this, - &default_decorate_res}(buffers); + &default_decorate_res}.run(buffers); return init.result.get(); } @@ -743,7 +643,7 @@ async_accept_ex( handler_type>{ init.completion_handler, *this, - decorator}(buffers); + decorator}.run(buffers); return init.result.get(); } @@ -768,7 +668,7 @@ async_accept( init.completion_handler, *this, req, - &default_decorate_res}({}); + &default_decorate_res}(); return init.result.get(); } @@ -798,73 +698,7 @@ async_accept_ex( init.completion_handler, *this, req, - decorator}({}); - return init.result.get(); -} - -template -template< - class Body, class Allocator, - class ConstBufferSequence, - class AcceptHandler> -async_return_type -stream:: -async_accept( - http::request> const& req, - ConstBufferSequence const& buffers, - AcceptHandler&& handler) -{ - static_assert(is_async_stream::value, - "AsyncStream requirements requirements not met"); - static_assert(is_const_buffer_sequence< - ConstBufferSequence>::value, - "ConstBufferSequence requirements not met"); - async_completion init{handler}; - reset(); - using boost::asio::asio_handler_is_continuation; - response_op< - handler_type>{ - init.completion_handler, - *this, - req, - &default_decorate_res}(buffers); - return init.result.get(); -} - -template -template< - class Body, class Allocator, - class ConstBufferSequence, - class ResponseDecorator, - class AcceptHandler> -async_return_type< - AcceptHandler, void(error_code)> -stream:: -async_accept_ex(http::request> const& req, - ConstBufferSequence const& buffers, - ResponseDecorator const& decorator, - AcceptHandler&& handler) -{ - static_assert(is_async_stream::value, - "AsyncStream requirements requirements not met"); - static_assert(is_const_buffer_sequence< - ConstBufferSequence>::value, - "ConstBufferSequence requirements not met"); - static_assert(detail::is_ResponseDecorator< - ResponseDecorator>::value, - "ResponseDecorator requirements not met"); - async_completion init{handler}; - reset(); - using boost::asio::asio_handler_is_continuation; - response_op< - handler_type>{ - init.completion_handler, - *this, - req, - decorator}(buffers); + decorator}(); return init.result.get(); } diff --git a/include/boost/beast/websocket/stream.hpp b/include/boost/beast/websocket/stream.hpp index b242430b..178bfd34 100644 --- a/include/boost/beast/websocket/stream.hpp +++ b/include/boost/beast/websocket/stream.hpp @@ -1915,212 +1915,6 @@ public: ResponseDecorator const& decorator, error_code& ec); - /** Respond to a WebSocket HTTP Upgrade request - - This function is used to synchronously send the HTTP response - to an HTTP request possibly containing a WebSocket Upgrade. - The call blocks until one of the following conditions is true: - - @li The response finishes sending. - - @li An error occurs on the stream. - - This function is implemented in terms of one or more calls to - the next layer's `read_some` and `write_some` functions. - - If the stream receives a valid HTTP WebSocket Upgrade request, - an HTTP response is sent back indicating a successful upgrade. - When this call returns, the stream is then ready to send and - receive WebSocket protocol frames and messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, - an HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure. - - The implementation uses fixed size internal storage to - copy the buffer. If the buffer is too large, the error - @ref error::buffer_overflow will be indicated. Applications - that wish to supply larger buffers should wrap the next layer - in a @ref buffered_read_stream and store the buffer contents in - the wrapper before calling accept. - - @param req An object containing the HTTP Upgrade request. - Ownership is not transferred, the implementation will not - access this object from other threads. - - @param buffers Caller provided data that has already been - received on the stream. This must not include the octets - corresponding to the HTTP Upgrade request. The implementation - will copy the caller provided data before the function returns. - - @throws system_error Thrown on failure. - */ - template - void - accept(http::request> const& req, - ConstBufferSequence const& buffers); - - /** Respond to a WebSocket HTTP Upgrade request - - This function is used to synchronously send the HTTP response - to an HTTP request possibly containing a WebSocket Upgrade. - The call blocks until one of the following conditions is true: - - @li The response finishes sending. - - @li An error occurs on the stream. - - This function is implemented in terms of one or more calls to - the next layer's `read_some` and `write_some` functions. - - If the stream receives a valid HTTP WebSocket Upgrade request, - an HTTP response is sent back indicating a successful upgrade. - When this call returns, the stream is then ready to send and - receive WebSocket protocol frames and messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, - an HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure. - - The implementation uses fixed size internal storage to - copy the buffer. If the buffer is too large, the error - @ref error::buffer_overflow will be indicated. Applications - that wish to supply larger buffers should wrap the next layer - in a @ref buffered_read_stream and store the buffer contents in - the wrapper before calling accept. - - @param req An object containing the HTTP Upgrade request. - Ownership is not transferred, the implementation will not - access this object from other threads. - - @param buffers Caller provided data that has already been - received on the stream. This must not include the octets - corresponding to the HTTP Upgrade request. The implementation - will copy the caller provided data before the function returns. - - @param decorator A function object which will be called to modify - the HTTP response object delivered by the implementation. This - could be used to set the Server field, subprotocols, or other - application or HTTP specific fields. The object will be called - with this equivalent signature: - @code void decorator( - response_type& res - ); @endcode - - @throws system_error Thrown on failure. - */ - template - void - accept_ex(http::request> const& req, - ConstBufferSequence const& buffers, - ResponseDecorator const& decorator); - - /** Respond to a WebSocket HTTP Upgrade request - - This function is used to synchronously send the HTTP response - to an HTTP request possibly containing a WebSocket Upgrade. - The call blocks until one of the following conditions is true: - - @li The response finishes sending. - - @li An error occurs on the stream. - - This function is implemented in terms of one or more calls to - the next layer's `read_some` and `write_some` functions. - - If the stream receives a valid HTTP WebSocket Upgrade request, - an HTTP response is sent back indicating a successful upgrade. - When this call returns, the stream is then ready to send and - receive WebSocket protocol frames and messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, - an HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure. - - The implementation uses fixed size internal storage to - copy the buffer. If the buffer is too large, the error - @ref error::buffer_overflow will be indicated. Applications - that wish to supply larger buffers should wrap the next layer - in a @ref buffered_read_stream and store the buffer contents in - the wrapper before calling accept. - - @param req An object containing the HTTP Upgrade request. - Ownership is not transferred, the implementation will not - access this object from other threads. - - @param buffers Caller provided data that has already been - received on the stream. This must not include the octets - corresponding to the HTTP Upgrade request. The implementation - will copy the caller provided data before the function returns. - - @param ec Set to indicate what error occurred, if any. - */ - template - void - accept(http::request> const& req, - ConstBufferSequence const& buffers, - error_code& ec); - - /** Respond to a WebSocket HTTP Upgrade request - - This function is used to synchronously send the HTTP response - to an HTTP request possibly containing a WebSocket Upgrade. - The call blocks until one of the following conditions is true: - - @li The response finishes sending. - - @li An error occurs on the stream. - - This function is implemented in terms of one or more calls to - the next layer's `read_some` and `write_some` functions. - - If the stream receives a valid HTTP WebSocket Upgrade request, - an HTTP response is sent back indicating a successful upgrade. - When this call returns, the stream is then ready to send and - receive WebSocket protocol frames and messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, - an HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure. - - The implementation uses fixed size internal storage to - copy the buffer. If the buffer is too large, the error - @ref error::buffer_overflow will be indicated. Applications - that wish to supply larger buffers should wrap the next layer - in a @ref buffered_read_stream and store the buffer contents in - the wrapper before calling accept. - - @param req An object containing the HTTP Upgrade request. - Ownership is not transferred, the implementation will not - access this object from other threads. - - @param buffers Caller provided data that has already been - received on the stream. This must not include the octets - corresponding to the HTTP Upgrade request. The implementation - will copy the caller provided data before the function returns. - - @param decorator A function object which will be called to modify - the HTTP response object delivered by the implementation. This - could be used to set the Server field, subprotocols, or other - application or HTTP specific fields. The object will be called - with this equivalent signature: - @code void decorator( - response_type& res - ); @endcode - - @param ec Set to indicate what error occurred, if any. - */ - template - void - accept_ex(http::request> const& req, - ConstBufferSequence const& buffers, - ResponseDecorator const& decorator, - error_code& ec); - /** Start reading and responding to a WebSocket HTTP Upgrade request. This function is used to asynchronously read an HTTP WebSocket @@ -2233,202 +2027,8 @@ public: this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. */ - template -#if BOOST_BEAST_DOXYGEN - void_or_deduced -#else - async_return_type< - AcceptHandler, void(error_code)> -#endif - async_accept_ex(ResponseDecorator const& decorator, - AcceptHandler&& handler); - - /** Start reading and responding to a WebSocket HTTP Upgrade request. - - This function is used to asynchronously read an HTTP WebSocket - Upgrade request and send the HTTP response. The function call - always returns immediately. The asynchronous operation will - continue until one of the following conditions is true: - - @li The request is received and the response finishes sending. - - @li An error occurs on the stream. - - This operation is implemented in terms of one or more calls to - the next layer's `async_read_some` and `async_write_some` - functions, and is known as a composed operation. The - program must ensure that the stream performs no other - asynchronous operations until this operation completes. - - If the stream receives a valid HTTP WebSocket Upgrade request, - an HTTP response is sent back indicating a successful upgrade. - When the completion handler is invoked, the stream is then - ready to send and receive WebSocket protocol frames and - messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, - an HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure, and - the completion handler will be invoked with a suitable error - code set. - - The implementation uses fixed size internal storage to - receive the request. If the request is too large, the error - @ref error::buffer_overflow will be indicated. Applications - that wish to receive larger requests should first read the - request using their own buffer and a suitable overload of - @ref http::read or @ref http::async_read, then call @ref accept - or @ref async_accept with the request. - - @param buffers Caller provided data that has already been - received on the stream. This may be used for implementations - allowing multiple protocols on the same stream. The - buffered data will first be applied to the handshake, and - then to received WebSocket frames. The implementation will - copy the caller provided data before the function returns. - - @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& ec // 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 BOOST_BEAST_DOXYGEN - void_or_deduced -#else - typename std::enable_if< - ! http::detail::is_header::value, - async_return_type>::type -#endif - async_accept(ConstBufferSequence const& buffers, - AcceptHandler&& handler); - - /** Start reading and responding to a WebSocket HTTP Upgrade request. - - This function is used to asynchronously read an HTTP WebSocket - Upgrade request and send the HTTP response. The function call - always returns immediately. The asynchronous operation will - continue until one of the following conditions is true: - - @li The request is received and the response finishes sending. - - @li An error occurs on the stream. - - This operation is implemented in terms of one or more calls to - the next layer's `async_read_some` and `async_write_some` - functions, and is known as a composed operation. The - program must ensure that the stream performs no other - asynchronous operations until this operation completes. - - If the stream receives a valid HTTP WebSocket Upgrade request, - an HTTP response is sent back indicating a successful upgrade. - When the completion handler is invoked, the stream is then - ready to send and receive WebSocket protocol frames and - messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, - an HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure, and - the completion handler will be invoked with a suitable error - code set. - - The implementation uses fixed size internal storage to - receive the request. If the request is too large, the error - @ref error::buffer_overflow will be indicated. Applications - that wish to receive larger requests should first read the - request using their own buffer and a suitable overload of - @ref http::read or @ref http::async_read, then call @ref accept - or @ref async_accept with the request. - - @param buffers Caller provided data that has already been - received on the stream. This may be used for implementations - allowing multiple protocols on the same stream. The - buffered data will first be applied to the handshake, and - then to received WebSocket frames. The implementation will - copy the caller provided data before the function returns. - - @param decorator A function object which will be called to modify - the HTTP response object delivered by the implementation. This - could be used to set the Server field, subprotocols, or other - application or HTTP specific fields. The object will be called - with this equivalent signature: - @code void decorator( - response_type& res - ); @endcode - - @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& ec // 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 BOOST_BEAST_DOXYGEN - void_or_deduced -#else - typename std::enable_if< - ! http::detail::is_header::value, - async_return_type>::type -#endif - async_accept_ex(ConstBufferSequence const& buffers, - ResponseDecorator const& decorator, - AcceptHandler&& handler); - - /** Start responding to a WebSocket HTTP Upgrade request. - - This function is used to asynchronously send the HTTP response - to an HTTP request possibly containing a WebSocket Upgrade - request. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: - - @li The response finishes sending. - - @li An error occurs on the stream. - - This operation is implemented in terms of one or more calls to - the next layer's `async_write_some` functions, and is known as - a composed operation. The program must ensure that the - stream performs no other operations until this operation - completes. - - If the stream receives a valid HTTP WebSocket Upgrade request, - an HTTP response is sent back indicating a successful upgrade. - When the completion handler is invoked, the stream is then - ready to send and receive WebSocket protocol frames and - messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, - an HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure, and - the completion handler will be invoked with a suitable error - code set. - - @param req An object containing the HTTP Upgrade request. - Ownership is not transferred, the implementation will not access - this object from other threads. - - @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& ec // 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 BOOST_BEAST_DOXYGEN void_or_deduced @@ -2436,93 +2036,26 @@ public: async_return_type< AcceptHandler, void(error_code)> #endif - async_accept(http::request> const& req, - AcceptHandler&& handler); + async_accept_ex( + ResponseDecorator const& decorator, + AcceptHandler&& handler); - /** Start responding to a WebSocket HTTP Upgrade request. + /** Start reading and responding to a WebSocket HTTP Upgrade request. - This function is used to asynchronously send the HTTP response - to an HTTP request possibly containing a WebSocket Upgrade - request. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: + This function is used to asynchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. The asynchronous operation will + continue until one of the following conditions is true: - @li The response finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. This operation is implemented in terms of one or more calls to - the next layer's `async_write_some` functions, and is known as - a composed operation. The program must ensure that the - stream performs no other operations until this operation - completes. - - If the stream receives a valid HTTP WebSocket Upgrade request, - an HTTP response is sent back indicating a successful upgrade. - When the completion handler is invoked, the stream is then - ready to send and receive WebSocket protocol frames and - messages. - If the HTTP Upgrade request is invalid or cannot be satisfied, - an HTTP response is sent indicating the reason and status code - (typically 400, "Bad Request"). This counts as a failure, and - the completion handler will be invoked with a suitable error - code set. - - @param req An object containing the HTTP Upgrade request. - Ownership is not transferred, the implementation will not access - this object from other threads. - - @param decorator A function object which will be called to modify - the HTTP response object delivered by the implementation. This - could be used to set the Server field, subprotocols, or other - application or HTTP specific fields. The object will be called - with this equivalent signature: - @code void decorator( - response_type& res - ); @endcode - - @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& ec // 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 BOOST_BEAST_DOXYGEN - void_or_deduced -#else - async_return_type< - AcceptHandler, void(error_code)> -#endif - async_accept_ex(http::request> const& req, - ResponseDecorator const& decorator, - AcceptHandler&& handler); - - /** Start responding to a WebSocket HTTP Upgrade request. - - This function is used to asynchronously send the HTTP response - to an HTTP request possibly containing a WebSocket Upgrade - request. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: - - @li The response finishes sending. - - @li An error occurs on the stream. - - This operation is implemented in terms of one or more calls to - the next layer's `async_write_some` functions, and is known as - a composed operation. The program must ensure that the - stream performs no other operations until this operation - completes. + the next layer's `async_read_some` and `async_write_some` + functions, and is known as a composed operation. The + program must ensure that the stream performs no other + asynchronous operations until this operation completes. If the stream receives a valid HTTP WebSocket Upgrade request, an HTTP response is sent back indicating a successful upgrade. @@ -2536,15 +2069,12 @@ public: code set. The implementation uses fixed size internal storage to - copy the buffer. If the buffer is too large, the error + receive the request. If the request is too large, the error @ref error::buffer_overflow will be indicated. Applications - that wish to supply larger buffers should wrap the next layer - in a @ref buffered_read_stream and store the buffer contents in - the wrapper before calling accept. - - @param req An object containing the HTTP Upgrade request. - Ownership is not transferred, the implementation will not access - this object from other threads. + that wish to receive larger requests should first read the + request using their own buffer and a suitable overload of + @ref http::read or @ref http::async_read, then call @ref accept + or @ref async_accept with the request. @param buffers Caller provided data that has already been received on the stream. This may be used for implementations @@ -2564,36 +2094,36 @@ public: this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. */ - template + template< + class ConstBufferSequence, + class AcceptHandler> #if BOOST_BEAST_DOXYGEN void_or_deduced #else - async_return_type< - AcceptHandler, void(error_code)> + typename std::enable_if< + ! http::detail::is_header::value, + async_return_type>::type #endif - async_accept(http::request> const& req, - ConstBufferSequence const& buffers, - AcceptHandler&& handler); + async_accept( + ConstBufferSequence const& buffers, + AcceptHandler&& handler); - /** Start responding to a WebSocket HTTP Upgrade request. + /** Start reading and responding to a WebSocket HTTP Upgrade request. - This function is used to asynchronously send the HTTP response - to an HTTP request possibly containing a WebSocket Upgrade - request. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: + This function is used to asynchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. The asynchronous operation will + continue until one of the following conditions is true: - @li The response finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. This operation is implemented in terms of one or more calls to - the next layer's `async_write_some` functions, and is known as - a composed operation. The program must ensure that the - stream performs no other operations until this operation - completes. + the next layer's `async_read_some` and `async_write_some` + functions, and is known as a composed operation. The + program must ensure that the stream performs no other + asynchronous operations until this operation completes. If the stream receives a valid HTTP WebSocket Upgrade request, an HTTP response is sent back indicating a successful upgrade. @@ -2607,15 +2137,12 @@ public: code set. The implementation uses fixed size internal storage to - copy the buffer. If the buffer is too large, the error + receive the request. If the request is too large, the error @ref error::buffer_overflow will be indicated. Applications - that wish to supply larger buffers should wrap the next layer - in a @ref buffered_read_stream and store the buffer contents in - the wrapper before calling accept. - - @param req An object containing the HTTP Upgrade request. - Ownership is not transferred, the implementation will not access - this object from other threads. + that wish to receive larger requests should first read the + request using their own buffer and a suitable overload of + @ref http::read or @ref http::async_read, then call @ref accept + or @ref async_accept with the request. @param buffers Caller provided data that has already been received on the stream. This may be used for implementations @@ -2645,12 +2172,138 @@ public: manner equivalent to using `boost::asio::io_service::post`. */ template< - class Body, class Allocator, class ConstBufferSequence, class ResponseDecorator, class AcceptHandler> #if BOOST_BEAST_DOXYGEN void_or_deduced +#else + typename std::enable_if< + ! http::detail::is_header::value, + async_return_type>::type +#endif + async_accept_ex( + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler); + + /** Start responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade + request. The function call always returns immediately. The + asynchronous operation will continue until one of the following + conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This operation is implemented in terms of one or more calls to + the next layer's `async_write_some` functions, and is known as + a composed operation. The program must ensure that the + stream performs no other operations until this operation + completes. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not access + this object from other threads. + + @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& ec // 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< + class Body, class Allocator, + class AcceptHandler> +#if BOOST_BEAST_DOXYGEN + void_or_deduced +#else + async_return_type< + AcceptHandler, void(error_code)> +#endif + async_accept( + http::request> const& req, + AcceptHandler&& handler); + + /** Start responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously send the HTTP response + to an HTTP request possibly containing a WebSocket Upgrade + request. The function call always returns immediately. The + asynchronous operation will continue until one of the following + conditions is true: + + @li The response finishes sending. + + @li An error occurs on the stream. + + This operation is implemented in terms of one or more calls to + the next layer's `async_write_some` functions, and is known as + a composed operation. The program must ensure that the + stream performs no other operations until this operation + completes. + + If the stream receives a valid HTTP WebSocket Upgrade request, + an HTTP response is sent back indicating a successful upgrade. + When the completion handler is invoked, the stream is then + ready to send and receive WebSocket protocol frames and + messages. + If the HTTP Upgrade request is invalid or cannot be satisfied, + an HTTP response is sent indicating the reason and status code + (typically 400, "Bad Request"). This counts as a failure, and + the completion handler will be invoked with a suitable error + code set. + + @param req An object containing the HTTP Upgrade request. + Ownership is not transferred, the implementation will not access + this object from other threads. + + @param decorator A function object which will be called to modify + the HTTP response object delivered by the implementation. This + could be used to set the Server field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + response_type& res + ); @endcode + + @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& ec // 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< + class Body, class Allocator, + class ResponseDecorator, + class AcceptHandler> +#if BOOST_BEAST_DOXYGEN + void_or_deduced #else async_return_type< AcceptHandler, void(error_code)> @@ -2658,7 +2311,6 @@ public: async_accept_ex( http::request> const& req, - ConstBufferSequence const& buffers, ResponseDecorator const& decorator, AcceptHandler&& handler); diff --git a/test/beast/websocket/stream.cpp b/test/beast/websocket/stream.cpp index f4229de1..a2f15d1f 100644 --- a/test/beast/websocket/stream.cpp +++ b/test/beast/websocket/stream.cpp @@ -17,8 +17,6 @@ #include #include #include -#include -#include #include #include #include @@ -409,15 +407,6 @@ public: ws.accept(req); } - template - void - accept(stream& ws, - http::request const& req, - Buffers const& buffers) const - { - ws.accept(req, buffers); - } - template void accept_ex(stream& ws, @@ -612,18 +601,6 @@ public: throw system_error{ec}; } - template - void - accept(stream& ws, - http::request const& req, - Buffers const& buffers) const - { - error_code ec; - ws.async_accept(req, buffers, yield_[ec]); - if(ec) - throw system_error{ec}; - } - template void @@ -877,6 +854,13 @@ public: } }; + auto const big = [] + { + std::string s; + s += "X1: " + std::string(2000, '*') + "\r\n"; + return s; + }(); + // request in stream doTestLoop([&](test::stream& ts) { @@ -894,6 +878,32 @@ public: // VFALCO validate contents of ws.next_layer().str? }); + // request in stream, oversized + { + stream ws{ios_, + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n"}; + error_code ec = test::error::fail_error; + try + { + c.accept(ws); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + // VFALCO Its the http error category... + BEAST_EXPECTS( + se.code() == http::error::buffer_overflow, + se.code().message()); + } + } + // request in stream, decorator doTestLoop([&](test::stream& ts) { @@ -912,6 +922,32 @@ public: BEAST_EXPECT(called); }); + // request in stream, decorator, oversized + { + stream ws{ios_, + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n"}; + try + { + bool called = false; + c.accept_ex(ws, res_decorator{called}); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + // VFALCO Its the http error category... + BEAST_EXPECTS( + se.code() == http::error::buffer_overflow, + se.code().message()); + } + } + // request in buffers doTestLoop([&](test::stream& ts) { @@ -927,6 +963,31 @@ public: )); }); + // request in buffers, oversize + { + stream ws{ios_}; + try + { + c.accept(ws, boost::asio::buffer( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n" + )); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == error::buffer_overflow, + se.code().message()); + } + } + // request in buffers, decorator doTestLoop([&](test::stream& ts) { @@ -944,6 +1005,32 @@ public: BEAST_EXPECT(called); }); + // request in buffers, decorator, oversized + { + stream ws{ios_}; + try + { + bool called = false; + c.accept_ex(ws, boost::asio::buffer( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n"), + res_decorator{called}); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == error::buffer_overflow, + se.code().message()); + } + } + // request in buffers and stream doTestLoop([&](test::stream& ts) { @@ -962,6 +1049,31 @@ public: // VFALCO validate contents of ws.next_layer().str? }); + // request in buffers and stream, oversized + { + stream ws{ios_, + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n"}; + try + { + c.accept(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + )); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == http::error::buffer_overflow, + se.code().message()); + } + } + // request in buffers and stream, decorator doTestLoop([&](test::stream& ts) { @@ -981,6 +1093,32 @@ public: BEAST_EXPECT(called); }); + // request in buffers and stream, decorator, oversize + { + stream ws{ios_, + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n"}; + try + { + bool called = false; + c.accept_ex(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n"), + res_decorator{called}); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == http::error::buffer_overflow, + se.code().message()); + } + } + // request in message doTestLoop([&](test::stream& ts) { @@ -1016,65 +1154,6 @@ public: BEAST_EXPECT(called); }); - // request in message, close frame in buffers - doTestLoop([&](test::stream& ts) - { - stream ws{ts}; - request_type req; - req.method(http::verb::get); - req.target("/"); - req.version = 11; - req.insert(http::field::host, "localhost"); - req.insert(http::field::upgrade, "websocket"); - req.insert(http::field::connection, "upgrade"); - req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); - req.insert(http::field::sec_websocket_version, "13"); - c.accept(ws, req, cbuf( - 0x88, 0x82, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x17)); - try - { - static_buffer<1> b; - c.read(ws, b); - fail("success", __FILE__, __LINE__); - } - catch(system_error const& e) - { - if(e.code() != websocket::error::closed) - throw; - } - }); - - // request in message, close frame in buffers, decorator - doTestLoop([&](test::stream& ts) - { - stream ws{ts}; - request_type req; - req.method(http::verb::get); - req.target("/"); - req.version = 11; - req.insert(http::field::host, "localhost"); - req.insert(http::field::upgrade, "websocket"); - req.insert(http::field::connection, "upgrade"); - req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); - req.insert(http::field::sec_websocket_version, "13"); - bool called = false; - c.accept_ex(ws, req, cbuf( - 0x88, 0x82, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x17), - res_decorator{called}); - BEAST_EXPECT(called); - try - { - static_buffer<1> b; - c.read(ws, b); - fail("success", __FILE__, __LINE__); - } - catch(system_error const& e) - { - if(e.code() != websocket::error::closed) - throw; - } - }); - // request in message, close frame in stream doTestLoop([&](test::stream& ts) { @@ -1103,35 +1182,6 @@ public: } }); - // request in message, close frame in stream and buffers - doTestLoop([&](test::stream& ts) - { - stream ws{ts}; - request_type req; - req.method(http::verb::get); - req.target("/"); - req.version = 11; - req.insert(http::field::host, "localhost"); - req.insert(http::field::upgrade, "websocket"); - req.insert(http::field::connection, "upgrade"); - req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); - req.insert(http::field::sec_websocket_version, "13"); - ts.str("xff\xff\xfc\x17"); - c.accept(ws, req, cbuf( - 0x88, 0x82, 0xff, 0xff)); - try - { - static_buffer<1> b; - c.read(ws, b); - fail("success", __FILE__, __LINE__); - } - catch(system_error const& e) - { - if(e.code() != websocket::error::closed) - throw; - } - }); - // failed handshake (missing Sec-WebSocket-Key) doTestLoop([&](test::stream& ts) { @@ -1158,6 +1208,24 @@ public: throw; } }); + + // Closed by client + { + stream ws{ios_}; + ws.next_layer().remote().close(); + try + { + c.accept(ws); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if(! BEAST_EXPECTS( + e.code() == error::closed, + e.code().message())) + throw; + } + } } void @@ -2059,14 +2127,15 @@ public: stream>::value); log << "sizeof(websocket::stream) == " << - sizeof(websocket::stream) << std::endl; + sizeof(websocket::stream) << std::endl; + + testAccept(); permessage_deflate pmd; pmd.client_enable = false; pmd.server_enable = false; testOptions(); - testAccept(); testHandshake(); testBadHandshakes(); testBadResponses();