Connection closure is a difficult part of the connection lifecycle with choices needing to be made by Hypercorn about how to respond and what to send to the ASGI application.
Before a connection is fully closed, it is often ‘half-closed’ by one side sending an EOF (empty bytestring b””). If sent by the client Hypercorn will not expect any further messages, but will allow messages to be sent to the client.
If the client disconnects unexpectedly, i.e. whilst the server is still expecting to read or send data, the read/send socket action will raise an exception. This exception is caught and a Closed event is sent to the protocol. The protocol should then send each stream a StreamClosed event and delete the stream.
When testing with the h11 connecion a EndOfMessage event generates an empty bytestring - this should not be sent as it would usually be ignored by the socket code. Hence why the helpers in the test code ignore empty bytestrings on send.
In the normal course of actions a stream should send a EndBody or EndData followed by a StreamClosed event to indicate that the stream has finished and the connection can be closed. However if the application errors the stream may only be able to send a StreamClosed event. Therefore the protocol only sends a StreamClosed event back to the stream on receipt of the StreamClosed from the stream.
The protocol only sends a Closed event to the server if the connection must be closed, e.g. HTTP/1 without keep alive or an error.
I’ve chosen to allow ASGI applications to continue to send messages to the server after the connection has closed and after the server has sent a disconnect message. Specifically Hypercorn will not error and instead no-op on receipt. This ensures that there isn’t a race condition after the server has sent the disconnect message.
Hypercorn guarantees to send the disconnect message, and send it only once, to each application instance. This message will be sent on closure of the connection (either on client or server closure).