ConnectionManagerChannelProvider.swift 6.8 KB

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