HTTP2ServerTransport+TransportServices.swift 9.0 KB

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