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);