GRPCClientConnection.swift 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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 and TCP_NODELAY.
  80. .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
  81. .channelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
  82. .channelInitializer { channel in
  83. configureTLS(mode: tlsMode, channel: channel, host: hostOverride ?? host, errorDelegate: errorDelegate).flatMap {
  84. channel.configureHTTP2Pipeline(mode: .client)
  85. }.flatMap { _ in
  86. channel.pipeline.addHandler(GRPCDelegatingErrorHandler(delegate: errorDelegate))
  87. }
  88. }
  89. return bootstrap.connect(host: host, port: port).flatMap { channel in
  90. // Check the handshake succeeded and a valid protocol was negotiated via ALPN.
  91. let tlsVerified: EventLoopFuture<Void>
  92. if case .none = tlsMode {
  93. tlsVerified = channel.eventLoop.makeSucceededFuture(())
  94. } else {
  95. // TODO: Use `handler(type:)` introduced in https://github.com/apple/swift-nio/pull/974
  96. // once it has been released.
  97. tlsVerified = channel.pipeline.context(handlerType: GRPCTLSVerificationHandler.self).map {
  98. $0.handler as! GRPCTLSVerificationHandler
  99. }.flatMap {
  100. // Use the result of the verification future to determine whether we should return a
  101. // connection to the caller. Note that even though it contains a `Void` it may also
  102. // contain an `Error`, which is what we are interested in here.
  103. $0.verification
  104. }
  105. }
  106. return tlsVerified.flatMap {
  107. // TODO: Use `handler(type:)` introduced in https://github.com/apple/swift-nio/pull/974
  108. // once it has been released.
  109. channel.pipeline.context(handlerType: HTTP2StreamMultiplexer.self)
  110. }.map {
  111. $0.handler as! HTTP2StreamMultiplexer
  112. }.map { multiplexer in
  113. GRPCClientConnection(channel: channel, multiplexer: multiplexer, host: host, httpProtocol: tlsMode.httpProtocol, errorDelegate: errorDelegate)
  114. }
  115. }
  116. }
  117. /// Configure an SSL handler on the channel, if one is required.
  118. ///
  119. /// - Parameters:
  120. /// - mode: TLS mode to use when creating the new handler.
  121. /// - channel: The channel on which to add the SSL handler.
  122. /// - host: The hostname of the server we're connecting to.
  123. /// - errorDelegate: The error delegate to use.
  124. /// - Returns: A future which will be succeeded when the pipeline has been configured.
  125. private static func configureTLS(mode tls: TLSMode, channel: Channel, host: String, errorDelegate: ClientErrorDelegate?) -> EventLoopFuture<Void> {
  126. let handlerAddedPromise: EventLoopPromise<Void> = channel.eventLoop.makePromise()
  127. do {
  128. guard let sslContext = try tls.makeSSLContext() else {
  129. handlerAddedPromise.succeed(())
  130. return handlerAddedPromise.futureResult
  131. }
  132. let sslHandler = try NIOSSLClientHandler(context: sslContext, serverHostname: host)
  133. let verificationHandler = GRPCTLSVerificationHandler(errorDelegate: errorDelegate)
  134. channel.pipeline.addHandlers(sslHandler, verificationHandler).cascade(to: handlerAddedPromise)
  135. } catch {
  136. handlerAddedPromise.fail(error)
  137. }
  138. return handlerAddedPromise.futureResult
  139. }
  140. public let channel: Channel
  141. public let multiplexer: HTTP2StreamMultiplexer
  142. public let host: String
  143. public let httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol
  144. public let errorDelegate: ClientErrorDelegate?
  145. init(channel: Channel, multiplexer: HTTP2StreamMultiplexer, host: String, httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol, errorDelegate: ClientErrorDelegate?) {
  146. self.channel = channel
  147. self.multiplexer = multiplexer
  148. self.host = host
  149. self.httpProtocol = httpProtocol
  150. self.errorDelegate = errorDelegate
  151. }
  152. /// Fired when the client shuts down.
  153. public var onClose: EventLoopFuture<Void> {
  154. return channel.closeFuture
  155. }
  156. public func close() -> EventLoopFuture<Void> {
  157. return channel.close(mode: .all)
  158. }
  159. }
  160. extension GRPCClientConnection {
  161. public enum TLSMode {
  162. case none
  163. case anonymous
  164. case custom(NIOSSLContext)
  165. /// Returns an SSL context for the TLS mode.
  166. ///
  167. /// - Returns: An SSL context for the TLS mode, or `nil` if TLS is not being used.
  168. public func makeSSLContext() throws -> NIOSSLContext? {
  169. switch self {
  170. case .none:
  171. return nil
  172. case .anonymous:
  173. return try NIOSSLContext(configuration: .forClient())
  174. case .custom(let context):
  175. return context
  176. }
  177. }
  178. /// Rethrns the HTTP protocol for the TLS mode.
  179. public var httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol {
  180. switch self {
  181. case .none:
  182. return .http
  183. case .anonymous, .custom:
  184. return .https
  185. }
  186. }
  187. }
  188. }