diff --git a/Sources/GRPCNIOTransportHTTP2TransportServices/HTTP2ClientTransport+TransportServices.swift b/Sources/GRPCNIOTransportHTTP2TransportServices/HTTP2ClientTransport+TransportServices.swift index 386b3a0..ffc80e2 100644 --- a/Sources/GRPCNIOTransportHTTP2TransportServices/HTTP2ClientTransport+TransportServices.swift +++ b/Sources/GRPCNIOTransportHTTP2TransportServices/HTTP2ClientTransport+TransportServices.swift @@ -175,6 +175,8 @@ extension HTTP2ClientTransport.TransportServices { } } + config.clientBootstrapNWParametersConfigurator?(bootstrap) + let (channel, multiplexer) = try await bootstrap.connect(to: address) { channel in channel.eventLoop.makeCompletedFuture { try channel.pipeline.syncOperations.configureGRPCClientPipeline( @@ -215,6 +217,11 @@ extension HTTP2ClientTransport.TransportServices { /// Channel callbacks for debugging. public var channelDebuggingCallbacks: HTTP2ClientTransport.Config.ChannelDebuggingCallbacks + /// Customise the NWParameters used in the NIO Transport Services bootstrap when creating the connection channel. + public var clientBootstrapNWParametersConfigurator: ( + @Sendable (NIOTSConnectionBootstrap) -> Void + )? + /// Creates a new connection configuration. /// /// - Parameters: @@ -223,6 +230,8 @@ extension HTTP2ClientTransport.TransportServices { /// - connection: Connection configuration. /// - compression: Compression configuration. /// - channelDebuggingCallbacks: Channel callbacks for debugging. + /// - clientBootstrapNWParametersConfigurator: Customise the NWParameters used in the NIO Transport + /// Services bootstrap when creating the connection channel. /// /// - SeeAlso: ``defaults(configure:)`` and ``defaults``. public init( @@ -230,7 +239,8 @@ extension HTTP2ClientTransport.TransportServices { backoff: HTTP2ClientTransport.Config.Backoff, connection: HTTP2ClientTransport.Config.Connection, compression: HTTP2ClientTransport.Config.Compression, - channelDebuggingCallbacks: HTTP2ClientTransport.Config.ChannelDebuggingCallbacks + channelDebuggingCallbacks: HTTP2ClientTransport.Config.ChannelDebuggingCallbacks, + clientBootstrapNWParametersConfigurator: (@Sendable (NIOTSConnectionBootstrap) -> Void)? ) { self.http2 = http2 self.connection = connection @@ -256,7 +266,8 @@ extension HTTP2ClientTransport.TransportServices { backoff: .defaults, connection: .defaults, compression: .defaults, - channelDebuggingCallbacks: .defaults + channelDebuggingCallbacks: .defaults, + clientBootstrapNWParametersConfigurator: nil ) configure(&config) return config diff --git a/Sources/GRPCNIOTransportHTTP2TransportServices/HTTP2ServerTransport+TransportServices.swift b/Sources/GRPCNIOTransportHTTP2TransportServices/HTTP2ServerTransport+TransportServices.swift index 0a70437..d8dedc5 100644 --- a/Sources/GRPCNIOTransportHTTP2TransportServices/HTTP2ServerTransport+TransportServices.swift +++ b/Sources/GRPCNIOTransportHTTP2TransportServices/HTTP2ServerTransport+TransportServices.swift @@ -57,6 +57,8 @@ extension HTTP2ServerTransport { .tlsOptions(try NWProtocolTLS.Options(tlsConfig)) } + config.serverBootstrapNWParametersConfigurator?(bootstrap) + let serverChannel = try await bootstrap .serverChannelOption(.socketOption(.so_reuseaddr), value: 1) @@ -164,6 +166,11 @@ extension HTTP2ServerTransport.TransportServices { /// Channel callbacks for debugging. public var channelDebuggingCallbacks: HTTP2ServerTransport.Config.ChannelDebuggingCallbacks + /// Customise the NWParameters used in the NIO Transport Services bootstrap when creating the listening channel. + public var serverBootstrapNWParametersConfigurator: ( + @Sendable (NIOTSListenerBootstrap) -> Void + )? + /// Construct a new `Config`. /// - Parameters: /// - compression: Compression configuration. @@ -171,6 +178,8 @@ extension HTTP2ServerTransport.TransportServices { /// - http2: HTTP2 configuration. /// - rpc: RPC configuration. /// - channelDebuggingCallbacks: Channel callbacks for debugging. + /// - serverBootstrapNWParametersConfigurator: Customise the NWParameters used in the NIO Transport + /// Services bootstrap when creating the listening channel. /// /// - SeeAlso: ``defaults(configure:)`` and ``defaults``. public init( @@ -178,13 +187,15 @@ extension HTTP2ServerTransport.TransportServices { connection: HTTP2ServerTransport.Config.Connection, http2: HTTP2ServerTransport.Config.HTTP2, rpc: HTTP2ServerTransport.Config.RPC, - channelDebuggingCallbacks: HTTP2ServerTransport.Config.ChannelDebuggingCallbacks + channelDebuggingCallbacks: HTTP2ServerTransport.Config.ChannelDebuggingCallbacks, + serverBootstrapNWParametersConfigurator: (@Sendable (NIOTSListenerBootstrap) -> Void)? ) { self.compression = compression self.connection = connection self.http2 = http2 self.rpc = rpc self.channelDebuggingCallbacks = channelDebuggingCallbacks + self.serverBootstrapNWParametersConfigurator = serverBootstrapNWParametersConfigurator } public static var defaults: Self { @@ -203,7 +214,8 @@ extension HTTP2ServerTransport.TransportServices { connection: .defaults, http2: .defaults, rpc: .defaults, - channelDebuggingCallbacks: .defaults + channelDebuggingCallbacks: .defaults, + serverBootstrapNWParametersConfigurator: nil ) configure(&config) return config diff --git a/Tests/GRPCNIOTransportHTTP2Tests/HTTP2TransportNIOTransportServicesTests.swift b/Tests/GRPCNIOTransportHTTP2Tests/HTTP2TransportNIOTransportServicesTests.swift index 4198c95..5e25c1e 100644 --- a/Tests/GRPCNIOTransportHTTP2Tests/HTTP2TransportNIOTransportServicesTests.swift +++ b/Tests/GRPCNIOTransportHTTP2Tests/HTTP2TransportNIOTransportServicesTests.swift @@ -20,6 +20,7 @@ import GRPCNIOTransportCore import GRPCNIOTransportHTTP2TransportServices import XCTest import NIOSSL +import NIOConcurrencyHelpers final class HTTP2TransportNIOTransportServicesTests: XCTestCase { func testGetListeningAddress_IPv4() async throws { @@ -207,6 +208,65 @@ final class HTTP2TransportNIOTransportServicesTests: XCTestCase { XCTAssertEqual(grpcTLSConfig.serverCertificateVerification, .fullVerification) XCTAssertEqual(grpcTLSConfig.trustRoots, .systemDefault) } + + func testServerNWParametersConfiguratorGetsCalled() async throws { + let configuratorCalledCount = NIOLockedValueBox(0) + let transport = GRPCNIOTransportCore.HTTP2ServerTransport.TransportServices( + address: .ipv4(host: "0.0.0.0", port: 0), + transportSecurity: .plaintext, + config: .defaults(configure: { config in + config.serverBootstrapNWParametersConfigurator = { _ in + configuratorCalledCount.withLockedValue { $0 += 1 } + } + }) + ) + + try await withThrowingDiscardingTaskGroup { group in + group.addTask { + try await transport.listen { _, _ in } + } + + group.addTask { + _ = try await transport.listeningAddress + XCTAssertEqual(1, configuratorCalledCount.withLockedValue({ $0 })) + transport.beginGracefulShutdown() + } + } + } + + func testClientNWParametersConfiguratorGetsCalled() async throws { + let (stream, continuation) = AsyncStream.makeStream(of: Void.self) + + let clientTransport = try GRPCNIOTransportCore.HTTP2ClientTransport.TransportServices( + target: .ipv4(host: "0.0.0.0", port: 0), + transportSecurity: .plaintext, + config: .defaults(configure: { config in + config.clientBootstrapNWParametersConfigurator = { _ in + continuation.finish() + } + }) + ) + + await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { + // It doesn't matter if the connection actually fails: the configurator will run before + // we attempt to connect anyways. + try? await clientTransport.connect() + } + + group.addTask { + // Timeout after a second if we haven't called the configurator closure. + try await Task.sleep(for: .seconds(1)) + XCTFail("clientBootstrapNWParametersConfigurator was not called.") + throw CancellationError() + } + + for await _ in stream { + // If/when we exit this loop, it means we have called the configurator closure + } + group.cancelAll() + } + } } enum HTTP2TransportNIOTransportServicesTestsError: Error {