HTTP2ServerTransport+TransportServices.swift 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  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 GRPCHTTP2Core
  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: GRPCHTTP2Core.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: GRPCHTTP2Core.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: GRPCHTTP2Core.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 (RPCStream<Inbound, Outbound>) async -> Void
  108. ) async throws {
  109. try await self.underlyingTransport.listen(streamHandler)
  110. }
  111. public func beginGracefulShutdown() {
  112. self.underlyingTransport.beginGracefulShutdown()
  113. }
  114. }
  115. }
  116. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  117. extension HTTP2ServerTransport.TransportServices {
  118. /// Configuration for the `TransportServices` transport.
  119. public struct Config: Sendable {
  120. /// Compression configuration.
  121. public var compression: HTTP2ServerTransport.Config.Compression
  122. /// Connection configuration.
  123. public var connection: HTTP2ServerTransport.Config.Connection
  124. /// HTTP2 configuration.
  125. public var http2: HTTP2ServerTransport.Config.HTTP2
  126. /// RPC configuration.
  127. public var rpc: HTTP2ServerTransport.Config.RPC
  128. /// The transport's security.
  129. public var transportSecurity: TransportSecurity
  130. /// Construct a new `Config`.
  131. /// - Parameters:
  132. /// - compression: Compression configuration.
  133. /// - connection: Connection configuration.
  134. /// - http2: HTTP2 configuration.
  135. /// - rpc: RPC configuration.
  136. /// - transportSecurity: The transport's security configuration.
  137. public init(
  138. compression: HTTP2ServerTransport.Config.Compression,
  139. connection: HTTP2ServerTransport.Config.Connection,
  140. http2: HTTP2ServerTransport.Config.HTTP2,
  141. rpc: HTTP2ServerTransport.Config.RPC,
  142. transportSecurity: TransportSecurity
  143. ) {
  144. self.compression = compression
  145. self.connection = connection
  146. self.http2 = http2
  147. self.rpc = rpc
  148. self.transportSecurity = transportSecurity
  149. }
  150. /// Default values for the different configurations.
  151. ///
  152. /// - Parameters:
  153. /// - transportSecurity: The transport's security configuration.
  154. /// - configure: A closure which allows you to modify the defaults before returning them.
  155. public static func defaults(
  156. transportSecurity: TransportSecurity,
  157. configure: (_ config: inout Self) -> Void = { _ in }
  158. ) -> Self {
  159. var config = Self(
  160. compression: .defaults,
  161. connection: .defaults,
  162. http2: .defaults,
  163. rpc: .defaults,
  164. transportSecurity: transportSecurity
  165. )
  166. configure(&config)
  167. return config
  168. }
  169. }
  170. }
  171. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  172. extension NIOTSListenerBootstrap {
  173. fileprivate func bind<Output: Sendable>(
  174. to address: GRPCHTTP2Core.SocketAddress,
  175. childChannelInitializer: @escaping @Sendable (any Channel) -> EventLoopFuture<Output>
  176. ) async throws -> NIOAsyncChannel<Output, Never> {
  177. if address.virtualSocket != nil {
  178. throw RuntimeError(
  179. code: .transportError,
  180. message: """
  181. Virtual sockets are not supported by 'HTTP2ServerTransport.TransportServices'. \
  182. Please use the 'HTTP2ServerTransport.Posix' transport.
  183. """
  184. )
  185. } else {
  186. return try await self.bind(
  187. to: NIOCore.SocketAddress(address),
  188. childChannelInitializer: childChannelInitializer
  189. )
  190. }
  191. }
  192. }
  193. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  194. extension ServerTransport where Self == HTTP2ServerTransport.TransportServices {
  195. /// Create a new `TransportServices` based HTTP/2 server transport.
  196. ///
  197. /// - Parameters:
  198. /// - address: The address to which the server should be bound.
  199. /// - config: The transport configuration.
  200. /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to the server on. This must
  201. /// be a `NIOTSEventLoopGroup` or an `EventLoop` from a `NIOTSEventLoopGroup`.
  202. public static func http2NIOTS(
  203. address: GRPCHTTP2Core.SocketAddress,
  204. config: HTTP2ServerTransport.TransportServices.Config,
  205. eventLoopGroup: NIOTSEventLoopGroup = .singletonNIOTSEventLoopGroup
  206. ) -> Self {
  207. return HTTP2ServerTransport.TransportServices(
  208. address: address,
  209. config: config,
  210. eventLoopGroup: eventLoopGroup
  211. )
  212. }
  213. }
  214. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  215. extension NWProtocolTLS.Options {
  216. convenience init(_ tlsConfig: HTTP2ServerTransport.TransportServices.Config.TLS) throws {
  217. self.init()
  218. guard let sec_identity = sec_identity_create(try tlsConfig.identityProvider()) else {
  219. throw RuntimeError(
  220. code: .transportError,
  221. message: """
  222. There was an issue creating the SecIdentity required to set up TLS. \
  223. Please check your TLS configuration.
  224. """
  225. )
  226. }
  227. sec_protocol_options_set_local_identity(
  228. self.securityProtocolOptions,
  229. sec_identity
  230. )
  231. sec_protocol_options_set_min_tls_protocol_version(
  232. self.securityProtocolOptions,
  233. .TLSv12
  234. )
  235. for `protocol` in ["grpc-exp", "h2"] {
  236. sec_protocol_options_add_tls_application_protocol(
  237. self.securityProtocolOptions,
  238. `protocol`
  239. )
  240. }
  241. }
  242. }
  243. #endif