@@ -98,25 +98,7 @@ public final class Server: @unchecked Sendable {
98
98
}
99
99
100
100
#if canImport(NIOSSL)
101
- // Making a `NIOSSLContext` is expensive, we should only do it once per TLS configuration so
102
- // we'll do it now, before accepting connections. Unfortunately our API isn't throwing so we'll
103
- // only surface any error when initializing a child channel.
104
- //
105
- // 'nil' means we're not using TLS, or we're using the Network.framework TLS backend. If we're
106
- // using the Network.framework TLS backend we'll apply the settings just below.
107
- let sslContext : Result < NIOSSLContext , Error > ?
108
-
109
- if let tlsConfiguration = configuration. tlsConfiguration {
110
- do {
111
- sslContext = try tlsConfiguration. makeNIOSSLContext ( ) . map { . success( $0) }
112
- } catch {
113
- sslContext = . failure( error)
114
- }
115
-
116
- } else {
117
- // No TLS configuration, no SSL context.
118
- sslContext = nil
119
- }
101
+ let sslContext = Self . makeNIOSSLContext ( configuration: configuration)
120
102
#endif // canImport(NIOSSL)
121
103
122
104
#if canImport(Network)
@@ -152,53 +134,10 @@ public final class Server: @unchecked Sendable {
152
134
)
153
135
// Set the handlers that are applied to the accepted Channels
154
136
. childChannelInitializer { channel in
155
- var configuration = configuration
156
- configuration. logger [ metadataKey: MetadataKey . connectionID] = " \( UUID ( ) . uuidString) "
157
- configuration. logger. addIPAddressMetadata (
158
- local: channel. localAddress,
159
- remote: channel. remoteAddress
160
- )
161
-
162
- do {
163
- let sync = channel. pipeline. syncOperations
137
+ Self . configureAcceptedChannel ( channel, configuration: configuration) { sync in
164
138
#if canImport(NIOSSL)
165
- if let sslContext = try sslContext? . get ( ) {
166
- let sslHandler : NIOSSLServerHandler
167
- if let verify = configuration. tlsConfiguration? . nioSSLCustomVerificationCallback {
168
- sslHandler = NIOSSLServerHandler (
169
- context: sslContext,
170
- customVerificationCallback: verify
171
- )
172
- } else {
173
- sslHandler = NIOSSLServerHandler ( context: sslContext)
174
- }
175
-
176
- try sync. addHandler ( sslHandler)
177
- }
139
+ try Self . addNIOSSLHandler ( sslContext, configuration: configuration, sync: sync)
178
140
#endif // canImport(NIOSSL)
179
-
180
- // Configures the pipeline based on whether the connection uses TLS or not.
181
- try sync. addHandler ( GRPCServerPipelineConfigurator ( configuration: configuration) )
182
-
183
- // Work around the zero length write issue, if needed.
184
- let requiresZeroLengthWorkaround = PlatformSupport . requiresZeroLengthWriteWorkaround (
185
- group: configuration. eventLoopGroup,
186
- hasTLS: configuration. tlsConfiguration != nil
187
- )
188
- if requiresZeroLengthWorkaround,
189
- #available( macOS 10 . 14 , iOS 12 . 0 , tvOS 12 . 0 , watchOS 6 . 0 , * )
190
- {
191
- try sync. addHandler ( NIOFilterEmptyWritesHandler ( ) )
192
- }
193
- } catch {
194
- return channel. eventLoop. makeFailedFuture ( error)
195
- }
196
-
197
- // Run the debug initializer, if there is one.
198
- if let debugAcceptedChannelInitializer = configuration. debugChannelInitializer {
199
- return debugAcceptedChannelInitializer ( channel)
200
- } else {
201
- return channel. eventLoop. makeSucceededVoidFuture ( )
202
141
}
203
142
}
204
143
@@ -210,11 +149,108 @@ public final class Server: @unchecked Sendable {
210
149
)
211
150
}
212
151
152
+ #if canImport(NIOSSL)
153
+ private static func makeNIOSSLContext(
154
+ configuration: Configuration
155
+ ) -> Result < NIOSSLContext , Error > ? {
156
+ // Making a `NIOSSLContext` is expensive, we should only do it once per TLS configuration so
157
+ // we'll do it now, before accepting connections. Unfortunately our API isn't throwing so we'll
158
+ // only surface any error when initializing a child channel.
159
+ //
160
+ // 'nil' means we're not using TLS, or we're using the Network.framework TLS backend. If we're
161
+ // using the Network.framework TLS backend we'll apply the settings just below.
162
+ let sslContext : Result < NIOSSLContext , Error > ?
163
+
164
+ if let tlsConfiguration = configuration. tlsConfiguration {
165
+ do {
166
+ sslContext = try tlsConfiguration. makeNIOSSLContext ( ) . map { . success( $0) }
167
+ } catch {
168
+ sslContext = . failure( error)
169
+ }
170
+
171
+ } else {
172
+ // No TLS configuration, no SSL context.
173
+ sslContext = nil
174
+ }
175
+
176
+ return sslContext
177
+ }
178
+
179
+ private static func addNIOSSLHandler(
180
+ _ sslContext: Result < NIOSSLContext , Error > ? ,
181
+ configuration: Configuration ,
182
+ sync: ChannelPipeline . SynchronousOperations
183
+ ) throws {
184
+ if let sslContext = try sslContext? . get ( ) {
185
+ let sslHandler : NIOSSLServerHandler
186
+ if let verify = configuration. tlsConfiguration? . nioSSLCustomVerificationCallback {
187
+ sslHandler = NIOSSLServerHandler (
188
+ context: sslContext,
189
+ customVerificationCallback: verify
190
+ )
191
+ } else {
192
+ sslHandler = NIOSSLServerHandler ( context: sslContext)
193
+ }
194
+
195
+ try sync. addHandler ( sslHandler)
196
+ }
197
+ }
198
+ #endif // canImport(NIOSSL)
199
+
200
+ private static func configureAcceptedChannel(
201
+ _ channel: Channel ,
202
+ configuration: Configuration ,
203
+ addNIOSSLIfNecessary: ( ChannelPipeline . SynchronousOperations ) throws -> Void
204
+ ) -> EventLoopFuture < Void > {
205
+ var configuration = configuration
206
+ configuration. logger [ metadataKey: MetadataKey . connectionID] = " \( UUID ( ) . uuidString) "
207
+ configuration. logger. addIPAddressMetadata (
208
+ local: channel. localAddress,
209
+ remote: channel. remoteAddress
210
+ )
211
+
212
+ do {
213
+ let sync = channel. pipeline. syncOperations
214
+ try addNIOSSLIfNecessary ( sync)
215
+
216
+ // Configures the pipeline based on whether the connection uses TLS or not.
217
+ try sync. addHandler ( GRPCServerPipelineConfigurator ( configuration: configuration) )
218
+
219
+ // Work around the zero length write issue, if needed.
220
+ let requiresZeroLengthWorkaround = PlatformSupport . requiresZeroLengthWriteWorkaround (
221
+ group: configuration. eventLoopGroup,
222
+ hasTLS: configuration. tlsConfiguration != nil
223
+ )
224
+ if requiresZeroLengthWorkaround,
225
+ #available( macOS 10 . 14 , iOS 12 . 0 , tvOS 12 . 0 , watchOS 6 . 0 , * )
226
+ {
227
+ try sync. addHandler ( NIOFilterEmptyWritesHandler ( ) )
228
+ }
229
+ } catch {
230
+ return channel. eventLoop. makeFailedFuture ( error)
231
+ }
232
+
233
+ // Run the debug initializer, if there is one.
234
+ if let debugAcceptedChannelInitializer = configuration. debugChannelInitializer {
235
+ return debugAcceptedChannelInitializer ( channel)
236
+ } else {
237
+ return channel. eventLoop. makeSucceededVoidFuture ( )
238
+ }
239
+ }
240
+
213
241
/// Starts a server with the given configuration. See `Server.Configuration` for the options
214
242
/// available to configure the server.
215
243
public static func start( configuration: Configuration ) -> EventLoopFuture < Server > {
216
- let quiescingHelper = ServerQuiescingHelper ( group: configuration. eventLoopGroup)
244
+ switch configuration. target. wrapped {
245
+ case . connectedSocket( let handle) where configuration. connectedSocketTargetIsAcceptedConnection:
246
+ return Self . startServerFromAcceptedConnection ( handle: handle, configuration: configuration)
247
+ case . connectedSocket, . hostAndPort, . unixDomainSocket, . socketAddress, . vsockAddress:
248
+ return Self . startServer ( configuration: configuration)
249
+ }
250
+ }
217
251
252
+ private static func startServer( configuration: Configuration ) -> EventLoopFuture < Server > {
253
+ let quiescingHelper = ServerQuiescingHelper ( group: configuration. eventLoopGroup)
218
254
return self . makeBootstrap ( configuration: configuration)
219
255
. serverChannelInitializer { channel in
220
256
channel. pipeline. addHandler ( quiescingHelper. makeServerChannelHandler ( channel: channel) )
@@ -229,13 +265,53 @@ public final class Server: @unchecked Sendable {
229
265
}
230
266
}
231
267
268
+ private static func startServerFromAcceptedConnection(
269
+ handle: NIOBSDSocket . Handle ,
270
+ configuration: Configuration
271
+ ) -> EventLoopFuture < Server > {
272
+ guard let bootstrap = ClientBootstrap ( validatingGroup: configuration. eventLoopGroup) else {
273
+ let status = GRPCStatus (
274
+ code: . unimplemented,
275
+ message: """
276
+ You must use a NIOPosix EventLoopGroup to create a server from an already accepted \
277
+ socket.
278
+ """
279
+ )
280
+ return configuration. eventLoopGroup. any ( ) . makeFailedFuture ( status)
281
+ }
282
+
283
+ #if canImport(NIOSSL)
284
+ let sslContext = Self . makeNIOSSLContext ( configuration: configuration)
285
+ #endif // canImport(NIOSSL)
286
+
287
+ return bootstrap. channelInitializer { channel in
288
+ Self . configureAcceptedChannel ( channel, configuration: configuration) { sync in
289
+ #if canImport(NIOSSL)
290
+ try Self . addNIOSSLHandler ( sslContext, configuration: configuration, sync: sync)
291
+ #endif // canImport(NIOSSL)
292
+ }
293
+ } . withConnectedSocket ( handle) . map { channel in
294
+ Server (
295
+ channel: channel,
296
+ quiescingHelper: nil ,
297
+ errorDelegate: configuration. errorDelegate
298
+ )
299
+ }
300
+ }
301
+
302
+ /// The listening server channel.
303
+ ///
304
+ /// If the server was created from an already accepted connection then this channel will
305
+ /// be for the accepted connection.
232
306
public let channel : Channel
233
- private let quiescingHelper : ServerQuiescingHelper
307
+
308
+ /// Quiescing helper. `nil` if `channel` is for an accepted connection.
309
+ private let quiescingHelper : ServerQuiescingHelper ?
234
310
private var errorDelegate : ServerErrorDelegate ?
235
311
236
312
private init (
237
313
channel: Channel ,
238
- quiescingHelper: ServerQuiescingHelper ,
314
+ quiescingHelper: ServerQuiescingHelper ? ,
239
315
errorDelegate: ServerErrorDelegate ?
240
316
) {
241
317
self . channel = channel
@@ -264,7 +340,13 @@ public final class Server: @unchecked Sendable {
264
340
/// Initiates a graceful shutdown. Existing RPCs may run to completion, any new RPCs or
265
341
/// connections will be rejected.
266
342
public func initiateGracefulShutdown( promise: EventLoopPromise < Void > ? ) {
267
- self . quiescingHelper. initiateShutdown ( promise: promise)
343
+ if let quiescingHelper = self . quiescingHelper {
344
+ quiescingHelper. initiateShutdown ( promise: promise)
345
+ } else {
346
+ // No quiescing helper: the channel must be for an already accepted connection.
347
+ self . channel. closeFuture. cascade ( to: promise)
348
+ self . channel. pipeline. fireUserInboundEventTriggered ( ChannelShouldQuiesceEvent ( ) )
349
+ }
268
350
}
269
351
270
352
/// Initiates a graceful shutdown. Existing RPCs may run to completion, any new RPCs or
@@ -436,6 +518,13 @@ extension Server {
436
518
/// CORS configuration for gRPC-Web support.
437
519
public var webCORS = Configuration . CORS ( )
438
520
521
+ /// Indicates whether a `connectedSocket` ``target`` is treated as an accepted connection.
522
+ ///
523
+ /// If ``target`` is a `connectedSocket` then this flag indicates whether that socket is for
524
+ /// an already accepted connection. If the value is `false` then the socket is treated as a
525
+ /// listener. This value is ignored if ``target`` is any value other than `connectedSocket`.
526
+ public var connectedSocketTargetIsAcceptedConnection : Bool = false
527
+
439
528
#if canImport(NIOSSL)
440
529
/// Create a `Configuration` with some pre-defined defaults.
441
530
///
0 commit comments