HTTP1ToGRPCServerCodec.swift 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  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 Logging
  18. import NIO
  19. import NIOFoundationCompat
  20. import NIOHPACK
  21. import NIOHTTP1
  22. import SwiftProtobuf
  23. /// Incoming gRPC package with a fixed message type.
  24. ///
  25. /// - Important: This is **NOT** part of the public API.
  26. public enum _GRPCServerRequestPart<Request> {
  27. case headers(HPACKHeaders)
  28. case message(Request)
  29. case end
  30. }
  31. public typealias _RawGRPCServerRequestPart = _GRPCServerRequestPart<ByteBuffer>
  32. /// Outgoing gRPC package with a fixed message type.
  33. ///
  34. /// - Important: This is **NOT** part of the public API.
  35. public enum _GRPCServerResponsePart<Response> {
  36. case headers(HPACKHeaders)
  37. case message(_MessageContext<Response>)
  38. case statusAndTrailers(GRPCStatus, HPACKHeaders)
  39. }
  40. public typealias _RawGRPCServerResponsePart = _GRPCServerResponsePart<ByteBuffer>
  41. /// A simple channel handler that translates HTTP1 data types into gRPC packets, and vice versa.
  42. ///
  43. /// We use HTTP1 (instead of HTTP2) primitives, as these are easier to work with than raw HTTP2
  44. /// primitives while providing all the functionality we need. In addition, it allows us to support
  45. /// gRPC-Web (gRPC over HTTP1).
  46. ///
  47. /// The translation from HTTP2 to HTTP1 is done by `HTTP2ToHTTP1ServerCodec`.
  48. public final class HTTP1ToGRPCServerCodec {
  49. public init(encoding: ServerMessageEncoding, logger: Logger) {
  50. self.encoding = encoding
  51. self.encodingHeaderValidator = MessageEncodingHeaderValidator(encoding: encoding)
  52. self.logger = logger
  53. self.messageReader = LengthPrefixedMessageReader()
  54. self.messageWriter = LengthPrefixedMessageWriter()
  55. }
  56. private var contentType: ContentType?
  57. private let encoding: ServerMessageEncoding
  58. private let encodingHeaderValidator: MessageEncodingHeaderValidator
  59. private var acceptEncodingHeader: String?
  60. private var responseEncodingHeader: String?
  61. private let logger: Logger
  62. private var stopwatch: Stopwatch?
  63. // The following buffers use force unwrapping explicitly. With optionals, developers
  64. // are encouraged to unwrap them using guard-else statements. These don't work cleanly
  65. // with structs, since the guard-else would create a new copy of the struct, which
  66. // would then have to be re-assigned into the class variable for the changes to take effect.
  67. // By force unwrapping, we avoid those reassignments, and the code is a bit cleaner.
  68. // Buffer to store binary encoded protos as they're being received if the proto is split across
  69. // multiple buffers.
  70. private var binaryRequestBuffer: NIO.ByteBuffer!
  71. // Buffers to store text encoded protos. Only used when content-type is application/grpc-web-text.
  72. // TODO(kaipi): Extract all gRPC Web processing logic into an independent handler only added on
  73. // the HTTP1.1 pipeline, as it's starting to get in the way of readability.
  74. private var requestTextBuffer: NIO.ByteBuffer!
  75. private var responseTextBuffers: CircularBuffer<ByteBuffer>?
  76. var inboundState = InboundState.expectingHeaders {
  77. willSet {
  78. guard newValue != self.inboundState else { return }
  79. self.logger.debug(
  80. "inbound state changed",
  81. metadata: ["old_state": "\(self.inboundState)", "new_state": "\(newValue)"]
  82. )
  83. }
  84. }
  85. var outboundState = OutboundState.expectingHeaders {
  86. willSet {
  87. guard newValue != self.outboundState else { return }
  88. self.logger.debug(
  89. "outbound state changed",
  90. metadata: ["old_state": "\(self.outboundState)", "new_state": "\(newValue)"]
  91. )
  92. }
  93. }
  94. var messageReader: LengthPrefixedMessageReader
  95. var messageWriter: LengthPrefixedMessageWriter
  96. }
  97. extension HTTP1ToGRPCServerCodec {
  98. enum InboundState {
  99. case expectingHeaders
  100. case expectingBody
  101. // ignore any additional messages; e.g. we've seen .end or we've sent an error and are waiting for the stream to close.
  102. case ignore
  103. }
  104. enum OutboundState {
  105. case expectingHeaders
  106. case expectingBodyOrStatus
  107. case ignore
  108. }
  109. }
  110. extension HTTP1ToGRPCServerCodec: ChannelInboundHandler {
  111. public typealias InboundIn = HTTPServerRequestPart
  112. public typealias InboundOut = _RawGRPCServerRequestPart
  113. public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
  114. let unwrappedData = self.unwrapInboundIn(data)
  115. if case .ignore = self.inboundState {
  116. switch unwrappedData {
  117. case let .body(body) where body.readableBytes == 0:
  118. break // Ignoring a read of zero bytes is always fine.
  119. case .end:
  120. break // Ignoring an end stream is not an event worth reporting.
  121. default:
  122. self.logger.notice("ignoring read data", metadata: ["data": "\(unwrappedData)"])
  123. }
  124. return
  125. }
  126. do {
  127. switch unwrappedData {
  128. case let .head(requestHead):
  129. self.inboundState = try self.processHead(context: context, requestHead: requestHead)
  130. case var .body(body):
  131. self.inboundState = try self.processBody(context: context, body: &body)
  132. case let .end(trailers):
  133. self.inboundState = try self.processEnd(context: context, trailers: trailers)
  134. }
  135. } catch {
  136. context.fireErrorCaught(error)
  137. self.inboundState = .ignore
  138. }
  139. }
  140. func processHead(context: ChannelHandlerContext,
  141. requestHead: HTTPRequestHead) throws -> InboundState {
  142. self.logger.debug("processing request head", metadata: ["head": "\(requestHead)"])
  143. guard case .expectingHeaders = self.inboundState else {
  144. self.logger.error(
  145. "invalid state while processing request head",
  146. metadata: ["state": "\(self.inboundState)", "head": "\(requestHead)"]
  147. )
  148. throw GRPCError.InvalidState("expected state .expectingHeaders, got \(self.inboundState)")
  149. .captureContext()
  150. }
  151. self.stopwatch = .start()
  152. self.logger.debug("rpc call started", metadata: [
  153. "path": "\(requestHead.uri)",
  154. "method": "\(requestHead.method)",
  155. "version": "\(requestHead.version)",
  156. ])
  157. if let contentType = requestHead.headers.first(name: GRPCHeaderName.contentType)
  158. .flatMap(ContentType.init) {
  159. self.contentType = contentType
  160. } else {
  161. self.logger.debug("no 'content-type' header, assuming content type is 'application/grpc'")
  162. // If the Content-Type is not present, assume the request is binary encoded gRPC.
  163. self.contentType = .protobuf
  164. }
  165. if self.contentType == .webTextProtobuf {
  166. self.requestTextBuffer = context.channel.allocator.buffer(capacity: 0)
  167. }
  168. // What compression was used for sending requests?
  169. let encodingHeader = requestHead.headers.first(name: GRPCHeaderName.encoding)
  170. switch self.encodingHeaderValidator.validate(requestEncoding: encodingHeader) {
  171. case let .supported(algorithm, limit, acceptableEncoding):
  172. self.messageReader = LengthPrefixedMessageReader(
  173. compression: algorithm,
  174. decompressionLimit: limit
  175. )
  176. if acceptableEncoding.isEmpty {
  177. self.acceptEncodingHeader = nil
  178. } else {
  179. self.acceptEncodingHeader = acceptableEncoding.joined(separator: ",")
  180. }
  181. case .noCompression:
  182. self.messageReader = LengthPrefixedMessageReader()
  183. self.acceptEncodingHeader = nil
  184. case let .unsupported(header, acceptableEncoding):
  185. let message: String
  186. let headers: HPACKHeaders
  187. if acceptableEncoding.isEmpty {
  188. message = "compression is not supported"
  189. headers = .init()
  190. } else {
  191. let advertised = acceptableEncoding.joined(separator: ",")
  192. message = "'\(header)' compression is not supported, supported: \(advertised)"
  193. headers = [GRPCHeaderName.acceptEncoding: advertised]
  194. }
  195. let status = GRPCStatus(code: .unimplemented, message: message)
  196. defer {
  197. self.write(
  198. context: context,
  199. data: NIOAny(OutboundIn.statusAndTrailers(status, headers)),
  200. promise: nil
  201. )
  202. self.flush(context: context)
  203. }
  204. // We're about to fast-fail, so ignore any following inbound messages.
  205. return .ignore
  206. }
  207. // What compression should we use for writing responses?
  208. let clientAcceptableEncoding = requestHead.headers[canonicalForm: GRPCHeaderName.acceptEncoding]
  209. if let responseEncoding = self.selectResponseEncoding(from: clientAcceptableEncoding) {
  210. self.messageWriter = LengthPrefixedMessageWriter(compression: responseEncoding)
  211. self.responseEncodingHeader = responseEncoding.name
  212. } else {
  213. self.messageWriter = LengthPrefixedMessageWriter(compression: .none)
  214. self.responseEncodingHeader = nil
  215. }
  216. // We normalize in the 2-to-1 handler.
  217. let headers = HPACKHeaders(httpHeaders: requestHead.headers, normalizeHTTPHeaders: false)
  218. context.fireChannelRead(self.wrapInboundOut(.headers(headers)))
  219. return .expectingBody
  220. }
  221. func processBody(context: ChannelHandlerContext, body: inout ByteBuffer) throws -> InboundState {
  222. self.logger.debug("processing body: \(body)")
  223. guard case .expectingBody = self.inboundState else {
  224. self.logger.error(
  225. "invalid state while processing body",
  226. metadata: ["state": "\(self.inboundState)", "body": "\(body)"]
  227. )
  228. throw GRPCError.InvalidState("expected state .expectingBody, got \(self.inboundState)")
  229. .captureContext()
  230. }
  231. // If the contentType is text, then decode the incoming bytes as base64 encoded, and append
  232. // it to the binary buffer. If the request is chunked, this section will process the text
  233. // in the biggest chunk that is multiple of 4, leaving the unread bytes in the textBuffer
  234. // where it will expect a new incoming chunk.
  235. if self.contentType == .webTextProtobuf {
  236. precondition(self.requestTextBuffer != nil)
  237. self.requestTextBuffer.writeBuffer(&body)
  238. // Read in chunks of 4 bytes as base64 encoded strings will always be multiples of 4.
  239. let readyBytes = self.requestTextBuffer
  240. .readableBytes - (self.requestTextBuffer.readableBytes % 4)
  241. guard let base64Encoded = requestTextBuffer.readString(length: readyBytes),
  242. let decodedData = Data(base64Encoded: base64Encoded) else {
  243. throw GRPCError.Base64DecodeError().captureContext()
  244. }
  245. body.writeContiguousBytes(decodedData)
  246. }
  247. self.messageReader.append(buffer: &body)
  248. do {
  249. // We may be re-entrantly called, and that re-entrant call may error. If the state changed for any reason,
  250. // stop looping.
  251. while self.inboundState == .expectingBody,
  252. let buffer = try self.messageReader.nextMessage() {
  253. context.fireChannelRead(self.wrapInboundOut(.message(buffer)))
  254. }
  255. } catch let grpcError as GRPCError.WithContext {
  256. context.fireErrorCaught(grpcError)
  257. return .ignore
  258. } catch {
  259. context.fireErrorCaught(GRPCError.DeserializationFailure().captureContext())
  260. return .ignore
  261. }
  262. // We may have been called re-entrantly and transitioned out of the state we were in (e.g. because of an
  263. // error). In all cases, if we get here we want to persist the current state.
  264. return self.inboundState
  265. }
  266. private func processEnd(context: ChannelHandlerContext,
  267. trailers: HTTPHeaders?) throws -> InboundState {
  268. self.logger.debug("processing end")
  269. if let trailers = trailers {
  270. self.logger.error(
  271. "unexpected trailers when processing stream end",
  272. metadata: ["trailers": "\(trailers)"]
  273. )
  274. throw GRPCError.InvalidState("unexpected trailers received").captureContext()
  275. }
  276. context.fireChannelRead(self.wrapInboundOut(.end))
  277. return .ignore
  278. }
  279. }
  280. extension HTTP1ToGRPCServerCodec: ChannelOutboundHandler {
  281. public typealias OutboundIn = _RawGRPCServerResponsePart
  282. public typealias OutboundOut = HTTPServerResponsePart
  283. public func write(context: ChannelHandlerContext, data: NIOAny,
  284. promise: EventLoopPromise<Void>?) {
  285. if case .ignore = self.outboundState {
  286. self.logger.notice("ignoring written data: \(data)")
  287. promise?.fail(GRPCError.InvalidState("rpc has already finished").captureContext())
  288. return
  289. }
  290. switch self.unwrapOutboundIn(data) {
  291. case var .headers(headers):
  292. guard case .expectingHeaders = self.outboundState else {
  293. self.logger.error(
  294. "invalid state while writing headers",
  295. metadata: ["state": "\(self.outboundState)", "headers": "\(headers)"]
  296. )
  297. return
  298. }
  299. var version = HTTPVersion(major: 2, minor: 0)
  300. if let contentType = self.contentType {
  301. headers.add(name: GRPCHeaderName.contentType, value: contentType.canonicalValue)
  302. if contentType != .protobuf {
  303. version = .init(major: 1, minor: 1)
  304. }
  305. }
  306. // Are we compressing responses?
  307. if let responseEncoding = self.responseEncodingHeader {
  308. headers.add(name: GRPCHeaderName.encoding, value: responseEncoding)
  309. }
  310. // The client may have sent us a message using an encoding we didn't advertise; we'll send
  311. // an accept-encoding header back if that's the case.
  312. if let acceptEncoding = self.acceptEncodingHeader {
  313. headers.add(name: GRPCHeaderName.acceptEncoding, value: acceptEncoding)
  314. }
  315. context.write(
  316. self
  317. .wrapOutboundOut(.head(HTTPResponseHead(
  318. version: version,
  319. status: .ok,
  320. headers: HTTPHeaders(headers.map { ($0.name, $0.value) })
  321. ))),
  322. promise: promise
  323. )
  324. self.outboundState = .expectingBodyOrStatus
  325. case let .message(messageContext):
  326. guard case .expectingBodyOrStatus = self.outboundState else {
  327. self.logger.error(
  328. "invalid state while writing message",
  329. metadata: ["state": "\(self.outboundState)"]
  330. )
  331. return
  332. }
  333. do {
  334. if self.contentType == .webTextProtobuf {
  335. // Store the response into an independent buffer. We can't return the message directly as
  336. // it needs to be aggregated with all the responses plus the trailers, in order to have
  337. // the base64 response properly encoded in a single byte stream.
  338. let buffer = try self.messageWriter.write(
  339. buffer: messageContext.message,
  340. allocator: context.channel.allocator,
  341. compressed: messageContext.compressed
  342. )
  343. self.appendResponseText(buffer)
  344. // Since we stored the written data, mark the write promise as successful so that the
  345. // ServerStreaming provider continues sending the data.
  346. promise?.succeed(())
  347. } else {
  348. let messageBuffer = try self.messageWriter.write(
  349. buffer: messageContext.message,
  350. allocator: context.channel.allocator,
  351. compressed: messageContext.compressed
  352. )
  353. context.write(self.wrapOutboundOut(.body(.byteBuffer(messageBuffer))), promise: promise)
  354. }
  355. } catch {
  356. let error = GRPCError.SerializationFailure().captureContext()
  357. promise?.fail(error)
  358. context.fireErrorCaught(error)
  359. self.outboundState = .ignore
  360. return
  361. }
  362. self.outboundState = .expectingBodyOrStatus
  363. case let .statusAndTrailers(status, trailers):
  364. // If we error before sending the initial headers then we won't have sent the request head.
  365. // NIOHTTP2 doesn't support sending a single frame as a "Trailers-Only" response so we still
  366. // need to loop back and send the request head first.
  367. if case .expectingHeaders = self.outboundState {
  368. self.write(context: context, data: NIOAny(OutboundIn.headers([:])), promise: nil)
  369. }
  370. var trailers = trailers
  371. trailers.add(name: GRPCHeaderName.statusCode, value: String(describing: status.code.rawValue))
  372. if let message = status.message.flatMap(GRPCStatusMessageMarshaller.marshall) {
  373. trailers.add(name: GRPCHeaderName.statusMessage, value: message)
  374. }
  375. if self.contentType == .webTextProtobuf {
  376. // Encode the trailers into the response byte stream as a length delimited message, as per
  377. // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md
  378. let textTrailers = trailers.map { name, value, _ in "\(name): \(value)" }
  379. .joined(separator: "\r\n")
  380. var trailersBuffer = context.channel.allocator.buffer(capacity: 5 + textTrailers.utf8.count)
  381. trailersBuffer.writeInteger(UInt8(0x80))
  382. trailersBuffer.writeInteger(UInt32(textTrailers.utf8.count))
  383. trailersBuffer.writeString(textTrailers)
  384. self.appendResponseText(trailersBuffer)
  385. // This code can only be called on the grpc-web path, so we know the response text buffers must be non-nil
  386. // and must contain at least one element.
  387. guard var buffers = self.responseTextBuffers else {
  388. preconditionFailure("Building web response text but responseTextBuffers are nil")
  389. }
  390. // Avoid a CoW
  391. self.responseTextBuffers = nil
  392. var responseTextBuffer = buffers.popFirst()!
  393. // Read the data from the first buffer.
  394. var accumulatedData = responseTextBuffer.readData(length: responseTextBuffer.readableBytes)!
  395. // Reserve enough capacity and append the remaining buffers.
  396. let requiredExtraCapacity = buffers.lazy.map { $0.readableBytes }.reduce(0, +)
  397. accumulatedData.reserveCapacity(accumulatedData.count + requiredExtraCapacity)
  398. while let buffer = buffers.popFirst() {
  399. accumulatedData.append(contentsOf: buffer.readableBytesView)
  400. }
  401. // Restore the buffers.
  402. self.responseTextBuffers = buffers
  403. // TODO: Binary responses that are non multiples of 3 will end = or == when encoded in
  404. // base64. Investigate whether this might have any effect on the transport mechanism and
  405. // client decoding. Initial results say that they are innocuous, but we might have to keep
  406. // an eye on this in case something trips up.
  407. let encodedData = accumulatedData.base64EncodedString()
  408. // Reuse our first buffer.
  409. responseTextBuffer.clear(minimumCapacity: Int(encodedData.utf8.count))
  410. responseTextBuffer.writeString(encodedData)
  411. // After collecting all response for gRPC Web connections, send one final aggregated
  412. // response.
  413. context.write(
  414. self.wrapOutboundOut(.body(.byteBuffer(responseTextBuffer))),
  415. promise: promise
  416. )
  417. context.write(self.wrapOutboundOut(.end(nil)), promise: promise)
  418. } else {
  419. let httpTrailers = HTTPHeaders(trailers.map { ($0.name, $0.value) })
  420. context.write(self.wrapOutboundOut(.end(httpTrailers)), promise: promise)
  421. }
  422. // Log the call duration and status
  423. if let stopwatch = self.stopwatch {
  424. self.stopwatch = nil
  425. let millis = stopwatch.elapsedMillis()
  426. self.logger.debug("rpc call finished", metadata: [
  427. "duration_ms": "\(millis)",
  428. "status_code": "\(status.code.rawValue)",
  429. ])
  430. }
  431. self.outboundState = .ignore
  432. self.inboundState = .ignore
  433. }
  434. }
  435. private func appendResponseText(_ buffer: ByteBuffer) {
  436. if self.responseTextBuffers == nil {
  437. self.responseTextBuffers = CircularBuffer()
  438. }
  439. self.responseTextBuffers!.append(buffer)
  440. }
  441. }
  442. private extension HTTP1ToGRPCServerCodec {
  443. /// Selects an appropriate response encoding from the list of encodings sent to us by the client.
  444. /// Returns `nil` if there were no appropriate algorithms, in which case the server will send
  445. /// messages uncompressed.
  446. func selectResponseEncoding(from acceptableEncoding: [Substring]) -> CompressionAlgorithm? {
  447. guard case let .enabled(configuration) = self.encoding else {
  448. return nil
  449. }
  450. return acceptableEncoding.compactMap {
  451. CompressionAlgorithm(rawValue: String($0))
  452. }.first {
  453. configuration.enabledAlgorithms.contains($0)
  454. }
  455. }
  456. }
  457. struct MessageEncodingHeaderValidator {
  458. var encoding: ServerMessageEncoding
  459. enum ValidationResult {
  460. /// The requested compression is supported.
  461. case supported(
  462. algorithm: CompressionAlgorithm,
  463. decompressionLimit: DecompressionLimit,
  464. acceptEncoding: [String]
  465. )
  466. /// The `requestEncoding` is not supported; `acceptEncoding` contains all algorithms we do
  467. /// support.
  468. case unsupported(requestEncoding: String, acceptEncoding: [String])
  469. /// No compression was requested.
  470. case noCompression
  471. }
  472. /// Validates the value of the 'grpc-encoding' header against compression algorithms supported and
  473. /// advertised by this peer.
  474. ///
  475. /// - Parameter requestEncoding: The value of the 'grpc-encoding' header.
  476. func validate(requestEncoding: String?) -> ValidationResult {
  477. switch (self.encoding, requestEncoding) {
  478. // Compression is enabled and the client sent a message encoding header. Do we support it?
  479. case let (.enabled(configuration), .some(header)):
  480. guard let algorithm = CompressionAlgorithm(rawValue: header) else {
  481. return .unsupported(
  482. requestEncoding: header,
  483. acceptEncoding: configuration.enabledAlgorithms.map { $0.name }
  484. )
  485. }
  486. if configuration.enabledAlgorithms.contains(algorithm) {
  487. return .supported(
  488. algorithm: algorithm,
  489. decompressionLimit: configuration.decompressionLimit,
  490. acceptEncoding: []
  491. )
  492. } else {
  493. // From: https://github.com/grpc/grpc/blob/master/doc/compression.md
  494. //
  495. // Note that a peer MAY choose to not disclose all the encodings it supports. However, if
  496. // it receives a message compressed in an undisclosed but supported encoding, it MUST
  497. // include said encoding in the response's grpc-accept-encoding header.
  498. return .supported(
  499. algorithm: algorithm,
  500. decompressionLimit: configuration.decompressionLimit,
  501. acceptEncoding: configuration.enabledAlgorithms.map { $0.name } + CollectionOfOne(header)
  502. )
  503. }
  504. // Compression is disabled and the client sent a message encoding header. We don't support this
  505. // unless the header is "identity", which is no compression. Note this is different to the
  506. // supported but not advertised case since we have explicitly not enabled compression.
  507. case let (.disabled, .some(header)):
  508. guard let algorithm = CompressionAlgorithm(rawValue: header) else {
  509. return .unsupported(
  510. requestEncoding: header,
  511. acceptEncoding: []
  512. )
  513. }
  514. if algorithm == .identity {
  515. return .noCompression
  516. } else {
  517. return .unsupported(requestEncoding: header, acceptEncoding: [])
  518. }
  519. // The client didn't send a message encoding header.
  520. case (_, .none):
  521. return .noCompression
  522. }
  523. }
  524. }