GRPCClientConnection.swift 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. /*
  2. * Copyright 2019, 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 Foundation
  17. import NIO
  18. import NIOHTTP2
  19. import NIOSSL
  20. import NIOTLS
  21. /// Underlying channel and HTTP/2 stream multiplexer.
  22. ///
  23. /// Different service clients implementing `GRPCClient` may share an instance of this class.
  24. ///
  25. /// The connection is initially setup with a handler to verify that TLS was established
  26. /// successfully (assuming TLS is being used).
  27. ///
  28. /// ▲ |
  29. /// HTTP2Frame│ │HTTP2Frame
  30. /// ┌─┴───────────────────────▼─┐
  31. /// │ HTTP2StreamMultiplexer |
  32. /// └─▲───────────────────────┬─┘
  33. /// HTTP2Frame│ │HTTP2Frame
  34. /// ┌─┴───────────────────────▼─┐
  35. /// │ NIOHTTP2Handler │
  36. /// └─▲───────────────────────┬─┘
  37. /// ByteBuffer│ │ByteBuffer
  38. /// ┌─┴───────────────────────▼─┐
  39. /// │ GRPCTLSVerificationHandler│
  40. /// └─▲───────────────────────┬─┘
  41. /// ByteBuffer│ │ByteBuffer
  42. /// ┌─┴───────────────────────▼─┐
  43. /// │ NIOSSLHandler │
  44. /// └─▲───────────────────────┬─┘
  45. /// ByteBuffer│ │ByteBuffer
  46. /// │ ▼
  47. ///
  48. /// The `GRPCTLSVerificationHandler` observes the outcome of the SSL handshake and determines
  49. /// whether a `GRPCClientConnection` should be returned to the user. In either eventuality, the
  50. /// handler removes itself from the pipeline once TLS has been verified. There is also a delegated
  51. /// error handler after the `HTTPStreamMultiplexer` in the main channel which uses the error
  52. /// delegate associated with this connection (see `GRPCDelegatingErrorHandler`).
  53. ///
  54. /// See `BaseClientCall` for a description of the remainder of the client pipeline.
  55. open class GRPCClientConnection {
  56. /// Starts a connection to the given host and port.
  57. ///
  58. /// - Parameters:
  59. /// - host: Host to connect to.
  60. /// - port: Port on the host to connect to.
  61. /// - eventLoopGroup: Event loop group to run the connection on.
  62. /// - errorDelegate: An error delegate which is called when errors are caught. Provided
  63. /// delegates **must not maintain a strong reference to this `GRPCClientConnection`**. Doing
  64. /// so will cause a retain cycle. Defaults to a delegate which logs errors in debug builds
  65. /// only.
  66. /// - tlsMode: How TLS should be configured for this connection.
  67. /// - hostOverride: Value to use for TLS SNI extension; this must not be an IP address. Ignored
  68. /// if `tlsMode` is `.none`.
  69. /// - Returns: A future which will be fulfilled with a connection to the remote peer.
  70. public static func start(
  71. host: String,
  72. port: Int,
  73. eventLoopGroup: EventLoopGroup,
  74. errorDelegate: ClientErrorDelegate? = DebugOnlyLoggingClientErrorDelegate.shared,
  75. tls tlsMode: TLSMode = .none,
  76. hostOverride: String? = nil
  77. ) throws -> EventLoopFuture<GRPCClientConnection> {
  78. let bootstrap = ClientBootstrap(group: eventLoopGroup)
  79. // Enable SO_REUSEADDR.
  80. .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
  81. .channelInitializer { channel in
  82. configureTLS(mode: tlsMode, channel: channel, host: hostOverride ?? host, errorDelegate: errorDelegate).flatMap {
  83. channel.configureHTTP2Pipeline(mode: .client)
  84. }.flatMap { _ in
  85. channel.pipeline.addHandler(GRPCDelegatingErrorHandler(delegate: errorDelegate))
  86. }
  87. }
  88. return bootstrap.connect(host: host, port: port).flatMap { channel in
  89. // Check the handshake succeeded and a valid protocol was negotiated via ALPN.
  90. let tlsVerified: EventLoopFuture<Void>
  91. if case .none = tlsMode {
  92. tlsVerified = channel.eventLoop.makeSucceededFuture(())
  93. } else {
  94. // TODO: Use `handler(type:)` introduced in https://github.com/apple/swift-nio/pull/974
  95. // once it has been released.
  96. tlsVerified = channel.pipeline.context(handlerType: GRPCTLSVerificationHandler.self).map {
  97. $0.handler as! GRPCTLSVerificationHandler
  98. }.flatMap {
  99. // Use the result of the verification future to determine whether we should return a
  100. // connection to the caller. Note that even though it contains a `Void` it may also
  101. // contain an `Error`, which is what we are interested in here.
  102. $0.verification
  103. }
  104. }
  105. return tlsVerified.flatMap {
  106. // TODO: Use `handler(type:)` introduced in https://github.com/apple/swift-nio/pull/974
  107. // once it has been released.
  108. channel.pipeline.context(handlerType: HTTP2StreamMultiplexer.self)
  109. }.map {
  110. $0.handler as! HTTP2StreamMultiplexer
  111. }.map { multiplexer in
  112. GRPCClientConnection(channel: channel, multiplexer: multiplexer, host: host, httpProtocol: tlsMode.httpProtocol, errorDelegate: errorDelegate)
  113. }
  114. }
  115. }
  116. /// Configure an SSL handler on the channel, if one is required.
  117. ///
  118. /// - Parameters:
  119. /// - mode: TLS mode to use when creating the new handler.
  120. /// - channel: The channel on which to add the SSL handler.
  121. /// - host: The hostname of the server we're connecting to.
  122. /// - errorDelegate: The error delegate to use.
  123. /// - Returns: A future which will be succeeded when the pipeline has been configured.
  124. private static func configureTLS(mode tls: TLSMode, channel: Channel, host: String, errorDelegate: ClientErrorDelegate?) -> EventLoopFuture<Void> {
  125. let handlerAddedPromise: EventLoopPromise<Void> = channel.eventLoop.makePromise()
  126. do {
  127. guard let sslContext = try tls.makeSSLContext() else {
  128. handlerAddedPromise.succeed(())
  129. return handlerAddedPromise.futureResult
  130. }
  131. let sslHandler = try NIOSSLClientHandler(context: sslContext, serverHostname: host)
  132. let verificationHandler = GRPCTLSVerificationHandler(errorDelegate: errorDelegate)
  133. channel.pipeline.addHandlers(sslHandler, verificationHandler).cascade(to: handlerAddedPromise)
  134. } catch {
  135. handlerAddedPromise.fail(error)
  136. }
  137. return handlerAddedPromise.futureResult
  138. }
  139. public let channel: Channel
  140. public let multiplexer: HTTP2StreamMultiplexer
  141. public let host: String
  142. public let httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol
  143. public let errorDelegate: ClientErrorDelegate?
  144. init(channel: Channel, multiplexer: HTTP2StreamMultiplexer, host: String, httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol, errorDelegate: ClientErrorDelegate?) {
  145. self.channel = channel
  146. self.multiplexer = multiplexer
  147. self.host = host
  148. self.httpProtocol = httpProtocol
  149. self.errorDelegate = errorDelegate
  150. }
  151. /// Fired when the client shuts down.
  152. public var onClose: EventLoopFuture<Void> {
  153. return channel.closeFuture
  154. }
  155. public func close() -> EventLoopFuture<Void> {
  156. return channel.close(mode: .all)
  157. }
  158. }
  159. extension GRPCClientConnection {
  160. public enum TLSMode {
  161. case none
  162. case anonymous
  163. case custom(NIOSSLContext)
  164. /// Returns an SSL context for the TLS mode.
  165. ///
  166. /// - Returns: An SSL context for the TLS mode, or `nil` if TLS is not being used.
  167. public func makeSSLContext() throws -> NIOSSLContext? {
  168. switch self {
  169. case .none:
  170. return nil
  171. case .anonymous:
  172. return try NIOSSLContext(configuration: .forClient())
  173. case .custom(let context):
  174. return context
  175. }
  176. }
  177. /// Rethrns the HTTP protocol for the TLS mode.
  178. public var httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol {
  179. switch self {
  180. case .none:
  181. return .http
  182. case .anonymous, .custom:
  183. return .https
  184. }
  185. }
  186. }
  187. }