mirror of
https://github.com/boostorg/beast.git
synced 2025-07-29 20:37:31 +02:00
websockets: Count pings from server as activity for idle_timeout
If the stream is receiving control packets like ping, don't count it as idle. This means you can enable `timeout_opt.keep_alive_ping` on only one side to get heartbeat. Make tests that verify current behaviour. Update documentation to match changes in PR #2718. Addresses issue #2716
This commit is contained in:
committed by
Mohammad Nejati
parent
84115e5b13
commit
f9387fda71
@ -59,8 +59,8 @@ There are three timeout settings which may be set independently on the stream:
|
|||||||
[[link beast.ref.boost__beast__websocket__stream_base__timeout.idle_timeout `timeout::idle_timeout`]]
|
[[link beast.ref.boost__beast__websocket__stream_base__timeout.idle_timeout `timeout::idle_timeout`]]
|
||||||
[`duration`]
|
[`duration`]
|
||||||
[
|
[
|
||||||
If no data is received from the peer for a time equal to the idle
|
If no data or control frames are received from the peer for a time
|
||||||
timeout, then the connection will time out.
|
equal to the idle timeout, then the connection will time out.
|
||||||
The value returned by
|
The value returned by
|
||||||
[link beast.ref.boost__beast__websocket__stream_base.none `stream_base::none()`]
|
[link beast.ref.boost__beast__websocket__stream_base.none `stream_base::none()`]
|
||||||
may be assigned to disable this timeout.
|
may be assigned to disable this timeout.
|
||||||
|
@ -439,12 +439,8 @@ struct stream<NextLayer, deflateSupported>::impl_type
|
|||||||
if(timeout_opt.idle_timeout != none())
|
if(timeout_opt.idle_timeout != none())
|
||||||
{
|
{
|
||||||
idle_counter = 0;
|
idle_counter = 0;
|
||||||
if(timeout_opt.keep_alive_pings)
|
timer.expires_after(
|
||||||
timer.expires_after(
|
timeout_opt.idle_timeout / 2);
|
||||||
timeout_opt.idle_timeout / 2);
|
|
||||||
else
|
|
||||||
timer.expires_after(
|
|
||||||
timeout_opt.idle_timeout);
|
|
||||||
|
|
||||||
BOOST_ASIO_HANDLER_LOCATION((
|
BOOST_ASIO_HANDLER_LOCATION((
|
||||||
__FILE__, __LINE__,
|
__FILE__, __LINE__,
|
||||||
@ -569,9 +565,9 @@ private:
|
|||||||
if(impl.timeout_opt.idle_timeout == none())
|
if(impl.timeout_opt.idle_timeout == none())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if( impl.timeout_opt.keep_alive_pings &&
|
if( impl.idle_counter < 1 )
|
||||||
impl.idle_counter < 1)
|
|
||||||
{
|
{
|
||||||
|
if( impl.timeout_opt.keep_alive_pings )
|
||||||
{
|
{
|
||||||
BOOST_ASIO_HANDLER_LOCATION((
|
BOOST_ASIO_HANDLER_LOCATION((
|
||||||
__FILE__, __LINE__,
|
__FILE__, __LINE__,
|
||||||
|
@ -116,8 +116,10 @@ struct stream_base
|
|||||||
An outstanding read operation must be pending, which will
|
An outstanding read operation must be pending, which will
|
||||||
complete immediately the error @ref beast::error::timeout.
|
complete immediately the error @ref beast::error::timeout.
|
||||||
|
|
||||||
@li When `keep_alive_pings` is `false`, the connection will be closed.
|
@li When `keep_alive_pings` is `false`, the connection will
|
||||||
An outstanding read operation must be pending, which will
|
be closed if there has been no activity. Both websocket
|
||||||
|
message frames and control frames count as activity. An
|
||||||
|
outstanding read operation must be pending, which will
|
||||||
complete immediately the error @ref beast::error::timeout.
|
complete immediately the error @ref beast::error::timeout.
|
||||||
*/
|
*/
|
||||||
bool keep_alive_pings;
|
bool keep_alive_pings;
|
||||||
|
@ -88,6 +88,79 @@ public:
|
|||||||
se.code().message());
|
se.code().message());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inactivity timeout doesn't happen when you get pings
|
||||||
|
{
|
||||||
|
echo_server es{log};
|
||||||
|
stream<test::stream> ws{ioc_};
|
||||||
|
|
||||||
|
// We have an inactivity timeout of 2s, but don't send pings
|
||||||
|
ws.set_option(stream_base::timeout{
|
||||||
|
stream_base::none(),
|
||||||
|
std::chrono::milliseconds(2000),
|
||||||
|
false
|
||||||
|
});
|
||||||
|
ws.next_layer().connect(es.stream());
|
||||||
|
ws.handshake("localhost", "/");
|
||||||
|
flat_buffer b;
|
||||||
|
bool got_timeout = false;
|
||||||
|
ws.async_read(b,
|
||||||
|
[&](error_code ec, std::size_t)
|
||||||
|
{
|
||||||
|
if(ec != beast::error::timeout)
|
||||||
|
BOOST_THROW_EXCEPTION(
|
||||||
|
system_error{ec});
|
||||||
|
got_timeout = true;
|
||||||
|
});
|
||||||
|
// We are connected, base state
|
||||||
|
BEAST_EXPECT(ws.impl_->idle_counter == 0);
|
||||||
|
|
||||||
|
test::run_for(ioc_, std::chrono::milliseconds(1250));
|
||||||
|
// After 1.25s idle, no timeout but idle counter is 1
|
||||||
|
BEAST_EXPECT(ws.impl_->idle_counter == 1);
|
||||||
|
|
||||||
|
es.async_ping();
|
||||||
|
test::run_for(ioc_, std::chrono::milliseconds(500));
|
||||||
|
// The server sent a ping at 1.25s mark, and we're now at 1.75s mark.
|
||||||
|
// We haven't hit the idle timer yet (happens at 1s, 2s, 3s)
|
||||||
|
BEAST_EXPECT(ws.impl_->idle_counter == 0);
|
||||||
|
BEAST_EXPECT(!got_timeout);
|
||||||
|
|
||||||
|
test::run_for(ioc_, std::chrono::milliseconds(750));
|
||||||
|
// At 2.5s total; should have triggered the idle timer
|
||||||
|
BEAST_EXPECT(ws.impl_->idle_counter == 1);
|
||||||
|
BEAST_EXPECT(!got_timeout);
|
||||||
|
|
||||||
|
test::run_for(ioc_, std::chrono::milliseconds(750));
|
||||||
|
// At 3s total; should have triggered the idle timer again without
|
||||||
|
// activity and triggered timeout.
|
||||||
|
BEAST_EXPECT(got_timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// inactivity timeout doesn't happen when you send pings
|
||||||
|
{
|
||||||
|
echo_server es{log};
|
||||||
|
stream<test::stream> ws{ioc_};
|
||||||
|
ws.set_option(stream_base::timeout{
|
||||||
|
stream_base::none(),
|
||||||
|
std::chrono::milliseconds(600),
|
||||||
|
true
|
||||||
|
});
|
||||||
|
unsigned n_pongs = 0;
|
||||||
|
ws.control_callback({[&](frame_type kind, string_view)
|
||||||
|
{
|
||||||
|
if (kind == frame_type::pong)
|
||||||
|
++n_pongs;
|
||||||
|
}});
|
||||||
|
ws.next_layer().connect(es.stream());
|
||||||
|
ws.handshake("localhost", "/");
|
||||||
|
flat_buffer b;
|
||||||
|
ws.async_read(b, test::fail_handler(asio::error::operation_aborted));
|
||||||
|
// We are connected, base state
|
||||||
|
test::run_for(ioc_, std::chrono::seconds(1));
|
||||||
|
// About a second later, we should have close to 5 pings/pongs, and no timeout
|
||||||
|
BEAST_EXPECTS(2 <= n_pongs && n_pongs <= 3, "Unexpected nr of pings: " + std::to_string(n_pongs));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -134,6 +134,12 @@ public:
|
|||||||
this));
|
this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
async_ping()
|
||||||
|
{
|
||||||
|
ws_.async_ping("", [](error_code){});
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
async_close()
|
async_close()
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user