@@ -98,25 +98,7 @@ public final class Server: @unchecked Sendable {
9898    } 
9999
100100    #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) 
120102    #endif  // canImport(NIOSSL) 
121103
122104    #if canImport(Network) 
@@ -152,53 +134,10 @@ public final class Server: @unchecked Sendable {
152134      ) 
153135      // Set the handlers that are applied to the accepted Channels
154136      . 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 
164138          #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) 
178140          #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 ( ) 
202141        } 
203142      } 
204143
@@ -210,11 +149,108 @@ public final class Server: @unchecked Sendable {
210149      ) 
211150  } 
212151
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+ 
213241  /// Starts a server with the given configuration. See `Server.Configuration` for the options
214242  /// available to configure the server.
215243  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+   } 
217251
252+   private  static  func  startServer( configuration:  Configuration )  ->  EventLoopFuture < Server >  { 
253+     let  quiescingHelper  =  ServerQuiescingHelper ( group:  configuration. eventLoopGroup) 
218254    return  self . makeBootstrap ( configuration:  configuration) 
219255      . serverChannelInitializer  {  channel in 
220256        channel. pipeline. addHandler ( quiescingHelper. makeServerChannelHandler ( channel:  channel) ) 
@@ -229,13 +265,53 @@ public final class Server: @unchecked Sendable {
229265      } 
230266  } 
231267
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.
232306  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 ? 
234310  private  var  errorDelegate :  ServerErrorDelegate ? 
235311
236312  private  init ( 
237313    channel:  Channel , 
238-     quiescingHelper:  ServerQuiescingHelper , 
314+     quiescingHelper:  ServerQuiescingHelper ? , 
239315    errorDelegate:  ServerErrorDelegate ? 
240316  )  { 
241317    self . channel =  channel
@@ -264,7 +340,13 @@ public final class Server: @unchecked Sendable {
264340  /// Initiates a graceful shutdown. Existing RPCs may run to completion, any new RPCs or
265341  /// connections will be rejected.
266342  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+     } 
268350  } 
269351
270352  /// Initiates a graceful shutdown. Existing RPCs may run to completion, any new RPCs or
@@ -436,6 +518,13 @@ extension Server {
436518    /// CORS configuration for gRPC-Web support.
437519    public  var  webCORS   =  Configuration . CORS ( ) 
438520
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+ 
439528    #if canImport(NIOSSL) 
440529    /// Create a `Configuration` with some pre-defined defaults.
441530    ///
0 commit comments