ConnectionManagerChannelProvider.swift 7.3 KB

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