From 8930d437b5e0f245e2f56f146149652ad2ad2e6c Mon Sep 17 00:00:00 2001 From: Damian Jarek Date: Sat, 24 Nov 2018 00:41:53 +0100 Subject: [PATCH] Enable explicit instantiation of websocket::stream: fix #1279, close #1319 This enables users to improve compilation performance by explicitly instantiating the stream template in another TU. Signed-off-by: Damian Jarek --- CHANGELOG.md | 1 + .../beast/_experimental/test/impl/stream.ipp | 1 - .../beast/websocket/detail/stream_base.hpp | 255 ++++++++++++++++++ include/boost/beast/websocket/impl/accept.ipp | 4 +- .../boost/beast/websocket/impl/handshake.ipp | 4 +- include/boost/beast/websocket/impl/stream.ipp | 238 +--------------- include/boost/beast/websocket/stream.hpp | 126 +-------- test/beast/websocket/CMakeLists.txt | 1 + test/beast/websocket/Jamfile | 1 + test/beast/websocket/stream_explicit.cpp | 15 ++ 10 files changed, 292 insertions(+), 354 deletions(-) create mode 100644 test/beast/websocket/stream_explicit.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 225b49d1..05e4d1bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Version 193: * Update ssl_stream signatures for networking changes * Fix test::stream async_result transformation * Tidy up test::stream +* Enable explicit instantiation of websocket::stream -------------------------------------------------------------------------------- diff --git a/include/boost/beast/_experimental/test/impl/stream.ipp b/include/boost/beast/_experimental/test/impl/stream.ipp index 320cd165..61d88433 100644 --- a/include/boost/beast/_experimental/test/impl/stream.ipp +++ b/include/boost/beast/_experimental/test/impl/stream.ipp @@ -291,7 +291,6 @@ async_read_some( { lock.unlock(); ++in_->nread; - error_code ec; if(in_->code == status::eof) ec = boost::asio::error::eof; else if(in_->code == status::reset) diff --git a/include/boost/beast/websocket/detail/stream_base.hpp b/include/boost/beast/websocket/detail/stream_base.hpp index f54e56d4..110fbeee 100644 --- a/include/boost/beast/websocket/detail/stream_base.hpp +++ b/include/boost/beast/websocket/detail/stream_base.hpp @@ -10,6 +10,9 @@ #ifndef BOOST_BEAST_WEBSOCKET_STREAM_BASE_HPP #define BOOST_BEAST_WEBSOCKET_STREAM_BASE_HPP +#include +#include +#include #include #include #include @@ -17,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -353,6 +357,165 @@ struct stream_base : stream_prng void do_context_takeover_read(role_type role); + + template + void + build_response_pmd( + http::response& res, + http::request> const& req) + { + detail::pmd_offer offer; + detail::pmd_offer unused; + detail::pmd_read(offer, req); + detail::pmd_negotiate(res, unused, offer, pmd_opts_); + } + + void + on_response_pmd(http::response const& res) + { + detail::pmd_offer offer; + detail::pmd_read(offer, res); + // VFALCO see if offer satisfies pmd_config_, + // return an error if not. + pmd_config_ = offer; // overwrite for now + } + + template + void + do_pmd_config( + http::basic_fields const& h) + { + detail::pmd_read(pmd_config_, h); + } + + void + set_option_pmd(permessage_deflate const& o) + { + if( o.server_max_window_bits > 15 || + o.server_max_window_bits < 9) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid server_max_window_bits"}); + if( o.client_max_window_bits > 15 || + o.client_max_window_bits < 9) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid client_max_window_bits"}); + if( o.compLevel < 0 || + o.compLevel > 9) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid compLevel"}); + if( o.memLevel < 1 || + o.memLevel > 9) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid memLevel"}); + pmd_opts_ = o; + } + + void + get_option_pmd(permessage_deflate& o) + { + o = pmd_opts_; + } + + + void + build_request_pmd(http::request& req) + { + if(pmd_opts_.client_enable) + { + detail::pmd_offer config; + config.accept = true; + config.server_max_window_bits = + pmd_opts_.server_max_window_bits; + config.client_max_window_bits = + pmd_opts_.client_max_window_bits; + config.server_no_context_takeover = + pmd_opts_.server_no_context_takeover; + config.client_no_context_takeover = + pmd_opts_.client_no_context_takeover; + detail::pmd_write(req, config); + } + } + + void + open_pmd(role_type role) + { + if(((role == role_type::client && + pmd_opts_.client_enable) || + (role == role_type::server && + pmd_opts_.server_enable)) && + pmd_config_.accept) + { + detail::pmd_normalize(pmd_config_); + pmd_.reset(new typename + detail::stream_base::pmd_type); + if(role == role_type::client) + { + pmd_->zi.reset( + pmd_config_.server_max_window_bits); + pmd_->zo.reset( + pmd_opts_.compLevel, + pmd_config_.client_max_window_bits, + pmd_opts_.memLevel, + zlib::Strategy::normal); + } + else + { + pmd_->zi.reset( + pmd_config_.client_max_window_bits); + pmd_->zo.reset( + pmd_opts_.compLevel, + pmd_config_.server_max_window_bits, + pmd_opts_.memLevel, + zlib::Strategy::normal); + } + } + } + + void close_pmd() + { + pmd_.reset(); + } + + bool pmd_enabled() const + { + return pmd_ != nullptr; + } + + std::size_t + read_size_hint_pmd( + std::size_t initial_size, + bool rd_done, + std::uint64_t rd_remain, + detail::frame_header const& rd_fh) const + { + using beast::detail::clamp; + std::size_t result; + BOOST_ASSERT(initial_size > 0); + if(! pmd_ || (! rd_done && ! pmd_->rd_set)) + { + // current message is uncompressed + + if(rd_done) + { + // first message frame + result = initial_size; + goto done; + } + else if(rd_fh.fin) + { + // last message frame + BOOST_ASSERT(rd_remain > 0); + result = clamp(rd_remain); + goto done; + } + } + result = (std::max)( + initial_size, clamp(rd_remain)); + done: + BOOST_ASSERT(result != 0); + return result; + } }; template<> @@ -402,6 +565,98 @@ struct stream_base : stream_prng do_context_takeover_read(role_type) { } + + template + void + build_response_pmd( + http::response&, + http::request> const&) + { + } + + void + on_response_pmd( + http::response const&) + { + } + + template + void + do_pmd_config(http::basic_fields const&) + { + } + + void + set_option_pmd(permessage_deflate const& o) + { + if(o.client_enable || o.server_enable) + { + // Can't enable permessage-deflate + // when deflateSupported == false. + // + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "deflateSupported == false"}); + } + } + + void + get_option_pmd(permessage_deflate& o) + { + o = {}; + o.client_enable = false; + o.server_enable = false; + } + + void + build_request_pmd( + http::request&) + { + } + + void open_pmd(role_type) + { + } + + void close_pmd() + { + } + + bool pmd_enabled() const + { + return false; + } + + std::size_t + read_size_hint_pmd( + std::size_t initial_size, + bool rd_done, + std::uint64_t rd_remain, + detail::frame_header const& rd_fh) const + { + using beast::detail::clamp; + std::size_t result; + BOOST_ASSERT(initial_size > 0); + // compression is not supported + if(rd_done) + { + // first message frame + result = initial_size; + } + else if(rd_fh.fin) + { + // last message frame + BOOST_ASSERT(rd_remain > 0); + result = clamp(rd_remain); + } + else + { + result = (std::max)( + initial_size, clamp(rd_remain)); + } + BOOST_ASSERT(result != 0); + return result; + } }; } // detail diff --git a/include/boost/beast/websocket/impl/accept.ipp b/include/boost/beast/websocket/impl/accept.ipp index c674d221..ddc6290d 100644 --- a/include/boost/beast/websocket/impl/accept.ipp +++ b/include/boost/beast/websocket/impl/accept.ipp @@ -139,7 +139,7 @@ operator()( ec = d.result; if(! ec) { - d.ws.do_pmd_config(d.res, is_deflate_supported{}); + d.ws.do_pmd_config(d.res); d.ws.open(role_type::server); } { @@ -763,7 +763,7 @@ do_accept( // teardown if Connection: close. return; } - do_pmd_config(res, is_deflate_supported{}); + this->do_pmd_config(res); open(role_type::server); } diff --git a/include/boost/beast/websocket/impl/handshake.ipp b/include/boost/beast/websocket/impl/handshake.ipp index a1a826be..63b7ac2a 100644 --- a/include/boost/beast/websocket/impl/handshake.ipp +++ b/include/boost/beast/websocket/impl/handshake.ipp @@ -135,7 +135,7 @@ operator()(error_code ec, std::size_t) BOOST_ASIO_CORO_REENTER(*this) { // Send HTTP Upgrade - d.ws.do_pmd_config(d.req, is_deflate_supported{}); + d.ws.do_pmd_config(d.req); BOOST_ASIO_CORO_YIELD http::async_write(d.ws.stream_, d.req, std::move(*this)); @@ -407,7 +407,7 @@ do_handshake( { auto const req = build_request( key, host, target, decorator); - do_pmd_config(req, is_deflate_supported{}); + this->do_pmd_config(req); http::write(stream_, req, ec); } if(ec) diff --git a/include/boost/beast/websocket/impl/stream.ipp b/include/boost/beast/websocket/impl/stream.ipp index 4aa72995..43fcb06e 100644 --- a/include/boost/beast/websocket/impl/stream.ipp +++ b/include/boost/beast/websocket/impl/stream.ipp @@ -69,45 +69,6 @@ read_size_hint(DynamicBuffer& buffer) const //------------------------------------------------------------------------------ -template -void -stream:: -set_option(permessage_deflate const& o, std::true_type) -{ - if( o.server_max_window_bits > 15 || - o.server_max_window_bits < 9) - BOOST_THROW_EXCEPTION(std::invalid_argument{ - "invalid server_max_window_bits"}); - if( o.client_max_window_bits > 15 || - o.client_max_window_bits < 9) - BOOST_THROW_EXCEPTION(std::invalid_argument{ - "invalid client_max_window_bits"}); - if( o.compLevel < 0 || - o.compLevel > 9) - BOOST_THROW_EXCEPTION(std::invalid_argument{ - "invalid compLevel"}); - if( o.memLevel < 1 || - o.memLevel > 9) - BOOST_THROW_EXCEPTION(std::invalid_argument{ - "invalid memLevel"}); - this->pmd_opts_ = o; -} - -template -void -stream:: -set_option(permessage_deflate const& o, std::false_type) -{ - if(o.client_enable || o.server_enable) - { - // Can't enable permessage-deflate - // when deflateSupported == false. - // - BOOST_THROW_EXCEPTION(std::invalid_argument{ - "deflateSupported == false"}); - } -} - template void stream:: @@ -134,45 +95,7 @@ open(role_type role) wr_cont_ = false; wr_buf_size_ = 0; - open_pmd(is_deflate_supported{}); -} - -template -inline -void -stream:: -open_pmd(std::true_type) -{ - if(((role_ == role_type::client && - this->pmd_opts_.client_enable) || - (role_ == role_type::server && - this->pmd_opts_.server_enable)) && - this->pmd_config_.accept) - { - pmd_normalize(this->pmd_config_); - this->pmd_.reset(new typename - detail::stream_base::pmd_type); - if(role_ == role_type::client) - { - this->pmd_->zi.reset( - this->pmd_config_.server_max_window_bits); - this->pmd_->zo.reset( - this->pmd_opts_.compLevel, - this->pmd_config_.client_max_window_bits, - this->pmd_opts_.memLevel, - zlib::Strategy::normal); - } - else - { - this->pmd_->zi.reset( - this->pmd_config_.client_max_window_bits); - this->pmd_->zo.reset( - this->pmd_opts_.compLevel, - this->pmd_config_.server_max_window_bits, - this->pmd_opts_.memLevel, - zlib::Strategy::normal); - } - } + this->open_pmd(role_); } template @@ -181,7 +104,7 @@ stream:: close() { wr_buf_.reset(); - close_pmd(is_deflate_supported{}); + this->close_pmd(); } template @@ -211,13 +134,12 @@ template inline void stream:: -begin_msg(std::true_type) +begin_msg() { wr_frag_ = wr_frag_opt_; - wr_compress_ = static_cast(this->pmd_); // Maintain the write buffer - if( wr_compress_ || + if( this->pmd_enabled() || role_ == role_type::client) { if(! wr_buf_ || wr_buf_size_ != wr_buf_opt_) @@ -234,98 +156,6 @@ begin_msg(std::true_type) } } -// Called before each write frame -template -inline -void -stream:: -begin_msg(std::false_type) -{ - wr_frag_ = wr_frag_opt_; - - // Maintain the write buffer - if(role_ == role_type::client) - { - if(! wr_buf_ || wr_buf_size_ != wr_buf_opt_) - { - wr_buf_size_ = wr_buf_opt_; - wr_buf_ = boost::make_unique_noinit< - std::uint8_t[]>(wr_buf_size_); - } - } - else - { - wr_buf_size_ = wr_buf_opt_; - wr_buf_.reset(); - } -} - -template -std::size_t -stream:: -read_size_hint( - std::size_t initial_size, - std::true_type) const -{ - using beast::detail::clamp; - std::size_t result; - BOOST_ASSERT(initial_size > 0); - if(! this->pmd_ || (! rd_done_ && ! this->pmd_->rd_set)) - { - // current message is uncompressed - - if(rd_done_) - { - // first message frame - result = initial_size; - goto done; - } - else if(rd_fh_.fin) - { - // last message frame - BOOST_ASSERT(rd_remain_ > 0); - result = clamp(rd_remain_); - goto done; - } - } - result = (std::max)( - initial_size, clamp(rd_remain_)); -done: - BOOST_ASSERT(result != 0); - return result; -} - -template -std::size_t -stream:: -read_size_hint( - std::size_t initial_size, - std::false_type) const -{ - using beast::detail::clamp; - std::size_t result; - BOOST_ASSERT(initial_size > 0); - // compression is not supported - if(rd_done_) - { - // first message frame - result = initial_size; - } - else if(rd_fh_.fin) - { - // last message frame - BOOST_ASSERT(rd_remain_ > 0); - result = clamp(rd_remain_); - } - else - { - result = (std::max)( - initial_size, clamp(rd_remain_)); - } - BOOST_ASSERT(result != 0); - return result; -} - //------------------------------------------------------------------------------ // Attempt to read a complete frame header. @@ -644,7 +474,7 @@ build_request(detail::sec_ws_key_type& key, detail::make_sec_ws_key(key); req.set(http::field::sec_websocket_key, key); req.set(http::field::sec_websocket_version, "13"); - build_request_pmd(req, is_deflate_supported{}); + this->build_request_pmd(req); decorator(req); if(! req.count(http::field::user_agent)) req.set(http::field::user_agent, @@ -652,28 +482,6 @@ build_request(detail::sec_ws_key_type& key, return req; } -template -inline -void -stream:: -build_request_pmd(request_type& req, std::true_type) -{ - if(this->pmd_opts_.client_enable) - { - detail::pmd_offer config; - config.accept = true; - config.server_max_window_bits = - this->pmd_opts_.server_max_window_bits; - config.client_max_window_bits = - this->pmd_opts_.client_max_window_bits; - config.server_no_context_takeover = - this->pmd_opts_.server_no_context_takeover; - config.client_no_context_takeover = - this->pmd_opts_.client_no_context_takeover; - detail::pmd_write(req, config); - } -} - template template response_type @@ -764,29 +572,12 @@ build_response( detail::make_sec_ws_accept(acc, key); res.set(http::field::sec_websocket_accept, acc); } - build_response_pmd(res, req, is_deflate_supported{}); + this->build_response_pmd(res, req); decorate(res); result = {}; return res; } -template -template -inline -void -stream:: -build_response_pmd( - response_type& res, - http::request> const& req, - std::true_type) -{ - detail::pmd_offer offer; - detail::pmd_offer unused; - pmd_read(offer, req); - pmd_negotiate(res, unused, offer, this->pmd_opts_); -} - // Called when the WebSocket Upgrade response is received template void @@ -830,25 +621,10 @@ on_response( } ec.assign(0, ec.category()); - on_response_pmd(res, is_deflate_supported{}); + this->on_response_pmd(res); open(role_type::client); } -template -inline -void -stream:: -on_response_pmd( - response_type const& res, - std::true_type) -{ - detail::pmd_offer offer; - pmd_read(offer, res); - // VFALCO see if offer satisfies pmd_config_, - // return an error if not. - this->pmd_config_ = offer; // overwrite for now -} - // _Fail the WebSocket Connection_ template void diff --git a/include/boost/beast/websocket/stream.hpp b/include/boost/beast/websocket/stream.hpp index cb8a00fc..b5618ded 100644 --- a/include/boost/beast/websocket/stream.hpp +++ b/include/boost/beast/websocket/stream.hpp @@ -26,9 +26,6 @@ #include #include #include -#include -#include -#include #include #include #include @@ -76,7 +73,7 @@ class frame_test; The @ref stream class template provides asynchronous and blocking message-oriented functionality necessary for clients and servers to utilize the WebSocket protocol. - + For asynchronous operations, the application must ensure that they are are all performed within the same implicit or explicit strand. @@ -136,7 +133,7 @@ class stream friend class read2_test; friend class stream_test; friend class write_test; - + /* The read buffer has to be at least as large as the largest possible control frame including the frame header. @@ -276,7 +273,7 @@ public: //-------------------------------------------------------------------------- /** Get the executor associated with the object. - + This function may be used to obtain the executor object that the stream uses to dispatch handlers for asynchronous operations. @@ -425,8 +422,8 @@ public: read_size_hint( std::size_t initial_size = +tcp_frame_size) const { - return read_size_hint(initial_size, - is_deflate_supported{}); + return this->read_size_hint_pmd( + initial_size, rd_done_, rd_remain_, rd_fh_); } /** Returns a suggested maximum buffer size for the next call to read. @@ -466,14 +463,14 @@ public: void set_option(permessage_deflate const& o) { - set_option(o, is_deflate_supported{}); + this->set_option_pmd(o); } /// Get the permessage-deflate extension options void get_option(permessage_deflate& o) { - get_option(o, is_deflate_supported{}); + this->get_option_pmd(o); } /** Set the automatic fragmentation option. @@ -3374,65 +3371,13 @@ private: static void default_decorate_req(request_type&) {} static void default_decorate_res(response_type&) {} - void - set_option(permessage_deflate const& o, std::true_type); - - void - set_option(permessage_deflate const&, std::false_type); - - void - get_option(permessage_deflate& o, std::true_type) - { - o = this->pmd_opts_; - } - - void - get_option(permessage_deflate& o, std::false_type) - { - o = {}; - o.client_enable = false; - o.server_enable = false; - } - void open(role_type role); - void open_pmd(std::true_type); - - void open_pmd(std::false_type) - { - } - void close(); - void close_pmd(std::true_type) - { - this->pmd_.reset(); - } - - void close_pmd(std::false_type) - { - } - void reset(); - void begin_msg() - { - begin_msg(is_deflate_supported{}); - } - - void begin_msg(std::true_type); - - void begin_msg(std::false_type); - - std::size_t - read_size_hint( - std::size_t initial_size, - std::true_type) const; - - std::size_t - read_size_hint( - std::size_t initial_size, - std::false_type) const; + void begin_msg(); bool check_open(error_code& ec) @@ -3485,14 +3430,6 @@ private: string_view target, Decorator const& decorator); - void - build_request_pmd(request_type& req, std::true_type); - - void - build_request_pmd(request_type&, std::false_type) - { - } - template< class Body, class Allocator, class Decorator> response_type @@ -3502,63 +3439,16 @@ private: Decorator const& decorator, error_code& ec); - template - void - build_response_pmd( - response_type& res, - http::request> const& req, - std::true_type); - - template - void - build_response_pmd( - response_type&, - http::request> const&, - std::false_type) - { - } - void on_response( response_type const& res, detail::sec_ws_key_type const& key, error_code& ec); - void - on_response_pmd( - response_type const& res, - std::true_type); - - void - on_response_pmd( - response_type const&, - std::false_type) - { - } - // // accept / handshake // - template - void - do_pmd_config( - http::basic_fields const& h, - std::true_type) - { - pmd_read(this->pmd_config_, h); - } - - template - void - do_pmd_config( - http::basic_fields const&, - std::false_type) - { - } - template void do_accept( diff --git a/test/beast/websocket/CMakeLists.txt b/test/beast/websocket/CMakeLists.txt index 91cbc904..432b7b76 100644 --- a/test/beast/websocket/CMakeLists.txt +++ b/test/beast/websocket/CMakeLists.txt @@ -29,6 +29,7 @@ add_executable (tests-beast-websocket rfc6455.cpp role.cpp stream.cpp + stream_explicit.cpp stream_fwd.cpp teardown.cpp utf8_checker.cpp diff --git a/test/beast/websocket/Jamfile b/test/beast/websocket/Jamfile index f4ea4bf9..e33199b8 100644 --- a/test/beast/websocket/Jamfile +++ b/test/beast/websocket/Jamfile @@ -20,6 +20,7 @@ local SOURCES = rfc6455.cpp role.cpp stream.cpp + stream_explicit.cpp stream_fwd.cpp teardown.cpp utf8_checker.cpp diff --git a/test/beast/websocket/stream_explicit.cpp b/test/beast/websocket/stream_explicit.cpp new file mode 100644 index 00000000..09ef0f21 --- /dev/null +++ b/test/beast/websocket/stream_explicit.cpp @@ -0,0 +1,15 @@ +// +// Copyright (w) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +// Test that header file is self-contained. +#include +#include + +template class boost::beast::websocket::stream; +template class boost::beast::websocket::stream;