diff --git a/CHANGELOG.md b/CHANGELOG.md index 80390c86..539372a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ HTTP WebSocket * Write buffer option does not change capacity +* Close connection during async_read on close frame Core diff --git a/doc/websocket.qbk b/doc/websocket.qbk index 2f326839..8834d134 100644 --- a/doc/websocket.qbk +++ b/doc/websocket.qbk @@ -14,8 +14,7 @@ Handshaking Messages Frames - Control frames - Pong messages + Control Frames Buffers Asynchronous interface The io_service @@ -292,42 +291,45 @@ void echo(beast::websocket::stream& ws) -[section:controlframes Control frames] +[section:control Control Frames] -During read operations, the implementation automatically reads and processes -WebSocket control frames such as ping, pong, and close. Pings are replied -to as soon as possible, 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 to the caller in a subsequent read operation, assuming no other error +Control frames are small (less than 128 bytes) messages entirely contained +in an individual WebSocket frame. They may be sent at any time by either +peer on an established connection, and can appear in between continuation +frames for a message. There are three types of control frames: ping, pong, +and close. + +A sent ping indicates a request that the sender wants to receive a pong. A +pong is a response to a ping. Pongs may be sent unsolicited, at any time. +One use for an unsolicited pong is to inform the remote peer that the +session is still active after a long period of inactivity. A close frame +indicates that the remote peer wishes to close the WebSocket connection. +The connection is considered gracefully closed when each side has sent +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 +to the caller in a subsequent read operation, assuming no other error takes place. -To ensure timely delivery of control frames, large messages can be broken up -into smaller sized frames. The automatic fragment option turns on this -feature, and the write buffer size option determines the maximum size of -the fragments: -``` - ... - ws.set_option(beast::websocket::auto_fragment{true}); - ws.set_option(beast::websocket::write_buffer_size{16384}); -``` +A consequence of this automatic behavior is that caller-initiated read +operations can cause socket writes. However, these writes will not +compete with caller-initiated write operations. For the purposes of +correctness with respect to the stream invariants, caller-initiated +read operations still only count as a read. This means that callers can +have a simultaneous active read and write operation in progress, while +the implementation also automatically handles control frames. -The WebSocket protocol defines a procedure and control message for initiating -a close of the session. Handling of close initiated by the remote end of the -connection is performed automatically. To manually initiate a close, use -[link beast.ref.websocket__stream.close `close`]: -``` - ws.close(); -``` +[heading Ping and Pong Frames] -[note To receive the [link beast.ref.websocket__error `error::closed`] -error, a read operation is required. ] - -[endsect] - - - -[section:pongs Pong messages] +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.websocket__stream.ping `ping`] and + [link beast.ref.websocket__stream.ping `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 @@ -348,9 +350,47 @@ 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. -[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.] +[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. +] + +[heading Close Frames] + +The WebSocket protocol defines a procedure and control message for initiating +a close of the session. Handling of close initiated by the remote end of the +connection is performed automatically. To manually initiate a close, use +the [link beast.ref.websocket__stream.close `close`] function: +``` + ws.close(); +``` + +When the remote peer initiates a close by sending a close frame, Beast +will handle it for you by causing the next read to return `error::closed`. +When this error code is delivered, it indicates to the application that +the WebSocket connection has been closed cleanly, and that the TCP/IP +connection has been closed. After initiating a close, it is necessary to +continue reading messages until receiving the error `error::closed`. This +is because the remote peer may still be sending message and control frames +before it receives and responds to the close frame. + +[important + To receive the [link beast.ref.websocket__error `error::closed`] + error, a read operation is required. +] + +[heading Auto-fragment] + +To ensure timely delivery of control frames, large messages can be broken up +into smaller sized frames. The automatic fragment option turns on this +feature, and the write buffer size option determines the maximum size of +the fragments: +``` + ... + ws.set_option(beast::websocket::auto_fragment{true}); + ws.set_option(beast::websocket::write_buffer_size{16384}); +``` [endsect] diff --git a/include/beast/websocket/impl/read.ipp b/include/beast/websocket/impl/read.ipp index 0b2feeb5..02ddd3c2 100644 --- a/include/beast/websocket/impl/read.ipp +++ b/include/beast/websocket/impl/read.ipp @@ -162,6 +162,7 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) do_pong = 11, do_close_resume = 13, do_close = 15, + do_teardown = 16, do_fail = 18, do_call_handler = 99 @@ -379,9 +380,8 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) d.state = do_close; break; } - // call handler; - ec = error::closed; - goto upcall; + d.state = do_teardown; + break; } //------------------------------------------------------------------ @@ -454,7 +454,7 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) //------------------------------------------------------------------ case do_close: - d.state = do_close + 1; + d.state = do_teardown; d.ws.wr_close_ = true; BOOST_ASSERT(! d.ws.wr_block_); d.ws.wr_block_ = &d; @@ -462,13 +462,15 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) d.fb.data(), std::move(*this)); return; - case do_close + 1: - d.state = do_close + 2; + //------------------------------------------------------------------ + + case do_teardown: + d.state = do_teardown + 1; websocket_helpers::call_async_teardown( d.ws.next_layer(), std::move(*this)); return; - case do_close + 2: + case do_teardown + 1: // call handler ec = error::closed; goto upcall;