Coverage Report

Created: 2026-02-11 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/grpc-swift/Sources/GRPC/Server.swift
Line
Count
Source
1
/*
2
 * Copyright 2019, gRPC Authors All rights reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
import Foundation
17
import Logging
18
import NIOCore
19
import NIOExtras
20
import NIOHTTP1
21
import NIOHTTP2
22
import NIOPosix
23
import NIOTransportServices
24
25
#if canImport(NIOSSL)
26
import NIOSSL
27
#endif
28
#if canImport(Network)
29
import Network
30
#endif
31
32
/// Wrapper object to manage the lifecycle of a gRPC server.
33
///
34
/// The pipeline is configured in three stages detailed below. Note: handlers marked with
35
/// a '*' are responsible for handling errors.
36
///
37
/// 1. Initial stage, prior to pipeline configuration.
38
///
39
///                        ┌─────────────────────────────────┐
40
///                        │ GRPCServerPipelineConfigurator* │
41
///                        └────▲───────────────────────┬────┘
42
///                   ByteBuffer│                       │ByteBuffer
43
///                           ┌─┴───────────────────────▼─┐
44
///                           │       NIOSSLHandler       │
45
///                           └─▲───────────────────────┬─┘
46
///                   ByteBuffer│                       │ByteBuffer
47
///                             │                       ▼
48
///
49
///    The `NIOSSLHandler` is optional and depends on how the framework user has configured
50
///    their server. The `GRPCServerPipelineConfigurator` detects which HTTP version is being used
51
///    (via ALPN if TLS is used or by parsing the first bytes on the connection otherwise) and
52
///    configures the pipeline accordingly.
53
///
54
/// 2. HTTP version detected. "HTTP Handlers" depends on the HTTP version determined by
55
///    `GRPCServerPipelineConfigurator`. In the case of HTTP/2:
56
///
57
///                           ┌─────────────────────────────────┐
58
///                           │      HTTP2StreamMultiplexer     │
59
///                           └─▲─────────────────────────────┬─┘
60
///                   HTTP2Frame│                             │HTTP2Frame
61
///                           ┌─┴─────────────────────────────▼─┐
62
///                           │           HTTP2Handler          │
63
///                           └─▲─────────────────────────────┬─┘
64
///                   ByteBuffer│                             │ByteBuffer
65
///                           ┌─┴─────────────────────────────▼─┐
66
///                           │          NIOSSLHandler          │
67
///                           └─▲─────────────────────────────┬─┘
68
///                   ByteBuffer│                             │ByteBuffer
69
///                             │                             ▼
70
///
71
///    The `HTTP2StreamMultiplexer` provides one `Channel` for each HTTP/2 stream (and thus each
72
///    RPC).
73
///
74
/// 3. The frames for each stream channel are routed by the `HTTP2ToRawGRPCServerCodec` handler to
75
///    a handler containing the user-implemented logic provided by a `CallHandlerProvider`:
76
///
77
///                           ┌─────────────────────────────────┐
78
///                           │         BaseCallHandler*        │
79
///                           └─▲─────────────────────────────┬─┘
80
///        GRPCServerRequestPart│                             │GRPCServerResponsePart
81
///                           ┌─┴─────────────────────────────▼─┐
82
///                           │    HTTP2ToRawGRPCServerCodec    │
83
///                           └─▲─────────────────────────────┬─┘
84
///      HTTP2Frame.FramePayload│                             │HTTP2Frame.FramePayload
85
///                             │                             ▼
86
///
87
/// - Note: This class is thread safe. It's marked as `@unchecked Sendable` because the non-Sendable
88
/// `errorDelegate` property is mutated, but it's done thread-safely, as it only happens inside the `EventLoop`.
89
public final class Server: @unchecked Sendable {
90
  /// Makes and configures a `ServerBootstrap` using the provided configuration.
91
0
  public class func makeBootstrap(configuration: Configuration) -> ServerBootstrapProtocol {
92
0
    let bootstrap = PlatformSupport.makeServerBootstrap(group: configuration.eventLoopGroup)
93
0
94
0
    // Backlog is only available on `ServerBootstrap`.
95
0
    if bootstrap is ServerBootstrap {
96
0
      // Specify a backlog to avoid overloading the server.
97
0
      _ = bootstrap.serverChannelOption(ChannelOptions.backlog, value: 256)
98
0
    }
99
0
100
    #if canImport(NIOSSL)
101
0
    let sslContext = Self.makeNIOSSLContext(configuration: configuration)
102
    #endif  // canImport(NIOSSL)
103
0
104
    #if canImport(Network)
105
    if let tlsConfiguration = configuration.tlsConfiguration {
106
      if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *),
107
        let transportServicesBootstrap = bootstrap as? NIOTSListenerBootstrap
108
      {
109
        _ = transportServicesBootstrap.tlsOptions(from: tlsConfiguration)
110
      }
111
    }
112
113
    if #available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *),
114
      let configurator = configuration.listenerNWParametersConfigurator,
115
      let transportServicesBootstrap = bootstrap as? NIOTSListenerBootstrap
116
    {
117
      _ = transportServicesBootstrap.configureNWParameters(configurator)
118
    }
119
120
    if #available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *),
121
      let configurator = configuration.childChannelNWParametersConfigurator,
122
      let transportServicesBootstrap = bootstrap as? NIOTSListenerBootstrap
123
    {
124
      _ = transportServicesBootstrap.configureChildNWParameters(configurator)
125
    }
126
    #endif  // canImport(Network)
127
0
128
0
    return
129
0
      bootstrap
130
0
      // Enable `SO_REUSEADDR` to avoid "address already in use" error.
131
0
      .serverChannelOption(
132
0
        ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR),
133
0
        value: 1
134
0
      )
135
0
      // Set the handlers that are applied to the accepted Channels
136
0
      .childChannelInitializer { channel in
137
0
        Self.configureAcceptedChannel(channel, configuration: configuration) { sync in
138
          #if canImport(NIOSSL)
139
0
          try Self.addNIOSSLHandler(sslContext, configuration: configuration, sync: sync)
140
          #endif  // canImport(NIOSSL)
141
0
        }
142
0
      }
143
0
144
0
      // Enable TCP_NODELAY and SO_REUSEADDR for the accepted Channels
145
0
      .childChannelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
146
0
      .childChannelOption(
147
0
        ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR),
148
0
        value: 1
149
0
      )
150
0
  }
151
152
  #if canImport(NIOSSL)
153
  private static func makeNIOSSLContext(
154
    configuration: Configuration
155
0
  ) -> Result<NIOSSLContext, Error>? {
156
0
    // Making a `NIOSSLContext` is expensive, we should only do it once per TLS configuration so
157
0
    // we'll do it now, before accepting connections. Unfortunately our API isn't throwing so we'll
158
0
    // only surface any error when initializing a child channel.
159
0
    //
160
0
    // 'nil' means we're not using TLS, or we're using the Network.framework TLS backend. If we're
161
0
    // using the Network.framework TLS backend we'll apply the settings just below.
162
0
    let sslContext: Result<NIOSSLContext, Error>?
163
0
164
0
    if let tlsConfiguration = configuration.tlsConfiguration {
165
0
      do {
166
0
        sslContext = try tlsConfiguration.makeNIOSSLContext().map { .success($0) }
167
0
      } catch {
168
0
        sslContext = .failure(error)
169
0
      }
170
0
171
0
    } else {
172
0
      // No TLS configuration, no SSL context.
173
0
      sslContext = nil
174
0
    }
175
0
176
0
    return sslContext
177
0
  }
178
179
  private static func addNIOSSLHandler(
180
    _ sslContext: Result<NIOSSLContext, Error>?,
181
    configuration: Configuration,
182
    sync: ChannelPipeline.SynchronousOperations
183
0
  ) throws {
184
0
    if let sslContext = try sslContext?.get() {
185
0
      let sslHandler: NIOSSLServerHandler
186
0
      if let verify = configuration.tlsConfiguration?.nioSSLCustomVerificationCallback {
187
0
        sslHandler = NIOSSLServerHandler(
188
0
          context: sslContext,
189
0
          customVerificationCallback: verify
190
0
        )
191
0
      } else {
192
0
        sslHandler = NIOSSLServerHandler(context: sslContext)
193
0
      }
194
0
195
0
      try sync.addHandler(sslHandler)
196
0
    }
197
0
  }
198
  #endif  // canImport(NIOSSL)
199
200
  private static func configureAcceptedChannel(
201
    _ channel: Channel,
202
    configuration: Configuration,
203
    addNIOSSLIfNecessary: (ChannelPipeline.SynchronousOperations) throws -> Void
204
0
  ) -> EventLoopFuture<Void> {
205
0
    var configuration = configuration
206
0
    configuration.logger[metadataKey: MetadataKey.connectionID] = "\(UUID().uuidString)"
207
0
    configuration.logger.addIPAddressMetadata(
208
0
      local: channel.localAddress,
209
0
      remote: channel.remoteAddress
210
0
    )
211
0
212
0
    do {
213
0
      let sync = channel.pipeline.syncOperations
214
0
      try addNIOSSLIfNecessary(sync)
215
0
216
0
      // Configures the pipeline based on whether the connection uses TLS or not.
217
0
      try sync.addHandler(GRPCServerPipelineConfigurator(configuration: configuration))
218
0
219
0
      // Work around the zero length write issue, if needed.
220
0
      let requiresZeroLengthWorkaround = PlatformSupport.requiresZeroLengthWriteWorkaround(
221
0
        group: configuration.eventLoopGroup,
222
0
        hasTLS: configuration.tlsConfiguration != nil
223
0
      )
224
0
      if requiresZeroLengthWorkaround,
225
0
        #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
226
0
      {
227
0
        try sync.addHandler(NIOFilterEmptyWritesHandler())
228
0
      }
229
0
    } catch {
230
0
      return channel.eventLoop.makeFailedFuture(error)
231
0
    }
232
0
233
0
    // Run the debug initializer, if there is one.
234
0
    if let debugAcceptedChannelInitializer = configuration.debugChannelInitializer {
235
0
      return debugAcceptedChannelInitializer(channel)
236
0
    } else {
237
0
      return channel.eventLoop.makeSucceededVoidFuture()
238
0
    }
239
0
  }
240
241
  /// Starts a server with the given configuration. See `Server.Configuration` for the options
242
  /// available to configure the server.
243
0
  public static func start(configuration: Configuration) -> EventLoopFuture<Server> {
244
0
    switch configuration.target.wrapped {
245
0
    case .connectedSocket(let handle) where configuration.connectedSocketTargetIsAcceptedConnection:
246
0
      return Self.startServerFromAcceptedConnection(handle: handle, configuration: configuration)
247
0
    case .connectedSocket, .hostAndPort, .unixDomainSocket, .socketAddress, .vsockAddress:
248
0
      return Self.startServer(configuration: configuration)
249
0
    }
250
0
  }
251
252
0
  private static func startServer(configuration: Configuration) -> EventLoopFuture<Server> {
253
0
    let quiescingHelper = ServerQuiescingHelper(group: configuration.eventLoopGroup)
254
0
    return self.makeBootstrap(configuration: configuration)
255
0
      .serverChannelInitializer { channel in
256
0
        channel.pipeline.addHandler(quiescingHelper.makeServerChannelHandler(channel: channel))
257
0
      }
258
0
      .bind(to: configuration.target)
259
0
      .map { channel in
260
0
        Server(
261
0
          channel: channel,
262
0
          quiescingHelper: quiescingHelper,
263
0
          errorDelegate: configuration.errorDelegate
264
0
        )
265
0
      }
266
0
  }
267
268
  private static func startServerFromAcceptedConnection(
269
    handle: NIOBSDSocket.Handle,
270
    configuration: Configuration
271
0
  ) -> EventLoopFuture<Server> {
272
0
    guard let bootstrap = ClientBootstrap(validatingGroup: configuration.eventLoopGroup) else {
273
0
      let status = GRPCStatus(
274
0
        code: .unimplemented,
275
0
        message: """
276
0
          You must use a NIOPosix EventLoopGroup to create a server from an already accepted \
277
0
          socket.
278
0
          """
279
0
      )
280
0
      return configuration.eventLoopGroup.any().makeFailedFuture(status)
281
0
    }
282
0
283
    #if canImport(NIOSSL)
284
0
    let sslContext = Self.makeNIOSSLContext(configuration: configuration)
285
    #endif  // canImport(NIOSSL)
286
0
287
0
    return bootstrap.channelInitializer { channel in
288
0
      Self.configureAcceptedChannel(channel, configuration: configuration) { sync in
289
        #if canImport(NIOSSL)
290
0
        try Self.addNIOSSLHandler(sslContext, configuration: configuration, sync: sync)
291
        #endif  // canImport(NIOSSL)
292
0
      }
293
0
    }.withConnectedSocket(handle).map { channel in
294
0
      Server(
295
0
        channel: channel,
296
0
        quiescingHelper: nil,
297
0
        errorDelegate: configuration.errorDelegate
298
0
      )
299
0
    }
300
0
  }
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.
306
  public let channel: Channel
307
308
  /// Quiescing helper. `nil` if `channel` is for an accepted connection.
309
  private let quiescingHelper: ServerQuiescingHelper?
310
  private var errorDelegate: ServerErrorDelegate?
311
312
  private init(
313
    channel: Channel,
314
    quiescingHelper: ServerQuiescingHelper?,
315
    errorDelegate: ServerErrorDelegate?
316
0
  ) {
317
0
    self.channel = channel
318
0
    self.quiescingHelper = quiescingHelper
319
0
320
0
    // Maintain a strong reference to ensure it lives as long as the server.
321
0
    self.errorDelegate = errorDelegate
322
0
323
0
    // If we have an error delegate, add a server channel error handler as well. We don't need to wait for the handler to
324
0
    // be added.
325
0
    if let errorDelegate = errorDelegate {
326
0
      _ = channel.pipeline.addHandler(ServerChannelErrorHandler(errorDelegate: errorDelegate))
327
0
    }
328
0
329
0
    // nil out errorDelegate to avoid retain cycles.
330
0
    self.onClose.whenComplete { _ in
331
0
      self.errorDelegate = nil
332
0
    }
333
0
  }
334
335
  /// Fired when the server shuts down.
336
0
  public var onClose: EventLoopFuture<Void> {
337
0
    return self.channel.closeFuture
338
0
  }
339
340
  /// Initiates a graceful shutdown. Existing RPCs may run to completion, any new RPCs or
341
  /// connections will be rejected.
342
0
  public func initiateGracefulShutdown(promise: EventLoopPromise<Void>?) {
343
0
    if let quiescingHelper = self.quiescingHelper {
344
0
      quiescingHelper.initiateShutdown(promise: promise)
345
0
    } else {
346
0
      // No quiescing helper: the channel must be for an already accepted connection.
347
0
      self.channel.closeFuture.cascade(to: promise)
348
0
      self.channel.pipeline.fireUserInboundEventTriggered(ChannelShouldQuiesceEvent())
349
0
    }
350
0
  }
351
352
  /// Initiates a graceful shutdown. Existing RPCs may run to completion, any new RPCs or
353
  /// connections will be rejected.
354
0
  public func initiateGracefulShutdown() -> EventLoopFuture<Void> {
355
0
    let promise = self.channel.eventLoop.makePromise(of: Void.self)
356
0
    self.initiateGracefulShutdown(promise: promise)
357
0
    return promise.futureResult
358
0
  }
359
360
  /// Shutdown the server immediately. Active RPCs and connections will be terminated.
361
0
  public func close(promise: EventLoopPromise<Void>?) {
362
0
    self.channel.close(mode: .all, promise: promise)
363
0
  }
364
365
  /// Shutdown the server immediately. Active RPCs and connections will be terminated.
366
0
  public func close() -> EventLoopFuture<Void> {
367
0
    return self.channel.close(mode: .all)
368
0
  }
369
}
370
371
public typealias BindTarget = ConnectionTarget
372
373
extension Server {
374
  /// The configuration for a server.
375
  public struct Configuration {
376
    /// The target to bind to.
377
    public var target: BindTarget
378
    /// The event loop group to run the connection on.
379
    public var eventLoopGroup: EventLoopGroup
380
381
    /// Providers the server should use to handle gRPC requests.
382
    public var serviceProviders: [CallHandlerProvider] {
383
0
      get {
384
0
        return Array(self.serviceProvidersByName.values)
385
0
      }
386
0
      set {
387
0
        self
388
0
          .serviceProvidersByName = Dictionary(
389
0
            uniqueKeysWithValues:
390
0
              newValue
391
0
              .map { ($0.serviceName, $0) }
392
0
          )
393
0
      }
394
    }
395
396
    /// An error delegate which is called when errors are caught. Provided delegates **must not
397
    /// maintain a strong reference to this `Server`**. Doing so will cause a retain cycle.
398
    public var errorDelegate: ServerErrorDelegate?
399
400
    #if canImport(NIOSSL)
401
    /// TLS configuration for this connection. `nil` if TLS is not desired.
402
    @available(*, deprecated, renamed: "tlsConfiguration")
403
    public var tls: TLS? {
404
0
      get {
405
0
        return self.tlsConfiguration?.asDeprecatedServerConfiguration
406
0
      }
407
0
      set {
408
0
        self.tlsConfiguration = newValue.map { GRPCTLSConfiguration(transforming: $0) }
409
0
      }
410
    }
411
    #endif  // canImport(NIOSSL)
412
413
    public var tlsConfiguration: GRPCTLSConfiguration?
414
415
    /// The connection keepalive configuration.
416
86.5k
    public var connectionKeepalive = ServerConnectionKeepalive()
417
418
    /// The amount of time to wait before closing connections. The idle timeout will start only
419
    /// if there are no RPCs in progress and will be cancelled as soon as any RPCs start.
420
86.5k
    public var connectionIdleTimeout: TimeAmount = .nanoseconds(.max)
421
422
    /// The compression configuration for requests and responses.
423
    ///
424
    /// If compression is enabled for the server it may be disabled for responses on any RPC by
425
    /// setting `compressionEnabled` to `false` on the context of the call.
426
    ///
427
    /// Compression may also be disabled at the message-level for streaming responses (i.e. server
428
    /// streaming and bidirectional streaming RPCs) by passing setting `compression` to `.disabled`
429
    /// in `sendResponse(_:compression)`.
430
    ///
431
    /// Defaults to ``ServerMessageEncoding/disabled``.
432
86.5k
    public var messageEncoding: ServerMessageEncoding = .disabled
433
434
    /// The maximum size in bytes of a message which may be received from a client. Defaults to 4MB.
435
86.5k
    public var maximumReceiveMessageLength: Int = 4 * 1024 * 1024 {
436
0
      willSet {
437
0
        precondition(newValue >= 0, "maximumReceiveMessageLength must be positive")
438
0
      }
439
    }
440
441
    /// The HTTP/2 flow control target window size. Defaults to 8MB. Values are clamped between
442
    /// 1 and 2^31-1 inclusive.
443
86.5k
    public var httpTargetWindowSize = 8 * 1024 * 1024 {
444
0
      didSet {
445
0
        self.httpTargetWindowSize = self.httpTargetWindowSize.clamped(to: 1 ... Int(Int32.max))
446
0
      }
447
    }
448
449
    /// The HTTP/2 max number of concurrent streams. Defaults to 100. Must be non-negative.
450
86.5k
    public var httpMaxConcurrentStreams: Int = 100 {
451
0
      willSet {
452
0
        precondition(newValue >= 0, "httpMaxConcurrentStreams must be non-negative")
453
0
      }
454
    }
455
456
    /// The HTTP/2 max frame size. Defaults to 16384. Value is clamped between 2^14 and 2^24-1
457
    /// octets inclusive (the minimum and maximum allowable values - HTTP/2 RFC 7540 4.2).
458
86.5k
    public var httpMaxFrameSize: Int = 16384 {
459
0
      didSet {
460
0
        self.httpMaxFrameSize = self.httpMaxFrameSize.clamped(to: 16384 ... 16_777_215)
461
0
      }
462
    }
463
464
    /// The HTTP/2 max number of reset streams. Defaults to 32. Must be non-negative.
465
86.5k
    public var httpMaxResetStreams: Int = 32 {
466
0
      willSet {
467
0
        precondition(newValue >= 0, "httpMaxResetStreams must be non-negative")
468
0
      }
469
    }
470
471
    /// The root server logger. Accepted connections will branch from this logger and RPCs on
472
    /// each connection will use a logger branched from the connections logger. This logger is made
473
    /// available to service providers via `context`. Defaults to a no-op logger.
474
86.5k
    public var logger = Logger(label: "io.grpc", factory: { _ in SwiftLogNoOpLogHandler() })
475
476
    /// A channel initializer which will be run after gRPC has initialized each accepted channel.
477
    /// This may be used to add additional handlers to the pipeline and is intended for debugging.
478
    /// This is analogous to `NIO.ServerBootstrap.childChannelInitializer`.
479
    ///
480
    /// - Warning: The initializer closure may be invoked *multiple times*. More precisely: it will
481
    ///   be invoked at most once per accepted connection.
482
    public var debugChannelInitializer: ((Channel) -> EventLoopFuture<Void>)?
483
484
    /// A calculated private cache of the service providers by name.
485
    ///
486
    /// This is how gRPC consumes the service providers internally. Caching this as stored data avoids
487
    /// the need to recalculate this dictionary each time we receive an rpc.
488
    internal var serviceProvidersByName: [Substring: CallHandlerProvider]
489
490
    #if canImport(Network)
491
    /// A closure allowing to customise the listener's `NWParameters` used when establishing a connection using `NIOTransportServices`.
492
    @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
493
    public var listenerNWParametersConfigurator: (@Sendable (NWParameters) -> Void)? {
494
      get {
495
        self._listenerNWParametersConfigurator as! (@Sendable (NWParameters) -> Void)?
496
      }
497
      set {
498
        self._listenerNWParametersConfigurator = newValue
499
      }
500
    }
501
502
    private var _listenerNWParametersConfigurator: (any Sendable)?
503
504
    /// A closure allowing to customise the child channels' `NWParameters` used when establishing connections using `NIOTransportServices`.
505
    @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
506
    public var childChannelNWParametersConfigurator: (@Sendable (NWParameters) -> Void)? {
507
      get {
508
        self._childChannelNWParametersConfigurator as! (@Sendable (NWParameters) -> Void)?
509
      }
510
      set {
511
        self._childChannelNWParametersConfigurator = newValue
512
      }
513
    }
514
515
    private var _childChannelNWParametersConfigurator: (any Sendable)?
516
    #endif
517
518
    /// CORS configuration for gRPC-Web support.
519
86.5k
    public var webCORS = Configuration.CORS()
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
86.5k
    public var connectedSocketTargetIsAcceptedConnection: Bool = false
527
528
    #if canImport(NIOSSL)
529
    /// Create a `Configuration` with some pre-defined defaults.
530
    ///
531
    /// - Parameters:
532
    ///   - target: The target to bind to.
533
    ///   -  eventLoopGroup: The event loop group to run the server on.
534
    ///   - serviceProviders: An array of `CallHandlerProvider`s which the server should use
535
    ///       to handle requests.
536
    ///   - errorDelegate: The error delegate, defaulting to a logging delegate.
537
    ///   - tls: TLS configuration, defaulting to `nil`.
538
    ///   - connectionKeepalive: The keepalive configuration to use.
539
    ///   - connectionIdleTimeout: The amount of time to wait before closing the connection, this is
540
    ///       indefinite by default.
541
    ///   - messageEncoding: Message compression configuration, defaulting to no compression.
542
    ///   - httpTargetWindowSize: The HTTP/2 flow control target window size.
543
    ///   - logger: A logger. Defaults to a no-op logger.
544
    ///   - debugChannelInitializer: A channel initializer which will be called for each connection
545
    ///     the server accepts after gRPC has initialized the channel. Defaults to `nil`.
546
    @available(*, deprecated, renamed: "default(target:eventLoopGroup:serviceProviders:)")
547
    public init(
548
      target: BindTarget,
549
      eventLoopGroup: EventLoopGroup,
550
      serviceProviders: [CallHandlerProvider],
551
      errorDelegate: ServerErrorDelegate? = nil,
552
      tls: TLS? = nil,
553
      connectionKeepalive: ServerConnectionKeepalive = ServerConnectionKeepalive(),
554
      connectionIdleTimeout: TimeAmount = .nanoseconds(.max),
555
      messageEncoding: ServerMessageEncoding = .disabled,
556
      httpTargetWindowSize: Int = 8 * 1024 * 1024,
557
0
      logger: Logger = Logger(label: "io.grpc", factory: { _ in SwiftLogNoOpLogHandler() }),
558
      debugChannelInitializer: ((Channel) -> EventLoopFuture<Void>)? = nil
559
0
    ) {
560
0
      self.target = target
561
0
      self.eventLoopGroup = eventLoopGroup
562
0
      self.serviceProvidersByName = Dictionary(
563
0
        uniqueKeysWithValues: serviceProviders.map { ($0.serviceName, $0) }
564
0
      )
565
0
      self.errorDelegate = errorDelegate
566
0
      self.tlsConfiguration = tls.map { GRPCTLSConfiguration(transforming: $0) }
567
0
      self.connectionKeepalive = connectionKeepalive
568
0
      self.connectionIdleTimeout = connectionIdleTimeout
569
0
      self.messageEncoding = messageEncoding
570
0
      self.httpTargetWindowSize = httpTargetWindowSize
571
0
      self.logger = logger
572
0
      self.debugChannelInitializer = debugChannelInitializer
573
0
    }
574
    #endif  // canImport(NIOSSL)
575
576
    private init(
577
      eventLoopGroup: EventLoopGroup,
578
      target: BindTarget,
579
      serviceProviders: [CallHandlerProvider]
580
31.0k
    ) {
581
31.0k
      self.eventLoopGroup = eventLoopGroup
582
31.0k
      self.target = target
583
31.0k
      self.serviceProvidersByName = Dictionary(
584
31.0k
        uniqueKeysWithValues: serviceProviders.map {
585
31.0k
          ($0.serviceName, $0)
586
31.0k
        }
587
31.0k
      )
588
31.0k
    }
589
590
    /// Make a new configuration using default values.
591
    ///
592
    /// - Parameters:
593
    ///   - target: The target to bind to.
594
    ///   - eventLoopGroup: The `EventLoopGroup` the server should run on.
595
    ///   - serviceProviders: An array of `CallHandlerProvider`s which the server should use
596
    ///       to handle requests.
597
    /// - Returns: A configuration with default values set.
598
    public static func `default`(
599
      target: BindTarget,
600
      eventLoopGroup: EventLoopGroup,
601
      serviceProviders: [CallHandlerProvider]
602
178k
    ) -> Configuration {
603
178k
      return .init(
604
178k
        eventLoopGroup: eventLoopGroup,
605
178k
        target: target,
606
178k
        serviceProviders: serviceProviders
607
178k
      )
608
178k
    }
609
  }
610
}
611
612
extension Server.Configuration {
613
  public struct CORS: Hashable, Sendable {
614
    /// Determines which 'origin' header field values are permitted in a CORS request.
615
    public var allowedOrigins: AllowedOrigins
616
    /// Sets the headers which are permitted in a response to a CORS request.
617
    public var allowedHeaders: [String]
618
    /// Enabling this value allows sets the "access-control-allow-credentials" header field
619
    /// to "true" in respones to CORS requests. This must be enabled if the client intends to send
620
    /// credentials.
621
    public var allowCredentialedRequests: Bool
622
    /// The maximum age in seconds which pre-flight CORS requests may be cached for.
623
    public var preflightCacheExpiration: Int
624
625
    public init(
626
      allowedOrigins: AllowedOrigins = .all,
627
      allowedHeaders: [String] = ["content-type", "x-grpc-web", "x-user-agent"],
628
      allowCredentialedRequests: Bool = false,
629
      preflightCacheExpiration: Int = 86400
630
86.5k
    ) {
631
86.5k
      self.allowedOrigins = allowedOrigins
632
86.5k
      self.allowedHeaders = allowedHeaders
633
86.5k
      self.allowCredentialedRequests = allowCredentialedRequests
634
86.5k
      self.preflightCacheExpiration = preflightCacheExpiration
635
86.5k
    }
636
  }
637
}
638
639
extension Server.Configuration.CORS {
640
  public struct AllowedOrigins: Hashable, Sendable {
641
    enum Wrapped: Hashable, Sendable {
642
      case all
643
      case originBased
644
      case only([String])
645
      case custom(AnyCustomCORSAllowedOrigin)
646
    }
647
648
    private(set) var wrapped: Wrapped
649
2
    private init(_ wrapped: Wrapped) {
650
2
      self.wrapped = wrapped
651
2
    }
652
653
    /// Allow all origin values.
654
    public static let all = Self(.all)
655
656
    /// Allow all origin values; similar to `all` but returns the value of the origin header field
657
    /// in the 'access-control-allow-origin' response header (rather than "*").
658
    public static let originBased = Self(.originBased)
659
660
    /// Allow only the given origin values.
661
0
    public static func only(_ allowed: [String]) -> Self {
662
0
      return Self(.only(allowed))
663
0
    }
664
665
    /// Provide a custom CORS origin check.
666
    ///
667
    /// - Parameter checkOrigin: A closure which is called with the value of the 'origin' header
668
    ///     and returns the value to use in the 'access-control-allow-origin' response header,
669
    ///     or `nil` if the origin is not allowed.
670
0
    public static func custom<C: GRPCCustomCORSAllowedOrigin>(_ custom: C) -> Self {
671
0
      return Self(.custom(AnyCustomCORSAllowedOrigin(custom)))
672
0
    }
673
  }
674
}
675
676
extension ServerBootstrapProtocol {
677
0
  fileprivate func bind(to target: BindTarget) -> EventLoopFuture<Channel> {
678
0
    switch target.wrapped {
679
0
    case let .hostAndPort(host, port):
680
0
      return self.bind(host: host, port: port)
681
0
682
0
    case let .unixDomainSocket(path):
683
0
      return self.bind(unixDomainSocketPath: path)
684
0
685
0
    case let .socketAddress(address):
686
0
      return self.bind(to: address)
687
0
688
0
    case let .connectedSocket(socket):
689
0
      return self.withBoundSocket(socket)
690
0
691
0
    case let .vsockAddress(address):
692
0
      return self.bind(to: address)
693
0
    }
694
0
  }
695
}
696
697
extension Comparable {
698
0
  internal func clamped(to range: ClosedRange<Self>) -> Self {
699
0
    return min(max(self, range.lowerBound), range.upperBound)
700
0
  }
701
}
702
703
public protocol GRPCCustomCORSAllowedOrigin: Sendable, Hashable {
704
  /// Returns the value to use for the 'access-control-allow-origin' response header for the given
705
  /// value of the 'origin' request header.
706
  ///
707
  /// - Parameter origin: The value of the 'origin' request header field.
708
  /// - Returns: The value to use for the 'access-control-allow-origin' header field or `nil` if no
709
  ///     CORS related headers should be returned.
710
  func check(origin: String) -> String?
711
}
712
713
extension Server.Configuration.CORS.AllowedOrigins {
714
  struct AnyCustomCORSAllowedOrigin: GRPCCustomCORSAllowedOrigin {
715
    private var checkOrigin: @Sendable (String) -> String?
716
    private let hashInto: @Sendable (inout Hasher) -> Void
717
    private let isEqualTo: @Sendable (any GRPCCustomCORSAllowedOrigin) -> Bool
718
719
0
    init<W: GRPCCustomCORSAllowedOrigin>(_ wrap: W) {
720
0
      self.checkOrigin = { wrap.check(origin: $0) }
721
0
      self.hashInto = { wrap.hash(into: &$0) }
722
0
      self.isEqualTo = { wrap == ($0 as? W) }
723
0
    }
724
725
0
    func check(origin: String) -> String? {
726
0
      return self.checkOrigin(origin)
727
0
    }
728
729
0
    func hash(into hasher: inout Hasher) {
730
0
      self.hashInto(&hasher)
731
0
    }
732
733
    static func == (
734
      lhs: Server.Configuration.CORS.AllowedOrigins.AnyCustomCORSAllowedOrigin,
735
      rhs: Server.Configuration.CORS.AllowedOrigins.AnyCustomCORSAllowedOrigin
736
0
    ) -> Bool {
737
0
      return lhs.isEqualTo(rhs)
738
0
    }
739
  }
740
}