Skip to content

Commit 3bbd57b

Browse files
authored
Expose APIs to customise NIOTS bootstraps (#2223)
`swift-nio-transport-services` added support for customising the `NWParameters` used in the underlying connections in apple/swift-nio-transport-services#230. This PR adds API in `grpc-swift` v1 to allow users to customise the bootstraps used when creating gRPC connections using NIOTS. This will allow them to, for example, customise their `NWParameters` using the new APIs in `swift-nio-transport-services`.
1 parent d38e16b commit 3bbd57b

File tree

9 files changed

+370
-6
lines changed

9 files changed

+370
-6
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ let packageDependencies: [Package.Dependency] = [
4040
),
4141
.package(
4242
url: "https://github.com/apple/swift-nio-transport-services.git",
43-
from: "1.15.0"
43+
from: "1.24.0"
4444
),
4545
.package(
4646
url: "https://github.com/apple/swift-nio-extras.git",

Sources/GRPC/ClientConnection.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ import Foundation
3333
import NIOSSL
3434
#endif
3535

36+
#if canImport(Network)
37+
import Network
38+
#endif
39+
3640
/// Provides a single, managed connection to a server which is guaranteed to always use the same
3741
/// `EventLoop`.
3842
///
@@ -469,6 +473,21 @@ extension ClientConnection {
469473
@preconcurrency
470474
public var debugChannelInitializer: (@Sendable (Channel) -> EventLoopFuture<Void>)?
471475

476+
#if canImport(Network)
477+
/// A closure allowing to customise the `NWParameters` used when establishing a connection using `NIOTransportServices`.
478+
@available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
479+
public var nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? {
480+
get {
481+
self._nwParametersConfigurator as! (@Sendable (NWParameters) -> Void)?
482+
}
483+
set {
484+
self._nwParametersConfigurator = newValue
485+
}
486+
}
487+
488+
private var _nwParametersConfigurator: (any Sendable)?
489+
#endif
490+
472491
#if canImport(NIOSSL)
473492
/// Create a `Configuration` with some pre-defined defaults. Prefer using
474493
/// `ClientConnection.secure(group:)` to build a connection secured with TLS or

Sources/GRPC/ConnectionManagerChannelProvider.swift

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ import NIOTransportServices
2222
import NIOSSL
2323
#endif
2424

25+
#if canImport(Network)
26+
import Network
27+
#endif
28+
2529
@usableFromInline
2630
internal protocol ConnectionManagerChannelProvider {
2731
/// Make an `EventLoopFuture<Channel>`.
@@ -72,6 +76,52 @@ internal struct DefaultChannelProvider: ConnectionManagerChannelProvider {
7276
@usableFromInline
7377
internal var debugChannelInitializer: Optional<(Channel) -> EventLoopFuture<Void>>
7478

79+
#if canImport(Network)
80+
@available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
81+
@usableFromInline
82+
internal var nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? {
83+
get {
84+
self._nwParametersConfigurator as! (@Sendable (NWParameters) -> Void)?
85+
}
86+
set {
87+
self._nwParametersConfigurator = newValue
88+
}
89+
}
90+
91+
private var _nwParametersConfigurator: (any Sendable)?
92+
#endif
93+
94+
#if canImport(Network)
95+
@inlinable
96+
@available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
97+
internal init(
98+
connectionTarget: ConnectionTarget,
99+
connectionKeepalive: ClientConnectionKeepalive,
100+
connectionIdleTimeout: TimeAmount,
101+
tlsMode: TLSMode,
102+
tlsConfiguration: GRPCTLSConfiguration?,
103+
httpTargetWindowSize: Int,
104+
httpMaxFrameSize: Int,
105+
errorDelegate: ClientErrorDelegate?,
106+
debugChannelInitializer: ((Channel) -> EventLoopFuture<Void>)?,
107+
nwParametersConfigurator: (@Sendable (NWParameters) -> Void)?
108+
) {
109+
self.init(
110+
connectionTarget: connectionTarget,
111+
connectionKeepalive: connectionKeepalive,
112+
connectionIdleTimeout: connectionIdleTimeout,
113+
tlsMode: tlsMode,
114+
tlsConfiguration: tlsConfiguration,
115+
httpTargetWindowSize: httpTargetWindowSize,
116+
httpMaxFrameSize: httpMaxFrameSize,
117+
errorDelegate: errorDelegate,
118+
debugChannelInitializer: debugChannelInitializer
119+
)
120+
121+
self.nwParametersConfigurator = nwParametersConfigurator
122+
}
123+
#endif
124+
75125
@inlinable
76126
internal init(
77127
connectionTarget: ConnectionTarget,
@@ -133,6 +183,12 @@ internal struct DefaultChannelProvider: ConnectionManagerChannelProvider {
133183
errorDelegate: configuration.errorDelegate,
134184
debugChannelInitializer: configuration.debugChannelInitializer
135185
)
186+
187+
#if canImport(Network)
188+
if #available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *) {
189+
self.nwParametersConfigurator = configuration.nwParametersConfigurator
190+
}
191+
#endif
136192
}
137193

138194
private var serverHostname: String? {
@@ -222,6 +278,15 @@ internal struct DefaultChannelProvider: ConnectionManagerChannelProvider {
222278
_ = bootstrap.connectTimeout(connectTimeout)
223279
}
224280

281+
#if canImport(Network)
282+
if #available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *),
283+
let configurator = self.nwParametersConfigurator,
284+
let transportServicesBootstrap = bootstrap as? NIOTSConnectionBootstrap
285+
{
286+
_ = transportServicesBootstrap.configureNWParameters(configurator)
287+
}
288+
#endif
289+
225290
return bootstrap.connect(to: self.connectionTarget)
226291
}
227292
}

Sources/GRPC/ConnectionPool/GRPCChannelPool.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import NIOPosix
1919

2020
import struct Foundation.UUID
2121

22+
#if canImport(Network)
23+
import Network
24+
#endif
25+
2226
public enum GRPCChannelPool {
2327
/// Make a new ``GRPCChannel`` on which calls may be made to gRPC services.
2428
///
@@ -191,6 +195,12 @@ extension GRPCChannelPool {
191195
return SwiftLogNoOpLogHandler()
192196
}
193197
)
198+
199+
#if canImport(Network)
200+
/// `TransportServices` related configuration. This will be ignored unless an appropriate event loop group
201+
/// (e.g. `NIOTSEventLoopGroup`) is used.
202+
public var transportServices: TransportServices = .defaults
203+
#endif
194204
}
195205
}
196206

@@ -299,6 +309,35 @@ extension GRPCChannelPool.Configuration {
299309
}
300310
}
301311

312+
#if canImport(Network)
313+
extension GRPCChannelPool.Configuration {
314+
public struct TransportServices: Sendable {
315+
/// Default transport services configuration.
316+
public static let defaults = Self()
317+
318+
@inlinable
319+
public static func with(_ configure: (inout Self) -> Void) -> Self {
320+
var configuration = Self.defaults
321+
configure(&configuration)
322+
return configuration
323+
}
324+
325+
/// A closure allowing to customise the `NWParameters` used when establishing a connection using `NIOTransportServices`.
326+
@available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
327+
public var nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? {
328+
get {
329+
self._nwParametersConfigurator as! (@Sendable (NWParameters) -> Void)?
330+
}
331+
set {
332+
self._nwParametersConfigurator = newValue
333+
}
334+
}
335+
336+
private var _nwParametersConfigurator: (any Sendable)?
337+
}
338+
}
339+
#endif // canImport(Network)
340+
302341
/// The ID of a connection in the connection pool.
303342
public struct GRPCConnectionID: Hashable, Sendable, CustomStringConvertible {
304343
private enum Value: Sendable, Hashable {

Sources/GRPC/ConnectionPool/PooledChannel.swift

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,36 @@ internal final class PooledChannel: GRPCChannel {
7979

8080
self._scheme = scheme
8181

82-
let provider = DefaultChannelProvider(
82+
let provider: DefaultChannelProvider
83+
#if canImport(Network)
84+
if #available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *) {
85+
provider = DefaultChannelProvider(
86+
connectionTarget: configuration.target,
87+
connectionKeepalive: configuration.keepalive,
88+
connectionIdleTimeout: configuration.idleTimeout,
89+
tlsMode: tlsMode,
90+
tlsConfiguration: configuration.transportSecurity.tlsConfiguration,
91+
httpTargetWindowSize: configuration.http2.targetWindowSize,
92+
httpMaxFrameSize: configuration.http2.maxFrameSize,
93+
errorDelegate: configuration.errorDelegate,
94+
debugChannelInitializer: configuration.debugChannelInitializer,
95+
nwParametersConfigurator: configuration.transportServices.nwParametersConfigurator
96+
)
97+
} else {
98+
provider = DefaultChannelProvider(
99+
connectionTarget: configuration.target,
100+
connectionKeepalive: configuration.keepalive,
101+
connectionIdleTimeout: configuration.idleTimeout,
102+
tlsMode: tlsMode,
103+
tlsConfiguration: configuration.transportSecurity.tlsConfiguration,
104+
httpTargetWindowSize: configuration.http2.targetWindowSize,
105+
httpMaxFrameSize: configuration.http2.maxFrameSize,
106+
errorDelegate: configuration.errorDelegate,
107+
debugChannelInitializer: configuration.debugChannelInitializer
108+
)
109+
}
110+
#else
111+
provider = DefaultChannelProvider(
83112
connectionTarget: configuration.target,
84113
connectionKeepalive: configuration.keepalive,
85114
connectionIdleTimeout: configuration.idleTimeout,
@@ -90,6 +119,7 @@ internal final class PooledChannel: GRPCChannel {
90119
errorDelegate: configuration.errorDelegate,
91120
debugChannelInitializer: configuration.debugChannelInitializer
92121
)
122+
#endif
93123

94124
self._pool = PoolManager.makeInitializedPoolManager(
95125
using: configuration.eventLoopGroup,

Sources/GRPC/Server.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,20 @@ public final class Server: @unchecked Sendable {
127127
_ = transportServicesBootstrap.tlsOptions(from: tlsConfiguration)
128128
}
129129
}
130+
131+
if #available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *),
132+
let configurator = configuration.listenerNWParametersConfigurator,
133+
let transportServicesBootstrap = bootstrap as? NIOTSListenerBootstrap
134+
{
135+
_ = transportServicesBootstrap.configureNWParameters(configurator)
136+
}
137+
138+
if #available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *),
139+
let configurator = configuration.childChannelNWParametersConfigurator,
140+
let transportServicesBootstrap = bootstrap as? NIOTSListenerBootstrap
141+
{
142+
_ = transportServicesBootstrap.configureChildNWParameters(configurator)
143+
}
130144
#endif // canImport(Network)
131145

132146
return
@@ -384,6 +398,34 @@ extension Server {
384398
/// the need to recalculate this dictionary each time we receive an rpc.
385399
internal var serviceProvidersByName: [Substring: CallHandlerProvider]
386400

401+
#if canImport(Network)
402+
/// A closure allowing to customise the listener's `NWParameters` used when establishing a connection using `NIOTransportServices`.
403+
@available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
404+
public var listenerNWParametersConfigurator: (@Sendable (NWParameters) -> Void)? {
405+
get {
406+
self._listenerNWParametersConfigurator as! (@Sendable (NWParameters) -> Void)?
407+
}
408+
set {
409+
self._listenerNWParametersConfigurator = newValue
410+
}
411+
}
412+
413+
private var _listenerNWParametersConfigurator: (any Sendable)?
414+
415+
/// A closure allowing to customise the child channels' `NWParameters` used when establishing connections using `NIOTransportServices`.
416+
@available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
417+
public var childChannelNWParametersConfigurator: (@Sendable (NWParameters) -> Void)? {
418+
get {
419+
self._childChannelNWParametersConfigurator as! (@Sendable (NWParameters) -> Void)?
420+
}
421+
set {
422+
self._childChannelNWParametersConfigurator = newValue
423+
}
424+
}
425+
426+
private var _childChannelNWParametersConfigurator: (any Sendable)?
427+
#endif
428+
387429
/// CORS configuration for gRPC-Web support.
388430
public var webCORS = Configuration.CORS()
389431

Tests/GRPCTests/ConnectionManagerTests.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ import XCTest
2222

2323
@testable import GRPC
2424

25+
#if canImport(Network)
26+
import NIOConcurrencyHelpers
27+
import NIOTransportServices
28+
import Network
29+
#endif
30+
2531
class ConnectionManagerTests: GRPCTestCase {
2632
private let loop = EmbeddedEventLoop()
2733
private let recorder = RecordingConnectivityDelegate()
@@ -1412,6 +1418,52 @@ extension ConnectionManagerTests {
14121418
XCTAssert(error is DoomedChannelError)
14131419
}
14141420
}
1421+
1422+
#if canImport(Network)
1423+
func testDefaultChannelProvider_NWParametersConfigurator() throws {
1424+
// For this test, we want an actual connection to be established, since otherwise the parameters
1425+
// configurator won't be run: NIOTS will only apply the parameters on the NWConnection at the
1426+
// point of activating it.
1427+
1428+
// Start a server
1429+
let serverConfig = Server.Configuration.default(
1430+
target: .hostAndPort("localhost", 0),
1431+
eventLoopGroup: NIOTSEventLoopGroup.singleton,
1432+
serviceProviders: []
1433+
)
1434+
let server = try Server.start(configuration: serverConfig).wait()
1435+
defer {
1436+
try? server.close().wait()
1437+
}
1438+
1439+
// Create a connection manager, and configure it to increase a counter in its NWParameters
1440+
// configurator closure.
1441+
let counter = NIOLockedValueBox(0)
1442+
let group = NIOTSEventLoopGroup.singleton
1443+
var configuration = ClientConnection.Configuration.default(
1444+
target: .socketAddress(server.channel.localAddress!),
1445+
eventLoopGroup: group
1446+
)
1447+
configuration.nwParametersConfigurator = { _ in
1448+
counter.withLockedValue { $0 += 1 }
1449+
}
1450+
let manager = ConnectionManager(
1451+
configuration: configuration,
1452+
connectivityDelegate: self.monitor,
1453+
idleBehavior: .closeWhenIdleTimeout,
1454+
logger: self.logger
1455+
)
1456+
defer {
1457+
try? manager.shutdown().wait()
1458+
}
1459+
1460+
// Wait for the connection to be established.
1461+
_ = try manager.getHTTP2Multiplexer().wait()
1462+
1463+
// At this point, the configurator must have been called.
1464+
XCTAssertEqual(1, counter.withLockedValue({ $0 }))
1465+
}
1466+
#endif
14151467
}
14161468

14171469
internal struct Change: Hashable, CustomStringConvertible {

0 commit comments

Comments
 (0)