BaseClientCall.swift 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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 NIOHTTP1
  19. import NIOHTTP2
  20. import SwiftProtobuf
  21. import Logging
  22. /// This class provides much of the boilerplate for the four types of gRPC call objects returned to framework
  23. /// users.
  24. ///
  25. /// Each call will be configured on a multiplexed channel on the given connection. The multiplexed
  26. /// channel will be configured as such:
  27. ///
  28. /// ┌──────────────────────────────┐
  29. /// │ ClientResponseChannelHandler │
  30. /// └────────────▲─────────────────┘
  31. /// │ ┌─────────────────────────────┐
  32. /// │ │ ClientRequestChannelHandler │
  33. /// │ └────────────────┬────────────┘
  34. /// GRPCClientResponsePart<T1>│ │GRPCClientRequestPart<T2>
  35. /// ┌─┴───────────────────────▼─┐
  36. /// │ GRPCClientCodec │
  37. /// └─▲───────────────────────┬─┘
  38. /// RawGRPCClientResponsePart│ │RawGRPCClientRequestPart
  39. /// ┌─┴───────────────────────▼─┐
  40. /// │ HTTP1ToRawGRPCClientCodec │
  41. /// └─▲───────────────────────┬─┘
  42. /// HTTPClientResponsePart│ │HTTPClientRequestPart
  43. /// ┌─┴───────────────────────▼─┐
  44. /// │ HTTP2ToHTTP1ClientCodec │
  45. /// └─▲───────────────────────┬─┘
  46. /// HTTP2Frame│ │HTTP2Frame
  47. /// | |
  48. ///
  49. /// Note: below the `HTTP2ToHTTP1ClientCodec` is the "main" pipeline provided by the channel in
  50. /// `ClientConnection`.
  51. ///
  52. /// Setup includes:
  53. /// - creation of an HTTP/2 stream for the call to execute on,
  54. /// - configuration of the NIO channel handlers for the stream, and
  55. /// - setting a call timeout, if one is provided.
  56. ///
  57. /// This class also provides much of the framework user facing functionality via conformance to `ClientCall`.
  58. open class BaseClientCall<RequestMessage: Message, ResponseMessage: Message> {
  59. internal let logger: Logger
  60. /// The underlying `ClientConnection` providing the HTTP/2 channel and multiplexer.
  61. internal let connection: ClientConnection
  62. /// Promise for an HTTP/2 stream to execute the call on.
  63. internal let streamPromise: EventLoopPromise<Channel>
  64. /// Channel handler for responses.
  65. internal let responseHandler: ClientResponseChannelHandler<ResponseMessage>
  66. /// Channel handler for requests.
  67. internal let requestHandler: ClientRequestChannelHandler<RequestMessage>
  68. // Note: documentation is inherited from the `ClientCall` protocol.
  69. public let subchannel: EventLoopFuture<Channel>
  70. public let initialMetadata: EventLoopFuture<HTTPHeaders>
  71. public let status: EventLoopFuture<GRPCStatus>
  72. /// Sets up a gRPC call.
  73. ///
  74. /// This involves creating a new HTTP/2 stream on the multiplexer provided by `connection`. The
  75. /// channel associated with the stream is configured to use the provided request and response
  76. /// handlers. Note that the request head will be sent automatically from the request handler when
  77. /// the channel becomes active.
  78. ///
  79. /// - Parameters:
  80. /// - connection: connection containing the HTTP/2 channel and multiplexer to use for this call.
  81. /// - responseHandler: a channel handler for receiving responses.
  82. /// - requestHandler: a channel handler for sending requests.
  83. init(
  84. connection: ClientConnection,
  85. responseHandler: ClientResponseChannelHandler<ResponseMessage>,
  86. requestHandler: ClientRequestChannelHandler<RequestMessage>,
  87. logger: Logger
  88. ) {
  89. self.logger = logger
  90. self.connection = connection
  91. self.responseHandler = responseHandler
  92. self.requestHandler = requestHandler
  93. self.streamPromise = connection.channel.eventLoop.makePromise()
  94. self.subchannel = self.streamPromise.futureResult
  95. self.initialMetadata = self.responseHandler.initialMetadataPromise.futureResult
  96. self.status = self.responseHandler.statusPromise.futureResult
  97. self.streamPromise.futureResult.whenFailure { error in
  98. self.logger.error("failed to create http/2 stream", metadata: [MetadataKey.error: "\(error)"])
  99. self.responseHandler.observeError(.unknown(error, origin: .client))
  100. }
  101. self.createStreamChannel()
  102. self.responseHandler.scheduleTimeout(eventLoop: connection.eventLoop)
  103. }
  104. /// Creates and configures an HTTP/2 stream channel. The `self.subchannel` future will hold the
  105. /// stream channel once it has been created.
  106. private func createStreamChannel() {
  107. self.connection.multiplexer.whenFailure { error in
  108. self.logger.error("failed to get http/2 multiplexer", metadata: [MetadataKey.error: "\(error)"])
  109. self.streamPromise.fail(error)
  110. }
  111. self.connection.multiplexer.whenSuccess { multiplexer in
  112. multiplexer.createStreamChannel(promise: self.streamPromise) { (subchannel, streamID) -> EventLoopFuture<Void> in
  113. subchannel.pipeline.addHandlers(
  114. HTTP2ToHTTP1ClientCodec(streamID: streamID, httpProtocol: self.connection.configuration.httpProtocol),
  115. HTTP1ToRawGRPCClientCodec(logger: self.logger),
  116. GRPCClientCodec<RequestMessage, ResponseMessage>(logger: self.logger),
  117. self.requestHandler,
  118. self.responseHandler)
  119. }
  120. }
  121. }
  122. }
  123. extension BaseClientCall: ClientCall {
  124. // Workaround for: https://bugs.swift.org/browse/SR-10128
  125. // Once resolved this can become a default implementation on `ClientCall`.
  126. public var trailingMetadata: EventLoopFuture<HTTPHeaders> {
  127. return status.map { $0.trailingMetadata }
  128. }
  129. public func cancel() {
  130. self.logger.info("cancelling call")
  131. self.connection.channel.eventLoop.execute {
  132. self.subchannel.whenComplete { result in
  133. switch result {
  134. case .success(let channel):
  135. self.logger.debug("firing .cancelled event")
  136. channel.pipeline.fireUserInboundEventTriggered(GRPCClientUserEvent.cancelled)
  137. case .failure(let error):
  138. self.logger.debug(
  139. "cancelling call will no-op because no http/2 stream creation",
  140. metadata: [MetadataKey.error: "\(error)"]
  141. )
  142. }
  143. }
  144. }
  145. }
  146. }
  147. /// Makes a request head.
  148. ///
  149. /// - Parameter path: The path of the gRPC call, e.g. "/serviceName/methodName".
  150. /// - Parameter host: The host serving the call.
  151. /// - Parameter callOptions: Options used when making this call.
  152. /// - Parameter requestID: The request ID used for this call. If `callOptions` specifies a
  153. /// non-nil `reqeuestIDHeader` then this request ID will be added to the headers with the
  154. /// specified header name.
  155. internal func makeRequestHead(path: String, host: String, callOptions: CallOptions, requestID: String) -> HTTPRequestHead {
  156. var headers: HTTPHeaders = [
  157. "content-type": "application/grpc",
  158. // Used to detect incompatible proxies, as per https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
  159. "te": "trailers",
  160. //! FIXME: Add a more specific user-agent, see: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#user-agents
  161. "user-agent": "grpc-swift-nio",
  162. // We're dealing with HTTP/1; the NIO HTTP2ToHTTP1Codec replaces "host" with ":authority".
  163. "host": host,
  164. GRPCHeaderName.acceptEncoding: CompressionMechanism.acceptEncodingHeader,
  165. ]
  166. if callOptions.timeout != .infinite {
  167. headers.add(name: GRPCHeaderName.timeout, value: String(describing: callOptions.timeout))
  168. }
  169. headers.add(contentsOf: callOptions.customMetadata)
  170. if let headerName = callOptions.requestIDHeader {
  171. headers.add(name: headerName, value: requestID)
  172. }
  173. let method: HTTPMethod = callOptions.cacheable ? .GET : .POST
  174. return HTTPRequestHead(version: .init(major: 2, minor: 0), method: method, uri: path, headers: headers)
  175. }