diff --git a/CHANGELOG.md b/CHANGELOG.md index 5863b5cb..393b2237 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ Version 67: * Merge stream_base to stream and tidy * 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: diff --git a/doc/7_6_control.qbk b/doc/7_6_control.qbk index ec79be7d..d39f4f08 100644 --- a/doc/7_6_control.qbk +++ b/doc/7_6_control.qbk @@ -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 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.beast__websocket__error `error::closed`] being delivered -to the caller in a subsequent read operation, assuming no other error -takes place. +error code +[link beast.ref.beast__websocket__error `error::closed`] +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 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, 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 by either peer on an established WebSocket connection. They are sent using the functions [link beast.ref.beast__websocket__stream.ping `ping`] and [link beast.ref.beast__websocket__stream.pong `pong`]. - -To be notified of ping and pong control frames, callers may register a -"ping callback" using [link beast.ref.beast__websocket__stream.set_option `set_option`]. +To be notified of control frames, including pings, pongs, and close frames, +callers may register a ['control callback] using +[link beast.ref.beast__websocket__stream.control_callback `control_callback`]. The object provided with this option should be callable with the following signature: @@ -66,9 +67,10 @@ to receive pings and pongs, a synchronous or asynchronous stream read function must be active. [note - 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. + When an asynchronous read function receives a control frame, the + control callback is invoked in the same manner as that used to + invoke the final completion handler of the corresponding read + function. ] [heading Close Frames] diff --git a/doc/quickref.xml b/doc/quickref.xml index 0986ad41..3e27d12a 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -132,6 +132,7 @@ close_code error + frame_type diff --git a/include/beast/websocket/impl/read.ipp b/include/beast/websocket/impl/read.ipp index f3fbde36..516f7466 100644 --- a/include/beast/websocket/impl/read.ipp +++ b/include/beast/websocket/impl/read.ipp @@ -437,8 +437,9 @@ operator()(error_code ec, ping_data payload; detail::read(payload, d.fb.data()); d.fb.consume(d.fb.size()); - if(d.ws.ping_cb_) - d.ws.ping_cb_(false, payload); + if(d.ws.ctrl_cb_) + d.ws.ctrl_cb_( + frame_type::ping, payload); if(d.ws.wr_close_) { // ignore ping when closing @@ -463,8 +464,9 @@ operator()(error_code ec, code = close_code::none; ping_data payload; detail::read(payload, d.fb.data()); - if(d.ws.ping_cb_) - d.ws.ping_cb_(true, payload); + if(d.ws.ctrl_cb_) + d.ws.ctrl_cb_( + frame_type::pong, payload); d.fb.consume(d.fb.size()); d.state = do_read_fh; break; @@ -478,6 +480,9 @@ operator()(error_code ec, d.state = do_fail; break; } + if(d.ws.ctrl_cb_) + d.ws.ctrl_cb_(frame_type::close, + d.ws.cr_.reason); if(! d.ws.wr_close_) { auto cr = d.ws.cr_; @@ -791,8 +796,8 @@ read_frame(DynamicBuffer& dynabuf, error_code& ec) ping_data payload; detail::read(payload, fb.data()); fb.consume(fb.size()); - if(ping_cb_) - ping_cb_(false, payload); + if(ctrl_cb_) + ctrl_cb_(frame_type::ping, payload); write_ping(fb, detail::opcode::pong, payload); boost::asio::write(stream_, fb.data(), ec); @@ -805,8 +810,8 @@ read_frame(DynamicBuffer& dynabuf, error_code& ec) { ping_data payload; detail::read(payload, fb.data()); - if(ping_cb_) - ping_cb_(true, payload); + if(ctrl_cb_) + ctrl_cb_(frame_type::pong, payload); continue; } BOOST_ASSERT(fh.op == detail::opcode::close); @@ -814,6 +819,8 @@ read_frame(DynamicBuffer& dynabuf, error_code& ec) detail::read(cr_, fb.data(), code); if(code != close_code::none) goto do_close; + if(ctrl_cb_) + ctrl_cb_(frame_type::close, cr_.reason); if(! wr_close_) { auto cr = cr_; diff --git a/include/beast/websocket/stream.hpp b/include/beast/websocket/stream.hpp index 2047edde..4f77114e 100644 --- a/include/beast/websocket/stream.hpp +++ b/include/beast/websocket/stream.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -47,6 +48,22 @@ using request_type = http::request; /// The type of object holding HTTP Upgrade responses using response_type = http::response; +/** 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; - using ping_callback_type = - std::function; + using control_cb_type = + std::function; struct op {}; @@ -125,7 +142,7 @@ class stream std::size_t rd_buf_size_ = 4096; // read buffer size detail::opcode wr_opcode_ = 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 bool failed_; // the connection failed @@ -453,10 +470,11 @@ public: 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 - received during a call to one of the following functions: + Sets the callback to be invoked whenever a ping, pong, + 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_frame @@ -464,33 +482,38 @@ public: @li @ref beast::websocket::stream::async_read_frame Unlike completion handlers, the callback will be invoked - for each received ping and pong during a call to any - synchronous or asynchronous read function. The operation is - passive, with no associated error code, and triggered by reads. + for each control frame 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( - bool is_pong, // `true` if this is a pong - ping_data const& payload // Payload of the pong frame + frame_type kind, // The type of frame + string_view payload // The payload in the frame ); @endcode - The value of `is_pong` will be `true` if a pong control frame - is received, and `false` if a ping control frame is received. + For close frames, the close reason code may be obtained by + calling the function @ref reason. - If the read operation receiving a ping or pong frame is an - asynchronous operation, the callback will be invoked using + If the read operation which receives the control frame is + an asynchronous operation, the callback will be invoked using 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. */ void - ping_callback( - std::function cb) + control_callback( + std::function cb) { - ping_cb_ = std::move(cb); + ctrl_cb_ = std::move(cb); } /** Set the read buffer size option. @@ -2757,7 +2780,7 @@ public: @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. @li The WebSocket close procedure is started if a close frame @@ -2794,7 +2817,7 @@ public: During reads, the implementation handles control frames as 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. @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 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. @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 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. @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 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. @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 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. @li A pong frame is sent when a ping frame is received. diff --git a/test/websocket/doc_snippets.cpp b/test/websocket/doc_snippets.cpp index 87733ec1..d3e6c053 100644 --- a/test/websocket/doc_snippets.cpp +++ b/test/websocket/doc_snippets.cpp @@ -187,11 +187,11 @@ boost::asio::ip::tcp::socket sock{ios}; { stream ws{ios}; //[ws_snippet_17 - ws.ping_callback( - [](bool is_pong, ping_data const& payload) + ws.control_callback( + [](frame_type kind, string_view payload) { // Do something with the payload - boost::ignore_unused(is_pong, payload); + boost::ignore_unused(kind, payload); }); //] diff --git a/test/websocket/stream.cpp b/test/websocket/stream.cpp index 2c670a60..02bab544 100644 --- a/test/websocket/stream.cpp +++ b/test/websocket/stream.cpp @@ -1656,15 +1656,17 @@ public: c.close(ws, {close_code::going_away, "Going away"}); restart(error::closed); + bool once; + // send ping and message - bool pong = false; - ws.ping_callback( - [&](bool is_pong, ping_data const& payload) + once = false; + ws.control_callback( + [&](frame_type kind, string_view s) { - BEAST_EXPECT(is_pong); - BEAST_EXPECT(! pong); - pong = true; - BEAST_EXPECT(payload == ""); + BEAST_EXPECT(kind == frame_type::pong); + BEAST_EXPECT(! once); + once = true; + BEAST_EXPECT(s == ""); }); c.ping(ws, ""); ws.binary(true); @@ -1673,18 +1675,21 @@ public: // receive echoed message multi_buffer db; c.read(ws, db); - BEAST_EXPECT(pong == 1); + BEAST_EXPECT(once); BEAST_EXPECT(ws.got_binary()); BEAST_EXPECT(to_string(db.data()) == "Hello"); } - ws.ping_callback({}); + ws.control_callback({}); // send ping and fragmented message - ws.ping_callback( - [&](bool is_pong, ping_data const& payload) + once = false; + ws.control_callback( + [&](frame_type kind, string_view s) { - BEAST_EXPECT(is_pong); - BEAST_EXPECT(payload == "payload"); + BEAST_EXPECT(kind == frame_type::pong); + BEAST_EXPECT(! once); + once = true; + BEAST_EXPECT(s == "payload"); }); ws.ping("payload"); c.write_frame(ws, false, sbuf("Hello, ")); @@ -1694,10 +1699,10 @@ public: // receive echoed message multi_buffer db; c.read(ws, db); - BEAST_EXPECT(pong == 1); + BEAST_EXPECT(once); BEAST_EXPECT(to_string(db.data()) == "Hello, World!"); } - ws.ping_callback({}); + ws.control_callback({}); // send pong c.pong(ws, "");