HTTP2ClientTransport+TransportServices.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  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 GRPCNIOTransportCore
  19. public import NIOTransportServices // has to be public because of default argument value in init
  20. public import NIOCore // has to be public because of EventLoopGroup param in init
  21. private import Network
  22. @available(gRPCSwiftNIOTransport 2.0, *)
  23. extension HTTP2ClientTransport {
  24. /// A `ClientTransport` using HTTP/2 built on top of `NIOTransportServices`.
  25. ///
  26. /// This transport builds on top of SwiftNIO's Transport Services networking layer and is the recommended
  27. /// variant for use on Darwin-based platforms (macOS, iOS, etc.).
  28. /// If you are targeting Linux platforms then you should use the `NIOPosix` variant of
  29. /// the `HTTP2ClientTransport`.
  30. ///
  31. /// To use this transport you need to provide a 'target' to connect to which will be resolved
  32. /// by an appropriate resolver from the resolver registry. By default the resolver registry can
  33. /// resolve DNS targets, IPv4 and IPv6 targets, and Unix domain socket targets. Virtual Socket
  34. /// targets are not supported with this transport. If you use a custom target you must also provide an
  35. /// appropriately configured registry.
  36. ///
  37. /// You can control various aspects of connection creation, management, security and RPC behavior via
  38. /// the ``Config``. Load balancing policies and other RPC specific behavior can be configured via
  39. /// the `ServiceConfig` (if it isn't provided by a resolver).
  40. ///
  41. /// Beyond creating the transport you don't need to interact with it directly, instead, pass it
  42. /// to a `GRPCClient`:
  43. ///
  44. /// ```swift
  45. /// try await withThrowingDiscardingTaskGroup { group in
  46. /// let transport = try HTTP2ClientTransport.TransportServices(
  47. /// target: .ipv4(host: "example.com"),
  48. /// transportSecurity: .plaintext
  49. /// )
  50. /// let client = GRPCClient(transport: transport)
  51. /// group.addTask {
  52. /// try await client.run()
  53. /// }
  54. ///
  55. /// // ...
  56. /// }
  57. /// ```
  58. public struct TransportServices: ClientTransport {
  59. public typealias Bytes = GRPCNIOTransportBytes
  60. private let channel: GRPCChannel
  61. public var retryThrottle: RetryThrottle? {
  62. self.channel.retryThrottle
  63. }
  64. /// Creates a new NIOTransportServices-based HTTP/2 client transport.
  65. ///
  66. /// - Parameters:
  67. /// - target: A target to resolve.
  68. /// - transportSecurity: The configuration for securing network traffic.
  69. /// - config: Configuration for the transport.
  70. /// - resolverRegistry: A registry of resolver factories.
  71. /// - serviceConfig: Service config controlling how the transport should establish and
  72. /// load-balance connections.
  73. /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to run connections on. This must
  74. /// be a `MultiThreadedEventLoopGroup` or an `EventLoop` from
  75. /// a `MultiThreadedEventLoopGroup`.
  76. /// - Throws: When no suitable resolver could be found for the `target`.
  77. public init(
  78. target: any ResolvableTarget,
  79. transportSecurity: TransportSecurity,
  80. config: Config = .defaults,
  81. resolverRegistry: NameResolverRegistry = .defaults,
  82. serviceConfig: ServiceConfig = ServiceConfig(),
  83. eventLoopGroup: any EventLoopGroup = .singletonNIOTSEventLoopGroup
  84. ) throws {
  85. guard let resolver = resolverRegistry.makeResolver(for: target) else {
  86. throw RuntimeError(
  87. code: .transportError,
  88. message: """
  89. No suitable resolvers to resolve '\(target)'. You must make sure that the resolver \
  90. registry has a suitable name resolver factory registered for the given target.
  91. """
  92. )
  93. }
  94. self.channel = GRPCChannel(
  95. resolver: resolver,
  96. connector: Connector(
  97. eventLoopGroup: eventLoopGroup,
  98. config: config,
  99. transportSecurity: transportSecurity
  100. ),
  101. config: GRPCChannel.Config(transportServices: config),
  102. defaultServiceConfig: serviceConfig
  103. )
  104. }
  105. public func connect() async throws {
  106. await self.channel.connect()
  107. }
  108. public func beginGracefulShutdown() {
  109. self.channel.beginGracefulShutdown()
  110. }
  111. public func withStream<T: Sendable>(
  112. descriptor: MethodDescriptor,
  113. options: CallOptions,
  114. _ closure: (RPCStream<Inbound, Outbound>, ClientContext) async throws -> T
  115. ) async throws -> T {
  116. try await self.channel.withStream(descriptor: descriptor, options: options, closure)
  117. }
  118. public func config(forMethod descriptor: MethodDescriptor) -> MethodConfig? {
  119. self.channel.config(forMethod: descriptor)
  120. }
  121. }
  122. }
  123. @available(gRPCSwiftNIOTransport 2.0, *)
  124. extension HTTP2ClientTransport.TransportServices {
  125. struct Connector: HTTP2Connector {
  126. private let config: HTTP2ClientTransport.TransportServices.Config
  127. private let transportSecurity: HTTP2ClientTransport.TransportServices.TransportSecurity
  128. private let eventLoopGroup: any EventLoopGroup
  129. init(
  130. eventLoopGroup: any EventLoopGroup,
  131. config: HTTP2ClientTransport.TransportServices.Config,
  132. transportSecurity: HTTP2ClientTransport.TransportServices.TransportSecurity
  133. ) {
  134. self.eventLoopGroup = eventLoopGroup
  135. self.config = config
  136. self.transportSecurity = transportSecurity
  137. }
  138. func establishConnection(
  139. to address: GRPCNIOTransportCore.SocketAddress,
  140. sniServerHostname: String?
  141. ) async throws -> HTTP2Connection {
  142. let bootstrap: NIOTSConnectionBootstrap
  143. let isPlainText: Bool
  144. switch self.transportSecurity.wrapped {
  145. case .plaintext:
  146. isPlainText = true
  147. bootstrap = NIOTSConnectionBootstrap(group: self.eventLoopGroup)
  148. .channelOption(NIOTSChannelOptions.waitForActivity, value: false)
  149. case .tls(let tlsConfig):
  150. isPlainText = false
  151. do {
  152. let options = try NWProtocolTLS.Options(tlsConfig, authority: sniServerHostname)
  153. bootstrap = NIOTSConnectionBootstrap(group: self.eventLoopGroup)
  154. .channelOption(NIOTSChannelOptions.waitForActivity, value: false)
  155. .tlsOptions(options)
  156. } catch {
  157. throw RuntimeError(
  158. code: .transportError,
  159. message: "Couldn't create NWProtocolTLS.Options, check your TLS configuration.",
  160. cause: error
  161. )
  162. }
  163. }
  164. let (channel, multiplexer) = try await bootstrap.connect(to: address) { channel in
  165. channel.eventLoop.makeCompletedFuture {
  166. try channel.pipeline.syncOperations.configureGRPCClientPipeline(
  167. channel: channel,
  168. config: GRPCChannel.Config(transportServices: self.config)
  169. )
  170. }.runInitializerIfSet(
  171. self.config.channelDebuggingCallbacks.onCreateTCPConnection,
  172. on: channel
  173. )
  174. }
  175. return HTTP2Connection(
  176. channel: channel,
  177. multiplexer: multiplexer,
  178. isPlaintext: isPlainText,
  179. onCreateHTTP2Stream: self.config.channelDebuggingCallbacks.onCreateHTTP2Stream
  180. )
  181. }
  182. }
  183. }
  184. @available(gRPCSwiftNIOTransport 2.0, *)
  185. extension HTTP2ClientTransport.TransportServices {
  186. /// Configuration for the `TransportServices` transport.
  187. public struct Config: Sendable {
  188. /// Configuration for HTTP/2 connections.
  189. public var http2: HTTP2ClientTransport.Config.HTTP2
  190. /// Configuration for backoff used when establishing a connection.
  191. public var backoff: HTTP2ClientTransport.Config.Backoff
  192. /// Configuration for backoff used when resolving names.
  193. @available(gRPCSwiftNIOTransport 2.4, *)
  194. public var resolverBackoff: HTTP2ClientTransport.Config.Backoff
  195. /// Configuration for connection management.
  196. public var connection: HTTP2ClientTransport.Config.Connection
  197. /// Compression configuration.
  198. public var compression: HTTP2ClientTransport.Config.Compression
  199. /// Channel callbacks for debugging.
  200. public var channelDebuggingCallbacks: HTTP2ClientTransport.Config.ChannelDebuggingCallbacks
  201. /// Creates a new connection configuration.
  202. ///
  203. /// - Parameters:
  204. /// - http2: HTTP2 configuration.
  205. /// - backoff: Backoff configuration.
  206. /// - connection: Connection configuration.
  207. /// - compression: Compression configuration.
  208. /// - channelDebuggingCallbacks: Channel callbacks for debugging.
  209. ///
  210. /// - SeeAlso: ``defaults(configure:)`` and ``defaults``.
  211. public init(
  212. http2: HTTP2ClientTransport.Config.HTTP2,
  213. backoff: HTTP2ClientTransport.Config.Backoff,
  214. connection: HTTP2ClientTransport.Config.Connection,
  215. compression: HTTP2ClientTransport.Config.Compression,
  216. channelDebuggingCallbacks: HTTP2ClientTransport.Config.ChannelDebuggingCallbacks
  217. ) {
  218. self.init(
  219. http2: http2,
  220. backoff: backoff,
  221. resolverBackoff: Self.defaults.resolverBackoff,
  222. connection: connection,
  223. compression: compression,
  224. channelDebuggingCallbacks: channelDebuggingCallbacks
  225. )
  226. }
  227. private init(
  228. http2: HTTP2ClientTransport.Config.HTTP2,
  229. backoff: HTTP2ClientTransport.Config.Backoff,
  230. resolverBackoff: HTTP2ClientTransport.Config.Backoff,
  231. connection: HTTP2ClientTransport.Config.Connection,
  232. compression: HTTP2ClientTransport.Config.Compression,
  233. channelDebuggingCallbacks: HTTP2ClientTransport.Config.ChannelDebuggingCallbacks
  234. ) {
  235. self.http2 = http2
  236. self.connection = connection
  237. self.backoff = backoff
  238. self.resolverBackoff = resolverBackoff
  239. self.compression = compression
  240. self.channelDebuggingCallbacks = channelDebuggingCallbacks
  241. }
  242. /// Default configuration.
  243. public static var defaults: Self {
  244. Self.defaults()
  245. }
  246. /// Default values.
  247. ///
  248. /// - Parameters:
  249. /// - configure: A closure which allows you to modify the defaults before returning them.
  250. public static func defaults(
  251. configure: (_ config: inout Self) -> Void = { _ in }
  252. ) -> Self {
  253. var config = Self(
  254. http2: .defaults,
  255. backoff: .defaults,
  256. resolverBackoff: .defaults,
  257. connection: .defaults,
  258. compression: .defaults,
  259. channelDebuggingCallbacks: .defaults
  260. )
  261. configure(&config)
  262. return config
  263. }
  264. }
  265. }
  266. @available(gRPCSwiftNIOTransport 2.0, *)
  267. extension GRPCChannel.Config {
  268. init(transportServices config: HTTP2ClientTransport.TransportServices.Config) {
  269. self.init(
  270. http2: config.http2,
  271. backoff: config.backoff,
  272. resolverBackoff: config.resolverBackoff,
  273. connection: config.connection,
  274. compression: config.compression
  275. )
  276. }
  277. }
  278. @available(gRPCSwiftNIOTransport 2.0, *)
  279. extension NIOTSConnectionBootstrap {
  280. fileprivate func connect<Output: Sendable>(
  281. to address: GRPCNIOTransportCore.SocketAddress,
  282. childChannelInitializer: @escaping @Sendable (any Channel) -> EventLoopFuture<Output>
  283. ) async throws -> Output {
  284. if address.virtualSocket != nil {
  285. throw RuntimeError(
  286. code: .transportError,
  287. message: """
  288. Virtual sockets are not supported by 'HTTP2ClientTransport.TransportServices'. \
  289. Please use the 'HTTP2ClientTransport.Posix' transport.
  290. """
  291. )
  292. } else {
  293. return try await self.connect(
  294. to: NIOCore.SocketAddress(address),
  295. channelInitializer: childChannelInitializer
  296. )
  297. }
  298. }
  299. }
  300. @available(gRPCSwiftNIOTransport 2.0, *)
  301. extension ClientTransport where Self == HTTP2ClientTransport.TransportServices {
  302. /// Create a new `TransportServices` based HTTP/2 client transport.
  303. ///
  304. /// - Parameters:
  305. /// - target: A target to resolve.
  306. /// - transportSecurity: The security settings applied to the transport.
  307. /// - config: Configuration for the transport.
  308. /// - resolverRegistry: A registry of resolver factories.
  309. /// - serviceConfig: Service config controlling how the transport should establish and
  310. /// load-balance connections.
  311. /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to run connections on. This must
  312. /// be a `NIOTSEventLoopGroup` or an `EventLoop` from
  313. /// a `NIOTSEventLoopGroup`.
  314. /// - Throws: When no suitable resolver could be found for the `target`.
  315. public static func http2NIOTS(
  316. target: any ResolvableTarget,
  317. transportSecurity: HTTP2ClientTransport.TransportServices.TransportSecurity,
  318. config: HTTP2ClientTransport.TransportServices.Config = .defaults,
  319. resolverRegistry: NameResolverRegistry = .defaults,
  320. serviceConfig: ServiceConfig = ServiceConfig(),
  321. eventLoopGroup: any EventLoopGroup = .singletonNIOTSEventLoopGroup
  322. ) throws -> Self {
  323. try HTTP2ClientTransport.TransportServices(
  324. target: target,
  325. transportSecurity: transportSecurity,
  326. config: config,
  327. resolverRegistry: resolverRegistry,
  328. serviceConfig: serviceConfig,
  329. eventLoopGroup: eventLoopGroup
  330. )
  331. }
  332. }
  333. @available(gRPCSwiftNIOTransport 2.0, *)
  334. extension NWProtocolTLS.Options {
  335. convenience init(
  336. _ tlsConfig: HTTP2ClientTransport.TransportServices.TLS,
  337. authority: String?
  338. ) throws {
  339. self.init()
  340. if let identityProvider = tlsConfig.identityProvider {
  341. guard let sec_identity = sec_identity_create(try identityProvider()) else {
  342. throw RuntimeError(
  343. code: .transportError,
  344. message: """
  345. There was an issue creating the SecIdentity required to set up TLS. \
  346. Please check your TLS configuration.
  347. """
  348. )
  349. }
  350. sec_protocol_options_set_local_identity(
  351. self.securityProtocolOptions,
  352. sec_identity
  353. )
  354. }
  355. switch tlsConfig.serverCertificateVerification.wrapped {
  356. case .doNotVerify:
  357. sec_protocol_options_set_peer_authentication_required(
  358. self.securityProtocolOptions,
  359. false
  360. )
  361. case .fullVerification:
  362. sec_protocol_options_set_peer_authentication_required(
  363. self.securityProtocolOptions,
  364. true
  365. )
  366. authority?.withCString { serverName in
  367. sec_protocol_options_set_tls_server_name(
  368. self.securityProtocolOptions,
  369. serverName
  370. )
  371. }
  372. case .noHostnameVerification:
  373. sec_protocol_options_set_peer_authentication_required(
  374. self.securityProtocolOptions,
  375. true
  376. )
  377. }
  378. sec_protocol_options_set_min_tls_protocol_version(
  379. self.securityProtocolOptions,
  380. .TLSv12
  381. )
  382. for `protocol` in ["grpc-exp", "h2"] {
  383. sec_protocol_options_add_tls_application_protocol(
  384. self.securityProtocolOptions,
  385. `protocol`
  386. )
  387. }
  388. self.setUpVerifyBlock(trustRootsSource: tlsConfig.trustRoots)
  389. }
  390. }
  391. #endif