Skip to content

Commit dac2d68

Browse files
authored
Move CompressionAlgorithm to core (#1858)
Motivation: `CallOptions` only let you enable/disable compression, it doesn't allow you to control which algorithm should be used. This is an unnecessary limitation. This was done because `CompressionAlgorithm` lives in the http2 module. Modifications: - Move `CompressionAlgorithm` to the core module - Rename 'identity' to 'none' as that's clearer for users - Add extensions in the http2 module to create an algorithm from its name - Add a `CompressionAlgorithmSet` type which uses an option set which allows for cheaper updates. - Update call options Result: - `CallOptions` is more flexible - Updating the call options set is cheaper
1 parent 192a0d3 commit dac2d68

File tree

10 files changed

+298
-121
lines changed

10 files changed

+298
-121
lines changed

Sources/GRPCCore/Call/Client/CallOptions.swift

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -77,46 +77,27 @@ public struct CallOptions: Sendable {
7777
/// reported to the client. Hedging is only suitable for idempotent RPCs.
7878
public var executionPolicy: RPCExecutionPolicy?
7979

80-
/// Whether compression is enabled or not for request and response messages.
81-
public var compression: Compression
82-
83-
public struct Compression: Sendable {
84-
/// Whether request messages should be compressed.
85-
///
86-
/// Note that this option is _advisory_: transports are not required to support compression.
87-
public var requests: Bool
88-
89-
/// Whether response messages are permitted to be compressed.
90-
public var responses: Bool
91-
92-
/// Creates a new ``Compression`` configuration.
93-
///
94-
/// - Parameters:
95-
/// - requests: Whether request messages should be compressed.
96-
/// - responses: Whether response messages may be compressed.
97-
public init(requests: Bool, responses: Bool) {
98-
self.requests = requests
99-
self.responses = responses
100-
}
101-
102-
/// Sets ``requests`` and ``responses`` to `true`.
103-
public static var enabled: Self {
104-
Self(requests: true, responses: true)
105-
}
106-
107-
/// Sets ``requests`` and ``responses`` to `false`.
108-
public static var disabled: Self {
109-
Self(requests: false, responses: false)
110-
}
111-
}
80+
/// The compression used for the call.
81+
///
82+
/// Compression in gRPC is asymmetrical: the server may compress response messages using a
83+
/// different algorithm than the client used to compress request messages. This configuration
84+
/// controls the compression used by the client for request messages.
85+
///
86+
/// Note that this configuration is advisory: not all transports support compression and may
87+
/// ignore this configuration. Transports which support compression will use this configuration
88+
/// in preference to the algorithm configured at a transport level. If the transport hasn't
89+
/// enabled the use of the algorithm then compression won't be used for the call.
90+
///
91+
/// If `nil` the value configured on the transport will be used instead.
92+
public var compression: CompressionAlgorithm?
11293

11394
internal init(
11495
timeout: Duration?,
11596
waitForReady: Bool?,
11697
maxRequestMessageBytes: Int?,
11798
maxResponseMessageBytes: Int?,
11899
executionPolicy: RPCExecutionPolicy?,
119-
compression: Compression
100+
compression: CompressionAlgorithm?
120101
) {
121102
self.timeout = timeout
122103
self.waitForReady = waitForReady
@@ -131,17 +112,15 @@ public struct CallOptions: Sendable {
131112
extension CallOptions {
132113
/// Default call options.
133114
///
134-
/// The default values defer values to the underlying transport. In most cases this means values
135-
/// are `nil`, with the exception of ``compression-swift.property`` which is set
136-
/// to ``Compression-swift.struct/disabled``.
115+
/// The default values (`nil`) defer values to the underlying transport.
137116
public static var defaults: Self {
138117
Self(
139118
timeout: nil,
140119
waitForReady: nil,
141120
maxRequestMessageBytes: nil,
142121
maxResponseMessageBytes: nil,
143122
executionPolicy: nil,
144-
compression: .disabled
123+
compression: nil
145124
)
146125
}
147126
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2024, 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+
17+
/// Message compression algorithms.
18+
public struct CompressionAlgorithm: Hashable, Sendable {
19+
@_spi(Package)
20+
public enum Value: UInt8, Hashable, Sendable, CaseIterable {
21+
case none = 0
22+
case deflate
23+
case gzip
24+
}
25+
26+
@_spi(Package)
27+
public let value: Value
28+
29+
fileprivate init(_ algorithm: Value) {
30+
self.value = algorithm
31+
}
32+
33+
/// No compression, sometimes referred to as 'identity' compression.
34+
public static var none: Self {
35+
Self(.none)
36+
}
37+
38+
/// The 'deflate' compression algorithm.
39+
public static var deflate: Self {
40+
Self(.deflate)
41+
}
42+
43+
/// The 'gzip' compression algorithm.
44+
public static var gzip: Self {
45+
Self(.gzip)
46+
}
47+
}
48+
49+
/// A set of compression algorithms.
50+
public struct CompressionAlgorithmSet: OptionSet, Hashable, Sendable {
51+
public var rawValue: UInt32
52+
53+
public init(rawValue: UInt32) {
54+
self.rawValue = rawValue
55+
}
56+
57+
private init(value: CompressionAlgorithm.Value) {
58+
self.rawValue = 1 << value.rawValue
59+
}
60+
61+
/// No compression, sometimes referred to as 'identity' compression.
62+
public static var none: Self {
63+
return Self(value: .none)
64+
}
65+
66+
/// The 'deflate' compression algorithm.
67+
public static var deflate: Self {
68+
return Self(value: .deflate)
69+
}
70+
71+
/// The 'gzip' compression algorithm.
72+
public static var gzip: Self {
73+
return Self(value: .gzip)
74+
}
75+
76+
/// All compression algorithms.
77+
public static var all: Self {
78+
return [.gzip, .deflate, .none]
79+
}
80+
81+
/// Returns whether a given algorithm is present in the set.
82+
///
83+
/// - Parameter algorithm: The algorithm to check.
84+
public func contains(_ algorithm: CompressionAlgorithm) -> Bool {
85+
return self.contains(CompressionAlgorithmSet(value: algorithm.value))
86+
}
87+
}
88+
89+
extension CompressionAlgorithmSet {
90+
/// A sequence of ``CompressionAlgorithm`` values present in the set.
91+
public var elements: Elements {
92+
Elements(algorithmSet: self)
93+
}
94+
95+
/// A sequence of ``CompressionAlgorithm`` values present in a ``CompressionAlgorithmSet``.
96+
public struct Elements: Sequence {
97+
public typealias Element = CompressionAlgorithm
98+
99+
private let algorithmSet: CompressionAlgorithmSet
100+
101+
init(algorithmSet: CompressionAlgorithmSet) {
102+
self.algorithmSet = algorithmSet
103+
}
104+
105+
public func makeIterator() -> Iterator {
106+
return Iterator(algorithmSet: self.algorithmSet)
107+
}
108+
109+
public struct Iterator: IteratorProtocol {
110+
private let algorithmSet: CompressionAlgorithmSet
111+
private var iterator: IndexingIterator<[CompressionAlgorithm.Value]>
112+
113+
init(algorithmSet: CompressionAlgorithmSet) {
114+
self.algorithmSet = algorithmSet
115+
self.iterator = CompressionAlgorithm.Value.allCases.makeIterator()
116+
}
117+
118+
public mutating func next() -> CompressionAlgorithm? {
119+
while let value = self.iterator.next() {
120+
if self.algorithmSet.contains(CompressionAlgorithmSet(value: value)) {
121+
return CompressionAlgorithm(value)
122+
}
123+
}
124+
125+
return nil
126+
}
127+
}
128+
}
129+
}

Sources/GRPCHTTP2Core/Client/GRPCClientStreamHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class GRPCClientStreamHandler: ChannelDuplexHandler {
3535
methodDescriptor: MethodDescriptor,
3636
scheme: Scheme,
3737
outboundEncoding: CompressionAlgorithm,
38-
acceptedEncodings: [CompressionAlgorithm],
38+
acceptedEncodings: CompressionAlgorithmSet,
3939
maximumPayloadSize: Int,
4040
skipStateMachineAssertions: Bool = false
4141
) {

Sources/GRPCHTTP2Core/Compression/CompressionAlgorithm.swift

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,39 +14,40 @@
1414
* limitations under the License.
1515
*/
1616

17-
/// Supported message compression algorithms.
18-
///
19-
/// These algorithms are indicated in the "grpc-encoding" header. As such, a lack of "grpc-encoding"
20-
/// header indicates that there is no message compression.
21-
public struct CompressionAlgorithm: Hashable, Sendable {
22-
/// Identity compression; "no" compression but indicated via the "grpc-encoding" header.
23-
public static let identity = CompressionAlgorithm(.identity)
24-
public static let deflate = CompressionAlgorithm(.deflate)
25-
public static let gzip = CompressionAlgorithm(.gzip)
17+
@_spi(Package) import GRPCCore
2618

27-
// The order here is important: most compression to least.
28-
public static let all: [CompressionAlgorithm] = [.gzip, .deflate, .identity]
29-
30-
public var name: String {
31-
return self.algorithm.rawValue
19+
extension CompressionAlgorithm {
20+
init?(name: String) {
21+
self.init(name: name[...])
3222
}
3323

34-
internal enum Algorithm: String {
35-
case identity
36-
case deflate
37-
case gzip
24+
init?(name: Substring) {
25+
switch name {
26+
case "gzip":
27+
self = .gzip
28+
case "deflate":
29+
self = .deflate
30+
case "identity":
31+
self = .none
32+
default:
33+
return nil
34+
}
3835
}
3936

40-
internal let algorithm: Algorithm
41-
42-
private init(_ algorithm: Algorithm) {
43-
self.algorithm = algorithm
37+
var name: String {
38+
switch self.value {
39+
case .gzip:
40+
return "gzip"
41+
case .deflate:
42+
return "deflate"
43+
case .none:
44+
return "identity"
45+
}
4446
}
47+
}
4548

46-
internal init?(rawValue: String) {
47-
guard let algorithm = Algorithm(rawValue: rawValue) else {
48-
return nil
49-
}
50-
self.algorithm = algorithm
49+
extension CompressionAlgorithmSet {
50+
var count: Int {
51+
self.rawValue.nonzeroBitCount
5152
}
5253
}

0 commit comments

Comments
 (0)