Add CloseableConnection capability#94
Conversation
| /// `true` when the request `.end` has been received on the inbound side, or no | ||
| /// request is currently in flight. `false` between receiving a request `.head` | ||
| /// and its `.end`. | ||
| private var requestEndReceived: Bool = true |
There was a problem hiding this comment.
Why is the default value true?
| if case .buffering = self.finalResponseState { | ||
| self.flushBuffer(context: context) | ||
| } | ||
| context.flush() |
There was a problem hiding this comment.
If self.flushBuffer() was called above, then context.flush() will be called twice (self.flushBuffer() also calls context.flush()).
| // The server requires a NIOAsyncChannel, so we create one from the test channel | ||
| let serverTestAsyncChannel = try await testChannel.eventLoop.submit { | ||
| try NIOAsyncChannel<NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>, Never>( | ||
| try NIOAsyncChannel<NIOHTTPServer.HTTP1ChildConnection, Never>( |
There was a problem hiding this comment.
nit:
| try NIOAsyncChannel<NIOHTTPServer.HTTP1ChildConnection, Never>( | |
| try NIOAsyncChannel<HTTP1ChildConnection, Never>( |
| /// The context's ``ConnectionContext/signalConnectionClose()`` synchronously | ||
| /// flips the shared close flag the channel's ``HTTPKeepAliveHandler`` | ||
| /// observes when writing the next response head. The synchronous set | ||
| /// side-steps any race between firing a NIO event off-loop and writing the | ||
| /// response head off-loop. The handler reacts by amending the next response | ||
| /// head with `Connection: close` and closing the channel once the response | ||
| /// `.end` is written. |
There was a problem hiding this comment.
nit: I think this detail is unnecessary for this function.
| /// If the request handler invokes | ||
| /// ``ConnectionContext/signalConnectionClose()`` while running, the | ||
| /// connection-context's `closeAction` fires `ChannelShouldQuiesceEvent` on | ||
| /// the connection-channel pipeline; NIO's HTTP/2 connection management | ||
| /// handler reacts by sending `GOAWAY`, letting other in-flight streams | ||
| /// complete normally, and finally closing the connection. |
There was a problem hiding this comment.
nit: I think this detail is unnecessary for this function.
| request, | ||
| requestContext, | ||
| reader, | ||
| responseSender in |
There was a problem hiding this comment.
formatting nit: it should be possible to put all these arguments on the same line. request and reader can be replaced with _ if the line length becomes too long. The same applies for all the other test cases below.
| #expect(response.headerFields[.connection] == "close") | ||
| // Look for any duplicate `Connection` headers; the field should | ||
| // appear exactly once with value "close". | ||
| let connectionValues = response.headerFields[values: .connection] | ||
| #expect(connectionValues == ["close"]) |
There was a problem hiding this comment.
| #expect(response.headerFields[.connection] == "close") | |
| // Look for any duplicate `Connection` headers; the field should | |
| // appear exactly once with value "close". | |
| let connectionValues = response.headerFields[values: .connection] | |
| #expect(connectionValues == ["close"]) | |
| // Look for any duplicate `Connection` headers; the field should | |
| // appear exactly once with value "close". | |
| let connectionValues = response.headerFields[values: .connection] | |
| #expect(connectionValues == ["close"]) |
| let _trailing = try await iterator.next() | ||
| #expect(_trailing == nil) |
There was a problem hiding this comment.
nit: the leading underscore in the variable name seems unnecessary. The same applies for the test case below.
| /// - On HTTP/1.1, the response carries `Connection: close` and the | ||
| /// channel is closed once the response has been written. |
There was a problem hiding this comment.
We should document that signalConnectionClose() must be called before sending the response for this to hold true.
| // We deliberately don't assert anything about the `Connection` | ||
| // header here: `responseSender.send(_:)` returns once the head | ||
| // is yielded to the async-stream queue, which may be before the | ||
| // keep-alive handler has actually processed it on the event | ||
| // loop. So the flag set immediately after `send` is sometimes | ||
| // still observed in time to amend the head. The reliable | ||
| // observable is that the channel closes after the response. |
There was a problem hiding this comment.
We can bypass this by creating a promise that the client will fulfil after it receives the head. The server should await that promise before calling requestContext.signalConnectionClose().
This is a follow-up of the connection lifecycle PR: #92
It adds support for signalling that the current connection should close after the in-flight response via a new
CloseableConnectionHTTPServerCapability.NIOHTTPServer.RequestContextconforms to it and exposes this capability viarequestContext.signalConnectionClose().