HTTP2ClientTransport+TransportServices.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 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. /// config: .defaults(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. private let channel: GRPCChannel
  60. public var retryThrottle: RetryThrottle? {
  61. self.channel.retryThrottle
  62. }
  63. /// Creates a new NIOTransportServices-based HTTP/2 client transport.
  64. ///
  65. /// - Parameters:
  66. /// - target: A target to resolve.
  67. /// - config: Configuration for the transport.
  68. /// - resolverRegistry: A registry of resolver factories.
  69. /// - serviceConfig: Service config controlling how the transport should establish and
  70. /// load-balance connections.
  71. /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to run connections on. This must
  72. /// be a `MultiThreadedEventLoopGroup` or an `EventLoop` from
  73. /// a `MultiThreadedEventLoopGroup`.
  74. /// - Throws: When no suitable resolver could be found for the `target`.
  75. public init(
  76. target: any ResolvableTarget,
  77. config: Config,
  78. resolverRegistry: NameResolverRegistry = .defaults,
  79. serviceConfig: ServiceConfig = ServiceConfig(),
  80. eventLoopGroup: any EventLoopGroup = .singletonNIOTSEventLoopGroup
  81. ) throws {
  82. guard let resolver = resolverRegistry.makeResolver(for: target) else {
  83. throw RuntimeError(
  84. code: .transportError,
  85. message: """
  86. No suitable resolvers to resolve '\(target)'. You must make sure that the resolver \
  87. registry has a suitable name resolver factory registered for the given target.
  88. """
  89. )
  90. }
  91. self.channel = GRPCChannel(
  92. resolver: resolver,
  93. connector: Connector(eventLoopGroup: eventLoopGroup, config: config),
  94. config: GRPCChannel.Config(transportServices: config),
  95. defaultServiceConfig: serviceConfig
  96. )
  97. }
  98. public func connect() async throws {
  99. await self.channel.connect()
  100. }
  101. public func beginGracefulShutdown() {
  102. self.channel.beginGracefulShutdown()
  103. }
  104. public func withStream<T: Sendable>(
  105. descriptor: MethodDescriptor,
  106. options: CallOptions,
  107. _ closure: (RPCStream<Inbound, Outbound>) async throws -> T
  108. ) async throws -> T {
  109. try await self.channel.withStream(descriptor: descriptor, options: options, closure)
  110. }
  111. public func config(forMethod descriptor: MethodDescriptor) -> MethodConfig? {
  112. self.channel.config(forMethod: descriptor)
  113. }
  114. }
  115. }
  116. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  117. extension HTTP2ClientTransport.TransportServices {
  118. struct Connector: HTTP2Connector {
  119. private let config: HTTP2ClientTransport.TransportServices.Config
  120. private let eventLoopGroup: any EventLoopGroup
  121. init(
  122. eventLoopGroup: any EventLoopGroup,
  123. config: HTTP2ClientTransport.TransportServices.Config
  124. ) {
  125. self.eventLoopGroup = eventLoopGroup
  126. self.config = config
  127. }
  128. func establishConnection(
  129. to address: GRPCNIOTransportCore.SocketAddress
  130. ) async throws -> HTTP2Connection {
  131. let bootstrap: NIOTSConnectionBootstrap
  132. let isPlainText: Bool
  133. switch self.config.transportSecurity.wrapped {
  134. case .plaintext:
  135. isPlainText = true
  136. bootstrap = NIOTSConnectionBootstrap(group: self.eventLoopGroup)
  137. case .tls(let tlsConfig):
  138. isPlainText = false
  139. bootstrap = NIOTSConnectionBootstrap(group: self.eventLoopGroup)
  140. .tlsOptions(try NWProtocolTLS.Options(tlsConfig))
  141. }
  142. let (channel, multiplexer) = try await bootstrap.connect(to: address) { channel in
  143. channel.eventLoop.makeCompletedFuture {
  144. try channel.pipeline.syncOperations.configureGRPCClientPipeline(
  145. channel: channel,
  146. config: GRPCChannel.Config(transportServices: self.config)
  147. )
  148. }
  149. }
  150. return HTTP2Connection(
  151. channel: channel,
  152. multiplexer: multiplexer,
  153. isPlaintext: isPlainText
  154. )
  155. }
  156. }
  157. }
  158. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  159. extension HTTP2ClientTransport.TransportServices {
  160. /// Configuration for the `TransportServices` transport.
  161. public struct Config: Sendable {
  162. /// Configuration for HTTP/2 connections.
  163. public var http2: HTTP2ClientTransport.Config.HTTP2
  164. /// Configuration for backoff used when establishing a connection.
  165. public var backoff: HTTP2ClientTransport.Config.Backoff
  166. /// Configuration for connection management.
  167. public var connection: HTTP2ClientTransport.Config.Connection
  168. /// Compression configuration.
  169. public var compression: HTTP2ClientTransport.Config.Compression
  170. /// The transport's security.
  171. public var transportSecurity: TransportSecurity
  172. /// Creates a new connection configuration.
  173. ///
  174. /// - Parameters:
  175. /// - http2: HTTP2 configuration.
  176. /// - backoff: Backoff configuration.
  177. /// - connection: Connection configuration.
  178. /// - compression: Compression configuration.
  179. /// - transportSecurity: The transport's security configuration.
  180. ///
  181. /// - SeeAlso: ``defaults(transportSecurity:configure:)``
  182. public init(
  183. http2: HTTP2ClientTransport.Config.HTTP2,
  184. backoff: HTTP2ClientTransport.Config.Backoff,
  185. connection: HTTP2ClientTransport.Config.Connection,
  186. compression: HTTP2ClientTransport.Config.Compression,
  187. transportSecurity: TransportSecurity
  188. ) {
  189. self.http2 = http2
  190. self.connection = connection
  191. self.backoff = backoff
  192. self.compression = compression
  193. self.transportSecurity = transportSecurity
  194. }
  195. /// Default values.
  196. ///
  197. /// - Parameters:
  198. /// - transportSecurity: The security settings applied to the transport.
  199. /// - configure: A closure which allows you to modify the defaults before returning them.
  200. public static func defaults(
  201. transportSecurity: TransportSecurity,
  202. configure: (_ config: inout Self) -> Void = { _ in }
  203. ) -> Self {
  204. var config = Self(
  205. http2: .defaults,
  206. backoff: .defaults,
  207. connection: .defaults,
  208. compression: .defaults,
  209. transportSecurity: transportSecurity
  210. )
  211. configure(&config)
  212. return config
  213. }
  214. }
  215. }
  216. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  217. extension GRPCChannel.Config {
  218. init(transportServices config: HTTP2ClientTransport.TransportServices.Config) {
  219. self.init(
  220. http2: config.http2,
  221. backoff: config.backoff,
  222. connection: config.connection,
  223. compression: config.compression
  224. )
  225. }
  226. }
  227. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  228. extension NIOTSConnectionBootstrap {
  229. fileprivate func connect<Output: Sendable>(
  230. to address: GRPCNIOTransportCore.SocketAddress,
  231. childChannelInitializer: @escaping @Sendable (any Channel) -> EventLoopFuture<Output>
  232. ) async throws -> Output {
  233. if address.virtualSocket != nil {
  234. throw RuntimeError(
  235. code: .transportError,
  236. message: """
  237. Virtual sockets are not supported by 'HTTP2ClientTransport.TransportServices'. \
  238. Please use the 'HTTP2ClientTransport.Posix' transport.
  239. """
  240. )
  241. } else {
  242. return try await self.connect(
  243. to: NIOCore.SocketAddress(address),
  244. channelInitializer: childChannelInitializer
  245. )
  246. }
  247. }
  248. }
  249. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  250. extension ClientTransport where Self == HTTP2ClientTransport.TransportServices {
  251. /// Create a new `TransportServices` based HTTP/2 client transport.
  252. ///
  253. /// - Parameters:
  254. /// - target: A target to resolve.
  255. /// - config: Configuration for the transport.
  256. /// - resolverRegistry: A registry of resolver factories.
  257. /// - serviceConfig: Service config controlling how the transport should establish and
  258. /// load-balance connections.
  259. /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to run connections on. This must
  260. /// be a `NIOTSEventLoopGroup` or an `EventLoop` from
  261. /// a `NIOTSEventLoopGroup`.
  262. /// - Throws: When no suitable resolver could be found for the `target`.
  263. public static func http2NIOTS(
  264. target: any ResolvableTarget,
  265. config: HTTP2ClientTransport.TransportServices.Config,
  266. resolverRegistry: NameResolverRegistry = .defaults,
  267. serviceConfig: ServiceConfig = ServiceConfig(),
  268. eventLoopGroup: any EventLoopGroup = .singletonNIOTSEventLoopGroup
  269. ) throws -> Self {
  270. try HTTP2ClientTransport.TransportServices(
  271. target: target,
  272. config: config,
  273. resolverRegistry: resolverRegistry,
  274. serviceConfig: serviceConfig,
  275. eventLoopGroup: eventLoopGroup
  276. )
  277. }
  278. }
  279. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  280. extension NWProtocolTLS.Options {
  281. convenience init(_ tlsConfig: HTTP2ClientTransport.TransportServices.Config.TLS) throws {
  282. self.init()
  283. guard let sec_identity = sec_identity_create(try tlsConfig.identityProvider()) else {
  284. throw RuntimeError(
  285. code: .transportError,
  286. message: """
  287. There was an issue creating the SecIdentity required to set up TLS. \
  288. Please check your TLS configuration.
  289. """
  290. )
  291. }
  292. sec_protocol_options_set_local_identity(
  293. self.securityProtocolOptions,
  294. sec_identity
  295. )
  296. sec_protocol_options_set_min_tls_protocol_version(
  297. self.securityProtocolOptions,
  298. .TLSv12
  299. )
  300. for `protocol` in ["grpc-exp", "h2"] {
  301. sec_protocol_options_add_tls_application_protocol(
  302. self.securityProtocolOptions,
  303. `protocol`
  304. )
  305. }
  306. }
  307. }
  308. #endif