Invoke callback on pings and pongs (API Change):

fix #248

This additionally invokes the pong callback for received pings, allowing
callers to more efficiently detect when a connection is still lively:

* pong_callback renamed to ping_callback
* Callback signature has an extra `bool` to indicate if the received
  control frame is a ping or pong.
This commit is contained in:
Vinnie Falco
2017-02-03 16:22:28 -05:00
parent a5a6563fe2
commit 765cb22b48
9 changed files with 131 additions and 107 deletions

View File

@ -1,3 +1,11 @@
1.0.0-b27
API Changes:
* Invoke callback on pings and pongs
--------------------------------------------------------------------------------
1.0.0-b26 1.0.0-b26
* Tidy up warnings and tests * Tidy up warnings and tests

View File

@ -129,7 +129,7 @@
<member><link linkend="beast.ref.websocket__keep_alive">keep_alive</link></member> <member><link linkend="beast.ref.websocket__keep_alive">keep_alive</link></member>
<member><link linkend="beast.ref.websocket__message_type">message_type</link></member> <member><link linkend="beast.ref.websocket__message_type">message_type</link></member>
<member><link linkend="beast.ref.websocket__permessage_deflate">permessage_deflate</link></member> <member><link linkend="beast.ref.websocket__permessage_deflate">permessage_deflate</link></member>
<member><link linkend="beast.ref.websocket__pong_callback">pong_callback</link></member> <member><link linkend="beast.ref.websocket__ping_callback">ping_callback</link></member>
<member><link linkend="beast.ref.websocket__read_buffer_size">read_buffer_size</link></member> <member><link linkend="beast.ref.websocket__read_buffer_size">read_buffer_size</link></member>
<member><link linkend="beast.ref.websocket__read_message_max">read_message_max</link></member> <member><link linkend="beast.ref.websocket__read_message_max">read_message_max</link></member>
<member><link linkend="beast.ref.websocket__write_buffer_size">write_buffer_size</link></member> <member><link linkend="beast.ref.websocket__write_buffer_size">write_buffer_size</link></member>

View File

@ -309,9 +309,9 @@ and received a close frame.
During read operations, Beast automatically reads and processes control During read operations, Beast automatically reads and processes control
frames. Pings are replied to as soon as possible with a pong, received frames. Pings are replied to as soon as possible with a pong, received
pongs are delivered to the pong callback. The receipt of a close frame ping and pongs are delivered to the ping callback. The receipt of a close
initiates the WebSocket close procedure, eventually resulting in the error frame initiates the WebSocket close procedure, eventually resulting in the
code [link beast.ref.websocket__error `error::closed`] being delivered error code [link beast.ref.websocket__error `error::closed`] being delivered
to the caller in a subsequent read operation, assuming no other error to the caller in a subsequent read operation, assuming no other error
takes place. takes place.
@ -331,29 +331,33 @@ using the functions
[link beast.ref.websocket__stream.ping `ping`] and [link beast.ref.websocket__stream.ping `ping`] and
[link beast.ref.websocket__stream.pong `pong`]. [link beast.ref.websocket__stream.pong `pong`].
To receive pong control frames, callers may register a "pong callback" using To be notified of ping and pong control frames, callers may register a
[link beast.ref.websocket__stream.set_option `set_option`]. The object provided "ping callback" using [link beast.ref.websocket__stream.set_option `set_option`].
with this option should be callable with the following signature: The object provided with this option should be callable with the following
signature:
``` ```
void on_pong(ping_data const& payload); void on_ping(bool is_pong, ping_data const& payload);
... ...
ws.set_option(pong_callback{&on_pong}); ws.set_option(ping_callback{&on_ping});
``` ```
When a pong callback is registered, any pongs received through either When a ping callback is registered, all pings and pongs received through
synchronous read functions or asynchronous read functions will invoke the either synchronous read functions or asynchronous read functions will
pong callback, passing the payload in the pong message as the argument. invoke the ping callback, with the value of `is_pong` set to `true` if a
pong was received else `false` if a ping was received. The payload of
the ping or pong control frame is passed in the payload argument.
Unlike regular completion handlers used in calls to asynchronous initiation Unlike regular completion handlers used in calls to asynchronous initiation
functions, the pong callback only needs to be set once. The callback is not functions, the ping callback only needs to be set once. The callback is not
reset when a pong is received. The same callback is used for both synchronous reset when a ping or pong is received. The same callback is used for both
and asynchronous reads. The pong callback is passive; in order to receive synchronous and asynchronous reads. The ping callback is passive; in order
pongs, a synchronous or asynchronous stream read function must be active. to receive pings and pongs, a synchronous or asynchronous stream read
function must be active.
[note [note
When an asynchronous read function receives a pong, the the pong When an asynchronous read function receives a ping or pong, the
callback is invoked in the same manner as that used to invoke the ping callback is invoked in the same manner as that used to invoke
final completion handler of the corresponding read function. the final completion handler of the corresponding read function.
] ]
[heading Close Frames] [heading Close Frames]

View File

@ -59,14 +59,14 @@ protected:
std::size_t wr_buf_size_ = 4096; // write buffer size std::size_t wr_buf_size_ = 4096; // write buffer size
std::size_t rd_buf_size_ = 4096; // read buffer size std::size_t rd_buf_size_ = 4096; // read buffer size
opcode wr_opcode_ = opcode::text; // outgoing message type opcode wr_opcode_ = opcode::text; // outgoing message type
pong_cb pong_cb_; // pong callback ping_cb ping_cb_; // ping callback
role_type role_; // server or client role_type role_; // server or client
bool failed_; // the connection failed bool failed_; // the connection failed
bool wr_close_; // sent close frame bool wr_close_; // sent close frame
op* wr_block_; // op currenly writing op* wr_block_; // op currenly writing
ping_data* pong_data_; // where to put pong payload ping_data* ping_data_; // where to put pong payload
invokable rd_op_; // invoked after write completes invokable rd_op_; // invoked after write completes
invokable wr_op_; // invoked after read completes invokable wr_op_; // invoked after read completes
close_reason cr_; // set from received close frame close_reason cr_; // set from received close frame
@ -212,7 +212,7 @@ open(role_type role)
rd_.cont = false; rd_.cont = false;
wr_close_ = false; wr_close_ = false;
wr_block_ = nullptr; // should be nullptr on close anyway wr_block_ = nullptr; // should be nullptr on close anyway
pong_data_ = nullptr; // should be nullptr on close anyway ping_data_ = nullptr; // should be nullptr on close anyway
wr_.cont = false; wr_.cont = false;
wr_.buf_size = 0; wr_.buf_size = 0;

View File

@ -427,9 +427,11 @@ operator()(error_code ec,
case do_control: case do_control:
if(d.fh.op == opcode::ping) if(d.fh.op == opcode::ping)
{ {
ping_data data; ping_data payload;
detail::read(data, d.fb.data()); detail::read(payload, d.fb.data());
d.fb.reset(); d.fb.reset();
if(d.ws.ping_cb_)
d.ws.ping_cb_(false, payload);
if(d.ws.wr_close_) if(d.ws.wr_close_)
{ {
// ignore ping when closing // ignore ping when closing
@ -437,7 +439,7 @@ operator()(error_code ec,
break; break;
} }
d.ws.template write_ping<static_streambuf>( d.ws.template write_ping<static_streambuf>(
d.fb, opcode::pong, data); d.fb, opcode::pong, payload);
if(d.ws.wr_block_) if(d.ws.wr_block_)
{ {
// suspend // suspend
@ -455,8 +457,8 @@ operator()(error_code ec,
code = close_code::none; code = close_code::none;
ping_data payload; ping_data payload;
detail::read(payload, d.fb.data()); detail::read(payload, d.fb.data());
if(d.ws.pong_cb_) if(d.ws.ping_cb_)
d.ws.pong_cb_(payload); d.ws.ping_cb_(true, payload);
d.fb.reset(); d.fb.reset();
d.state = do_read_fh; d.state = do_read_fh;
break; break;
@ -762,11 +764,13 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec)
// Process control frame // Process control frame
if(fh.op == opcode::ping) if(fh.op == opcode::ping)
{ {
ping_data data; ping_data payload;
detail::read(data, fb.data()); detail::read(payload, fb.data());
fb.reset(); fb.reset();
if(ping_cb_)
ping_cb_(false, payload);
write_ping<static_streambuf>( write_ping<static_streambuf>(
fb, opcode::pong, data); fb, opcode::pong, payload);
boost::asio::write(stream_, fb.data(), ec); boost::asio::write(stream_, fb.data(), ec);
failed_ = ec != 0; failed_ = ec != 0;
if(failed_) if(failed_)
@ -777,8 +781,8 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec)
{ {
ping_data payload; ping_data payload;
detail::read(payload, fb.data()); detail::read(payload, fb.data());
if(pong_cb_) if(ping_cb_)
pong_cb_(payload); ping_cb_(true, payload);
continue; continue;
} }
BOOST_ASSERT(fh.op == opcode::close); BOOST_ASSERT(fh.op == opcode::close);

View File

@ -76,7 +76,7 @@ reset()
wr_close_ = false; wr_close_ = false;
wr_.cont = false; wr_.cont = false;
wr_block_ = nullptr; // should be nullptr on close anyway wr_block_ = nullptr; // should be nullptr on close anyway
pong_data_ = nullptr; // should be nullptr on close anyway ping_data_ = nullptr; // should be nullptr on close anyway
stream_.buffer().consume( stream_.buffer().consume(
stream_.buffer().size()); stream_.buffer().size());

View File

@ -188,7 +188,7 @@ struct message_type
namespace detail { namespace detail {
using pong_cb = std::function<void(ping_data const&)>; using ping_cb = std::function<void(bool, ping_data const&)>;
} // detail } // detail
@ -233,48 +233,54 @@ struct permessage_deflate
int memLevel = 4; int memLevel = 4;
}; };
/** Pong callback option. /** Ping callback option.
Sets the callback to be invoked whenever a pong is received Sets the callback to be invoked whenever a ping or pong is
during a call to @ref beast::websocket::stream::read, received during a call to
@ref beast::websocket::stream::read,
@ref beast::websocket::stream::read_frame, @ref beast::websocket::stream::read_frame,
@ref beast::websocket::stream::async_read, or @ref beast::websocket::stream::async_read, or
@ref beast::websocket::stream::async_read_frame. @ref beast::websocket::stream::async_read_frame.
Unlike completion handlers, the callback will be invoked for Unlike completion handlers, the callback will be invoked for
each received pong during a call to any synchronous or each received ping and pong pong during a call to any
asynchronous read function. The operation is passive, with synchronous or asynchronous read function. The operation is
no associated error code, and triggered by reads. passive, with no associated error code, and triggered by reads.
The signature of the callback must be: The signature of the callback must be:
@code @code
void callback( void
callback(
bool is_pong, // `true` if this is a pong
ping_data const& payload // Payload of the pong frame ping_data const& payload // Payload of the pong frame
); );
@endcode @endcode
If the read operation receiving a pong frame is an asynchronous The value of `is_pong` will be `true` if a pong control frame
operation, the callback will be invoked using the same method as is received, and `false` if a ping control frame is received.
that used to invoke the final handler.
If the read operation receiving a ping or pong frame is an
asynchronous operation, the callback will be invoked using
the same method as that used to invoke the final handler.
@note Objects of this type are used with @note Objects of this type are used with
@ref beast::websocket::stream::set_option. @ref beast::websocket::stream::set_option.
To remove the pong callback, construct the option with To remove the ping callback, construct the option with
no parameters: `set_option(pong_callback{})` no parameters: `set_option(ping_callback{})`
*/ */
#if GENERATING_DOCS #if GENERATING_DOCS
using pong_callback = implementation_defined; using ping_callback = implementation_defined;
#else #else
struct pong_callback struct ping_callback
{ {
detail::pong_cb value; detail::ping_cb value;
pong_callback() = default; ping_callback() = default;
pong_callback(pong_callback&&) = default; ping_callback(ping_callback&&) = default;
pong_callback(pong_callback const&) = default; ping_callback(ping_callback const&) = default;
explicit explicit
pong_callback(detail::pong_cb f) ping_callback(detail::ping_cb f)
: value(std::move(f)) : value(std::move(f))
{ {
} }

View File

@ -225,11 +225,11 @@ public:
o = pmd_opts_; o = pmd_opts_;
} }
/// Set the pong callback /// Set the ping callback
void void
set_option(pong_callback o) set_option(ping_callback o)
{ {
pong_cb_ = std::move(o.value); ping_cb_ = std::move(o.value);
} }
/// Set the read buffer size /// Set the read buffer size
@ -1119,12 +1119,11 @@ public:
hold all the message payload bytes (which may be zero in length). hold all the message payload bytes (which may be zero in length).
Control frames encountered while reading frame or message data Control frames encountered while reading frame or message data
are handled automatically. Pings are replied to automatically, are handled automatically. Pings are replied to with a pong,
pongs are routed to the pong callback if the option is set, received pings and pongs invoke the @ref ping_callback if the
and close frames initiate the WebSocket close procedure. When a option is set, and close frames initiate the WebSocket close
close frame is received, this call will eventually return procedure. When a close frame is received, this call will
@ref error::closed. Because of the need to handle control frames, eventually return @ref error::closed.
read operations can cause writes to take place.
@param op A value to receive the message type. @param op A value to receive the message type.
This object must remain valid until the handler is called. This object must remain valid until the handler is called.
@ -1155,13 +1154,12 @@ public:
hold all the message payload bytes (which may be zero in length). hold all the message payload bytes (which may be zero in length).
Control frames encountered while reading frame or message data Control frames encountered while reading frame or message data
are handled automatically. Pings are replied to automatically, are handled automatically. Pings are replied to with a pong,
pongs are routed to the pong callback if the option is set, received pings and pongs invoke the @ref ping_callback if the
and close frames initiate the WebSocket close procedure. When a option is set, and close frames initiate the WebSocket close
close frame is received, this call will eventually return procedure. When a close frame is received, this call will
@ref error::closed. Because of the need to handle control frames, eventually return @ref error::closed.
read operations can cause writes to take place.
@param op A value to receive the message type. @param op A value to receive the message type.
This object must remain valid until the handler is called. This object must remain valid until the handler is called.
@ -1196,14 +1194,17 @@ public:
hold all the message payload bytes (which may be zero in length). hold all the message payload bytes (which may be zero in length).
Control frames encountered while reading frame or message data Control frames encountered while reading frame or message data
are handled automatically. Pings are replied to automatically, are handled automatically. Pings are replied to with a pong,
pongs are routed to the pong callback if the option is set, received pings and pongs invoke the @ref ping_callback if the
and close frames initiate the WebSocket close procedure. When a option is set, and close frames initiate the WebSocket close
close frame is received, this call will eventually return procedure. When a close frame is received, this call will
@ref error::closed. Because of the need to handle control eventually return @ref error::closed.
frames, these read operations can cause writes to take place.
Despite this, calls to `async_read` and `async_read_frame` Because of the need to handle control frames, read operations
only count as read operations. can cause writes to take place. These writes are managed
transparently; callers can still have one active asynchronous
read and asynchronous write operation pending simultaneously
(a user initiated call to @ref async_close counts as a write).
@param op A value to receive the message type. @param op A value to receive the message type.
This object must remain valid until the handler is called. This object must remain valid until the handler is called.
@ -1255,13 +1256,12 @@ public:
fi.fin == true, and zero bytes placed into the stream buffer. fi.fin == true, and zero bytes placed into the stream buffer.
Control frames encountered while reading frame or message data Control frames encountered while reading frame or message data
are handled automatically. Pings are replied to automatically, are handled automatically. Pings are replied to with a pong,
pongs are routed to the pong callback if the option is set, received pings and pongs invoke the @ref ping_callback if the
and close frames initiate the WebSocket close procedure. When a option is set, and close frames initiate the WebSocket close
close frame is received, this call will eventually return procedure. When a close frame is received, this call will
@ref error::closed. Because of the need to handle control frames, eventually return @ref error::closed.
read operations can cause writes to take place.
@param fi An object to store metadata about the message. @param fi An object to store metadata about the message.
@param dynabuf A dynamic buffer to hold the message data after @param dynabuf A dynamic buffer to hold the message data after
@ -1294,13 +1294,12 @@ public:
fi.fin == true, and zero bytes placed into the stream buffer. fi.fin == true, and zero bytes placed into the stream buffer.
Control frames encountered while reading frame or message data Control frames encountered while reading frame or message data
are handled automatically. Pings are replied to automatically, are handled automatically. Pings are replied to with a pong,
pongs are routed to the pong callback if the option is set, received pings and pongs invoke the @ref ping_callback if the
and close frames initiate the WebSocket close procedure. When a option is set, and close frames initiate the WebSocket close
close frame is received, this call will eventually return procedure. When a close frame is received, this call will
@ref error::closed. Because of the need to handle control frames, eventually return @ref error::closed.
read operations can cause writes to take place.
@param fi An object to store metadata about the message. @param fi An object to store metadata about the message.
@param dynabuf A dynamic buffer to hold the message data after @param dynabuf A dynamic buffer to hold the message data after
@ -1338,16 +1337,17 @@ public:
the stream buffer. the stream buffer.
Control frames encountered while reading frame or message data Control frames encountered while reading frame or message data
are handled automatically. Pings are replied to automatically, are handled automatically. Pings are replied to with a pong,
pongs are routed to the pong callback if the option is set, received pings and pongs invoke the @ref ping_callback if the
and close frames initiate the WebSocket close procedure. When a option is set, and close frames initiate the WebSocket close
close frame is received, this call will eventually return procedure. When a close frame is received, this call will
@ref error::closed. Because of the need to handle control frames, eventually return @ref error::closed.
read operations can cause writes to take place. These writes are
managed transparently; callers can still have one active Because of the need to handle control frames, read operations
asynchronous read and asynchronous write operation pending can cause writes to take place. These writes are managed
simultaneously (a user initiated call to @ref async_close transparently; callers can still have one active asynchronous
counts as a write). read and asynchronous write operation pending simultaneously
(a user initiated call to @ref async_close counts as a write).
@param fi An object to store metadata about the message. @param fi An object to store metadata about the message.
This object must remain valid until the handler is called. This object must remain valid until the handler is called.

View File

@ -1062,9 +1062,10 @@ public:
// send ping and message // send ping and message
bool pong = false; bool pong = false;
ws.set_option(pong_callback{ ws.set_option(ping_callback{
[&](ping_data const& payload) [&](bool is_pong, ping_data const& payload)
{ {
BEAST_EXPECT(is_pong);
BEAST_EXPECT(! pong); BEAST_EXPECT(! pong);
pong = true; pong = true;
BEAST_EXPECT(payload == ""); BEAST_EXPECT(payload == "");
@ -1081,12 +1082,13 @@ public:
BEAST_EXPECT(op == opcode::binary); BEAST_EXPECT(op == opcode::binary);
BEAST_EXPECT(to_string(db.data()) == "Hello"); BEAST_EXPECT(to_string(db.data()) == "Hello");
} }
ws.set_option(pong_callback{}); ws.set_option(ping_callback{});
// send ping and fragmented message // send ping and fragmented message
ws.set_option(pong_callback{ ws.set_option(ping_callback{
[&](ping_data const& payload) [&](bool is_pong, ping_data const& payload)
{ {
BEAST_EXPECT(is_pong);
BEAST_EXPECT(payload == "payload"); BEAST_EXPECT(payload == "payload");
}}); }});
ws.ping("payload"); ws.ping("payload");
@ -1101,7 +1103,7 @@ public:
BEAST_EXPECT(pong == 1); BEAST_EXPECT(pong == 1);
BEAST_EXPECT(to_string(db.data()) == "Hello, World!"); BEAST_EXPECT(to_string(db.data()) == "Hello, World!");
} }
ws.set_option(pong_callback{}); ws.set_option(ping_callback{});
// send pong // send pong
c.pong(ws, ""); c.pong(ws, "");