Browse Source

Simplify compression representation. (#688)

Motivation:

Since we don't support compression, there's no need for any scaffolding
that we do have to be public. Moreover there's no need to over
complicate something which isn't yet user facing.

Modifications:

Remove `CompressionMechanism` and replace it with
`CompressionAlgorithm`; an enum containing all compression algorithms we
support. At the moment this is limited to `identity`.

Result:

Simpler code, smaller public API.
George Barnett 6 years ago
parent
commit
d424a7d901

+ 24 - 0
Sources/GRPC/CompressionAlgorithm.swift

@@ -0,0 +1,24 @@
+/*
+ * Copyright 2020, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/// Supported message compression algorithms.
+///
+/// These algorithms are indicated in the "grpc-encoding" header. As such, a lack of "grpc-encoding"
+/// header indicates that there is no message compression.
+enum CompressionAlgorithm: String {
+  /// Identity compression; "no" compression but indicated via the "grpc-encoding" header.
+  case identity
+}

+ 0 - 76
Sources/GRPC/CompressionMechanism.swift

@@ -1,76 +0,0 @@
-/*
- * Copyright 2019, gRPC Authors All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import Foundation
-
-public enum CompressionError: Error {
-  case unsupported(CompressionMechanism)
-}
-
-/// The mechanism to use for message compression.
-public enum CompressionMechanism: String, CaseIterable {
-  // No compression was indicated.
-  case none
-
-  // Compression indicated via a header.
-  case gzip
-  case deflate
-  case snappy
-  // This is the same as `.none` but was indicated via a "grpc-encoding" and may be listed
-  // in the "grpc-accept-encoding" header. If this is the compression mechanism being used
-  // then the compression flag should be indicated in length-prefxied messages (see
-  // `LengthPrefixedMessageReader`).
-  case identity
-
-  // Compression indicated via a header, but not one listed in the specification.
-  case unknown
-
-  init(value: String?) {
-    self = value.map { CompressionMechanism(rawValue: $0) ?? .unknown } ?? .none
-  }
-
-  /// Whether the compression flag in gRPC length-prefixed messages should be set or not.
-  ///
-  /// See `LengthPrefixedMessageReader` for the message format.
-  public var requiresFlag: Bool {
-    switch self {
-    case .none:
-      return false
-
-    case .identity, .gzip, .deflate, .snappy, .unknown:
-      return true
-    }
-  }
-
-  /// Whether the given compression is supported.
-  public var supported: Bool {
-    switch self {
-    case .identity, .none:
-      return true
-
-    case .gzip, .deflate, .snappy, .unknown:
-      return false
-    }
-  }
-
-  /// A string containing the supported compression mechanisms to list in the "grpc-accept-encoding" header.
-  static let acceptEncodingHeader: String = {
-    return CompressionMechanism
-      .allCases
-      .filter { $0.supported && $0.requiresFlag }
-      .map { $0.rawValue }
-      .joined(separator: ", ")
-  }()
-}

+ 11 - 8
Sources/GRPC/GRPCClientStateMachine.swift

@@ -561,17 +561,20 @@ extension GRPCClientStateMachine.State {
     }
     }
 
 
     // What compression mechanism is the server using, if any?
     // What compression mechanism is the server using, if any?
-    let compression = CompressionMechanism(value: headers.first(name: GRPCHeaderName.encoding))
+    let compression: CompressionAlgorithm?
 
 
-    // From: https://github.com/grpc/grpc/blob/master/doc/compression.md
-    //
-    // "If a server sent data which is compressed by an algorithm that is not supported by the
-    // client, an INTERNAL error status will occur on the client side."
-    guard compression.supported else {
-      return .failure(.unsupportedMessageEncoding(compression.rawValue))
+    if let encodingHeader = headers.first(name: GRPCHeaderName.encoding) {
+      compression = CompressionAlgorithm(rawValue: encodingHeader)
+      // The algorithm isn't supported.
+      if compression == nil {
+        return .failure(.unsupportedMessageEncoding(encodingHeader))
+      }
+    } else {
+      // No compression was specified, this is fine.
+      compression = nil
     }
     }
 
 
-    let reader = LengthPrefixedMessageReader(compressionMechanism: compression)
+    let reader = LengthPrefixedMessageReader(compression: compression)
     return .success(.reading(arity, reader))
     return .success(.reading(arity, reader))
   }
   }
 
 

+ 2 - 3
Sources/GRPC/HTTP1ToRawGRPCServerCodec.swift

@@ -35,7 +35,7 @@ public enum _RawGRPCServerResponsePart {
 
 
 /// A simple channel handler that translates HTTP1 data types into gRPC packets, and vice versa.
 /// A simple channel handler that translates HTTP1 data types into gRPC packets, and vice versa.
 ///
 ///
-/// This codec allows us to use the "raw" gRPC protocol on a low level, with further handlers operationg the protocol
+/// This codec allows us to use the "raw" gRPC protocol on a low level, with further handlers operating the protocol
 /// on a "higher" level.
 /// on a "higher" level.
 ///
 ///
 /// We use HTTP1 (instead of HTTP2) primitives, as these are easier to work with than raw HTTP2
 /// We use HTTP1 (instead of HTTP2) primitives, as these are easier to work with than raw HTTP2
@@ -50,8 +50,7 @@ public final class HTTP1ToRawGRPCServerCodec {
     var accessLog = Logger(subsystem: .serverAccess)
     var accessLog = Logger(subsystem: .serverAccess)
     accessLog[metadataKey: MetadataKey.requestID] = logger[metadataKey: MetadataKey.requestID]
     accessLog[metadataKey: MetadataKey.requestID] = logger[metadataKey: MetadataKey.requestID]
     self.accessLog = accessLog
     self.accessLog = accessLog
-
-    self.messageReader = LengthPrefixedMessageReader(compressionMechanism: .none)
+    self.messageReader = LengthPrefixedMessageReader()
   }
   }
 
 
   // 1-byte for compression flag, 4-bytes for message length.
   // 1-byte for compression flag, 4-bytes for message length.

+ 4 - 4
Sources/GRPC/LengthPrefixedMessageReader.swift

@@ -32,10 +32,10 @@ import Logging
 /// [gRPC Protocol](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md)
 /// [gRPC Protocol](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md)
 internal struct LengthPrefixedMessageReader {
 internal struct LengthPrefixedMessageReader {
   /// The mechanism that messages will be compressed with.
   /// The mechanism that messages will be compressed with.
-  var compressionMechanism: CompressionMechanism
+  var compression: CompressionAlgorithm?
 
 
-  init(compressionMechanism: CompressionMechanism) {
-    self.compressionMechanism = compressionMechanism
+  init(compression: CompressionAlgorithm? = nil) {
+    self.compression = compression
   }
   }
 
 
   /// The result of trying to parse a message with the bytes we currently have.
   /// The result of trying to parse a message with the bytes we currently have.
@@ -150,7 +150,7 @@ internal struct LengthPrefixedMessageReader {
   }
   }
 
 
   private func handleCompressionFlag(enabled flagEnabled: Bool) throws {
   private func handleCompressionFlag(enabled flagEnabled: Bool) throws {
-    if flagEnabled && !(self.compressionMechanism.requiresFlag && self.compressionMechanism.supported) {
+    if flagEnabled && self.compression == nil {
       throw GRPCError.CompressionUnsupported().captureContext()
       throw GRPCError.CompressionUnsupported().captureContext()
     }
     }
   }
   }

+ 9 - 4
Sources/GRPC/LengthPrefixedMessageWriter.swift

@@ -19,10 +19,15 @@ import NIO
 internal struct LengthPrefixedMessageWriter {
 internal struct LengthPrefixedMessageWriter {
   static let metadataLength = 5
   static let metadataLength = 5
 
 
-  private let compression: CompressionMechanism
+  /// The compression algorithm to use, if one should be used.
+  private let compression: CompressionAlgorithm?
 
 
-  init(compression: CompressionMechanism) {
-    precondition(compression.supported, "compression mechanism \(compression) is not supported")
+  /// Whether the compression message flag should be set.
+  private var shouldSetCompressionFlag: Bool {
+    return self.compression != nil
+  }
+
+  init(compression: CompressionAlgorithm? = nil) {
     self.compression = compression
     self.compression = compression
   }
   }
 
 
@@ -38,7 +43,7 @@ internal struct LengthPrefixedMessageWriter {
     buffer.reserveCapacity(LengthPrefixedMessageWriter.metadataLength + message.count)
     buffer.reserveCapacity(LengthPrefixedMessageWriter.metadataLength + message.count)
 
 
     //! TODO: Add compression support, use the length and compressed content.
     //! TODO: Add compression support, use the length and compressed content.
-    buffer.writeInteger(Int8(compression.requiresFlag ? 1 : 0))
+    buffer.writeInteger(Int8(self.shouldSetCompressionFlag ? 1 : 0))
     buffer.writeInteger(UInt32(message.count))
     buffer.writeInteger(UInt32(message.count))
     buffer.writeBytes(message)
     buffer.writeBytes(message)
   }
   }

+ 1 - 1
Sources/GRPC/ReadWriteStates.swift

@@ -28,7 +28,7 @@ struct PendingWriteState {
   var arity: MessageArity
   var arity: MessageArity
 
 
   /// The compression used when writing messages.
   /// The compression used when writing messages.
-  var compression: CompressionMechanism
+  var compression: CompressionAlgorithm?
 
 
   /// The 'content-type' being written.
   /// The 'content-type' being written.
   var contentType: ContentType
   var contentType: ContentType

+ 4 - 4
Tests/GRPCTests/GRPCClientStateMachineTests.swift

@@ -714,7 +714,7 @@ extension GRPCClientStateMachineTests {
 
 
     switch stateMachine.state {
     switch stateMachine.state {
     case let .clientActiveServerActive(_, .reading(_, reader)):
     case let .clientActiveServerActive(_, .reading(_, reader)):
-      XCTAssertEqual(reader.compressionMechanism, .identity)
+      XCTAssertEqual(reader.compression, .identity)
     default:
     default:
       XCTFail("unexpected state \(stateMachine.state)")
       XCTFail("unexpected state \(stateMachine.state)")
     }
     }
@@ -738,7 +738,7 @@ extension GRPCClientStateMachineTests {
     headers.add(name: "grpc-encoding", value: "not-a-known-compression-(probably)")
     headers.add(name: "grpc-encoding", value: "not-a-known-compression-(probably)")
 
 
     stateMachine.receiveResponseHeaders(headers).assertFailure {
     stateMachine.receiveResponseHeaders(headers).assertFailure {
-      XCTAssertEqual($0, .unsupportedMessageEncoding("unknown"))
+      XCTAssertEqual($0, .unsupportedMessageEncoding("not-a-known-compression-(probably)"))
     }
     }
   }
   }
 
 
@@ -930,12 +930,12 @@ extension Result {
 
 
 extension ReadState {
 extension ReadState {
   static func one() -> ReadState {
   static func one() -> ReadState {
-    let reader = LengthPrefixedMessageReader(compressionMechanism: .none)
+    let reader = LengthPrefixedMessageReader()
     return .reading(.one, reader)
     return .reading(.one, reader)
   }
   }
 
 
   static func many() -> ReadState {
   static func many() -> ReadState {
-    let reader = LengthPrefixedMessageReader(compressionMechanism: .none)
+    let reader = LengthPrefixedMessageReader()
     return .reading(.many, reader)
     return .reading(.many, reader)
   }
   }
 
 

+ 4 - 20
Tests/GRPCTests/LengthPrefixedMessageReaderTests.swift

@@ -23,7 +23,7 @@ class LengthPrefixedMessageReaderTests: GRPCTestCase {
 
 
   override func setUp() {
   override func setUp() {
     super.setUp()
     super.setUp()
-    self.reader = LengthPrefixedMessageReader(compressionMechanism: .none)
+    self.reader = LengthPrefixedMessageReader()
   }
   }
 
 
   var allocator = ByteBufferAllocator()
   var allocator = ByteBufferAllocator()
@@ -206,24 +206,10 @@ class LengthPrefixedMessageReaderTests: GRPCTestCase {
     self.assertMessagesEqual(expected: [0x00, 0x01], actual: try reader.nextMessage())
     self.assertMessagesEqual(expected: [0x00, 0x01], actual: try reader.nextMessage())
   }
   }
 
 
-  func testNextMessageThrowsWhenCompressionMechanismIsNotSupported() throws {
-    // Unknown should never be supported.
-    reader.compressionMechanism = .unknown
-    XCTAssertFalse(reader.compressionMechanism.supported)
-
-    var buffer = byteBuffer(withBytes: lengthPrefixedTwoByteMessage(withCompression: true))
-    reader.append(buffer: &buffer)
-
-    XCTAssertThrowsError(try reader.nextMessage()) { error in
-      let errorWithContext = error as? GRPCError.WithContext
-      XCTAssertTrue(errorWithContext?.error is GRPCError.CompressionUnsupported)
-    }
-  }
-
   func testNextMessageThrowsWhenCompressionFlagIsSetButNotExpected() throws {
   func testNextMessageThrowsWhenCompressionFlagIsSetButNotExpected() throws {
-    // Default compression mechanism is `.none` which requires that no
+    // Default compression mechanism is `nil` which requires that no
     // compression flag is set as it indicates a lack of message encoding header.
     // compression flag is set as it indicates a lack of message encoding header.
-    XCTAssertFalse(reader.compressionMechanism.requiresFlag)
+    XCTAssertNil(self.reader.compression)
 
 
     var buffer = byteBuffer(withBytes: lengthPrefixedTwoByteMessage(withCompression: true))
     var buffer = byteBuffer(withBytes: lengthPrefixedTwoByteMessage(withCompression: true))
     reader.append(buffer: &buffer)
     reader.append(buffer: &buffer)
@@ -236,9 +222,7 @@ class LengthPrefixedMessageReaderTests: GRPCTestCase {
 
 
   func testNextMessageDoesNotThrowWhenCompressionFlagIsExpectedButNotSet() throws {
   func testNextMessageDoesNotThrowWhenCompressionFlagIsExpectedButNotSet() throws {
     // `.identity` should always be supported and requires a flag.
     // `.identity` should always be supported and requires a flag.
-    reader.compressionMechanism = .identity
-    XCTAssertTrue(reader.compressionMechanism.supported)
-    XCTAssertTrue(reader.compressionMechanism.requiresFlag)
+    reader.compression = .identity
 
 
     var buffer = byteBuffer(withBytes: lengthPrefixedTwoByteMessage())
     var buffer = byteBuffer(withBytes: lengthPrefixedTwoByteMessage())
     reader.append(buffer: &buffer)
     reader.append(buffer: &buffer)

+ 0 - 1
Tests/GRPCTests/XCTestManifests.swift

@@ -477,7 +477,6 @@ extension LengthPrefixedMessageReaderTests {
         ("testNextMessageReturnsNilWhenNotAllMessageBytesAreAvailable", testNextMessageReturnsNilWhenNotAllMessageBytesAreAvailable),
         ("testNextMessageReturnsNilWhenNotAllMessageBytesAreAvailable", testNextMessageReturnsNilWhenNotAllMessageBytesAreAvailable),
         ("testNextMessageReturnsNilWhenNotAllMessageLengthIsAvailable", testNextMessageReturnsNilWhenNotAllMessageLengthIsAvailable),
         ("testNextMessageReturnsNilWhenNotAllMessageLengthIsAvailable", testNextMessageReturnsNilWhenNotAllMessageLengthIsAvailable),
         ("testNextMessageThrowsWhenCompressionFlagIsSetButNotExpected", testNextMessageThrowsWhenCompressionFlagIsSetButNotExpected),
         ("testNextMessageThrowsWhenCompressionFlagIsSetButNotExpected", testNextMessageThrowsWhenCompressionFlagIsSetButNotExpected),
-        ("testNextMessageThrowsWhenCompressionMechanismIsNotSupported", testNextMessageThrowsWhenCompressionMechanismIsNotSupported),
         ("testNextMessageWhenMultipleMessagesAreBuffered", testNextMessageWhenMultipleMessagesAreBuffered),
         ("testNextMessageWhenMultipleMessagesAreBuffered", testNextMessageWhenMultipleMessagesAreBuffered),
     ]
     ]
 }
 }