From eab13037326be29e9f06213fe34d8fb281e168c5 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Wed, 17 Jul 2024 13:23:43 -0700 Subject: [PATCH] Updated compression response predicate to return an intent enum rather than a boolean --- .../HTTPResponseCompressor.swift | 34 ++++++++++++++++--- .../HTTPResponseCompressorTest.swift | 14 ++++---- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/Sources/NIOHTTPCompression/HTTPResponseCompressor.swift b/Sources/NIOHTTPCompression/HTTPResponseCompressor.swift index 7477723e..ebdd9e1a 100644 --- a/Sources/NIOHTTPCompression/HTTPResponseCompressor.swift +++ b/Sources/NIOHTTPCompression/HTTPResponseCompressor.swift @@ -78,13 +78,37 @@ public final class HTTPResponseCompressor: ChannelDuplexHandler, RemovableChanne /// /// - Parameter responseHeaders: The headers that will be used for the response. These can be modified as needed at this stage, to clean up any marker headers used to statelessly determine if compression should occur, and the new headers will be used when writing the response. Compression headers are not yet provided and should not be set; ``HTTPResponseCompressor`` will set them accordingly based on the result of this predicate. /// - Parameter isCompressionSupported: Set to `true` if the client requested compatible compression, and if the HTTP response supports it, otherwise `false`. - /// - Returns: Return `true` if the compressor should proceed to compress the response, or `false` if the response should not be compressed. + /// - Returns: Return ``CompressionIntent/compressIfPossible`` if the compressor should proceed to compress the response, or ``CompressionIntent/doNotCompress`` if the response should not be compressed. /// - /// - Note: Returning `true` when compression is not supported will not enable compression, and the modified headers will always be used. + /// - Note: Returning ``CompressionIntent/compressIfPossible`` is only a suggestion — when compression is not supported, the response will be returned as is along with any modified headers. public typealias ResponseCompressionPredicate = ( _ responseHeaders: inout HTTPResponseHead, _ isCompressionSupported: Bool - ) -> Bool + ) -> CompressionIntent + + /// A signal a ``ResponseCompressionPredicate`` returns to indicate if it intends for compression to be used or not when supported by HTTP. + public struct CompressionIntent: Sendable, Hashable { + /// The internal type ``CompressionIntent`` uses. + enum RawValue { + /// The response should be compressed if supported by the HTTP protocol. + case compressIfPossible + /// The response should not be compressed even if supported by the HTTP protocol. + case doNotCompress + } + + /// The raw value of the intent. + let rawValue: RawValue + + /// Initialize the raw value with an internal intent. + init(_ rawValue: RawValue) { + self.rawValue = rawValue + } + + /// The response should be compressed if supported by the HTTP protocol. + public static let compressIfPossible = CompressionIntent(.compressIfPossible) + /// The response should not be compressed even if supported by the HTTP protocol. + public static let doNotCompress = CompressionIntent(.doNotCompress) + } /// Errors which can occur when compressing public enum CompressionError: Error { @@ -152,10 +176,10 @@ public final class HTTPResponseCompressor: ChannelDuplexHandler, RemovableChanne let requestSupportsCompression = algorithm != nil && responseHead.status.mayHaveResponseBody /// If a predicate was set, ask it if we should compress when compression is supported, and give the predicate a chance to clean up any marker headers that may have been set even if compression were not supported. - let predicateSupportsCompression = responseCompressionPredicate?(&responseHead, requestSupportsCompression) ?? true + let predicateCompressionIntent = responseCompressionPredicate?(&responseHead, requestSupportsCompression) ?? .compressIfPossible /// Make sure that compression should proceed, otherwise stop here and supply the response headers before configuring the compressor. - guard let algorithm, requestSupportsCompression, predicateSupportsCompression else { + guard let algorithm, requestSupportsCompression, predicateCompressionIntent == .compressIfPossible else { context.write(wrapOutboundOut(.head(responseHead)), promise: promise) return } diff --git a/Tests/NIOHTTPCompressionTests/HTTPResponseCompressorTest.swift b/Tests/NIOHTTPCompressionTests/HTTPResponseCompressorTest.swift index 343142cd..99a56730 100644 --- a/Tests/NIOHTTPCompressionTests/HTTPResponseCompressorTest.swift +++ b/Tests/NIOHTTPCompressionTests/HTTPResponseCompressorTest.swift @@ -742,7 +742,7 @@ class HTTPResponseCompressorTest: XCTestCase { defer { predicateWasCalled.fulfill() } XCTAssertEqual(responseHeaders.headers, ["Content-Type" : "json"]) XCTAssertEqual(isCompressionSupported, true) - return true + return .compressIfPossible } let channel = try compressionChannel(compressor: compressor) @@ -770,7 +770,7 @@ class HTTPResponseCompressorTest: XCTestCase { defer { predicateWasCalled.fulfill() } XCTAssertEqual(responseHeaders.headers, ["Content-Type" : "json"]) XCTAssertEqual(isCompressionSupported, false) - return true + return .compressIfPossible } let channel = try compressionChannel(compressor: compressor) @@ -798,7 +798,7 @@ class HTTPResponseCompressorTest: XCTestCase { XCTAssertEqual(responseHeaders.status, .notModified) XCTAssertEqual(responseHeaders.headers, ["Content-Type" : "json"]) XCTAssertEqual(isCompressionSupported, false) - return true + return .compressIfPossible } let channel = EmbeddedChannel() @@ -836,7 +836,7 @@ class HTTPResponseCompressorTest: XCTestCase { defer { predicateWasCalled.fulfill() } XCTAssertEqual(responseHeaders.headers, ["Content-Type" : "json"]) XCTAssertEqual(isCompressionSupported, true) - return false + return .doNotCompress } let channel = try compressionChannel(compressor: compressor) @@ -863,7 +863,7 @@ class HTTPResponseCompressorTest: XCTestCase { defer { predicateWasCalled.fulfill() } XCTAssertEqual(responseHeaders.headers, ["Content-Type" : "json"]) XCTAssertEqual(isCompressionSupported, false) - return false + return .doNotCompress } let channel = try compressionChannel(compressor: compressor) @@ -891,7 +891,7 @@ class HTTPResponseCompressorTest: XCTestCase { XCTAssertEqual(responseHeaders.status, .notModified) XCTAssertEqual(responseHeaders.headers, ["Content-Type" : "json"]) XCTAssertEqual(isCompressionSupported, false) - return false + return .doNotCompress } let channel = EmbeddedChannel() @@ -932,7 +932,7 @@ class HTTPResponseCompressorTest: XCTestCase { XCTAssertEqual(responseHeaders.headers, ["Content-Type" : "json", "X-Compression" : isEnabled ? "enable" : "disable"]) responseHeaders.headers.remove(name: "X-Compression") XCTAssertEqual(isCompressionSupported, true) - return isEnabled + return isEnabled ? .compressIfPossible : .doNotCompress } let channel = try compressionChannel(compressor: compressor)