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 @@
HandshakingMessagesFrames
- Control frames
- Pong messages
+ Control FramesBuffersAsynchronous interfaceThe 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;