ConnectionManagerChannelProvider.swift 6.9 KB

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