ClientConnection.swift 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  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. import Logging
  22. /// Provides a single, managed connection to a server.
  23. ///
  24. /// The connection to the server is provided by a single channel which will attempt to reconnect
  25. /// to the server if the connection is dropped. This connection is guaranteed to always use the same
  26. /// event loop.
  27. ///
  28. /// The connection is initially setup with a handler to verify that TLS was established
  29. /// successfully (assuming TLS is being used).
  30. ///
  31. /// ┌──────────────────────────┐
  32. /// │ DelegatingErrorHandler │
  33. /// └──────────▲───────────────┘
  34. /// HTTP2Frame│
  35. /// ┌──────────┴───────────────┐
  36. /// │ SettingsObservingHandler │
  37. /// └──────────▲───────────────┘
  38. /// HTTP2Frame│
  39. /// │ ⠇ ⠇ ⠇ ⠇
  40. /// │ ┌┴─▼┐ ┌┴─▼┐
  41. /// │ │ | │ | HTTP/2 streams
  42. /// │ └▲─┬┘ └▲─┬┘
  43. /// │ │ │ │ │ HTTP2Frame
  44. /// ┌─┴────────────────┴─▼───┴─▼┐
  45. /// │ HTTP2StreamMultiplexer |
  46. /// └─▲───────────────────────┬─┘
  47. /// HTTP2Frame│ │HTTP2Frame
  48. /// ┌─┴───────────────────────▼─┐
  49. /// │ NIOHTTP2Handler │
  50. /// └─▲───────────────────────┬─┘
  51. /// ByteBuffer│ │ByteBuffer
  52. /// ┌─┴───────────────────────▼─┐
  53. /// │ TLSVerificationHandler │
  54. /// └─▲───────────────────────┬─┘
  55. /// ByteBuffer│ │ByteBuffer
  56. /// ┌─┴───────────────────────▼─┐
  57. /// │ NIOSSLHandler │
  58. /// └─▲───────────────────────┬─┘
  59. /// ByteBuffer│ │ByteBuffer
  60. /// │ ▼
  61. ///
  62. /// The `TLSVerificationHandler` observes the outcome of the SSL handshake and determines
  63. /// whether a `ClientConnection` should be returned to the user. In either eventuality, the
  64. /// handler removes itself from the pipeline once TLS has been verified. There is also a handler
  65. /// after the multiplexer for observing the initial settings frame, after which it determines that
  66. /// the connection state is `.ready` and removes itself from the channel. Finally there is a
  67. /// delegated error handler which uses the error delegate associated with this connection
  68. /// (see `DelegatingErrorHandler`).
  69. ///
  70. /// See `BaseClientCall` for a description of the pipelines associated with each HTTP/2 stream.
  71. public class ClientConnection {
  72. private let id: String
  73. private let logger: Logger
  74. /// The channel which will handle gRPC calls.
  75. internal var channel: EventLoopFuture<Channel> {
  76. willSet {
  77. self.willSetChannel(to: newValue)
  78. }
  79. didSet {
  80. self.didSetChannel(to: self.channel)
  81. }
  82. }
  83. /// HTTP multiplexer from the `channel` handling gRPC calls.
  84. internal var multiplexer: EventLoopFuture<HTTP2StreamMultiplexer>
  85. /// The configuration for this client.
  86. internal let configuration: Configuration
  87. internal let scheme: String
  88. internal let authority: String
  89. /// A monitor for the connectivity state.
  90. public let connectivity: ConnectivityStateMonitor
  91. /// The `EventLoop` this connection is using.
  92. public var eventLoop: EventLoop {
  93. return self.channel.eventLoop
  94. }
  95. /// Creates a new connection from the given configuration.
  96. ///
  97. /// - Important: Users should prefer using `ClientConnection.secure(group:)` to build a connection
  98. /// with TLS, or `ClientConnection.insecure(group:)` to build a connection without TLS.
  99. public init(configuration: Configuration) {
  100. self.configuration = configuration
  101. self.scheme = configuration.tls == nil ? "http" : "https"
  102. self.authority = configuration.target.host
  103. let id = String(describing: UUID())
  104. self.id = id
  105. var logger = Logger(subsystem: .clientChannel)
  106. logger[metadataKey: MetadataKey.connectionID] = "\(id)"
  107. self.logger = logger
  108. self.connectivity = ConnectivityStateMonitor(
  109. delegate: configuration.connectivityStateDelegate,
  110. logger: logger
  111. )
  112. let eventLoop = configuration.eventLoopGroup.next()
  113. self.channel = ClientConnection.makeChannel(
  114. configuration: self.configuration,
  115. eventLoop: eventLoop,
  116. connectivity: self.connectivity,
  117. backoffIterator: configuration.connectionBackoff?.makeIterator(),
  118. logger: logger
  119. )
  120. self.multiplexer = self.channel.flatMap {
  121. $0.pipeline.handler(type: HTTP2StreamMultiplexer.self)
  122. }
  123. // `willSet` and `didSet` are *not* called on initialization, call them explicitly now.
  124. self.willSetChannel(to: self.channel)
  125. self.didSetChannel(to: self.channel)
  126. }
  127. /// Closes the connection to the server.
  128. public func close() -> EventLoopFuture<Void> {
  129. if self.connectivity.state == .shutdown {
  130. // We're already shutdown or in the process of shutting down.
  131. return self.channel.flatMap { $0.closeFuture }
  132. } else {
  133. self.connectivity.initiateUserShutdown()
  134. return self.channel.flatMap { $0.close() }
  135. }
  136. }
  137. }
  138. extension ClientConnection {
  139. /// Register a callback on the close future of the given `channel` to replace the channel (if
  140. /// possible) and also replace the `multiplexer` with that from the new channel.
  141. ///
  142. /// - Parameter channel: The channel that will be set.
  143. private func willSetChannel(to channel: EventLoopFuture<Channel>) {
  144. // If we're about to set the channel and the user has initiated a shutdown (i.e. while the new
  145. // channel was being created) then it is no longer needed.
  146. guard !self.connectivity.userHasInitiatedShutdown else {
  147. channel.whenSuccess { channel in
  148. self.logger.debug("user initiated shutdown during connection, closing channel")
  149. channel.close(mode: .all, promise: nil)
  150. }
  151. return
  152. }
  153. // If we get a channel and it closes then create a new one, if necessary.
  154. channel.flatMap { $0.closeFuture }.whenComplete { result in
  155. switch result {
  156. case .success:
  157. self.logger.debug("client connection shutdown successfully")
  158. case .failure(let error):
  159. self.logger.warning(
  160. "client connection shutdown failed",
  161. metadata: [MetadataKey.error: "\(error)"]
  162. )
  163. }
  164. guard self.connectivity.canAttemptReconnect else {
  165. return
  166. }
  167. // Something went wrong, but we'll try to fix it so let's update our state to reflect that.
  168. self.connectivity.state = .transientFailure
  169. self.logger.debug("client connection channel closed, creating a new one")
  170. self.channel = ClientConnection.makeChannel(
  171. configuration: self.configuration,
  172. eventLoop: channel.eventLoop,
  173. connectivity: self.connectivity,
  174. backoffIterator: self.configuration.connectionBackoff?.makeIterator(),
  175. logger: self.logger
  176. )
  177. }
  178. self.multiplexer = channel.flatMap {
  179. $0.pipeline.handler(type: HTTP2StreamMultiplexer.self)
  180. }
  181. }
  182. /// Register a callback on the given `channel` to update the connectivity state.
  183. ///
  184. /// - Parameter channel: The channel that was set.
  185. private func didSetChannel(to channel: EventLoopFuture<Channel>) {
  186. channel.whenFailure { _ in
  187. self.connectivity.state = .shutdown
  188. }
  189. }
  190. }
  191. // Note: documentation is inherited.
  192. extension ClientConnection: GRPCChannel {
  193. public func makeUnaryCall<Request: GRPCPayload, Response: GRPCPayload>(
  194. path: String,
  195. request: Request,
  196. callOptions: CallOptions
  197. ) -> UnaryCall<Request, Response> where Request : GRPCPayload, Response : GRPCPayload {
  198. return UnaryCall(
  199. path: path,
  200. scheme: self.scheme,
  201. authority: self.authority,
  202. callOptions: callOptions,
  203. eventLoop: self.eventLoop,
  204. multiplexer: self.multiplexer,
  205. errorDelegate: self.configuration.errorDelegate,
  206. logger: self.logger,
  207. request: request
  208. )
  209. }
  210. public func makeClientStreamingCall<Request: GRPCPayload, Response: GRPCPayload>(
  211. path: String,
  212. callOptions: CallOptions
  213. ) -> ClientStreamingCall<Request, Response> {
  214. return ClientStreamingCall(
  215. path: path,
  216. scheme: self.scheme,
  217. authority: self.authority,
  218. callOptions: callOptions,
  219. eventLoop: self.eventLoop,
  220. multiplexer: self.multiplexer,
  221. errorDelegate: self.configuration.errorDelegate,
  222. logger: self.logger
  223. )
  224. }
  225. public func makeServerStreamingCall<Request: GRPCPayload, Response: GRPCPayload>(
  226. path: String,
  227. request: Request,
  228. callOptions: CallOptions,
  229. handler: @escaping (Response) -> Void
  230. ) -> ServerStreamingCall<Request, Response> {
  231. return ServerStreamingCall(
  232. path: path,
  233. scheme: self.scheme,
  234. authority: self.authority,
  235. callOptions: callOptions,
  236. eventLoop: self.eventLoop,
  237. multiplexer: self.multiplexer,
  238. errorDelegate: self.configuration.errorDelegate,
  239. logger: self.logger,
  240. request: request,
  241. handler: handler
  242. )
  243. }
  244. public func makeBidirectionalStreamingCall<Request: GRPCPayload, Response: GRPCPayload>(
  245. path: String,
  246. callOptions: CallOptions,
  247. handler: @escaping (Response) -> Void
  248. ) -> BidirectionalStreamingCall<Request, Response> {
  249. return BidirectionalStreamingCall(
  250. path: path,
  251. scheme: self.scheme,
  252. authority: self.authority,
  253. callOptions: callOptions,
  254. eventLoop: self.eventLoop,
  255. multiplexer: self.multiplexer,
  256. errorDelegate: self.configuration.errorDelegate,
  257. logger: self.logger,
  258. handler: handler
  259. )
  260. }
  261. }
  262. extension ClientConnection {
  263. /// Attempts to create a new `Channel` using the given configuration.
  264. ///
  265. /// This involves: creating a `ClientBootstrapProtocol`, connecting to a target and verifying that
  266. /// the TLS handshake was successful (if TLS was configured). We _may_ additiionally set a
  267. /// connection timeout and schedule a retry attempt (should the connection fail) if a
  268. /// `ConnectionBackoffIterator` is provided.
  269. ///
  270. /// - Parameter configuration: The configuration to start the connection with.
  271. /// - Parameter eventLoop: The event loop to use for this connection.
  272. /// - Parameter connectivity: A connectivity state monitor.
  273. /// - Parameter backoffIterator: An `Iterator` for `ConnectionBackoff` providing a sequence of
  274. /// connection timeouts and backoff to use when attempting to create a connection.
  275. private class func makeChannel(
  276. configuration: Configuration,
  277. eventLoop: EventLoop,
  278. connectivity: ConnectivityStateMonitor,
  279. backoffIterator: ConnectionBackoffIterator?,
  280. logger: Logger
  281. ) -> EventLoopFuture<Channel> {
  282. guard connectivity.state == .idle || connectivity.state == .transientFailure else {
  283. return configuration.eventLoopGroup.next().makeFailedFuture(GRPCStatus.processingError)
  284. }
  285. logger.debug("attempting to connect", metadata: ["target": "\(configuration.target)", "event_loop": "\(eventLoop)"])
  286. connectivity.state = .connecting
  287. let timeoutAndBackoff = backoffIterator?.next()
  288. let bootstrap = self.makeBootstrap(
  289. configuration: configuration,
  290. eventLoop: eventLoop,
  291. timeout: timeoutAndBackoff?.timeout,
  292. connectivityMonitor: connectivity,
  293. logger: logger
  294. )
  295. let channel = bootstrap.connect(to: configuration.target).flatMap { channel -> EventLoopFuture<Channel> in
  296. if configuration.tls != nil {
  297. return channel.verifyTLS().map { channel }
  298. } else {
  299. return channel.eventLoop.makeSucceededFuture(channel)
  300. }
  301. }
  302. // If we don't have backoff then we can't retry, just return the `channel` no matter what
  303. // state we are in.
  304. guard let backoff = timeoutAndBackoff?.backoff else {
  305. logger.debug("backoff exhausted, no more connection attempts will be made")
  306. return channel
  307. }
  308. // If our connection attempt was unsuccessful, schedule another attempt in some time.
  309. return channel.flatMapError { error in
  310. logger.notice("connection attempt failed", metadata: [MetadataKey.error: "\(error)"])
  311. // We will try to connect again: the failure is transient.
  312. connectivity.state = .transientFailure
  313. return ClientConnection.scheduleReconnectAttempt(
  314. in: backoff,
  315. on: channel.eventLoop,
  316. configuration: configuration,
  317. connectivity: connectivity,
  318. backoffIterator: backoffIterator,
  319. logger: logger
  320. )
  321. }
  322. }
  323. /// Schedule an attempt to make a channel in `timeout` seconds on the given `eventLoop`.
  324. private class func scheduleReconnectAttempt(
  325. in timeout: TimeInterval,
  326. on eventLoop: EventLoop,
  327. configuration: Configuration,
  328. connectivity: ConnectivityStateMonitor,
  329. backoffIterator: ConnectionBackoffIterator?,
  330. logger: Logger
  331. ) -> EventLoopFuture<Channel> {
  332. logger.debug("scheduling connection attempt", metadata: ["delay_seconds": "\(timeout)"])
  333. // The `futureResult` of the scheduled task is of type
  334. // `EventLoopFuture<EventLoopFuture<Channel>>`, so we need to `flatMap` it to
  335. // remove a level of indirection.
  336. return eventLoop.scheduleTask(in: .seconds(timeInterval: timeout)) {
  337. ClientConnection.makeChannel(
  338. configuration: configuration,
  339. eventLoop: eventLoop,
  340. connectivity: connectivity,
  341. backoffIterator: backoffIterator,
  342. logger: logger
  343. )
  344. }.futureResult.flatMap { channel in
  345. channel
  346. }
  347. }
  348. /// Makes and configures a `ClientBootstrap` using the provided configuration.
  349. ///
  350. /// Enables `SO_REUSEADDR` and `TCP_NODELAY` and configures the `channelInitializer` to use the
  351. /// handlers detailed in the documentation for `ClientConnection`.
  352. ///
  353. /// - Parameter configuration: The configuration to prepare the bootstrap with.
  354. /// - Parameter eventLoop: The `EventLoop` to use for the bootstrap.
  355. /// - Parameter timeout: The connection timeout in seconds.
  356. /// - Parameter connectivityMonitor: The connectivity state monitor for the created channel.
  357. private class func makeBootstrap(
  358. configuration: Configuration,
  359. eventLoop: EventLoop,
  360. timeout: TimeInterval?,
  361. connectivityMonitor: ConnectivityStateMonitor,
  362. logger: Logger
  363. ) -> ClientBootstrapProtocol {
  364. // Provide a server hostname if we're using TLS. Prefer the override.
  365. var serverHostname: String? = configuration.tls.map {
  366. if let hostnameOverride = $0.hostnameOverride {
  367. logger.debug("using hostname override for TLS", metadata: ["server-hostname": "\(hostnameOverride)"])
  368. return hostnameOverride
  369. } else {
  370. let host = configuration.target.host
  371. logger.debug("using host from connection target for TLS", metadata: ["server-hostname": "\(host)"])
  372. return host
  373. }
  374. }
  375. if let hostname = serverHostname, hostname.isIPAddress {
  376. logger.debug("IP address cannot be used for TLS SNI extension. No host used", metadata: ["server-hostname": "\(hostname)"])
  377. serverHostname = nil
  378. }
  379. let bootstrap = PlatformSupport.makeClientBootstrap(group: eventLoop, logger: logger)
  380. // Enable SO_REUSEADDR and TCP_NODELAY.
  381. .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
  382. .channelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
  383. .channelInitializer { channel in
  384. channel.configureGRPCClient(
  385. tlsConfiguration: configuration.tls?.configuration,
  386. tlsServerHostname: serverHostname,
  387. connectivityMonitor: connectivityMonitor,
  388. errorDelegate: configuration.errorDelegate,
  389. logger: logger
  390. )
  391. }
  392. if let timeout = timeout {
  393. logger.debug("setting connect timeout", metadata: ["timeout_seconds" : "\(timeout)"])
  394. return bootstrap.connectTimeout(.seconds(timeInterval: timeout))
  395. } else {
  396. logger.debug("no connect timeout provided")
  397. return bootstrap
  398. }
  399. }
  400. }
  401. // MARK: - Configuration structures
  402. /// A target to connect to.
  403. public enum ConnectionTarget {
  404. /// The host and port.
  405. case hostAndPort(String, Int)
  406. /// The path of a Unix domain socket.
  407. case unixDomainSocket(String)
  408. /// A NIO socket address.
  409. case socketAddress(SocketAddress)
  410. var host: String {
  411. switch self {
  412. case .hostAndPort(let host, _):
  413. return host
  414. case .socketAddress(.v4(let address)):
  415. return address.host
  416. case .socketAddress(.v6(let address)):
  417. return address.host
  418. case .unixDomainSocket, .socketAddress(.unixDomainSocket):
  419. return "localhost"
  420. }
  421. }
  422. }
  423. extension ClientConnection {
  424. /// The configuration for a connection.
  425. public struct Configuration {
  426. /// The target to connect to.
  427. public var target: ConnectionTarget
  428. /// The event loop group to run the connection on.
  429. public var eventLoopGroup: EventLoopGroup
  430. /// An error delegate which is called when errors are caught. Provided delegates **must not
  431. /// maintain a strong reference to this `ClientConnection`**. Doing so will cause a retain
  432. /// cycle.
  433. public var errorDelegate: ClientErrorDelegate?
  434. /// A delegate which is called when the connectivity state is changed.
  435. public var connectivityStateDelegate: ConnectivityStateDelegate?
  436. /// TLS configuration for this connection. `nil` if TLS is not desired.
  437. public var tls: TLS?
  438. /// The connection backoff configuration. If no connection retrying is required then this should
  439. /// be `nil`.
  440. public var connectionBackoff: ConnectionBackoff?
  441. /// The HTTP protocol used for this connection.
  442. public var httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol {
  443. return self.tls == nil ? .http : .https
  444. }
  445. /// Create a `Configuration` with some pre-defined defaults.
  446. ///
  447. /// - Parameter target: The target to connect to.
  448. /// - Parameter eventLoopGroup: The event loop group to run the connection on.
  449. /// - Parameter errorDelegate: The error delegate, defaulting to a delegate which will log only
  450. /// on debug builds.
  451. /// - Parameter connectivityStateDelegate: A connectivity state delegate, defaulting to `nil`.
  452. /// - Parameter tlsConfiguration: TLS configuration, defaulting to `nil`.
  453. /// - Parameter connectionBackoff: The connection backoff configuration to use.
  454. /// - Parameter messageEncoding: Message compression configuration, defaults to no compression.
  455. public init(
  456. target: ConnectionTarget,
  457. eventLoopGroup: EventLoopGroup,
  458. errorDelegate: ClientErrorDelegate? = LoggingClientErrorDelegate(),
  459. connectivityStateDelegate: ConnectivityStateDelegate? = nil,
  460. tls: Configuration.TLS? = nil,
  461. connectionBackoff: ConnectionBackoff? = ConnectionBackoff()
  462. ) {
  463. self.target = target
  464. self.eventLoopGroup = eventLoopGroup
  465. self.errorDelegate = errorDelegate
  466. self.connectivityStateDelegate = connectivityStateDelegate
  467. self.tls = tls
  468. self.connectionBackoff = connectionBackoff
  469. }
  470. }
  471. }
  472. // MARK: - Configuration helpers/extensions
  473. fileprivate extension ClientBootstrapProtocol {
  474. /// Connect to the given connection target.
  475. ///
  476. /// - Parameter target: The target to connect to.
  477. func connect(to target: ConnectionTarget) -> EventLoopFuture<Channel> {
  478. switch target {
  479. case .hostAndPort(let host, let port):
  480. return self.connect(host: host, port: port)
  481. case .unixDomainSocket(let path):
  482. return self.connect(unixDomainSocketPath: path)
  483. case .socketAddress(let address):
  484. return self.connect(to: address)
  485. }
  486. }
  487. }
  488. extension Channel {
  489. /// Configure the channel with TLS.
  490. ///
  491. /// This function adds two handlers to the pipeline: the `NIOSSLClientHandler` to handle TLS, and
  492. /// the `TLSVerificationHandler` which verifies that a successful handshake was completed.
  493. ///
  494. /// - Parameter configuration: The configuration to configure the channel with.
  495. /// - Parameter serverHostname: The server hostname to use if the hostname should be verified.
  496. /// - Parameter errorDelegate: The error delegate to use for the TLS verification handler.
  497. func configureTLS(
  498. _ configuration: TLSConfiguration,
  499. serverHostname: String?,
  500. errorDelegate: ClientErrorDelegate?,
  501. logger: Logger
  502. ) -> EventLoopFuture<Void> {
  503. do {
  504. let sslClientHandler = try NIOSSLClientHandler(
  505. context: try NIOSSLContext(configuration: configuration),
  506. serverHostname: serverHostname
  507. )
  508. return self.pipeline.addHandlers(sslClientHandler, TLSVerificationHandler(logger: logger))
  509. } catch {
  510. return self.eventLoop.makeFailedFuture(error)
  511. }
  512. }
  513. /// Returns the `verification` future from the `TLSVerificationHandler` in this channels pipeline.
  514. func verifyTLS() -> EventLoopFuture<Void> {
  515. return self.pipeline.handler(type: TLSVerificationHandler.self).flatMap {
  516. $0.verification
  517. }
  518. }
  519. func configureGRPCClient(
  520. tlsConfiguration: TLSConfiguration?,
  521. tlsServerHostname: String?,
  522. connectivityMonitor: ConnectivityStateMonitor,
  523. errorDelegate: ClientErrorDelegate?,
  524. logger: Logger
  525. ) -> EventLoopFuture<Void> {
  526. let tlsConfigured = tlsConfiguration.map {
  527. self.configureTLS($0, serverHostname: tlsServerHostname, errorDelegate: errorDelegate, logger: logger)
  528. }
  529. return (tlsConfigured ?? self.eventLoop.makeSucceededFuture(())).flatMap {
  530. self.configureHTTP2Pipeline(mode: .client)
  531. }.flatMap { _ in
  532. let settingsObserver = InitialSettingsObservingHandler(
  533. connectivityStateMonitor: connectivityMonitor,
  534. logger: logger
  535. )
  536. let errorHandler = DelegatingErrorHandler(
  537. logger: logger,
  538. delegate: errorDelegate
  539. )
  540. return self.pipeline.addHandlers(settingsObserver, errorHandler)
  541. }
  542. }
  543. func configureGRPCClient(
  544. errorDelegate: ClientErrorDelegate?,
  545. logger: Logger
  546. ) -> EventLoopFuture<Void> {
  547. return self.configureHTTP2Pipeline(mode: .client).flatMap { _ in
  548. self.pipeline.addHandler(DelegatingErrorHandler(logger: logger, delegate: errorDelegate))
  549. }
  550. }
  551. }
  552. fileprivate extension TimeAmount {
  553. /// Creates a new `TimeAmount` from the given time interval in seconds.
  554. ///
  555. /// - Parameter timeInterval: The amount of time in seconds
  556. static func seconds(timeInterval: TimeInterval) -> TimeAmount {
  557. return .nanoseconds(Int64(timeInterval * 1_000_000_000))
  558. }
  559. }
  560. fileprivate extension String {
  561. var isIPAddress: Bool {
  562. // We need some scratch space to let inet_pton write into.
  563. var ipv4Addr = in_addr()
  564. var ipv6Addr = in6_addr()
  565. return self.withCString { ptr in
  566. return inet_pton(AF_INET, ptr, &ipv4Addr) == 1 ||
  567. inet_pton(AF_INET6, ptr, &ipv6Addr) == 1
  568. }
  569. }
  570. }