control_callback replaces ping_callback (API Change):

fix #322

The new callback is informed of all frame types including close.

Actions Required:

* Change calls to websocket::stream::ping_callback to use
  websocket::stream::control_callback

* Change user defined ping callbacks to have the new
  signature and adjust the callback definition appropriately.
This commit is contained in:
Vinnie Falco
2017-06-24 12:11:46 -07:00
parent 44824a4166
commit e7b01dc5c4
7 changed files with 110 additions and 60 deletions

View File

@ -5,6 +5,18 @@ Version 67:
* Merge stream_base to stream and tidy * Merge stream_base to stream and tidy
* Use boost::string_view * Use boost::string_view
API Changes:
* control_callback replaces ping_callback
Actions Required:
* Change calls to websocket::stream::ping_callback to use
websocket::stream::control_callback
* Change user defined ping callbacks to have the new
signature and adjust the callback definition appropriately.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
Version 66: Version 66:

View File

@ -25,9 +25,10 @@ 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
ping and pongs are delivered to the ping callback. The receipt of a close ping and pongs are delivered to the ping callback. The receipt of a close
frame initiates the WebSocket close procedure, eventually resulting in the frame initiates the WebSocket close procedure, eventually resulting in the
error code [link beast.ref.beast__websocket__error `error::closed`] being delivered error code
to the caller in a subsequent read operation, assuming no other error [link beast.ref.beast__websocket__error `error::closed`]
takes place. being delivered to the caller in a subsequent read operation, assuming
no other error takes place.
A consequence of this automatic behavior is that caller-initiated read A consequence of this automatic behavior is that caller-initiated read
operations can cause socket writes. However, these writes will not operations can cause socket writes. However, these writes will not
@ -37,16 +38,16 @@ read operations still only count as a read. This means that callers can
have a simultaneously active read, write, and ping operation in progress, have a simultaneously active read, write, and ping operation in progress,
while the implementation also automatically handles control frames. while the implementation also automatically handles control frames.
[heading Ping and Pong Frames] [heading Control Callback]
Ping and pong messages are control frames which may be sent at any time Ping and pong messages are control frames which may be sent at any time
by either peer on an established WebSocket connection. They are sent by either peer on an established WebSocket connection. They are sent
using the functions using the functions
[link beast.ref.beast__websocket__stream.ping `ping`] and [link beast.ref.beast__websocket__stream.ping `ping`] and
[link beast.ref.beast__websocket__stream.pong `pong`]. [link beast.ref.beast__websocket__stream.pong `pong`].
To be notified of control frames, including pings, pongs, and close frames,
To be notified of ping and pong control frames, callers may register a callers may register a ['control callback] using
"ping callback" using [link beast.ref.beast__websocket__stream.set_option `set_option`]. [link beast.ref.beast__websocket__stream.control_callback `control_callback`].
The object provided with this option should be callable with the following The object provided with this option should be callable with the following
signature: signature:
@ -66,9 +67,10 @@ to receive pings and pongs, a synchronous or asynchronous stream read
function must be active. function must be active.
[note [note
When an asynchronous read function receives a ping or pong, the When an asynchronous read function receives a control frame, the
ping callback is invoked in the same manner as that used to invoke control callback is invoked in the same manner as that used to
the final completion handler of the corresponding read function. invoke the final completion handler of the corresponding read
function.
] ]
[heading Close Frames] [heading Close Frames]

View File

@ -132,6 +132,7 @@
<simplelist type="vert" columns="1"> <simplelist type="vert" columns="1">
<member><link linkend="beast.ref.beast__websocket__close_code">close_code</link></member> <member><link linkend="beast.ref.beast__websocket__close_code">close_code</link></member>
<member><link linkend="beast.ref.beast__websocket__error">error</link></member> <member><link linkend="beast.ref.beast__websocket__error">error</link></member>
<member><link linkend="beast.ref.beast__websocket__frame_type">frame_type</link></member>
</simplelist> </simplelist>
</entry> </entry>
</row> </row>

View File

@ -437,8 +437,9 @@ operator()(error_code ec,
ping_data payload; ping_data payload;
detail::read(payload, d.fb.data()); detail::read(payload, d.fb.data());
d.fb.consume(d.fb.size()); d.fb.consume(d.fb.size());
if(d.ws.ping_cb_) if(d.ws.ctrl_cb_)
d.ws.ping_cb_(false, payload); d.ws.ctrl_cb_(
frame_type::ping, payload);
if(d.ws.wr_close_) if(d.ws.wr_close_)
{ {
// ignore ping when closing // ignore ping when closing
@ -463,8 +464,9 @@ 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.ping_cb_) if(d.ws.ctrl_cb_)
d.ws.ping_cb_(true, payload); d.ws.ctrl_cb_(
frame_type::pong, payload);
d.fb.consume(d.fb.size()); d.fb.consume(d.fb.size());
d.state = do_read_fh; d.state = do_read_fh;
break; break;
@ -478,6 +480,9 @@ operator()(error_code ec,
d.state = do_fail; d.state = do_fail;
break; break;
} }
if(d.ws.ctrl_cb_)
d.ws.ctrl_cb_(frame_type::close,
d.ws.cr_.reason);
if(! d.ws.wr_close_) if(! d.ws.wr_close_)
{ {
auto cr = d.ws.cr_; auto cr = d.ws.cr_;
@ -791,8 +796,8 @@ read_frame(DynamicBuffer& dynabuf, error_code& ec)
ping_data payload; ping_data payload;
detail::read(payload, fb.data()); detail::read(payload, fb.data());
fb.consume(fb.size()); fb.consume(fb.size());
if(ping_cb_) if(ctrl_cb_)
ping_cb_(false, payload); ctrl_cb_(frame_type::ping, payload);
write_ping<static_buffer>(fb, write_ping<static_buffer>(fb,
detail::opcode::pong, payload); detail::opcode::pong, payload);
boost::asio::write(stream_, fb.data(), ec); boost::asio::write(stream_, fb.data(), ec);
@ -805,8 +810,8 @@ read_frame(DynamicBuffer& dynabuf, error_code& ec)
{ {
ping_data payload; ping_data payload;
detail::read(payload, fb.data()); detail::read(payload, fb.data());
if(ping_cb_) if(ctrl_cb_)
ping_cb_(true, payload); ctrl_cb_(frame_type::pong, payload);
continue; continue;
} }
BOOST_ASSERT(fh.op == detail::opcode::close); BOOST_ASSERT(fh.op == detail::opcode::close);
@ -814,6 +819,8 @@ read_frame(DynamicBuffer& dynabuf, error_code& ec)
detail::read(cr_, fb.data(), code); detail::read(cr_, fb.data(), code);
if(code != close_code::none) if(code != close_code::none)
goto do_close; goto do_close;
if(ctrl_cb_)
ctrl_cb_(frame_type::close, cr_.reason);
if(! wr_close_) if(! wr_close_)
{ {
auto cr = cr_; auto cr = cr_;

View File

@ -31,6 +31,7 @@
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
#include <functional>
#include <limits> #include <limits>
#include <type_traits> #include <type_traits>
@ -47,6 +48,22 @@ using request_type = http::request<http::empty_body>;
/// The type of object holding HTTP Upgrade responses /// The type of object holding HTTP Upgrade responses
using response_type = http::response<http::string_body>; using response_type = http::response<http::string_body>;
/** The type of received control frame.
Values of this type are passed to the control frame
callback set using @ref stream::control_callback.
*/
enum class frame_type
{
/// A close frame was received
close,
/// A ping frame was received
ping,
/// A pong frame was received
pong
};
//-------------------------------------------------------------------- //--------------------------------------------------------------------
@ -112,8 +129,8 @@ class stream
friend class frame_test; friend class frame_test;
using ping_callback_type = using control_cb_type =
std::function<void(bool, ping_data const&)>; std::function<void(frame_type, string_view)>;
struct op {}; struct op {};
@ -125,7 +142,7 @@ class stream
std::size_t rd_buf_size_ = 4096; // read buffer size std::size_t rd_buf_size_ = 4096; // read buffer size
detail::opcode wr_opcode_ = detail::opcode wr_opcode_ =
detail::opcode::text; // outgoing message type detail::opcode::text; // outgoing message type
ping_callback_type ping_cb_; // ping callback control_cb_type ctrl_cb_; // control callback
role_type role_; // server or client role_type role_; // server or client
bool failed_; // the connection failed bool failed_; // the connection failed
@ -453,10 +470,11 @@ public:
return wr_opcode_ == detail::opcode::binary; return wr_opcode_ == detail::opcode::binary;
} }
/** Set the ping callback. /** Set the control frame callback.
Sets the callback to be invoked whenever a ping or pong is Sets the callback to be invoked whenever a ping, pong,
received during a call to one of the following functions: or close control frame is received during a call to one
of the following functions:
@li @ref beast::websocket::stream::read @li @ref beast::websocket::stream::read
@li @ref beast::websocket::stream::read_frame @li @ref beast::websocket::stream::read_frame
@ -464,33 +482,38 @@ public:
@li @ref beast::websocket::stream::async_read_frame @li @ref beast::websocket::stream::async_read_frame
Unlike completion handlers, the callback will be invoked Unlike completion handlers, the callback will be invoked
for each received ping and pong during a call to any for each control frame during a call to any synchronous
synchronous or asynchronous read function. The operation is or asynchronous read function. The operation is passive,
passive, with no associated error code, and triggered by reads. 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 void
callback( callback(
bool is_pong, // `true` if this is a pong frame_type kind, // The type of frame
ping_data const& payload // Payload of the pong frame string_view payload // The payload in the frame
); );
@endcode @endcode
The value of `is_pong` will be `true` if a pong control frame For close frames, the close reason code may be obtained by
is received, and `false` if a ping control frame is received. calling the function @ref reason.
If the read operation receiving a ping or pong frame is an If the read operation which receives the control frame is
asynchronous operation, the callback will be invoked using an asynchronous operation, the callback will be invoked using
the same method as that used to invoke the final handler. the same method as that used to invoke the final handler.
@note It is not necessary to send a close frame upon receipt
of a close frame. The implementation does this automatically.
Attempting to send a close frame after a close frame is
received will result in undefined behavior.
@param cb The callback to set. @param cb The callback to set.
*/ */
void void
ping_callback( control_callback(
std::function<void(bool, ping_data const&)> cb) std::function<void(frame_type, string_view)> cb)
{ {
ping_cb_ = std::move(cb); ctrl_cb_ = std::move(cb);
} }
/** Set the read buffer size option. /** Set the read buffer size option.
@ -2757,7 +2780,7 @@ public:
@li A pong frame is sent when a ping frame is received. @li A pong frame is sent when a ping frame is received.
@li The @ref ping_callback is invoked when a ping frame @li The @ref control_callback is invoked when a ping frame
or pong frame is received. or pong frame is received.
@li The WebSocket close procedure is started if a close frame @li The WebSocket close procedure is started if a close frame
@ -2794,7 +2817,7 @@ public:
During reads, the implementation handles control frames as During reads, the implementation handles control frames as
follows: follows:
@li The @ref ping_callback is invoked when a ping frame @li The @ref control_callback is invoked when a ping frame
or pong frame is received. or pong frame is received.
@li A pong frame is sent when a ping frame is received. @li A pong frame is sent when a ping frame is received.
@ -2838,7 +2861,7 @@ public:
During reads, the implementation handles control frames as During reads, the implementation handles control frames as
follows: follows:
@li The @ref ping_callback is invoked when a ping frame @li The @ref control_callback is invoked when a ping frame
or pong frame is received. or pong frame is received.
@li A pong frame is sent when a ping frame is received. @li A pong frame is sent when a ping frame is received.
@ -2895,7 +2918,7 @@ public:
During reads, the implementation handles control frames as During reads, the implementation handles control frames as
follows: follows:
@li The @ref ping_callback is invoked when a ping frame @li The @ref control_callback is invoked when a ping frame
or pong frame is received. or pong frame is received.
@li A pong frame is sent when a ping frame is received. @li A pong frame is sent when a ping frame is received.
@ -2931,7 +2954,7 @@ public:
During reads, the implementation handles control frames as During reads, the implementation handles control frames as
follows: follows:
@li The @ref ping_callback is invoked when a ping frame @li The @ref control_callback is invoked when a ping frame
or pong frame is received. or pong frame is received.
@li A pong frame is sent when a ping frame is received. @li A pong frame is sent when a ping frame is received.
@ -2971,7 +2994,7 @@ public:
During reads, the implementation handles control frames as During reads, the implementation handles control frames as
follows: follows:
@li The @ref ping_callback is invoked when a ping frame @li The @ref control_callback is invoked when a ping frame
or pong frame is received. or pong frame is received.
@li A pong frame is sent when a ping frame is received. @li A pong frame is sent when a ping frame is received.

View File

@ -187,11 +187,11 @@ boost::asio::ip::tcp::socket sock{ios};
{ {
stream<boost::asio::ip::tcp::socket> ws{ios}; stream<boost::asio::ip::tcp::socket> ws{ios};
//[ws_snippet_17 //[ws_snippet_17
ws.ping_callback( ws.control_callback(
[](bool is_pong, ping_data const& payload) [](frame_type kind, string_view payload)
{ {
// Do something with the payload // Do something with the payload
boost::ignore_unused(is_pong, payload); boost::ignore_unused(kind, payload);
}); });
//] //]

View File

@ -1656,15 +1656,17 @@ public:
c.close(ws, {close_code::going_away, "Going away"}); c.close(ws, {close_code::going_away, "Going away"});
restart(error::closed); restart(error::closed);
bool once;
// send ping and message // send ping and message
bool pong = false; once = false;
ws.ping_callback( ws.control_callback(
[&](bool is_pong, ping_data const& payload) [&](frame_type kind, string_view s)
{ {
BEAST_EXPECT(is_pong); BEAST_EXPECT(kind == frame_type::pong);
BEAST_EXPECT(! pong); BEAST_EXPECT(! once);
pong = true; once = true;
BEAST_EXPECT(payload == ""); BEAST_EXPECT(s == "");
}); });
c.ping(ws, ""); c.ping(ws, "");
ws.binary(true); ws.binary(true);
@ -1673,18 +1675,21 @@ public:
// receive echoed message // receive echoed message
multi_buffer db; multi_buffer db;
c.read(ws, db); c.read(ws, db);
BEAST_EXPECT(pong == 1); BEAST_EXPECT(once);
BEAST_EXPECT(ws.got_binary()); BEAST_EXPECT(ws.got_binary());
BEAST_EXPECT(to_string(db.data()) == "Hello"); BEAST_EXPECT(to_string(db.data()) == "Hello");
} }
ws.ping_callback({}); ws.control_callback({});
// send ping and fragmented message // send ping and fragmented message
ws.ping_callback( once = false;
[&](bool is_pong, ping_data const& payload) ws.control_callback(
[&](frame_type kind, string_view s)
{ {
BEAST_EXPECT(is_pong); BEAST_EXPECT(kind == frame_type::pong);
BEAST_EXPECT(payload == "payload"); BEAST_EXPECT(! once);
once = true;
BEAST_EXPECT(s == "payload");
}); });
ws.ping("payload"); ws.ping("payload");
c.write_frame(ws, false, sbuf("Hello, ")); c.write_frame(ws, false, sbuf("Hello, "));
@ -1694,10 +1699,10 @@ public:
// receive echoed message // receive echoed message
multi_buffer db; multi_buffer db;
c.read(ws, db); c.read(ws, db);
BEAST_EXPECT(pong == 1); BEAST_EXPECT(once);
BEAST_EXPECT(to_string(db.data()) == "Hello, World!"); BEAST_EXPECT(to_string(db.data()) == "Hello, World!");
} }
ws.ping_callback({}); ws.control_callback({});
// send pong // send pong
c.pong(ws, ""); c.pong(ws, "");