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
* 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__message_type">message_type</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_message_max">read_message_max</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
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
initiates the WebSocket close procedure, eventually resulting in the error
code [link beast.ref.websocket__error `error::closed`] being delivered
ping and pongs are delivered to the ping callback. The receipt of a close
frame initiates the WebSocket close procedure, eventually resulting in the
error code [link beast.ref.websocket__error `error::closed`] being delivered
to the caller in a subsequent read operation, assuming no other error
takes place.
@ -331,29 +331,33 @@ using the functions
[link beast.ref.websocket__stream.ping `ping`] and
[link beast.ref.websocket__stream.pong `pong`].
To receive pong control frames, callers may register a "pong callback" using
[link beast.ref.websocket__stream.set_option `set_option`]. The object provided
with this option should be callable with the following signature:
To be notified of ping and pong control frames, callers may register a
"ping callback" using [link beast.ref.websocket__stream.set_option `set_option`].
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
synchronous read functions or asynchronous read functions will invoke the
pong callback, passing the payload in the pong message as the argument.
When a ping callback is registered, all pings and pongs received through
either synchronous read functions or asynchronous read functions will
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
functions, the pong 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
and asynchronous reads. The pong callback is passive; in order to receive
pongs, a synchronous or asynchronous stream read function must be active.
functions, the ping callback only needs to be set once. The callback is not
reset when a ping or pong is received. The same callback is used for both
synchronous and asynchronous reads. The ping callback is passive; in order
to receive pings and pongs, a synchronous or asynchronous stream read
function must be active.
[note
When an asynchronous read function receives a pong, the the pong
callback is invoked in the same manner as that used to invoke the
final completion handler of the corresponding read function.
When an asynchronous read function receives a ping or pong, the
ping callback is invoked in the same manner as that used to invoke
the final completion handler of the corresponding read function.
]
[heading Close Frames]

View File

@ -59,14 +59,14 @@ protected:
std::size_t wr_buf_size_ = 4096; // write buffer size
std::size_t rd_buf_size_ = 4096; // read buffer size
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
bool failed_; // the connection failed
bool wr_close_; // sent close frame
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 wr_op_; // invoked after read completes
close_reason cr_; // set from received close frame
@ -212,7 +212,7 @@ open(role_type role)
rd_.cont = false;
wr_close_ = false;
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_.buf_size = 0;

View File

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

View File

@ -76,7 +76,7 @@ reset()
wr_close_ = false;
wr_.cont = false;
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().size());

View File

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

View File

@ -225,11 +225,11 @@ public:
o = pmd_opts_;
}
/// Set the pong callback
/// Set the ping callback
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
@ -1119,12 +1119,11 @@ public:
hold all the message payload bytes (which may be zero in length).
Control frames encountered while reading frame or message data
are handled automatically. Pings are replied to automatically,
pongs are routed to the pong callback if the option is set,
and close frames initiate the WebSocket close procedure. When a
close frame is received, this call will eventually return
@ref error::closed. Because of the need to handle control frames,
read operations can cause writes to take place.
are handled automatically. Pings are replied to with a pong,
received pings and pongs invoke the @ref ping_callback if the
option is set, and close frames initiate the WebSocket close
procedure. When a close frame is received, this call will
eventually return @ref error::closed.
@param op A value to receive the message type.
This object must remain valid until the handler is called.
@ -1155,12 +1154,11 @@ public:
hold all the message payload bytes (which may be zero in length).
Control frames encountered while reading frame or message data
are handled automatically. Pings are replied to automatically,
pongs are routed to the pong callback if the option is set,
and close frames initiate the WebSocket close procedure. When a
close frame is received, this call will eventually return
@ref error::closed. Because of the need to handle control frames,
read operations can cause writes to take place.
are handled automatically. Pings are replied to with a pong,
received pings and pongs invoke the @ref ping_callback if the
option is set, and close frames initiate the WebSocket close
procedure. When a close frame is received, this call will
eventually return @ref error::closed.
@param op A value to receive the message type.
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).
Control frames encountered while reading frame or message data
are handled automatically. Pings are replied to automatically,
pongs are routed to the pong callback if the option is set,
and close frames initiate the WebSocket close procedure. When a
close frame is received, this call will eventually return
@ref error::closed. Because of the need to handle control
frames, these read operations can cause writes to take place.
Despite this, calls to `async_read` and `async_read_frame`
only count as read operations.
are handled automatically. Pings are replied to with a pong,
received pings and pongs invoke the @ref ping_callback if the
option is set, and close frames initiate the WebSocket close
procedure. When a close frame is received, this call will
eventually return @ref error::closed.
Because of the need to handle control frames, 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.
This object must remain valid until the handler is called.
@ -1255,12 +1256,11 @@ public:
fi.fin == true, and zero bytes placed into the stream buffer.
Control frames encountered while reading frame or message data
are handled automatically. Pings are replied to automatically,
pongs are routed to the pong callback if the option is set,
and close frames initiate the WebSocket close procedure. When a
close frame is received, this call will eventually return
@ref error::closed. Because of the need to handle control frames,
read operations can cause writes to take place.
are handled automatically. Pings are replied to with a pong,
received pings and pongs invoke the @ref ping_callback if the
option is set, and close frames initiate the WebSocket close
procedure. When a close frame is received, this call will
eventually return @ref error::closed.
@param fi An object to store metadata about the message.
@ -1294,12 +1294,11 @@ public:
fi.fin == true, and zero bytes placed into the stream buffer.
Control frames encountered while reading frame or message data
are handled automatically. Pings are replied to automatically,
pongs are routed to the pong callback if the option is set,
and close frames initiate the WebSocket close procedure. When a
close frame is received, this call will eventually return
@ref error::closed. Because of the need to handle control frames,
read operations can cause writes to take place.
are handled automatically. Pings are replied to with a pong,
received pings and pongs invoke the @ref ping_callback if the
option is set, and close frames initiate the WebSocket close
procedure. When a close frame is received, this call will
eventually return @ref error::closed.
@param fi An object to store metadata about the message.
@ -1338,16 +1337,17 @@ public:
the stream buffer.
Control frames encountered while reading frame or message data
are handled automatically. Pings are replied to automatically,
pongs are routed to the pong callback if the option is set,
and close frames initiate the WebSocket close procedure. When a
close frame is received, this call will eventually return
@ref error::closed. Because of the need to handle control frames,
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).
are handled automatically. Pings are replied to with a pong,
received pings and pongs invoke the @ref ping_callback if the
option is set, and close frames initiate the WebSocket close
procedure. When a close frame is received, this call will
eventually return @ref error::closed.
Because of the need to handle control frames, 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 fi An object to store metadata about the message.
This object must remain valid until the handler is called.

View File

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