HTTP2ServerTransport+TransportServices.swift 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. /*
  2. * Copyright 2024, 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. #if canImport(Network)
  17. public import GRPCCore
  18. public import NIOTransportServices // has to be public because of default argument value in init
  19. public import GRPCNIOTransportCore
  20. private import NIOCore
  21. private import NIOExtras
  22. private import NIOHTTP2
  23. private import Network
  24. private import Synchronization
  25. extension HTTP2ServerTransport {
  26. /// A NIO Transport Services-backed implementation of a server transport.
  27. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  28. public struct TransportServices: ServerTransport, ListeningServerTransport {
  29. private struct ListenerFactory: HTTP2ListenerFactory {
  30. let config: Config
  31. func makeListeningChannel(
  32. eventLoopGroup: any EventLoopGroup,
  33. address: GRPCNIOTransportCore.SocketAddress,
  34. serverQuiescingHelper: ServerQuiescingHelper
  35. ) async throws -> NIOAsyncChannel<AcceptedChannel, Never> {
  36. let bootstrap: NIOTSListenerBootstrap
  37. let requireALPN: Bool
  38. let scheme: Scheme
  39. switch self.config.transportSecurity.wrapped {
  40. case .plaintext:
  41. requireALPN = false
  42. scheme = .http
  43. bootstrap = NIOTSListenerBootstrap(group: eventLoopGroup)
  44. case .tls(let tlsConfig):
  45. requireALPN = tlsConfig.requireALPN
  46. scheme = .https
  47. bootstrap = NIOTSListenerBootstrap(group: eventLoopGroup)
  48. .tlsOptions(try NWProtocolTLS.Options(tlsConfig))
  49. }
  50. let serverChannel =
  51. try await bootstrap
  52. .serverChannelOption(.socketOption(.so_reuseaddr), value: 1)
  53. .serverChannelInitializer { channel in
  54. let quiescingHandler = serverQuiescingHelper.makeServerChannelHandler(channel: channel)
  55. return channel.pipeline.addHandler(quiescingHandler)
  56. }
  57. .bind(to: address) { channel in
  58. channel.eventLoop.makeCompletedFuture {
  59. return try channel.pipeline.syncOperations.configureGRPCServerPipeline(
  60. channel: channel,
  61. compressionConfig: self.config.compression,
  62. connectionConfig: self.config.connection,
  63. http2Config: self.config.http2,
  64. rpcConfig: self.config.rpc,
  65. requireALPN: requireALPN,
  66. scheme: scheme
  67. )
  68. }
  69. }
  70. return serverChannel
  71. }
  72. }
  73. private let underlyingTransport: CommonHTTP2ServerTransport<ListenerFactory>
  74. /// The listening address for this server transport.
  75. ///
  76. /// It is an `async` property because it will only return once the address has been successfully bound.
  77. ///
  78. /// - Throws: A runtime error will be thrown if the address could not be bound or is not bound any
  79. /// longer, because the transport isn't listening anymore. It can also throw if the transport returned an
  80. /// invalid address.
  81. public var listeningAddress: GRPCNIOTransportCore.SocketAddress {
  82. get async throws {
  83. try await self.underlyingTransport.listeningAddress
  84. }
  85. }
  86. /// Create a new `TransportServices` transport.
  87. ///
  88. /// - Parameters:
  89. /// - address: The address to which the server should be bound.
  90. /// - config: The transport configuration.
  91. /// - eventLoopGroup: The ELG from which to get ELs to run this transport.
  92. public init(
  93. address: GRPCNIOTransportCore.SocketAddress,
  94. config: Config,
  95. eventLoopGroup: NIOTSEventLoopGroup = .singletonNIOTSEventLoopGroup
  96. ) {
  97. let factory = ListenerFactory(config: config)
  98. let helper = ServerQuiescingHelper(group: eventLoopGroup)
  99. self.underlyingTransport = CommonHTTP2ServerTransport(
  100. address: address,
  101. eventLoopGroup: eventLoopGroup,
  102. quiescingHelper: helper,
  103. listenerFactory: factory
  104. )
  105. }
  106. public func listen(
  107. streamHandler: @escaping @Sendable (
  108. _ stream: RPCStream<Inbound, Outbound>,
  109. _ context: ServerContext
  110. ) async -> Void
  111. ) async throws {
  112. try await self.underlyingTransport.listen(streamHandler: streamHandler)
  113. }
  114. public func beginGracefulShutdown() {
  115. self.underlyingTransport.beginGracefulShutdown()
  116. }
  117. }
  118. }
  119. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  120. extension HTTP2ServerTransport.TransportServices {
  121. /// Configuration for the `TransportServices` transport.
  122. public struct Config: Sendable {
  123. /// Compression configuration.
  124. public var compression: HTTP2ServerTransport.Config.Compression
  125. /// Connection configuration.
  126. public var connection: HTTP2ServerTransport.Config.Connection
  127. /// HTTP2 configuration.
  128. public var http2: HTTP2ServerTransport.Config.HTTP2
  129. /// RPC configuration.
  130. public var rpc: HTTP2ServerTransport.Config.RPC
  131. /// The transport's security.
  132. public var transportSecurity: TransportSecurity
  133. /// Construct a new `Config`.
  134. /// - Parameters:
  135. /// - compression: Compression configuration.
  136. /// - connection: Connection configuration.
  137. /// - http2: HTTP2 configuration.
  138. /// - rpc: RPC configuration.
  139. /// - transportSecurity: The transport's security configuration.
  140. public init(
  141. compression: HTTP2ServerTransport.Config.Compression,
  142. connection: HTTP2ServerTransport.Config.Connection,
  143. http2: HTTP2ServerTransport.Config.HTTP2,
  144. rpc: HTTP2ServerTransport.Config.RPC,
  145. transportSecurity: TransportSecurity
  146. ) {
  147. self.compression = compression
  148. self.connection = connection
  149. self.http2 = http2
  150. self.rpc = rpc
  151. self.transportSecurity = transportSecurity
  152. }
  153. /// Default values for the different configurations.
  154. ///
  155. /// - Parameters:
  156. /// - transportSecurity: The transport's security configuration.
  157. /// - configure: A closure which allows you to modify the defaults before returning them.
  158. public static func defaults(
  159. transportSecurity: TransportSecurity,
  160. configure: (_ config: inout Self) -> Void = { _ in }
  161. ) -> Self {
  162. var config = Self(
  163. compression: .defaults,
  164. connection: .defaults,
  165. http2: .defaults,
  166. rpc: .defaults,
  167. transportSecurity: transportSecurity
  168. )
  169. configure(&config)
  170. return config
  171. }
  172. }
  173. }
  174. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  175. extension NIOTSListenerBootstrap {
  176. fileprivate func bind<Output: Sendable>(
  177. to address: GRPCNIOTransportCore.SocketAddress,
  178. childChannelInitializer: @escaping @Sendable (any Channel) -> EventLoopFuture<Output>
  179. ) async throws -> NIOAsyncChannel<Output, Never> {
  180. if address.virtualSocket != nil {
  181. throw RuntimeError(
  182. code: .transportError,
  183. message: """
  184. Virtual sockets are not supported by 'HTTP2ServerTransport.TransportServices'. \
  185. Please use the 'HTTP2ServerTransport.Posix' transport.
  186. """
  187. )
  188. } else {
  189. return try await self.bind(
  190. to: NIOCore.SocketAddress(address),
  191. childChannelInitializer: childChannelInitializer
  192. )
  193. }
  194. }
  195. }
  196. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  197. extension ServerTransport where Self == HTTP2ServerTransport.TransportServices {
  198. /// Create a new `TransportServices` based HTTP/2 server transport.
  199. ///
  200. /// - Parameters:
  201. /// - address: The address to which the server should be bound.
  202. /// - config: The transport configuration.
  203. /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to the server on. This must
  204. /// be a `NIOTSEventLoopGroup` or an `EventLoop` from a `NIOTSEventLoopGroup`.
  205. public static func http2NIOTS(
  206. address: GRPCNIOTransportCore.SocketAddress,
  207. config: HTTP2ServerTransport.TransportServices.Config,
  208. eventLoopGroup: NIOTSEventLoopGroup = .singletonNIOTSEventLoopGroup
  209. ) -> Self {
  210. return HTTP2ServerTransport.TransportServices(
  211. address: address,
  212. config: config,
  213. eventLoopGroup: eventLoopGroup
  214. )
  215. }
  216. }
  217. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  218. extension NWProtocolTLS.Options {
  219. convenience init(_ tlsConfig: HTTP2ServerTransport.TransportServices.Config.TLS) throws {
  220. self.init()
  221. guard let sec_identity = sec_identity_create(try tlsConfig.identityProvider()) else {
  222. throw RuntimeError(
  223. code: .transportError,
  224. message: """
  225. There was an issue creating the SecIdentity required to set up TLS. \
  226. Please check your TLS configuration.
  227. """
  228. )
  229. }
  230. sec_protocol_options_set_local_identity(
  231. self.securityProtocolOptions,
  232. sec_identity
  233. )
  234. sec_protocol_options_set_min_tls_protocol_version(
  235. self.securityProtocolOptions,
  236. .TLSv12
  237. )
  238. for `protocol` in ["grpc-exp", "h2"] {
  239. sec_protocol_options_add_tls_application_protocol(
  240. self.securityProtocolOptions,
  241. `protocol`
  242. )
  243. }
  244. }
  245. }
  246. #endif