Add "debug initializer" hook for channels (#801)

Motivation:

As requested in #596, it can be handy to have a lower-level access to
channels (HTTP/1 connection, HTTP/2 connection, or HTTP/2 stream) to
enable a more fine-grained interaction for, say, observability, testing,
etc.

Modifications:

- Add 3 new properties (`http1_1ConnectionDebugInitializer`,
`http2ConnectionDebugInitializer` and
`http2StreamChannelDebugInitializer`) to `HTTPClient.Configuration` with
access to the respective channels. These properties are of `Optional`
type `@Sendable (Channel) -> EventLoopFuture<Void>` and are called when
creating a connection/stream.

Result:

Provides APIs for a lower-level access to channels.

---------

Co-authored-by: Cory Benfield <lukasa@apple.com>
Co-authored-by: David Nadoba <d_nadoba@apple.com>
Co-authored-by: George Barnett <gbarnett@apple.com>
This commit is contained in:
Clinton Nkwocha
2025-04-25 13:11:02 +01:00
committed by GitHub
parent 373862aa09
commit a3d00a65b9
5 changed files with 264 additions and 9 deletions
@@ -35,6 +35,9 @@ final class HTTP2Connection {
let multiplexer: HTTP2StreamMultiplexer
let logger: Logger
/// A method with access to the stream channel that is called when creating the stream.
let streamChannelDebugInitializer: (@Sendable (Channel) -> EventLoopFuture<Void>)?
/// the connection pool that created the connection
let delegate: HTTP2ConnectionDelegate
@@ -95,7 +98,8 @@ final class HTTP2Connection {
decompression: HTTPClient.Decompression,
maximumConnectionUses: Int?,
delegate: HTTP2ConnectionDelegate,
logger: Logger
logger: Logger,
streamChannelDebugInitializer: (@Sendable (Channel) -> EventLoopFuture<Void>)? = nil
) {
self.channel = channel
self.id = connectionID
@@ -114,6 +118,7 @@ final class HTTP2Connection {
)
self.delegate = delegate
self.state = .initialized
self.streamChannelDebugInitializer = streamChannelDebugInitializer
}
deinit {
@@ -128,7 +133,8 @@ final class HTTP2Connection {
delegate: HTTP2ConnectionDelegate,
decompression: HTTPClient.Decompression,
maximumConnectionUses: Int?,
logger: Logger
logger: Logger,
streamChannelDebugInitializer: (@Sendable (Channel) -> EventLoopFuture<Void>)? = nil
) -> EventLoopFuture<(HTTP2Connection, Int)> {
let connection = HTTP2Connection(
channel: channel,
@@ -136,7 +142,8 @@ final class HTTP2Connection {
decompression: decompression,
maximumConnectionUses: maximumConnectionUses,
delegate: delegate,
logger: logger
logger: logger,
streamChannelDebugInitializer: streamChannelDebugInitializer
)
return connection._start0().map { maxStreams in (connection, maxStreams) }
}
@@ -259,8 +266,14 @@ final class HTTP2Connection {
self.openStreams.remove(box)
}
channel.write(request, promise: nil)
return channel.eventLoop.makeSucceededVoidFuture()
if let streamChannelDebugInitializer = self.streamChannelDebugInitializer {
return streamChannelDebugInitializer(channel).map { _ in
channel.write(request, promise: nil)
}
} else {
channel.write(request, promise: nil)
return channel.eventLoop.makeSucceededVoidFuture()
}
} catch {
return channel.eventLoop.makeFailedFuture(error)
}