From 765cb22b48c1a7fbcefb98406b68e8e149149233 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 3 Feb 2017 16:22:28 -0500 Subject: [PATCH] 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. --- CHANGELOG.md | 8 ++ doc/quickref.xml | 2 +- doc/websocket.qbk | 40 ++++---- .../beast/websocket/detail/stream_base.hpp | 6 +- include/beast/websocket/impl/read.ipp | 24 +++-- include/beast/websocket/impl/stream.ipp | 2 +- include/beast/websocket/option.hpp | 46 +++++---- include/beast/websocket/stream.hpp | 96 +++++++++---------- test/websocket/stream.cpp | 14 +-- 9 files changed, 131 insertions(+), 107 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aec8f03..f1f6179c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/doc/quickref.xml b/doc/quickref.xml index 238a49fe..0bfafa6d 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -129,7 +129,7 @@ keep_alive message_type permessage_deflate - pong_callback + ping_callback read_buffer_size read_message_max write_buffer_size diff --git a/doc/websocket.qbk b/doc/websocket.qbk index 904a703c..e87e7fad 100644 --- a/doc/websocket.qbk +++ b/doc/websocket.qbk @@ -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] diff --git a/include/beast/websocket/detail/stream_base.hpp b/include/beast/websocket/detail/stream_base.hpp index b183fc46..4f1fa999 100644 --- a/include/beast/websocket/detail/stream_base.hpp +++ b/include/beast/websocket/detail/stream_base.hpp @@ -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; diff --git a/include/beast/websocket/impl/read.ipp b/include/beast/websocket/impl/read.ipp index b495e304..71eae270 100644 --- a/include/beast/websocket/impl/read.ipp +++ b/include/beast/websocket/impl/read.ipp @@ -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( - 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( - 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); diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp index a8c2ff61..7a58c6aa 100644 --- a/include/beast/websocket/impl/stream.ipp +++ b/include/beast/websocket/impl/stream.ipp @@ -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()); diff --git a/include/beast/websocket/option.hpp b/include/beast/websocket/option.hpp index e656d064..8f176f2c 100644 --- a/include/beast/websocket/option.hpp +++ b/include/beast/websocket/option.hpp @@ -188,7 +188,7 @@ struct message_type namespace detail { -using pong_cb = std::function; +using ping_cb = std::function; } // 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)) { } diff --git a/include/beast/websocket/stream.hpp b/include/beast/websocket/stream.hpp index 8c1c3cde..b7752435 100644 --- a/include/beast/websocket/stream.hpp +++ b/include/beast/websocket/stream.hpp @@ -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,13 +1154,12 @@ 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,13 +1256,12 @@ 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. @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. 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. @param dynabuf A dynamic buffer to hold the message data after @@ -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. diff --git a/test/websocket/stream.cpp b/test/websocket/stream.cpp index 63256766..2554cc72 100644 --- a/test/websocket/stream.cpp +++ b/test/websocket/stream.cpp @@ -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, "");