From fa1f4c7d5625d4cef003280a09e45e1e6a036908 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 25 Apr 2017 09:35:22 -0700 Subject: [PATCH] Refactor websocket decorators (API Change): fix #80, #212, fix #303, fix #314, fix #317 websocket::stream now provides the following families of functions for performing handshakes: When operating in the server role: * stream::accept * stream::accept_ex * stream::async_accept * stream::async_accept_ex When operating in the client role: * stream::handshake * stream::handshake_ex * stream::async_handshake * stream::async_handshake_ex Member functions ending with "_ex" allow an additional RequestDecorator parameter (for the accept family of functions) or ResponseDecorator parameter (for the handshake family of functions). The decorator is called to optionally modify the contents of the HTTP request or HTTP response object generated by the implementation, before the message is sent. This permits callers to set the User-Agent or Server fields, add or modify HTTP fields related to subprotocols, or perform any required transformation of the HTTP message for application-specific needs. The handshake() family of functions now have an additional set of overloads accepting a parameter of type response_type&, allowing the caller to receive the HTTP Response to the Upgrade handshake. This permits inspection of the response to handle things like subprotocols, authentication, or other application-specific needs. The new implementation does not require any state to be stored in the stream object. Therefore, websocket::stream objects are now smaller in size. The overload of set_option for setting a decorator on the stream is removed. The only way to set decorators now is with a suitable overload of accept or handshake. --- CHANGELOG.md | 1 + doc/quickref.xml | 1 - examples/websocket_async_echo_server.hpp | 29 +- examples/websocket_sync_echo_server.hpp | 29 +- extras/beast/test/string_ostream.hpp | 3 +- include/beast/websocket/detail/decorator.hpp | 166 --- .../beast/websocket/detail/stream_base.hpp | 8 +- .../beast/websocket/detail/type_traits.hpp | 32 + include/beast/websocket/impl/accept.ipp | 350 ++++- include/beast/websocket/impl/handshake.ipp | 230 ++- include/beast/websocket/impl/stream.ipp | 86 +- include/beast/websocket/option.hpp | 51 - include/beast/websocket/stream.hpp | 1280 ++++++++++++++++- test/websocket/stream.cpp | 446 +++++- .../websocket/websocket_async_echo_server.hpp | 29 +- test/websocket/websocket_sync_echo_server.hpp | 29 +- 16 files changed, 2255 insertions(+), 515 deletions(-) delete mode 100644 include/beast/websocket/detail/decorator.hpp create mode 100644 include/beast/websocket/detail/type_traits.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index dd4bedf9..40698038 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ API Changes: * Provide websocket::stream accept() overloads +* Refactor websocket decorators -------------------------------------------------------------------------------- diff --git a/doc/quickref.xml b/doc/quickref.xml index 94a1a3c3..a1afe585 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -112,7 +112,6 @@ Options auto_fragment - decorate keep_alive message_type permessage_deflate diff --git a/examples/websocket_async_echo_server.hpp b/examples/websocket_async_echo_server.hpp index bd0c8481..e6ed81ef 100644 --- a/examples/websocket_async_echo_server.hpp +++ b/examples/websocket_async_echo_server.hpp @@ -38,25 +38,6 @@ public: using endpoint_type = boost::asio::ip::tcp::endpoint; private: - struct identity - { - template - void - operator()(beast::http::message< - true, Body, Fields>& req) const - { - req.fields.replace("User-Agent", "async_echo_client"); - } - - template - void - operator()(beast::http::message< - false, Body, Fields>& resp) const - { - resp.fields.replace("Server", "async_echo_server"); - } - }; - /** A container of type-erased option setters. */ template @@ -159,8 +140,6 @@ public: , acceptor_(ios_) , work_(ios_) { - opts_.set_option( - beast::websocket::decorate(identity{})); thread_.reserve(threads); for(std::size_t i = 0; i < threads; ++i) thread_.emplace_back( @@ -282,7 +261,13 @@ private: void run() { auto& d = *d_; - d.ws.async_accept(std::move(*this)); + d.ws.async_accept_ex( + [](beast::websocket::response_type& res) + { + res.fields.insert( + "Server", "async_echo_server"); + }, + std::move(*this)); } void operator()(error_code ec, std::size_t) diff --git a/examples/websocket_sync_echo_server.hpp b/examples/websocket_sync_echo_server.hpp index 1f41b52c..ab8bd5d5 100644 --- a/examples/websocket_sync_echo_server.hpp +++ b/examples/websocket_sync_echo_server.hpp @@ -38,25 +38,6 @@ public: using socket_type = boost::asio::ip::tcp::socket; private: - struct identity - { - template - void - operator()(beast::http::message< - true, Body, Fields>& req) const - { - req.fields.replace("User-Agent", "sync_echo_client"); - } - - template - void - operator()(beast::http::message< - false, Body, Fields>& resp) const - { - resp.fields.replace("Server", "sync_echo_server"); - } - }; - /** A container of type-erased option setters. */ template @@ -151,8 +132,6 @@ public: , sock_(ios_) , acceptor_(ios_) { - opts_.set_option( - beast::websocket::decorate(identity{})); } /** Destructor. @@ -293,7 +272,13 @@ private: socket_type> ws{std::move(sock)}; opts_.set_options(ws); error_code ec; - ws.accept(ec); + ws.accept_ex( + [](beast::websocket::response_type& res) + { + res.fields.insert( + "Server", "sync_echo_server"); + }, + ec); if(ec) { fail("accept", ec, id, ep); diff --git a/extras/beast/test/string_ostream.hpp b/extras/beast/test/string_ostream.hpp index 52a9bf5d..bc5b7896 100644 --- a/extras/beast/test/string_ostream.hpp +++ b/extras/beast/test/string_ostream.hpp @@ -54,6 +54,7 @@ public: read_some(MutableBufferSequence const& buffers, error_code& ec) { + ec = boost::asio::error::eof; return 0; } @@ -66,7 +67,7 @@ public: async_completion completion{handler}; ios_.post(bind_handler(completion.handler, - error_code{}, 0)); + boost::asio::error::eof, 0)); return completion.result.get(); } diff --git a/include/beast/websocket/detail/decorator.hpp b/include/beast/websocket/detail/decorator.hpp deleted file mode 100644 index bb40e58f..00000000 --- a/include/beast/websocket/detail/decorator.hpp +++ /dev/null @@ -1,166 +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_WEBSOCKET_DETAIL_DECORATOR_HPP -#define BEAST_WEBSOCKET_DETAIL_DECORATOR_HPP - -#include -#include -#include -#include -#include - -namespace beast { -namespace websocket { -namespace detail { - -using request_type = http::request_header; - -using response_type = http::response_header; - -struct abstract_decorator -{ - virtual - ~abstract_decorator() = default; - - virtual - void - operator()(request_type& req) const = 0; - - virtual - void - operator()(response_type& res) const = 0; -}; - -template -class decorator : public abstract_decorator -{ - F f_; - - class call_req_possible - { - template().operator()( - std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - public: - using type = decltype(check(0)); - }; - - class call_res_possible - { - template().operator()( - std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - public: - using type = decltype(check(0)); - }; - -public: - decorator(F&& t) - : f_(std::move(t)) - { - } - - decorator(F const& t) - : f_(t) - { - } - - void - operator()(request_type& req) const override - { - (*this)(req, typename call_req_possible::type{}); - } - - void - operator()(response_type& res) const override - { - (*this)(res, typename call_res_possible::type{}); - } - -private: - void - operator()(request_type& req, std::true_type) const - { - f_(req); - } - - void - operator()(request_type& req, std::false_type) const - { - req.fields.replace("User-Agent", - std::string{"Beast/"} + BEAST_VERSION_STRING); - } - - void - operator()(response_type& res, std::true_type) const - { - f_(res); - } - - void - operator()(response_type& res, std::false_type) const - { - res.fields.replace("Server", - std::string{"Beast/"} + BEAST_VERSION_STRING); - } -}; - -class decorator_type -{ - std::shared_ptr p_; - -public: - decorator_type() = delete; - decorator_type(decorator_type&&) = default; - decorator_type(decorator_type const&) = default; - decorator_type& operator=(decorator_type&&) = default; - decorator_type& operator=(decorator_type const&) = default; - - template::type, - decorator_type>::value>> - decorator_type(F&& f) - : p_(std::make_shared>( - std::forward(f))) - { - BOOST_ASSERT(p_); - } - - void - operator()(request_type& req) - { - (*p_)(req); - BOOST_ASSERT(p_); - } - - void - operator()(response_type& res) - { - (*p_)(res); - BOOST_ASSERT(p_); - } -}; - -struct default_decorator -{ -}; - -} // detail -} // websocket -} // beast - -#endif diff --git a/include/beast/websocket/detail/stream_base.hpp b/include/beast/websocket/detail/stream_base.hpp index 5b5aaa99..66ede62e 100644 --- a/include/beast/websocket/detail/stream_base.hpp +++ b/include/beast/websocket/detail/stream_base.hpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -50,7 +49,6 @@ protected: struct op {}; detail::maskgen maskgen_; // source of mask keys - decorator_type d_; // adorns http messages bool keep_alive_ = false; // close on failed upgrade std::size_t rd_msg_max_ = 16 * 1024 * 1024; // max message size @@ -153,16 +151,12 @@ protected: // Offer for clients, negotiated result for servers pmd_offer pmd_config_; + stream_base() = default; stream_base(stream_base&&) = default; stream_base(stream_base const&) = delete; stream_base& operator=(stream_base&&) = default; stream_base& operator=(stream_base const&) = delete; - stream_base() - : d_(detail::default_decorator{}) - { - } - template void open(role_type role); diff --git a/include/beast/websocket/detail/type_traits.hpp b/include/beast/websocket/detail/type_traits.hpp new file mode 100644 index 00000000..58be3caf --- /dev/null +++ b/include/beast/websocket/detail/type_traits.hpp @@ -0,0 +1,32 @@ +// +// 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_WEBSOCKET_DETAIL_TYPE_TRAITS_HPP +#define BEAST_WEBSOCKET_DETAIL_TYPE_TRAITS_HPP + +#include +#include + +namespace beast { +namespace websocket { +namespace detail { + +template +using is_RequestDecorator = + typename beast::detail::is_call_possible::type; + +template +using is_ResponseDecorator = + typename beast::detail::is_call_possible::type; + +} // detail +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/impl/accept.ipp b/include/beast/websocket/impl/accept.ipp index 489440b7..2543d0f7 100644 --- a/include/beast/websocket/impl/accept.ipp +++ b/include/beast/websocket/impl/accept.ipp @@ -8,6 +8,7 @@ #ifndef BEAST_WEBSOCKET_IMPL_ACCEPT_IPP #define BEAST_WEBSOCKET_IMPL_ACCEPT_IPP +#include #include #include #include @@ -38,23 +39,27 @@ class stream::response_op http::response_header res; int state = 0; - template + template data(Handler&, stream& ws_, http::header const& req, - bool cont_) + Decorator const& decorator, + bool cont_) : cont(cont_) , ws(ws_) - , res(ws_.build_response(req)) + , res(ws_.build_response(req, decorator)) { } - template + template data(Handler&, stream& ws_, http::header const& req, - Buffers const& buffers, bool cont_) + Buffers const& buffers, + Decorator const& decorator, + bool cont_) : cont(cont_) , ws(ws_) - , res(ws_.build_response(req)) + , res(ws_.build_response(req, decorator)) { using boost::asio::buffer_copy; using boost::asio::buffer_size; @@ -155,26 +160,32 @@ operator()(error_code ec, bool again) // read and respond to an upgrade request // template -template +template class stream::accept_op { struct data { bool cont; stream& ws; + Decorator decorator; http::header_parser p; int state = 0; - // VFALCO These lines are formatted to work around a codecov defect - data(Handler& handler, stream& ws_) : cont(beast_asio_helpers::is_continuation(handler)) - , ws(ws_) {} + data(Handler& handler, stream& ws_, + Decorator const& decorator_) + : cont(beast_asio_helpers::is_continuation(handler)) + , ws(ws_) + , decorator(decorator_) + { + } template data(Handler& handler, stream& ws_, - Buffers const& buffers) - : cont(beast_asio_helpers:: - is_continuation(handler)) + Buffers const& buffers, + Decorator const& decorator_) + : cont(beast_asio_helpers::is_continuation(handler)) , ws(ws_) + , decorator(decorator_) { using boost::asio::buffer_copy; using boost::asio::buffer_size; @@ -235,9 +246,9 @@ public: }; template -template +template void -stream::accept_op:: +stream::accept_op:: operator()(error_code ec, std::size_t bytes_used, bool again) { @@ -263,10 +274,19 @@ operator()(error_code ec, // moved to the stack before releasing // the handler. auto& ws = d.ws; - auto m = d.p.release(); + auto const req = d.p.release(); + auto const decorator = d.decorator; + #if 1 response_op{ d_.release_handler(), - ws, std::move(m), true}; + ws, req, decorator, true}; + #else + // VFALCO This *should* work but breaks + // coroutine invariants in the unit test. + // Also it calls reset() when it shouldn't. + ws.async_accept_ex( + req, decorator, d_.release_handler()); + #endif return; } } @@ -289,6 +309,23 @@ accept() throw system_error{ec}; } +template +template +void +stream:: +accept_ex(ResponseDecorator const& decorator) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + error_code ec; + accept_ex(decorator, ec); + if(ec) + throw system_error{ec}; +} + template void stream:: @@ -297,7 +334,22 @@ accept(error_code& ec) static_assert(is_SyncStream::value, "SyncStream requirements not met"); reset(); - do_accept(ec); + do_accept(&default_decorate_res, ec); +} + +template +template +void +stream:: +accept_ex(ResponseDecorator const& decorator, error_code& ec) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + reset(); + do_accept(decorator, ec); } template @@ -317,6 +369,28 @@ accept(ConstBufferSequence const& buffers) throw system_error{ec}; } +template +template< + class ConstBufferSequence, class ResponseDecorator> +void +stream:: +accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const &decorator) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + error_code ec; + accept_ex(buffers, decorator, ec); + if(ec) + throw system_error{ec}; +} + template template void @@ -334,7 +408,32 @@ accept(ConstBufferSequence const& buffers, error_code& ec) stream_.buffer().commit(buffer_copy( stream_.buffer().prepare( buffer_size(buffers)), buffers)); - do_accept(ec); + do_accept(&default_decorate_res, ec); +} + +template +template< + class ConstBufferSequence, class ResponseDecorator> +void +stream:: +accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, error_code& ec) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + reset(); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + stream_.buffer().commit(buffer_copy( + stream_.buffer().prepare( + buffer_size(buffers)), buffers)); + do_accept(decorator, ec); } template @@ -351,6 +450,24 @@ accept(http::header const& req) throw system_error{ec}; } +template +template +void +stream:: +accept_ex(http::header const& req, + ResponseDecorator const& decorator) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + error_code ec; + accept_ex(req, decorator, ec); + if(ec) + throw system_error{ec}; +} + template template void @@ -361,15 +478,30 @@ accept(http::header const& req, static_assert(is_SyncStream::value, "SyncStream requirements not met"); reset(); - do_accept(req, ec); + do_accept(req, &default_decorate_res, ec); +} + +template +template +void +stream:: +accept_ex(http::header const& req, + ResponseDecorator const& decorator, error_code& ec) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + reset(); + do_accept(req, decorator, ec); } template template void stream:: -accept( - http::header const& req, +accept(http::header const& req, ConstBufferSequence const& buffers) { static_assert(is_SyncStream::value, @@ -383,14 +515,35 @@ accept( throw system_error{ec}; } +template +template +void +stream:: +accept_ex(http::header const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(is_ConstBufferSequence< + 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) + throw system_error{ec}; +} + template template void stream:: -accept( - http::header const& req, - ConstBufferSequence const& buffers, - error_code& ec) +accept(http::header const& req, + ConstBufferSequence const& buffers, error_code& ec) { static_assert(is_SyncStream::value, "SyncStream requirements not met"); @@ -403,7 +556,34 @@ accept( stream_.buffer().commit(buffer_copy( stream_.buffer().prepare( buffer_size(buffers)), buffers)); - do_accept(req, ec); + do_accept(req, &default_decorate_res, ec); +} + +template +template +void +stream:: +accept_ex(http::header const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + error_code& ec) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(is_ConstBufferSequence< + 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; + stream_.buffer().commit(buffer_copy( + stream_.buffer().prepare( + buffer_size(buffers)), buffers)); + do_accept(req, decorator, ec); } //------------------------------------------------------------------------------ @@ -420,8 +600,30 @@ async_accept(AcceptHandler&& handler) beast::async_completion completion{handler}; reset(); - accept_op{ - completion.handler, *this}; + accept_op{completion.handler, + *this, &default_decorate_res}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_accept_ex(ResponseDecorator const& decorator, + AcceptHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + beast::async_completion completion{handler}; + reset(); + accept_op{ + completion.handler, *this, decorator}; return completion.result.get(); } @@ -441,8 +643,35 @@ async_accept(ConstBufferSequence const& buffers, beast::async_completion completion{handler}; reset(); - accept_op{ - completion.handler, *this, buffers}; + accept_op{completion.handler, + *this, buffers, &default_decorate_res}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_accept_ex(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements requirements not met"); + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + beast::async_completion completion{handler}; + reset(); + accept_op{ + completion.handler, *this, buffers, decorator}; return completion.result.get(); } @@ -461,11 +690,35 @@ async_accept(http::header const& req, reset(); response_op{ completion.handler, *this, req, - beast_asio_helpers:: + &default_decorate_res, beast_asio_helpers:: is_continuation(completion.handler)}; return completion.result.get(); } +template +template +typename async_completion::result_type +stream:: +async_accept_ex(http::header const& req, + ResponseDecorator const& decorator, AcceptHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + beast::async_completion completion{handler}; + reset(); + response_op{ + completion.handler, *this, req, decorator, + beast_asio_helpers::is_continuation( + completion.handler)}; + return completion.result.get(); +} + template template @@ -486,8 +739,37 @@ async_accept(http::header const& req, reset(); response_op{ completion.handler, *this, req, buffers, - beast_asio_helpers:: - is_continuation(completion.handler)}; + &default_decorate_res, beast_asio_helpers:: + is_continuation(completion.handler)}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_accept_ex(http::header const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements requirements not met"); + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + static_assert(detail::is_ResponseDecorator< + ResponseDecorator>::value, + "ResponseDecorator requirements not met"); + beast::async_completion completion{handler}; + reset(); + response_op{ + completion.handler, *this, req, buffers, decorator, + beast_asio_helpers::is_continuation( + completion.handler)}; return completion.result.get(); } diff --git a/include/beast/websocket/impl/handshake.ipp b/include/beast/websocket/impl/handshake.ipp index b788e4ed..07569b6c 100644 --- a/include/beast/websocket/impl/handshake.ipp +++ b/include/beast/websocket/impl/handshake.ipp @@ -8,6 +8,7 @@ #ifndef BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP #define BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP +#include #include #include #include @@ -33,18 +34,24 @@ class stream::handshake_op { bool cont; stream& ws; + response_type* res_p; std::string key; - http::request_header req; - http::response res; + request_type req; + response_type res; int state = 0; + template data(Handler& handler, stream& ws_, - boost::string_ref const& host, - boost::string_ref const& resource) + response_type* res_p_, + boost::string_ref const& host, + boost::string_ref const& resource, + Decorator const& decorator) : cont(beast_asio_helpers:: is_continuation(handler)) , ws(ws_) - , req(ws.build_request(host, resource, key)) + , res_p(res_p_) + , req(ws.build_request(key, + host, resource, decorator)) { ws.reset(); } @@ -117,10 +124,14 @@ operator()(error_code ec, bool again) d.state = 1; // VFALCO Do we need the ability to move // a message on the async_write? + // pmd_read( d.ws.pmd_config_, d.req.fields); http::async_write(d.ws.stream_, d.req, std::move(*this)); + // TODO We don't need d.req now. Figure + // out a way to make it a parameter instead + // of a state variable to reduce footprint. return; } @@ -143,24 +154,94 @@ operator()(error_code ec, bool again) } } } + if(d.res_p) + swap(d.res, *d.res_p); d_.invoke(ec); } template template -typename async_completion< - HandshakeHandler, void(error_code)>::result_type +typename async_completion::result_type stream:: async_handshake(boost::string_ref const& host, - boost::string_ref const& resource, HandshakeHandler&& handler) + boost::string_ref const& resource, + HandshakeHandler&& handler) { static_assert(is_AsyncStream::value, "AsyncStream requirements not met"); - beast::async_completion< - HandshakeHandler, void(error_code) - > completion{handler}; + beast::async_completion completion{handler}; handshake_op{ - completion.handler, *this, host, resource}; + completion.handler, *this, nullptr, + host, resource, &default_decorate_req}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_handshake(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + HandshakeHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements not met"); + beast::async_completion completion{handler}; + handshake_op{ + completion.handler, *this, &res, + host, resource, &default_decorate_req}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_handshake_ex(boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + HandshakeHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + beast::async_completion completion{handler}; + handshake_op{ + completion.handler, *this, nullptr, + host, resource, decorator}; + return completion.result.get(); +} + +template +template +typename async_completion::result_type +stream:: +async_handshake_ex(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + HandshakeHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + beast::async_completion completion{handler}; + handshake_op{ + completion.handler, *this, &res, + host, resource, decorator}; return completion.result.get(); } @@ -173,7 +254,62 @@ handshake(boost::string_ref const& host, static_assert(is_SyncStream::value, "SyncStream requirements not met"); error_code ec; - handshake(host, resource, ec); + handshake( + host, resource, ec); + if(ec) + throw system_error{ec}; +} + +template +void +stream:: +handshake(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + error_code ec; + handshake(res, host, resource, ec); + if(ec) + throw system_error{ec}; +} + +template +template +void +stream:: +handshake_ex(boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + error_code ec; + handshake_ex(host, resource, decorator, ec); + if(ec) + throw system_error{ec}; +} + +template +template +void +stream:: +handshake_ex(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + error_code ec; + handshake_ex(res, host, resource, decorator, ec); if(ec) throw system_error{ec}; } @@ -186,21 +322,59 @@ handshake(boost::string_ref const& host, { static_assert(is_SyncStream::value, "SyncStream requirements not met"); - reset(); - std::string key; - { - auto const req = - build_request(host, resource, key); - pmd_read(pmd_config_, req.fields); - http::write(stream_, req, ec); - } - if(ec) - return; - http::response res; - http::read(next_layer(), stream_.buffer(), res, ec); - if(ec) - return; - do_response(res, key, ec); + do_handshake(nullptr, + host, resource, &default_decorate_req, ec); +} + +template +void +stream:: +handshake(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + error_code& ec) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + do_handshake(&res, + host, resource, &default_decorate_req, ec); +} + +template +template +void +stream:: +handshake_ex(boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + error_code& ec) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + do_handshake(nullptr, + host, resource, decorator, ec); +} + +template +template +void +stream:: +handshake_ex(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + error_code& ec) +{ + static_assert(is_SyncStream::value, + "SyncStream requirements not met"); + static_assert(detail::is_RequestDecorator< + RequestDecorator>::value, + "RequestDecorator requirements not met"); + do_handshake(&res, + host, resource, decorator, ec); } //------------------------------------------------------------------------------ diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp index 8bba955d..717e9aef 100644 --- a/include/beast/websocket/impl/stream.ipp +++ b/include/beast/websocket/impl/stream.ipp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -83,9 +84,11 @@ reset() } template +template void stream:: -do_accept(error_code& ec) +do_accept( + Decorator const& decorator, error_code& ec) { http::header_parser p; auto const bytes_used = http::read_some( @@ -94,17 +97,17 @@ do_accept(error_code& ec) return; BOOST_ASSERT(p.got_header()); stream_.buffer().consume(bytes_used); - do_accept(p.get(), ec); + do_accept(p.get(), decorator, ec); } template -template +template void stream:: do_accept(http::header const& req, - error_code& ec) + Decorator const& decorator, error_code& ec) { - auto const res = build_response(req); + auto const res = build_response(req, decorator); http::write(stream_, res, ec); if(ec) return; @@ -120,12 +123,44 @@ do_accept(http::header const& req, } template -http::request_header +template +void stream:: -build_request(boost::string_ref const& host, - boost::string_ref const& resource, std::string& key) +do_handshake(response_type* res_p, + boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + error_code& ec) { - http::request_header req; + response_type res; + reset(); + std::string key; + { + auto const req = build_request( + key, host, resource, decorator); + pmd_read(pmd_config_, req.fields); + http::write(stream_, req, ec); + } + if(ec) + return; + http::read(next_layer(), stream_.buffer(), res, ec); + if(ec) + return; + do_response(res, key, ec); + if(res_p) + swap(res, *res_p); +} + +template +template +request_type +stream:: +build_request(std::string& key, + boost::string_ref const& host, + boost::string_ref const& resource, + Decorator const& decorator) +{ + request_type req; req.url = { resource.data(), resource.size() }; req.version = 11; req.method = "GET"; @@ -150,28 +185,42 @@ build_request(boost::string_ref const& host, detail::pmd_write( req.fields, config); } - d_(req); + decorator(req); + if(! req.fields.exists("User-Agent")) + req.fields.insert("User-Agent", + std::string("Beast/") + BEAST_VERSION_STRING); return req; } template -http::response_header +template +response_type stream:: -build_response(http::request_header const& req) +build_response(request_type const& req, + Decorator const& decorator) { + auto const decorate = + [&decorator](response_type& res) + { + decorator(res); + if(! res.fields.exists("Server")) + res.fields.insert("Server", + std::string("Beast/") + + BEAST_VERSION_STRING); + }; auto err = [&](std::string const& text) { - http::response res; + response_type res; res.status = 400; res.reason = http::reason_string(res.status); res.version = req.version; res.body = text; - d_(res); prepare(res, (is_keep_alive(req) && keep_alive_) ? http::connection::keep_alive : http::connection::close); + decorate(res); return res; }; if(req.version < 11) @@ -193,20 +242,20 @@ build_response(http::request_header const& req) return err("Missing Sec-WebSocket-Version"); if(version != "13") { - http::response res; + response_type res; res.status = 426; res.reason = http::reason_string(res.status); res.version = req.version; res.fields.insert("Sec-WebSocket-Version", "13"); - d_(res); prepare(res, (is_keep_alive(req) && keep_alive_) ? http::connection::keep_alive : http::connection::close); + decorate(res); return res; } } - http::response_header res; + response_type res; { detail::pmd_offer offer; detail::pmd_offer unused; @@ -225,8 +274,7 @@ build_response(http::request_header const& req) res.fields.insert("Sec-WebSocket-Accept", detail::make_sec_ws_accept(key)); } - res.fields.replace("Server", "Beast.WebSocket"); - d_(res); + decorate(res); return res; } diff --git a/include/beast/websocket/option.hpp b/include/beast/websocket/option.hpp index cdfe5037..feba8912 100644 --- a/include/beast/websocket/option.hpp +++ b/include/beast/websocket/option.hpp @@ -10,7 +10,6 @@ #include #include -#include #include #include #include @@ -59,56 +58,6 @@ struct auto_fragment }; #endif -/** HTTP decorator option. - - The decorator transforms the HTTP requests and responses used - when requesting or responding to the WebSocket Upgrade. This may - be used to set or change header fields. For example to set the - Server or User-Agent fields. The default setting applies no - transformation to the HTTP message. - - The context in which the decorator is called depends on the - type of operation performed: - - @li For synchronous operations, the implementation will call the - decorator before the operation unblocks. - - @li For asynchronous operations, the implementation guarantees - that calls to the decorator will be made from the same implicit - or explicit strand used to call the asynchronous initiation - function. - - The default setting is no decorator. - - @note Objects of this type are used with - @ref beast::websocket::stream::set_option. - - @par Example - Setting the decorator. - @code - struct identity - { - template - void - operator()(http::message& m) - { - if(isRequest) - m.fields.replace("User-Agent", "MyClient"); - else - m.fields.replace("Server", "MyServer"); - } - }; - ... - websocket::stream ws(ios); - ws.set_option(decorate(identity{})); - @endcode -*/ -#if BEAST_DOXYGEN -using decorate = implementation_defined; -#else -using decorate = detail::decorator_type; -#endif - /** Keep-alive option. Determines if the connection is closed after a failed upgrade diff --git a/include/beast/websocket/stream.hpp b/include/beast/websocket/stream.hpp index 3f6dddab..7a58481c 100644 --- a/include/beast/websocket/stream.hpp +++ b/include/beast/websocket/stream.hpp @@ -26,6 +26,14 @@ namespace beast { namespace websocket { +/// The type of object holding HTTP Upgrade requests +using request_type = http::request_header; + +/// The type of object holding HTTP Upgrade responses +using response_type = + //http::response_header; + http::response; + /** Information about a WebSocket frame. This information is provided to callers during frame @@ -60,7 +68,7 @@ struct frame_info you would write: @code - websocket::stream ws(io_service); + websocket::stream ws{io_service}; @endcode Alternatively, you can write: @code @@ -79,7 +87,6 @@ struct frame_info @par Concepts @b `AsyncStream`, - @b `Decorator`, @b `DynamicBuffer`, @b `SyncStream` */ @@ -175,33 +182,6 @@ public: wr_autofrag_ = o.value; } - /** Set the decorator used for HTTP messages. - - The value for this option is a callable type with two - optional signatures: - - @code - void(request_type&); - - void(response_type&); - @endcode - - If a matching signature is provided, the callable type - will be invoked with the HTTP request or HTTP response - object as appropriate. When a signature is omitted, - a default consisting of the string Beast followed by - the version number is used. - */ - void -#if BEAST_DOXYGEN - set_option(implementation_defined o) -#else - set_option(detail::decorator_type const& o) -#endif - { - d_ = o; - } - /// Set the keep-alive option void set_option(keep_alive const& o) @@ -367,6 +347,44 @@ public: void accept(); + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The HTTP request finishes receiving, and the HTTP 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. + + @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(ResponseDecorator const& decorator); + /** Read and respond to a WebSocket HTTP Upgrade request. This function is used to synchronously read an HTTP WebSocket @@ -395,6 +413,45 @@ public: void accept(error_code& ec); + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The HTTP request finishes receiving, and the HTTP 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. + + @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(ResponseDecorator const& decorator, + error_code& ec); + /** Read and respond to a WebSocket HTTP Upgrade request. This function is used to synchronously read an HTTP WebSocket @@ -428,6 +485,50 @@ public: void accept(ConstBufferSequence const& buffers); + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The HTTP request finishes receiving, and the HTTP 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. + + @param buffers Caller provided data that has already been + received on the stream. 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(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator); + /** Read and respond to a WebSocket HTTP Upgrade request. This function is used to synchronously read an HTTP WebSocket @@ -461,6 +562,51 @@ public: void accept(ConstBufferSequence const& buffers, error_code& ec); + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read an HTTP WebSocket + Upgrade request and send the HTTP response. The call blocks + until one of the following conditions is true: + + @li The HTTP request finishes receiving, and the HTTP 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. + + @param buffers Caller provided data that has already been + received on the stream. 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(ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + error_code& ec); + /** Respond to a WebSocket HTTP Upgrade request This function is used to synchronously send the HTTP response @@ -493,6 +639,48 @@ public: void accept(http::header const& req); + /** 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 HTTP 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. + + @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 + + @throws system_error Thrown on failure. + */ + template + void + accept_ex(http::header const& req, + ResponseDecorator const& decorator); + /** Respond to a WebSocket HTTP Upgrade request This function is used to synchronously send the HTTP response @@ -523,7 +711,51 @@ public: */ template void - accept(http::header const& req, error_code& ec); + accept(http::header const& req, + 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 HTTP 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. + + @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 ec Set to indicate what error occurred, if any. + */ + template + void + accept_ex(http::header const& req, + ResponseDecorator const& decorator, + error_code& ec); /** Respond to a WebSocket HTTP Upgrade request @@ -563,6 +795,55 @@ public: accept(http::header 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 HTTP 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. + + @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::header 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 @@ -601,6 +882,56 @@ public: accept(http::header 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 HTTP 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. + + @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::header 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 @@ -635,7 +966,7 @@ public: 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 + 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 @@ -651,6 +982,66 @@ public: #endif async_accept(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 HTTP request finishes receiving, and the HTTP 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. + + @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 BEAST_DOXYGEN + void_or_deduced +#else + typename async_completion< + AcceptHandler, void(error_code)>::result_type +#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 @@ -692,7 +1083,7 @@ public: 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 + 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 @@ -709,6 +1100,75 @@ public: 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 HTTP request finishes receiving, and the HTTP 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. + + @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 BEAST_DOXYGEN + void_or_deduced +#else + typename async_completion::result_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 @@ -747,7 +1207,7 @@ public: 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 + 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 @@ -764,6 +1224,72 @@ public: async_accept(http::header 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 HTTP 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 BEAST_DOXYGEN + void_or_deduced +#else + typename async_completion::result_type +#endif + async_accept_ex(http::header 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 @@ -809,7 +1335,7 @@ public: 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 + 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 @@ -828,6 +1354,80 @@ public: ConstBufferSequence const& buffers, 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 HTTP 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 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 BEAST_DOXYGEN + void_or_deduced +#else + typename async_completion::result_type +#endif + async_accept_ex(http::header const& req, + ConstBufferSequence const& buffers, + ResponseDecorator const& decorator, + AcceptHandler&& handler); + /** Send an HTTP WebSocket Upgrade request and receive the response. This function is used to synchronously send the WebSocket @@ -856,11 +1456,11 @@ public: @par Example @code - websocket::stream ws(io_service); + websocket::stream ws{io_service}; ... try { - ws.upgrade("localhost", "/"); + ws.handshake("localhost", "/"); } catch(...) { @@ -872,6 +1472,178 @@ public: handshake(boost::string_ref const& host, boost::string_ref const& resource); + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li A HTTP request finishes sending and an HTTP response finishes + receiving. + + @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. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. + + @param host The name of the remote host, + required by the HTTP protocol. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. + + @throws system_error Thrown on failure. + + @par Example + @code + websocket::stream ws{io_service}; + ... + try + { + response_type res; + ws.handshake(res, "localhost", "/"); + } + catch(...) + { + // An error occurred. + } + @endcode + */ + void + handshake(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li A HTTP request finishes sending and an HTTP response finishes + receiving. + + @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. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param host The name of the remote host, + required by the HTTP protocol. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @throws system_error Thrown on failure. + + @par Example + @code + websocket::stream ws{io_service}; + ... + try + { + ws.handshake("localhost", "/", + [](request_type& req) + { + req.fields.insert("User-Agent", "Beast"); + }); + } + catch(...) + { + // An error occurred. + } + @endcode + */ + template + void + handshake_ex(boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li A HTTP request finishes sending and an HTTP response finishes + receiving. + + @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. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. + + @param host The name of the remote host, + required by the HTTP protocol. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @throws system_error Thrown on failure. + + @par Example + @code + websocket::stream ws{io_service}; + ... + try + { + response_type res; + ws.handshake(res, "localhost", "/", + [](request_type& req) + { + req.fields.insert("User-Agent", "Beast"); + }); + } + catch(...) + { + // An error occurred. + } + @endcode + */ + template + void + handshake_ex(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator); + /** Send an HTTP WebSocket Upgrade request and receive the response. This function is used to synchronously send the WebSocket @@ -900,10 +1672,10 @@ public: @par Example @code - websocket::stream ws(io_service); + websocket::stream ws{io_service}; ... error_code ec; - ws.upgrade(host, resource, ec); + ws.handshake(host, resource, ec); if(ec) { // An error occurred. @@ -914,6 +1686,177 @@ public: handshake(boost::string_ref const& host, boost::string_ref const& resource, error_code& ec); + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li A HTTP request finishes sending and an HTTP response finishes + receiving. + + @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. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param host The name of the remote host, + required by the HTTP protocol. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. + + @param ec Set to indicate what error occurred, if any. + + @param res The HTTP Upgrade response returned by the remote + endpoint. If `ec is set, the return value is undefined. + + @par Example + @code + websocket::stream ws{io_service}; + ... + error_code ec; + response_type res; + ws.handshake(res, host, resource, ec); + if(ec) + { + // An error occurred. + } + @endcode + */ + void + handshake(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + error_code& ec); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li A HTTP request finishes sending and an HTTP response finishes + receiving. + + @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. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param host The name of the remote host, + required by the HTTP protocol. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @param ec Set to indicate what error occurred, if any. + + @par Example + @code + websocket::stream ws{io_service}; + ... + error_code ec; + ws.handshake("localhost", "/", + [](request_type& req) + { + req.fields.insert("User-Agent", "Beast"); + }, + ec); + if(ec) + { + // An error occurred. + } + @endcode + */ + template + void + handshake_ex(boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + error_code& ec); + + /** Send an HTTP WebSocket Upgrade request and receive the response. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li A HTTP request finishes sending and an HTTP response finishes + receiving. + + @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. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. + + @param host The name of the remote host, + required by the HTTP protocol. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @endcode + + @param ec Set to indicate what error occurred, if any. + + @par Example + @code + websocket::stream ws{io_service}; + ... + error_code ec; + response_type res; + ws.handshake(res, "localhost", "/", + [](request_type& req) + { + req.fields.insert("User-Agent", "Beast"); + }, + ec); + if(ec) + { + // An error occurred. + } + @endcode + */ + template + void + handshake_ex(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + error_code& ec); + /** Start an asynchronous operation to send an upgrade request and receive the response. This function is used to asynchronously send the HTTP WebSocket @@ -944,11 +1887,11 @@ public: required by the HTTP protocol. Copies may be made as needed. - @param h The handler to be called when the request completes. + @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 + 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 @@ -959,11 +1902,198 @@ public: #if BEAST_DOXYGEN void_or_deduced #else - typename async_completion< - HandshakeHandler, void(error_code)>::result_type + typename async_completion::result_type #endif async_handshake(boost::string_ref const& host, - boost::string_ref const& resource, HandshakeHandler&& h); + boost::string_ref const& resource, + HandshakeHandler&& handler); + + /** Start an asynchronous operation to send an upgrade request and receive the response. + + This function is used to asynchronously send the HTTP WebSocket + upgrade request and receive the HTTP WebSocket Upgrade response. + This function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is + true: + + @li A HTTP request finishes sending and an HTTP response finishes + receiving. + + @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 operations until this operation + completes. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. The caller must ensure this object is valid for at + least until the completion handler is invoked. + + @param host The name of the remote host, required by + the HTTP protocol. Copies may be made as needed. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. Copies may be made as + needed. + + @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 BEAST_DOXYGEN + void_or_deduced +#else + typename async_completion::result_type +#endif + async_handshake(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + HandshakeHandler&& handler); + + /** Start an asynchronous operation to send an upgrade request and receive the response. + + This function is used to asynchronously send the HTTP WebSocket + upgrade request and receive the HTTP WebSocket Upgrade response. + This function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is + true: + + @li A HTTP request finishes sending and an HTTP response finishes + receiving. + + @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 operations until this operation + completes. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param host The name of the remote host, required by + the HTTP protocol. Copies may be made as needed. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. Copies may be made as + needed. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @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 BEAST_DOXYGEN + void_or_deduced +#else + typename async_completion::result_type +#endif + async_handshake_ex(boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + HandshakeHandler&& handler); + + /** Start an asynchronous operation to send an upgrade request and receive the response. + + This function is used to asynchronously send the HTTP WebSocket + upgrade request and receive the HTTP WebSocket Upgrade response. + This function call always returns immediately. The asynchronous + operation will continue until one of the following conditions is + true: + + @li A HTTP request finishes sending and an HTTP response finishes + receiving. + + @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 operations until this operation + completes. + + The operation is successful if the received HTTP response indicates + a successful HTTP Upgrade (represented by a Status-Code of 101, + "switching protocols"). + + @param res The HTTP Upgrade response returned by the remote + endpoint. The caller must ensure this object is valid for at + least until the completion handler is invoked. + + @param host The name of the remote host, required by + the HTTP protocol. Copies may be made as needed. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. Copies may be made as + needed. + + @param decorator A function object which will be called to modify + the HTTP request object generated by the implementation. This + could be used to set the User-Agent field, subprotocols, or other + application or HTTP specific fields. The object will be called + with this equivalent signature: + @code void decorator( + request_type& req + ); @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 BEAST_DOXYGEN + void_or_deduced +#else + typename async_completion::result_type +#endif + async_handshake_ex(response_type& res, + boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + HandshakeHandler&& handler); /** Send a WebSocket close frame. @@ -1058,7 +2188,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -1140,7 +2270,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -1237,7 +2367,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -1385,7 +2515,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -1543,7 +2673,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -1669,7 +2799,7 @@ public: function signature of the handler must be: @code void handler( - error_code const& error // Result of operation + error_code const& ec // Result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -1790,7 +2920,7 @@ public: 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 + error_code const& ec // Result of operation ); @endcode */ template @@ -1804,7 +2934,7 @@ public: ConstBufferSequence const& buffers, WriteHandler&& handler); private: - template class accept_op; + template class accept_op; template class close_op; template class handshake_op; template class ping_op; @@ -1814,24 +2944,50 @@ private: template class read_op; template class read_frame_op; + static + void + default_decorate_req(request_type& res) + { + } + + static + void + default_decorate_res(response_type& res) + { + } + void reset(); + template void - do_accept(error_code& ec); - - template - void - do_accept(http::header const& req, + do_accept(Decorator const& decorator, error_code& ec); - http::request_header - build_request(boost::string_ref const& host, - boost::string_ref const& resource, - std::string& key); + template + void + do_accept(http::header const& req, + Decorator const& decorator, error_code& ec); - http::response_header - build_response(http::request_header const& req); + template + void + do_handshake(response_type* res_p, + boost::string_ref const& host, + boost::string_ref const& resource, + RequestDecorator const& decorator, + error_code& ec); + + template + request_type + build_request(std::string& key, + boost::string_ref const& host, + boost::string_ref const& resource, + Decorator const& decorator); + + template + response_type + build_response(request_type const& req, + Decorator const& decorator); void do_response(http::response_header const& resp, diff --git a/test/websocket/stream.cpp b/test/websocket/stream.cpp index b08a6697..c3072ea7 100644 --- a/test/websocket/stream.cpp +++ b/test/websocket/stream.cpp @@ -111,33 +111,6 @@ public: return false; } - struct test_decorator - { - int& what; - - test_decorator(test_decorator const&) = default; - - test_decorator(int& what_) - : what(what_) - { - what = 0; - } - - template - void - operator()(http::header&) const - { - what |= 1; - } - - template - void - operator()(http::header&) const - { - what |= 2; - } - }; - struct SyncClient { template @@ -173,6 +146,46 @@ public: ws.accept(req, buffers); } + template + void + accept_ex(stream& ws, + Decorator const& d) const + { + ws.accept_ex(d); + } + + template + void + accept_ex(stream& ws, + Buffers const& buffers, + Decorator const& d) const + { + ws.accept_ex(buffers, d); + } + + template + void + accept_ex(stream& ws, + http::header const& req, + Decorator const& d) const + { + ws.accept_ex(req, d); + } + + template + void + accept_ex(stream& ws, + http::header const& req, + Buffers const& buffers, + Decorator const& d) const + { + ws.accept_ex(req, buffers, d); + } + template void handshake(stream& ws, @@ -182,6 +195,37 @@ public: ws.handshake(uri, path); } + template + void + handshake(stream& ws, + response_type& res, + boost::string_ref const& uri, + boost::string_ref const& path) const + { + ws.handshake(res, uri, path); + } + + template + void + handshake_ex(stream& ws, + boost::string_ref const& uri, + boost::string_ref const& path, + Decorator const& d) const + { + ws.handshake_ex(uri, path, d); + } + + template + void + handshake_ex(stream& ws, + response_type& res, + boost::string_ref const& uri, + boost::string_ref const& path, + Decorator const& d) const + { + ws.handshake_ex(res, uri, path, d); + } + template void ping(stream& ws, @@ -301,14 +345,56 @@ public: } template + class Decorator> void - accept(stream& ws, + accept_ex(stream& ws, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept_ex(stream& ws, + Buffers const& buffers, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(buffers, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept_ex(stream& ws, + http::header const& req, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(req, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept_ex(stream& ws, http::header const& req, Buffers const& buffers, - error_code& ec) const + Decorator const& d) const { - ws.async_accept(req, buffers, yield_[ec]); + error_code ec; + ws.async_accept_ex( + req, buffers, d, yield_[ec]); + if(ec) + throw system_error{ec}; } template @@ -318,7 +404,51 @@ public: boost::string_ref const& path) const { error_code ec; - ws.async_handshake(uri, path, yield_[ec]); + ws.async_handshake( + uri, path, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake(stream& ws, + response_type& res, + boost::string_ref const& uri, + boost::string_ref const& path) const + { + error_code ec; + ws.async_handshake( + res, uri, path, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake_ex(stream& ws, + boost::string_ref const& uri, + boost::string_ref const& path, + Decorator const &d) const + { + error_code ec; + ws.async_handshake_ex( + uri, path, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake_ex(stream& ws, + response_type& res, + boost::string_ref const& uri, + boost::string_ref const& path, + Decorator const &d) const + { + error_code ec; + ws.async_handshake_ex( + res, uri, path, d, yield_[ec]); if(ec) throw system_error{ec}; } @@ -436,6 +566,28 @@ public: } } + //-------------------------------------------------------------------------- + + class res_decorator + { + bool& b_; + + public: + res_decorator(res_decorator const&) = default; + + explicit + res_decorator(bool& b) + : b_(b) + { + } + + void + operator()(response_type&) const + { + b_ = true; + } + }; + template void testAccept(Client const& c) @@ -460,7 +612,22 @@ public: "\r\n" , 20}; c.accept(ws); - //log << ws.next_layer().str << std::endl; + // VFALCO validate contents of ws.next_layer().str? + } + { + stream> ws{fc, 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" + "\r\n" + , 20}; + bool called = false; + c.accept_ex(ws, res_decorator{called}); + BEAST_EXPECT(called); } // request in buffers { @@ -476,6 +643,21 @@ public: "\r\n" )); } + { + stream> ws{fc, ios_}; + bool called = false; + c.accept_ex(ws, sbuf( + "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" + "\r\n"), + res_decorator{called}); + BEAST_EXPECT(called); + } // request in buffers and stream { stream> ws{fc, ios_, + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + , 16}; + 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}); + BEAST_EXPECT(called); + } // request in message { - http::request_header req; + request_type req; req.method = "GET"; req.url = "/"; req.version = 11; @@ -506,9 +704,26 @@ public: test::string_ostream>> ws{fc, ios_}; c.accept(ws, req); } + { + request_type req; + req.method = "GET"; + req.url = "/"; + req.version = 11; + req.fields.insert("Host", "localhost"); + req.fields.insert("Upgrade", "websocket"); + req.fields.insert("Connection", "upgrade"); + req.fields.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.fields.insert("Sec-WebSocket-Version", "13"); + stream> ws{fc, ios_}; + bool called = false; + c.accept_ex(ws, req, + res_decorator{called}); + BEAST_EXPECT(called); + } // request in message, close frame in buffers { - http::request_header req; + request_type req; req.method = "GET"; req.url = "/"; req.version = 11; @@ -534,9 +749,39 @@ public: throw; } } + { + request_type req; + req.method = "GET"; + req.url = "/"; + req.version = 11; + req.fields.insert("Host", "localhost"); + req.fields.insert("Upgrade", "websocket"); + req.fields.insert("Connection", "upgrade"); + req.fields.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.fields.insert("Sec-WebSocket-Version", "13"); + stream> ws{fc, ios_}; + bool called = false; + c.accept_ex(ws, req, + cbuf(0x88, 0x82, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x17), + res_decorator{called}); + BEAST_EXPECT(called); + try + { + opcode op; + streambuf sb; + c.read(ws, op, sb); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if(e.code() != websocket::error::closed) + throw; + } + } // request in message, close frame in stream { - http::request_header req; + request_type req; req.method = "GET"; req.url = "/"; req.version = 11; @@ -564,7 +809,7 @@ public: } // request in message, close frame in stream and buffers { - http::request_header req; + request_type req; req.method = "GET"; req.url = "/"; req.version = 11; @@ -609,8 +854,10 @@ public: } catch(system_error const& e) { - if(e.code() != - websocket::error::handshake_failed) + if( e.code() != + websocket::error::handshake_failed && + e.code() != + boost::asio::error::eof) throw; } } @@ -635,6 +882,107 @@ public: }); } + //-------------------------------------------------------------------------- + + class req_decorator + { + bool& b_; + + public: + req_decorator(req_decorator const&) = default; + + explicit + req_decorator(bool& b) + : b_(b) + { + } + + void + operator()(request_type&) const + { + b_ = true; + } + }; + + template + void + testHandshake(endpoint_type const& ep, Client const& c) + { + static std::size_t constexpr limit = 200; + std::size_t n; + for(n = 0; n < limit; ++n) + { + test::fail_counter fc{n}; + try + { + // handshake + { + stream> ws{fc, ios_}; + ws.next_layer().next_layer().connect(ep); + c.handshake(ws, "localhost", "/"); + } + // handshake, response + { + stream> ws{fc, ios_}; + ws.next_layer().next_layer().connect(ep); + response_type res; + c.handshake(ws, res, "localhost", "/"); + // VFALCO validate res? + } + // handshake_ex + { + stream> ws{fc, ios_}; + ws.next_layer().next_layer().connect(ep); + bool called = false; + c.handshake_ex(ws, "localhost", "/", + req_decorator{called}); + BEAST_EXPECT(called); + } + // handshake_ex, response + { + stream> ws{fc, ios_}; + ws.next_layer().next_layer().connect(ep); + bool called = false; + response_type res; + c.handshake_ex(ws, res, "localhost", "/", + req_decorator{called}); + // VFALCO validate res? + BEAST_EXPECT(called); + } + } + catch(system_error const&) + { + continue; + } + break; + } + BEAST_EXPECT(n < limit); + } + + void + testHandshake() + { + error_code ec; + ::websocket::async_echo_server server{nullptr, 1}; + auto const any = endpoint_type{ + address_type::from_string("127.0.0.1"), 0}; + server.open(any, ec); + BEAST_EXPECTS(! ec, ec.message()); + auto const ep = server.local_endpoint(); + testHandshake(ep, SyncClient{}); + yield_to( + [&](yield_context yield) + { + testHandshake(ep, AsyncClient{yield}); + }); + } + + //-------------------------------------------------------------------------- + void testBadHandshakes() { auto const check = @@ -823,24 +1171,6 @@ public: ); } - void - testDecorator(endpoint_type const& ep) - { - error_code ec; - socket_type sock{ios_}; - sock.connect(ep, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - stream ws{sock}; - int what; - ws.set_option(decorate(test_decorator{what})); - BEAST_EXPECT(what == 0); - ws.handshake("localhost", "/", ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - BEAST_EXPECT(what == 1); - } - void testMask(endpoint_type const& ep, yield_context do_yield) { @@ -1525,6 +1855,7 @@ public: testOptions(); testAccept(); + testHandshake(); testBadHandshakes(); testBadResponses(); @@ -1539,7 +1870,6 @@ public: server.open(any, ec); BEAST_EXPECTS(! ec, ec.message()); auto const ep = server.local_endpoint(); - testDecorator(ep); //testInvokable1(ep); testInvokable2(ep); testInvokable3(ep); diff --git a/test/websocket/websocket_async_echo_server.hpp b/test/websocket/websocket_async_echo_server.hpp index 4932e35e..ebbada11 100644 --- a/test/websocket/websocket_async_echo_server.hpp +++ b/test/websocket/websocket_async_echo_server.hpp @@ -38,25 +38,6 @@ public: using endpoint_type = boost::asio::ip::tcp::endpoint; private: - struct identity - { - template - void - operator()(beast::http::message< - true, Body, Fields>& req) const - { - req.fields.replace("User-Agent", "async_echo_client"); - } - - template - void - operator()(beast::http::message< - false, Body, Fields>& resp) const - { - resp.fields.replace("Server", "async_echo_server"); - } - }; - /** A container of type-erased option setters. */ template @@ -159,8 +140,6 @@ public: , acceptor_(ios_) , work_(ios_) { - opts_.set_option( - beast::websocket::decorate(identity{})); thread_.reserve(threads); for(std::size_t i = 0; i < threads; ++i) thread_.emplace_back( @@ -282,7 +261,13 @@ private: void run() { auto& d = *d_; - d.ws.async_accept(std::move(*this)); + d.ws.async_accept_ex( + [](beast::websocket::response_type& res) + { + res.fields.insert( + "Server", "async_echo_server"); + }, + std::move(*this)); } template diff --git a/test/websocket/websocket_sync_echo_server.hpp b/test/websocket/websocket_sync_echo_server.hpp index 8f1dbf07..3c3ab4de 100644 --- a/test/websocket/websocket_sync_echo_server.hpp +++ b/test/websocket/websocket_sync_echo_server.hpp @@ -38,25 +38,6 @@ public: using socket_type = boost::asio::ip::tcp::socket; private: - struct identity - { - template - void - operator()(beast::http::message< - true, Body, Fields>& req) const - { - req.fields.replace("User-Agent", "sync_echo_client"); - } - - template - void - operator()(beast::http::message< - false, Body, Fields>& resp) const - { - resp.fields.replace("Server", "sync_echo_server"); - } - }; - /** A container of type-erased option setters. */ template @@ -151,8 +132,6 @@ public: , sock_(ios_) , acceptor_(ios_) { - opts_.set_option( - beast::websocket::decorate(identity{})); } /** Destructor. @@ -312,7 +291,13 @@ private: socket_type> ws{std::move(sock)}; opts_.set_options(ws); error_code ec; - ws.accept(ec); + ws.accept_ex( + [](beast::websocket::response_type& res) + { + res.fields.insert( + "Server", "sync_echo_server"); + }, + ec); if(ec) { fail("accept", ec, id, ep);