ConnectionManagerChannelProvider.swift 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /*
  2. * Copyright 2021, 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. import Logging
  17. import NIOCore
  18. import NIOPosix
  19. import NIOTransportServices
  20. #if canImport(NIOSSL)
  21. import NIOSSL
  22. #endif
  23. @usableFromInline
  24. internal protocol ConnectionManagerChannelProvider {
  25. /// Make an `EventLoopFuture<Channel>`.
  26. ///
  27. /// - Parameters:
  28. /// - connectionManager: The `ConnectionManager` requesting the `Channel`.
  29. /// - eventLoop: The `EventLoop` to use for the`Channel`.
  30. /// - connectTimeout: Optional connection timeout when starting the connection.
  31. /// - logger: A logger.
  32. func makeChannel(
  33. managedBy connectionManager: ConnectionManager,
  34. onEventLoop eventLoop: EventLoop,
  35. connectTimeout: TimeAmount?,
  36. logger: Logger
  37. ) -> EventLoopFuture<Channel>
  38. }
  39. @usableFromInline
  40. internal struct DefaultChannelProvider: ConnectionManagerChannelProvider {
  41. @usableFromInline
  42. enum TLSMode {
  43. #if canImport(NIOSSL)
  44. case configureWithNIOSSL(Result<NIOSSLContext, Error>)
  45. #endif // canImport(NIOSSL)
  46. case configureWithNetworkFramework
  47. case disabled
  48. }
  49. @usableFromInline
  50. internal var connectionTarget: ConnectionTarget
  51. @usableFromInline
  52. internal var connectionKeepalive: ClientConnectionKeepalive
  53. @usableFromInline
  54. internal var connectionIdleTimeout: TimeAmount
  55. @usableFromInline
  56. internal var tlsMode: TLSMode
  57. @usableFromInline
  58. internal var tlsConfiguration: GRPCTLSConfiguration?
  59. @usableFromInline
  60. internal var httpTargetWindowSize: Int
  61. @usableFromInline
  62. internal var httpMaxFrameSize: Int
  63. @usableFromInline
  64. internal var errorDelegate: Optional<ClientErrorDelegate>
  65. @usableFromInline
  66. internal var debugChannelInitializer: Optional<(Channel) -> EventLoopFuture<Void>>
  67. @inlinable
  68. internal init(
  69. connectionTarget: ConnectionTarget,
  70. connectionKeepalive: ClientConnectionKeepalive,
  71. connectionIdleTimeout: TimeAmount,
  72. tlsMode: TLSMode,
  73. tlsConfiguration: GRPCTLSConfiguration?,
  74. httpTargetWindowSize: Int,
  75. httpMaxFrameSize: Int,
  76. errorDelegate: ClientErrorDelegate?,
  77. debugChannelInitializer: ((Channel) -> EventLoopFuture<Void>)?
  78. ) {
  79. self.connectionTarget = connectionTarget
  80. self.connectionKeepalive = connectionKeepalive
  81. self.connectionIdleTimeout = connectionIdleTimeout
  82. self.tlsMode = tlsMode
  83. self.tlsConfiguration = tlsConfiguration
  84. self.httpTargetWindowSize = httpTargetWindowSize
  85. self.httpMaxFrameSize = httpMaxFrameSize
  86. self.errorDelegate = errorDelegate
  87. self.debugChannelInitializer = debugChannelInitializer
  88. }
  89. internal init(configuration: ClientConnection.Configuration) {
  90. // Making a `NIOSSLContext` is expensive and we should only do it (at most) once per TLS
  91. // configuration. We do it now and store it in our `tlsMode` and surface any error during
  92. // channel creation (we're limited by our API in when we can throw any error).
  93. let tlsMode: TLSMode
  94. if let tlsConfiguration = configuration.tlsConfiguration {
  95. if tlsConfiguration.isNetworkFrameworkTLSBackend {
  96. tlsMode = .configureWithNetworkFramework
  97. } else {
  98. #if canImport(NIOSSL)
  99. // The '!' is okay here, we have a `tlsConfiguration` (so we must be using TLS) and we know
  100. // it's not backed by Network.framework, so it must be backed by NIOSSL.
  101. tlsMode = .configureWithNIOSSL(Result { try tlsConfiguration.makeNIOSSLContext()! })
  102. #else
  103. // TLS is configured, and we aren't using a Network.framework TLS backend, so we must be
  104. // using NIOSSL, so we must be able to import it.
  105. fatalError()
  106. #endif // canImport(NIOSSL)
  107. }
  108. } else {
  109. tlsMode = .disabled
  110. }
  111. self.init(
  112. connectionTarget: configuration.target,
  113. connectionKeepalive: configuration.connectionKeepalive,
  114. connectionIdleTimeout: configuration.connectionIdleTimeout,
  115. tlsMode: tlsMode,
  116. tlsConfiguration: configuration.tlsConfiguration,
  117. httpTargetWindowSize: configuration.httpTargetWindowSize,
  118. httpMaxFrameSize: configuration.httpMaxFrameSize,
  119. errorDelegate: configuration.errorDelegate,
  120. debugChannelInitializer: configuration.debugChannelInitializer
  121. )
  122. }
  123. private var serverHostname: String? {
  124. let hostname = self.tlsConfiguration?.hostnameOverride ?? self.connectionTarget.host
  125. return hostname.isIPAddress ? nil : hostname
  126. }
  127. private var hasTLS: Bool {
  128. return self.tlsConfiguration != nil
  129. }
  130. private func requiresZeroLengthWorkaround(eventLoop: EventLoop) -> Bool {
  131. return PlatformSupport.requiresZeroLengthWriteWorkaround(group: eventLoop, hasTLS: self.hasTLS)
  132. }
  133. @usableFromInline
  134. internal func makeChannel(
  135. managedBy connectionManager: ConnectionManager,
  136. onEventLoop eventLoop: EventLoop,
  137. connectTimeout: TimeAmount?,
  138. logger: Logger
  139. ) -> EventLoopFuture<Channel> {
  140. let hostname = self.serverHostname
  141. let needsZeroLengthWriteWorkaround = self.requiresZeroLengthWorkaround(eventLoop: eventLoop)
  142. var bootstrap = PlatformSupport.makeClientBootstrap(
  143. group: eventLoop,
  144. tlsConfiguration: self.tlsConfiguration,
  145. logger: logger
  146. )
  147. bootstrap =
  148. bootstrap
  149. .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
  150. .channelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
  151. .channelInitializer { channel in
  152. let sync = channel.pipeline.syncOperations
  153. do {
  154. if needsZeroLengthWriteWorkaround {
  155. try sync.addHandler(NIOFilterEmptyWritesHandler())
  156. }
  157. // We have a NIOSSL context to apply. If we're using TLS from NIOTS then the bootstrap
  158. // will already have the TLS options applied.
  159. switch self.tlsMode {
  160. #if canImport(NIOSSL)
  161. case let .configureWithNIOSSL(sslContext):
  162. try sync.configureNIOSSLForGRPCClient(
  163. sslContext: sslContext,
  164. serverHostname: hostname,
  165. customVerificationCallback: self.tlsConfiguration?.nioSSLCustomVerificationCallback,
  166. logger: logger
  167. )
  168. #endif // canImport(NIOSSL)
  169. // Network.framework TLS configuration is applied when creating the bootstrap so is a
  170. // no-op here.
  171. case .configureWithNetworkFramework,
  172. .disabled:
  173. ()
  174. }
  175. try sync.configureHTTP2AndGRPCHandlersForGRPCClient(
  176. channel: channel,
  177. connectionManager: connectionManager,
  178. connectionKeepalive: self.connectionKeepalive,
  179. connectionIdleTimeout: self.connectionIdleTimeout,
  180. httpTargetWindowSize: self.httpTargetWindowSize,
  181. httpMaxFrameSize: self.httpMaxFrameSize,
  182. errorDelegate: self.errorDelegate,
  183. logger: logger
  184. )
  185. } catch {
  186. return channel.eventLoop.makeFailedFuture(error)
  187. }
  188. // Run the debug initializer, if there is one.
  189. if let debugInitializer = self.debugChannelInitializer {
  190. return debugInitializer(channel)
  191. } else {
  192. return channel.eventLoop.makeSucceededVoidFuture()
  193. }
  194. }
  195. if let connectTimeout = connectTimeout {
  196. _ = bootstrap.connectTimeout(connectTimeout)
  197. }
  198. return bootstrap.connect(to: self.connectionTarget)
  199. }
  200. }