GRPCClientConnection.swift 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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. open class GRPCClientConnection {
  25. /// Starts a connection to the given host and port.
  26. ///
  27. /// - Parameters:
  28. /// - host: Host to connect to.
  29. /// - port: Port on the host to connect to.
  30. /// - eventLoopGroup: Event loop group to run the connection on.
  31. /// - tlsMode: How TLS should be configured for this connection.
  32. /// - hostOverride: Value to use for TLS SNI extension; this must not be an IP address. Ignored
  33. /// if `tlsMode` is `.none`.
  34. /// - Returns: A future which will be fulfilled with a connection to the remote peer.
  35. public static func start(
  36. host: String,
  37. port: Int,
  38. eventLoopGroup: EventLoopGroup,
  39. tls tlsMode: TLSMode = .none,
  40. hostOverride: String? = nil
  41. ) throws -> EventLoopFuture<GRPCClientConnection> {
  42. let bootstrap = ClientBootstrap(group: eventLoopGroup)
  43. // Enable SO_REUSEADDR.
  44. .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
  45. .channelInitializer { channel in
  46. configureTLS(mode: tlsMode, channel: channel, host: hostOverride ?? host).flatMap {
  47. channel.configureHTTP2Pipeline(mode: .client)
  48. }.map { _ in }
  49. }
  50. return bootstrap.connect(host: host, port: port).flatMap { channel in
  51. // Check the handshake succeeded and a valid protocol was negotiated via ALPN.
  52. let tlsVerified: EventLoopFuture<Void>
  53. if case .none = tlsMode {
  54. tlsVerified = channel.eventLoop.makeSucceededFuture(())
  55. } else {
  56. // TODO: Use `handler(type:)` introduced in https://github.com/apple/swift-nio/pull/974
  57. // once it has been released.
  58. tlsVerified = channel.pipeline.context(handlerType: GRPCTLSVerificationHandler.self).map {
  59. $0.handler as! GRPCTLSVerificationHandler
  60. }.flatMap {
  61. // Use the result of the verification future to determine whether we should return a
  62. // connection to the caller. Note that even though it contains a `Void` it may also
  63. // contain an `Error`, which is what we are interested in here.
  64. $0.verification
  65. }
  66. }
  67. return tlsVerified.flatMap {
  68. // TODO: Use `handler(type:)` introduced in https://github.com/apple/swift-nio/pull/974
  69. // once it has been released.
  70. channel.pipeline.context(handlerType: HTTP2StreamMultiplexer.self)
  71. }.map {
  72. $0.handler as! HTTP2StreamMultiplexer
  73. }.map { multiplexer in
  74. GRPCClientConnection(channel: channel, multiplexer: multiplexer, host: host, httpProtocol: tlsMode.httpProtocol)
  75. }
  76. }
  77. }
  78. /// Configure an SSL handler on the channel, if one is required.
  79. ///
  80. /// - Parameters:
  81. /// - mode: TLS mode to use when creating the new handler.
  82. /// - channel: The channel on which to add the SSL handler.
  83. /// - host: The hostname of the server we're connecting to.
  84. /// - Returns: A future which will be succeeded when the pipeline has been configured.
  85. private static func configureTLS(mode tls: TLSMode, channel: Channel, host: String) -> EventLoopFuture<Void> {
  86. let handlerAddedPromise: EventLoopPromise<Void> = channel.eventLoop.makePromise()
  87. do {
  88. guard let sslContext = try tls.makeSSLContext() else {
  89. handlerAddedPromise.succeed(())
  90. return handlerAddedPromise.futureResult
  91. }
  92. let sslHandler = try NIOSSLClientHandler(context: sslContext, serverHostname: host)
  93. let verificationHandler = GRPCTLSVerificationHandler()
  94. channel.pipeline.addHandlers(sslHandler, verificationHandler).cascade(to: handlerAddedPromise)
  95. } catch {
  96. handlerAddedPromise.fail(error)
  97. }
  98. return handlerAddedPromise.futureResult
  99. }
  100. public let channel: Channel
  101. public let multiplexer: HTTP2StreamMultiplexer
  102. public let host: String
  103. public let httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol
  104. init(channel: Channel, multiplexer: HTTP2StreamMultiplexer, host: String, httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol) {
  105. self.channel = channel
  106. self.multiplexer = multiplexer
  107. self.host = host
  108. self.httpProtocol = httpProtocol
  109. }
  110. /// Fired when the client shuts down.
  111. public var onClose: EventLoopFuture<Void> {
  112. return channel.closeFuture
  113. }
  114. public func close() -> EventLoopFuture<Void> {
  115. return channel.close(mode: .all)
  116. }
  117. }
  118. extension GRPCClientConnection {
  119. public enum TLSMode {
  120. case none
  121. case anonymous
  122. case custom(NIOSSLContext)
  123. /// Returns an SSL context for the TLS mode.
  124. ///
  125. /// - Returns: An SSL context for the TLS mode, or `nil` if TLS is not being used.
  126. public func makeSSLContext() throws -> NIOSSLContext? {
  127. switch self {
  128. case .none:
  129. return nil
  130. case .anonymous:
  131. return try NIOSSLContext(configuration: .forClient())
  132. case .custom(let context):
  133. return context
  134. }
  135. }
  136. /// Rethrns the HTTP protocol for the TLS mode.
  137. public var httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol {
  138. switch self {
  139. case .none:
  140. return .http
  141. case .anonymous, .custom:
  142. return .https
  143. }
  144. }
  145. }
  146. }