HTTP2ClientTransport+TransportServices.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  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 connection management.
  193. public var connection: HTTP2ClientTransport.Config.Connection
  194. /// Compression configuration.
  195. public var compression: HTTP2ClientTransport.Config.Compression
  196. /// Channel callbacks for debugging.
  197. public var channelDebuggingCallbacks: HTTP2ClientTransport.Config.ChannelDebuggingCallbacks
  198. /// Creates a new connection configuration.
  199. ///
  200. /// - Parameters:
  201. /// - http2: HTTP2 configuration.
  202. /// - backoff: Backoff configuration.
  203. /// - connection: Connection configuration.
  204. /// - compression: Compression configuration.
  205. /// - channelDebuggingCallbacks: Channel callbacks for debugging.
  206. ///
  207. /// - SeeAlso: ``defaults(configure:)`` and ``defaults``.
  208. public init(
  209. http2: HTTP2ClientTransport.Config.HTTP2,
  210. backoff: HTTP2ClientTransport.Config.Backoff,
  211. connection: HTTP2ClientTransport.Config.Connection,
  212. compression: HTTP2ClientTransport.Config.Compression,
  213. channelDebuggingCallbacks: HTTP2ClientTransport.Config.ChannelDebuggingCallbacks
  214. ) {
  215. self.http2 = http2
  216. self.connection = connection
  217. self.backoff = backoff
  218. self.compression = compression
  219. self.channelDebuggingCallbacks = channelDebuggingCallbacks
  220. }
  221. /// Default configuration.
  222. public static var defaults: Self {
  223. Self.defaults()
  224. }
  225. /// Default values.
  226. ///
  227. /// - Parameters:
  228. /// - configure: A closure which allows you to modify the defaults before returning them.
  229. public static func defaults(
  230. configure: (_ config: inout Self) -> Void = { _ in }
  231. ) -> Self {
  232. var config = Self(
  233. http2: .defaults,
  234. backoff: .defaults,
  235. connection: .defaults,
  236. compression: .defaults,
  237. channelDebuggingCallbacks: .defaults
  238. )
  239. configure(&config)
  240. return config
  241. }
  242. }
  243. }
  244. @available(gRPCSwiftNIOTransport 2.0, *)
  245. extension GRPCChannel.Config {
  246. init(transportServices config: HTTP2ClientTransport.TransportServices.Config) {
  247. self.init(
  248. http2: config.http2,
  249. backoff: config.backoff,
  250. connection: config.connection,
  251. compression: config.compression
  252. )
  253. }
  254. }
  255. @available(gRPCSwiftNIOTransport 2.0, *)
  256. extension NIOTSConnectionBootstrap {
  257. fileprivate func connect<Output: Sendable>(
  258. to address: GRPCNIOTransportCore.SocketAddress,
  259. childChannelInitializer: @escaping @Sendable (any Channel) -> EventLoopFuture<Output>
  260. ) async throws -> Output {
  261. if address.virtualSocket != nil {
  262. throw RuntimeError(
  263. code: .transportError,
  264. message: """
  265. Virtual sockets are not supported by 'HTTP2ClientTransport.TransportServices'. \
  266. Please use the 'HTTP2ClientTransport.Posix' transport.
  267. """
  268. )
  269. } else {
  270. return try await self.connect(
  271. to: NIOCore.SocketAddress(address),
  272. channelInitializer: childChannelInitializer
  273. )
  274. }
  275. }
  276. }
  277. @available(gRPCSwiftNIOTransport 2.0, *)
  278. extension ClientTransport where Self == HTTP2ClientTransport.TransportServices {
  279. /// Create a new `TransportServices` based HTTP/2 client transport.
  280. ///
  281. /// - Parameters:
  282. /// - target: A target to resolve.
  283. /// - transportSecurity: The security settings applied to the transport.
  284. /// - config: Configuration for the transport.
  285. /// - resolverRegistry: A registry of resolver factories.
  286. /// - serviceConfig: Service config controlling how the transport should establish and
  287. /// load-balance connections.
  288. /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to run connections on. This must
  289. /// be a `NIOTSEventLoopGroup` or an `EventLoop` from
  290. /// a `NIOTSEventLoopGroup`.
  291. /// - Throws: When no suitable resolver could be found for the `target`.
  292. public static func http2NIOTS(
  293. target: any ResolvableTarget,
  294. transportSecurity: HTTP2ClientTransport.TransportServices.TransportSecurity,
  295. config: HTTP2ClientTransport.TransportServices.Config = .defaults,
  296. resolverRegistry: NameResolverRegistry = .defaults,
  297. serviceConfig: ServiceConfig = ServiceConfig(),
  298. eventLoopGroup: any EventLoopGroup = .singletonNIOTSEventLoopGroup
  299. ) throws -> Self {
  300. try HTTP2ClientTransport.TransportServices(
  301. target: target,
  302. transportSecurity: transportSecurity,
  303. config: config,
  304. resolverRegistry: resolverRegistry,
  305. serviceConfig: serviceConfig,
  306. eventLoopGroup: eventLoopGroup
  307. )
  308. }
  309. }
  310. @available(gRPCSwiftNIOTransport 2.0, *)
  311. extension NWProtocolTLS.Options {
  312. convenience init(
  313. _ tlsConfig: HTTP2ClientTransport.TransportServices.TLS,
  314. authority: String?
  315. ) throws {
  316. self.init()
  317. if let identityProvider = tlsConfig.identityProvider {
  318. guard let sec_identity = sec_identity_create(try identityProvider()) else {
  319. throw RuntimeError(
  320. code: .transportError,
  321. message: """
  322. There was an issue creating the SecIdentity required to set up TLS. \
  323. Please check your TLS configuration.
  324. """
  325. )
  326. }
  327. sec_protocol_options_set_local_identity(
  328. self.securityProtocolOptions,
  329. sec_identity
  330. )
  331. }
  332. switch tlsConfig.serverCertificateVerification.wrapped {
  333. case .doNotVerify:
  334. sec_protocol_options_set_peer_authentication_required(
  335. self.securityProtocolOptions,
  336. false
  337. )
  338. case .fullVerification:
  339. sec_protocol_options_set_peer_authentication_required(
  340. self.securityProtocolOptions,
  341. true
  342. )
  343. authority?.withCString { serverName in
  344. sec_protocol_options_set_tls_server_name(
  345. self.securityProtocolOptions,
  346. serverName
  347. )
  348. }
  349. case .noHostnameVerification:
  350. sec_protocol_options_set_peer_authentication_required(
  351. self.securityProtocolOptions,
  352. true
  353. )
  354. }
  355. sec_protocol_options_set_min_tls_protocol_version(
  356. self.securityProtocolOptions,
  357. .TLSv12
  358. )
  359. for `protocol` in ["grpc-exp", "h2"] {
  360. sec_protocol_options_add_tls_application_protocol(
  361. self.securityProtocolOptions,
  362. `protocol`
  363. )
  364. }
  365. self.setUpVerifyBlock(trustRootsSource: tlsConfig.trustRoots)
  366. }
  367. }
  368. #endif