From fc7b47fc5dee3b402a55850e8bcd7c5c2fbedfac Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 16 Feb 2019 14:01:37 -0800 Subject: [PATCH] Use suggested timeouts in Websocket examples --- CHANGELOG.md | 1 + doc/qbk/02_examples/0_examples.qbk | 18 +- .../server-flex/advanced_server_flex.cpp | 191 +----------------- example/advanced/server/advanced_server.cpp | 139 +------------ .../async-ssl/websocket_client_async_ssl.cpp | 9 + .../client/async/websocket_client_async.cpp | 9 + .../coro-ssl/websocket_client_coro_ssl.cpp | 9 + .../client/coro/websocket_client_coro.cpp | 9 + .../async-ssl/websocket_server_async_ssl.cpp | 9 + .../server/async/websocket_server_async.cpp | 4 + .../server/chat-multi/websocket_session.cpp | 4 + .../coro-ssl/websocket_server_coro_ssl.cpp | 9 + .../server/coro/websocket_server_coro.cpp | 5 + .../websocket_server_stackless_ssl.cpp | 12 ++ .../stackless/websocket_server_stackless.cpp | 5 + include/boost/beast/websocket/stream_base.hpp | 1 + 16 files changed, 105 insertions(+), 329 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b244fd26..e3e9c9a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Version 216: * Refactor websocket::stream operations * Add websocket::stream timeouts +* Use suggested timeouts in Websocket examples -------------------------------------------------------------------------------- diff --git a/doc/qbk/02_examples/0_examples.qbk b/doc/qbk/02_examples/0_examples.qbk index 370fa54b..4d07e0de 100644 --- a/doc/qbk/02_examples/0_examples.qbk +++ b/doc/qbk/02_examples/0_examples.qbk @@ -53,7 +53,7 @@ in the [source_file example] directory. These HTTP clients submit a GET request to a server specified on the command line, and prints the resulting response. The crawl client asynchronously fetches the document root of the 10,000 top ranked domains, this may be -used to evaluate robustness. +used to evaluate robustness. All asynchronous support provide timeouts. [table [[Description] [Source File] [Source File (using SSL)]] @@ -77,7 +77,7 @@ used to evaluate robustness. These WebSocket clients connect to a server and send a message, then receive a message and print the response -before disconnecting. +before disconnecting. All asynchronous clients support timeouts. [table [[Description] [Source File] [Source File (using SSL)]] @@ -102,7 +102,7 @@ before disconnecting. [section Servers] These HTTP servers deliver files from a root directory specified on the -command line. +command line. All asynchronous servers support timeouts. [table [[Description] [Source File] [Source File (using SSL)]] @@ -137,7 +137,8 @@ command line. ]] These WebSocket servers echo back any message received, keeping the -session open until the client disconnects. +session open until the client disconnects. All asynchronous servers +support timeouts. [table [[Description] [Source File] [Source File (using SSL)]] @@ -177,20 +178,18 @@ and illustrate the implementation of advanced features. [ [Advanced] [[itemized_list + [Timeouts] [HTTP pipelining] - [Asynchronous timeouts] [Dual protocols: HTTP and WebSocket] - [WebSocket use idle ping for timeout] [Clean exit via SIGINT (CTRL+C) or SIGTERM (kill)] ]] [[example_src example/advanced/server/advanced_server.cpp advanced_server.cpp]] ][ [Advanced, flex (plain + SSL)] [[itemized_list + [Timeouts] [HTTP pipelining] - [Asynchronous timeouts] [Dual protocols: HTTP and WebSocket] - [WebSocket use idle ping for timeout] [Flexible ports: plain and SSL on the same port] [Clean exit via SIGINT (CTRL+C) or SIGTERM (kill)] ]] @@ -198,8 +197,9 @@ and illustrate the implementation of advanced features. ][ [Chat Server, multi-threaded] [[itemized_list - [Multi-user Chat] [Broadcasting Messages] + [Multi-user Chat Server] + [JavaScript Browser Client] [Dual protocols: HTTP and WebSocket] [Clean exit via SIGINT (CTRL+C) or SIGTERM (kill)] ]] diff --git a/example/advanced/server-flex/advanced_server_flex.cpp b/example/advanced/server-flex/advanced_server_flex.cpp index c60803bf..32bb8383 100644 --- a/example/advanced/server-flex/advanced_server_flex.cpp +++ b/example/advanced/server-flex/advanced_server_flex.cpp @@ -239,35 +239,17 @@ class websocket_session } beast::flat_buffer buffer_; - char ping_state_ = 0; - -protected: - net::steady_timer timer_; public: - // Construct the session - explicit - websocket_session(net::io_context& ioc) - : timer_(ioc, - (std::chrono::steady_clock::time_point::max)()) - { - } - // Start the asynchronous operation template void do_accept(http::request> req) { - // Set the control callback. This will be called - // on every incoming ping, pong, and close frame. - derived().ws().control_callback( - std::bind( - &websocket_session::on_control_callback, - this, - std::placeholders::_1, - std::placeholders::_2)); - - // VFALCO What about the timer? + // Set suggested timeout settings for the websocket + derived().ws().set_option( + websocket::stream_base::suggested_settings( + websocket::role_type::server)); // Accept the websocket handshake derived().ws().async_accept( @@ -280,10 +262,6 @@ public: void on_accept(beast::error_code ec) { - // Happens when the timer closes the socket - if(ec == net::error::operation_aborted) - return; - if(ec) return fail(ec, "accept"); @@ -291,99 +269,6 @@ public: do_read(); } - // Called when the timer expires. - void - on_timer(beast::error_code ec) - { - if(ec && ec != net::error::operation_aborted) - return fail(ec, "timer"); - - // See if the timer really expired since the deadline may have moved. - if(timer_.expiry() <= std::chrono::steady_clock::now()) - { - // If this is the first time the timer expired, - // send a ping to see if the other end is there. - if(derived().ws().is_open() && ping_state_ == 0) - { - // Note that we are sending a ping - ping_state_ = 1; - - // Set the timer - timer_.expires_after(std::chrono::seconds(15)); - - // Now send the ping - derived().ws().async_ping({}, - beast::bind_front_handler( - &websocket_session::on_ping, - derived().shared_from_this())); - } - else - { - // The timer expired while trying to handshake, - // or we sent a ping and it never completed or - // we never got back a control frame, so close. - - derived().do_timeout(); - return; - } - } - - // Wait on the timer - timer_.async_wait( - net::bind_executor( - derived().ws().get_executor(), // use the strand - beast::bind_front_handler( - &websocket_session::on_timer, - derived().shared_from_this()))); - } - - // Called to indicate activity from the remote peer - void - activity() - { - // Note that the connection is alive - ping_state_ = 0; - - // Set the timer - timer_.expires_after(std::chrono::seconds(15)); - } - - // Called after a ping is sent. - void - on_ping(beast::error_code ec) - { - // Happens when the timer closes the socket - if(ec == net::error::operation_aborted) - return; - - if(ec) - return fail(ec, "ping"); - - // Note that the ping was sent. - if(ping_state_ == 1) - { - ping_state_ = 2; - } - else - { - // ping_state_ could have been set to 0 - // if an incoming control frame was received - // at exactly the same time we sent a ping. - BOOST_ASSERT(ping_state_ == 0); - } - } - - void - on_control_callback( - websocket::frame_type kind, - beast::string_view payload) - { - boost::ignore_unused(kind, payload); - - // Note that there is activity - activity(); - } - void do_read() { @@ -402,10 +287,6 @@ public: { boost::ignore_unused(bytes_transferred); - // Happens when the timer closes the socket - if(ec == net::error::operation_aborted) - return; - // This indicates that the websocket_session was closed if(ec == websocket::error::closed) return; @@ -413,9 +294,6 @@ public: if(ec) fail(ec, "read"); - // Note that there is activity - activity(); - // Echo the message derived().ws().text(derived().ws().got_text()); derived().ws().async_write( @@ -432,10 +310,6 @@ public: { boost::ignore_unused(bytes_transferred); - // Happens when the timer closes the socket - if(ec == net::error::operation_aborted) - return; - if(ec) return fail(ec, "write"); @@ -461,9 +335,7 @@ public: explicit plain_websocket_session( beast::tcp_stream&& stream) - : websocket_session( - stream.get_executor().context()) - , ws_(std::move(stream)) + : ws_(std::move(stream)) { } @@ -480,34 +352,10 @@ public: void run(http::request> req) { - // Run the timer. The timer is operated - // continuously, this simplifies the code. - on_timer({}); - // Accept the WebSocket upgrade request do_accept(std::move(req)); } - void - do_timeout() - { - // This is so the close can have a timeout - if(close_) - return; - close_ = true; - - // VFALCO This doesn't look right... - // Set the timer - timer_.expires_after(std::chrono::seconds(15)); - - // Close the WebSocket Connection - ws_.async_close( - websocket::close_code::normal, - beast::bind_front_handler( - &plain_websocket_session::on_close, - shared_from_this())); - } - void on_close(beast::error_code ec) { @@ -536,9 +384,7 @@ public: explicit ssl_websocket_session(beast::ssl_stream< beast::tcp_stream>&& stream) - : websocket_session( - stream.get_executor().context()) - , ws_(std::move(stream)) + : ws_(std::move(stream)) { } @@ -555,10 +401,6 @@ public: void run(http::request> req) { - // Run the timer. The timer is operated - // continuously, this simplifies the code. - on_timer({}); - // Accept the WebSocket upgrade request do_accept(std::move(req)); } @@ -568,9 +410,6 @@ public: { eof_ = true; - // Set the timer - timer_.expires_after(std::chrono::seconds(15)); - // Perform the SSL shutdown ws_.next_layer().async_shutdown( beast::bind_front_handler( @@ -581,29 +420,11 @@ public: void on_shutdown(beast::error_code ec) { - // Happens when the shutdown times out - if(ec == net::error::operation_aborted) - return; - if(ec) return fail(ec, "shutdown"); // At this point the connection is closed gracefully } - - void - do_timeout() - { - // If this is true it means we timed out performing the shutdown - if(eof_) - return; - - // Start the timer again - timer_.expires_at( - (std::chrono::steady_clock::time_point::max)()); - on_timer({}); - do_eof(); - } }; template diff --git a/example/advanced/server/advanced_server.cpp b/example/advanced/server/advanced_server.cpp index 8ace7c89..f8f6f922 100644 --- a/example/advanced/server/advanced_server.cpp +++ b/example/advanced/server/advanced_server.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -221,7 +220,6 @@ fail(beast::error_code ec, char const* what) class websocket_session : public std::enable_shared_from_this { websocket::stream> ws_; - net::steady_timer timer_; beast::flat_buffer buffer_; char ping_state_ = 0; @@ -230,9 +228,11 @@ public: explicit websocket_session(tcp::socket socket) : ws_(std::move(socket)) - , timer_(ws_.get_executor().context(), - (std::chrono::steady_clock::time_point::max)()) { + // Set suggested timeout settings for the websocket + ws_.set_option( + websocket::stream_base::suggested_settings( + websocket::role_type::server)); } // Start the asynchronous accept operation @@ -240,22 +240,6 @@ public: void do_accept(http::request> req) { - // Set the control callback. This will be called - // on every incoming ping, pong, and close frame. - ws_.control_callback( - std::bind( - &websocket_session::on_control_callback, - this, - std::placeholders::_1, - std::placeholders::_2)); - - // Run the timer. The timer is operated - // continuously, this simplifies the code. - on_timer({}); - - // Set the timer - timer_.expires_after(std::chrono::seconds(15)); - // Accept the websocket handshake ws_.async_accept( req, @@ -267,10 +251,6 @@ public: void on_accept(beast::error_code ec) { - // Happens when the timer closes the socket - if(ec == net::error::operation_aborted) - return; - if(ec) return fail(ec, "accept"); @@ -278,102 +258,6 @@ public: do_read(); } - // Called when the timer expires. - void - on_timer(beast::error_code ec) - { - if(ec && ec != net::error::operation_aborted) - return fail(ec, "timer"); - - // See if the timer really expired since the deadline may have moved. - if(timer_.expiry() <= std::chrono::steady_clock::now()) - { - // If this is the first time the timer expired, - // send a ping to see if the other end is there. - if(ws_.is_open() && ping_state_ == 0) - { - // Note that we are sending a ping - ping_state_ = 1; - - // Set the timer - timer_.expires_after(std::chrono::seconds(15)); - - // Now send the ping - ws_.async_ping({}, - beast::bind_front_handler( - &websocket_session::on_ping, - shared_from_this())); - } - else - { - // The timer expired while trying to handshake, - // or we sent a ping and it never completed or - // we never got back a control frame, so close. - - // Closing the socket cancels all outstanding operations. They - // will complete with net::error::operation_aborted - beast::get_lowest_layer(ws_).socket().shutdown(tcp::socket::shutdown_both, ec); - beast::get_lowest_layer(ws_).socket().close(ec); - return; - } - } - - // Wait on the timer - timer_.async_wait( - net::bind_executor( - ws_.get_executor(), // use the strand - beast::bind_front_handler( - &websocket_session::on_timer, - shared_from_this()))); - } - - // Called to indicate activity from the remote peer - void - activity() - { - // Note that the connection is alive - ping_state_ = 0; - - // Set the timer - timer_.expires_after(std::chrono::seconds(15)); - } - - // Called after a ping is sent. - void - on_ping(beast::error_code ec) - { - // Happens when the timer closes the socket - if(ec == net::error::operation_aborted) - return; - - if(ec) - return fail(ec, "ping"); - - // Note that the ping was sent. - if(ping_state_ == 1) - { - ping_state_ = 2; - } - else - { - // ping_state_ could have been set to 0 - // if an incoming control frame was received - // at exactly the same time we sent a ping. - BOOST_ASSERT(ping_state_ == 0); - } - } - - void - on_control_callback( - websocket::frame_type kind, - beast::string_view payload) - { - boost::ignore_unused(kind, payload); - - // Note that there is activity - activity(); - } - void do_read() { @@ -392,10 +276,6 @@ public: { boost::ignore_unused(bytes_transferred); - // Happens when the timer closes the socket - if(ec == net::error::operation_aborted) - return; - // This indicates that the websocket_session was closed if(ec == websocket::error::closed) return; @@ -403,9 +283,6 @@ public: if(ec) fail(ec, "read"); - // Note that there is activity - activity(); - // Echo the message ws_.text(ws_.got_text()); ws_.async_write( @@ -422,10 +299,6 @@ public: { boost::ignore_unused(bytes_transferred); - // Happens when the timer closes the socket - if(ec == net::error::operation_aborted) - return; - if(ec) return fail(ec, "write"); @@ -615,10 +488,6 @@ public: { boost::ignore_unused(bytes_transferred); - // Happens when the timer closes the socket - if(ec == net::error::operation_aborted) - return; - if(ec) return fail(ec, "write"); diff --git a/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp b/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp index ab812d68..603de9a3 100644 --- a/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp +++ b/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp @@ -123,6 +123,15 @@ public: if(ec) return fail(ec, "ssl_handshake"); + // Turn off the timeout on the tcp_stream, because + // the websocket stream has its own timeout system. + beast::get_lowest_layer(ws_).expires_never(); + + // Set suggested timeout settings for the websocket + ws_.set_option( + websocket::stream_base::suggested_settings( + websocket::role_type::client)); + // Perform the websocket handshake ws_.async_handshake(host_, "/", beast::bind_front_handler( diff --git a/example/websocket/client/async/websocket_client_async.cpp b/example/websocket/client/async/websocket_client_async.cpp index 05040014..3e35e2cb 100644 --- a/example/websocket/client/async/websocket_client_async.cpp +++ b/example/websocket/client/async/websocket_client_async.cpp @@ -101,6 +101,15 @@ public: if(ec) return fail(ec, "connect"); + // Turn off the timeout on the tcp_stream, because + // the websocket stream has its own timeout system. + beast::get_lowest_layer(ws_).expires_never(); + + // Set suggested timeout settings for the websocket + ws_.set_option( + websocket::stream_base::suggested_settings( + websocket::role_type::client)); + // Perform the websocket handshake ws_.async_handshake(host_, "/", beast::bind_front_handler( diff --git a/example/websocket/client/coro-ssl/websocket_client_coro_ssl.cpp b/example/websocket/client/coro-ssl/websocket_client_coro_ssl.cpp index 1d8f997b..16de37ad 100644 --- a/example/websocket/client/coro-ssl/websocket_client_coro_ssl.cpp +++ b/example/websocket/client/coro-ssl/websocket_client_coro_ssl.cpp @@ -79,6 +79,15 @@ do_session( if(ec) return fail(ec, "ssl_handshake"); + // Turn off the timeout on the tcp_stream, because + // the websocket stream has its own timeout system. + beast::get_lowest_layer(ws).expires_never(); + + // Set suggested timeout settings for the websocket + ws.set_option( + websocket::stream_base::suggested_settings( + websocket::role_type::client)); + // Perform the websocket handshake ws.async_handshake(host, "/", yield[ec]); if(ec) diff --git a/example/websocket/client/coro/websocket_client_coro.cpp b/example/websocket/client/coro/websocket_client_coro.cpp index df0d526a..4f1e164e 100644 --- a/example/websocket/client/coro/websocket_client_coro.cpp +++ b/example/websocket/client/coro/websocket_client_coro.cpp @@ -65,6 +65,15 @@ do_session( if(ec) return fail(ec, "connect"); + // Turn off the timeout on the tcp_stream, because + // the websocket stream has its own timeout system. + beast::get_lowest_layer(ws).expires_never(); + + // Set suggested timeout settings for the websocket + ws.set_option( + websocket::stream_base::suggested_settings( + websocket::role_type::client)); + // Perform the websocket handshake ws.async_handshake(host, "/", yield[ec]); if(ec) diff --git a/example/websocket/server/async-ssl/websocket_server_async_ssl.cpp b/example/websocket/server/async-ssl/websocket_server_async_ssl.cpp index f3b6004c..21f46b10 100644 --- a/example/websocket/server/async-ssl/websocket_server_async_ssl.cpp +++ b/example/websocket/server/async-ssl/websocket_server_async_ssl.cpp @@ -80,6 +80,15 @@ public: if(ec) return fail(ec, "handshake"); + // Turn off the timeout on the tcp_stream, because + // the websocket stream has its own timeout system. + beast::get_lowest_layer(ws_).expires_never(); + + // Set suggested timeout settings for the websocket + ws_.set_option( + websocket::stream_base::suggested_settings( + websocket::role_type::server)); + // Accept the websocket handshake ws_.async_accept( beast::bind_front_handler( diff --git a/example/websocket/server/async/websocket_server_async.cpp b/example/websocket/server/async/websocket_server_async.cpp index 7e4c0281..4cbdc728 100644 --- a/example/websocket/server/async/websocket_server_async.cpp +++ b/example/websocket/server/async/websocket_server_async.cpp @@ -52,6 +52,10 @@ public: session(tcp::socket socket) : ws_(std::move(socket)) { + // Set suggested timeout settings for the websocket + ws_.set_option( + websocket::stream_base::suggested_settings( + websocket::role_type::server)); } // Start the asynchronous operation diff --git a/example/websocket/server/chat-multi/websocket_session.cpp b/example/websocket/server/chat-multi/websocket_session.cpp index 70af3eba..960b1ab6 100644 --- a/example/websocket/server/chat-multi/websocket_session.cpp +++ b/example/websocket/server/chat-multi/websocket_session.cpp @@ -17,6 +17,10 @@ websocket_session( : ws_(std::move(socket)) , state_(state) { + // Set suggested timeout settings for the websocket + ws_.set_option( + websocket::stream_base::suggested_settings( + websocket::role_type::server)); } websocket_session:: diff --git a/example/websocket/server/coro-ssl/websocket_server_coro_ssl.cpp b/example/websocket/server/coro-ssl/websocket_server_coro_ssl.cpp index 67040fb2..541750db 100644 --- a/example/websocket/server/coro-ssl/websocket_server_coro_ssl.cpp +++ b/example/websocket/server/coro-ssl/websocket_server_coro_ssl.cpp @@ -66,6 +66,15 @@ do_session( if(ec) return fail(ec, "handshake"); + // Turn off the timeout on the tcp_stream, because + // the websocket stream has its own timeout system. + beast::get_lowest_layer(ws).expires_never(); + + // Set suggested timeout settings for the websocket + ws.set_option( + websocket::stream_base::suggested_settings( + websocket::role_type::server)); + // Accept the websocket handshake ws.async_accept(yield[ec]); if(ec) diff --git a/example/websocket/server/coro/websocket_server_coro.cpp b/example/websocket/server/coro/websocket_server_coro.cpp index 2f31fbfa..4e94c8ac 100644 --- a/example/websocket/server/coro/websocket_server_coro.cpp +++ b/example/websocket/server/coro/websocket_server_coro.cpp @@ -51,6 +51,11 @@ do_session(ws_type& ws, net::yield_context yield) { beast::error_code ec; + // Set suggested timeout settings for the websocket + ws.set_option( + websocket::stream_base::suggested_settings( + websocket::role_type::server)); + // Accept the websocket handshake ws.async_accept(yield[ec]); if(ec) diff --git a/example/websocket/server/stackless-ssl/websocket_server_stackless_ssl.cpp b/example/websocket/server/stackless-ssl/websocket_server_stackless_ssl.cpp index b98fe86b..928c670d 100644 --- a/example/websocket/server/stackless-ssl/websocket_server_stackless_ssl.cpp +++ b/example/websocket/server/stackless-ssl/websocket_server_stackless_ssl.cpp @@ -79,6 +79,9 @@ public: reenter(*this) { + // Set the timeout. + beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30)); + // Perform the SSL handshake yield ws_.next_layer().async_handshake( ssl::stream_base::server, @@ -90,6 +93,15 @@ public: if(ec) return fail(ec, "handshake"); + // Turn off the timeout on the tcp_stream, because + // the websocket stream has its own timeout system. + beast::get_lowest_layer(ws_).expires_never(); + + // Set suggested timeout settings for the websocket + ws_.set_option( + websocket::stream_base::suggested_settings( + websocket::role_type::server)); + // Accept the websocket handshake yield ws_.async_accept( std::bind( diff --git a/example/websocket/server/stackless/websocket_server_stackless.cpp b/example/websocket/server/stackless/websocket_server_stackless.cpp index a6d17a32..09d5a690 100644 --- a/example/websocket/server/stackless/websocket_server_stackless.cpp +++ b/example/websocket/server/stackless/websocket_server_stackless.cpp @@ -74,6 +74,11 @@ public: boost::ignore_unused(bytes_transferred); reenter(*this) { + // Set suggested timeout settings for the websocket + ws_.set_option( + websocket::stream_base::suggested_settings( + websocket::role_type::server)); + // Accept the websocket handshake yield ws_.async_accept( std::bind( diff --git a/include/boost/beast/websocket/stream_base.hpp b/include/boost/beast/websocket/stream_base.hpp index 62c3ac1b..26dba83b 100644 --- a/include/boost/beast/websocket/stream_base.hpp +++ b/include/boost/beast/websocket/stream_base.hpp @@ -118,6 +118,7 @@ struct stream_base opt.keep_alive_pings = true; break; } + return opt; } protected: