2
0

ConnectionManagerChannelProvider.swift 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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. #if canImport(NIOSSL)
  20. import NIOSSL
  21. #endif
  22. import NIOTransportServices
  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 = bootstrap
  148. .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
  149. .channelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
  150. .channelInitializer { channel in
  151. let sync = channel.pipeline.syncOperations
  152. do {
  153. if needsZeroLengthWriteWorkaround {
  154. try sync.addHandler(NIOFilterEmptyWritesHandler())
  155. }
  156. // We have a NIOSSL context to apply. If we're using TLS from NIOTS then the bootstrap
  157. // will already have the TLS options applied.
  158. switch self.tlsMode {
  159. #if canImport(NIOSSL)
  160. case let .configureWithNIOSSL(sslContext):
  161. try sync.configureNIOSSLForGRPCClient(
  162. sslContext: sslContext,
  163. serverHostname: hostname,
  164. customVerificationCallback: self.tlsConfiguration?.nioSSLCustomVerificationCallback,
  165. logger: logger
  166. )
  167. #endif // canImport(NIOSSL)
  168. // Network.framework TLS configuration is applied when creating the bootstrap so is a
  169. // no-op here.
  170. case .configureWithNetworkFramework,
  171. .disabled:
  172. ()
  173. }
  174. try sync.configureHTTP2AndGRPCHandlersForGRPCClient(
  175. channel: channel,
  176. connectionManager: connectionManager,
  177. connectionKeepalive: self.connectionKeepalive,
  178. connectionIdleTimeout: self.connectionIdleTimeout,
  179. httpTargetWindowSize: self.httpTargetWindowSize,
  180. httpMaxFrameSize: self.httpMaxFrameSize,
  181. errorDelegate: self.errorDelegate,
  182. logger: logger
  183. )
  184. } catch {
  185. return channel.eventLoop.makeFailedFuture(error)
  186. }
  187. // Run the debug initializer, if there is one.
  188. if let debugInitializer = self.debugChannelInitializer {
  189. return debugInitializer(channel)
  190. } else {
  191. return channel.eventLoop.makeSucceededVoidFuture()
  192. }
  193. }
  194. if let connectTimeout = connectTimeout {
  195. _ = bootstrap.connectTimeout(connectTimeout)
  196. }
  197. return bootstrap.connect(to: self.connectionTarget)
  198. }
  199. }