2
0

ConnectionManagerChannelProvider.swift 7.1 KB

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