mirror of
https://github.com/apple/swift-nio-extras.git
synced 2025-05-15 01:18:58 +08:00
Enable automatic compression format detection (#208)
Co-authored-by: Cory Benfield <lukasa@apple.com>
This commit is contained in:
parent
3bbec9883a
commit
8b9030df7c
@ -94,18 +94,41 @@ public enum NIOHTTPDecompression {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var window: CInt {
|
|
||||||
switch self {
|
|
||||||
case .deflate:
|
|
||||||
return 15
|
|
||||||
case .gzip:
|
|
||||||
return 15 + 16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Decompressor {
|
struct Decompressor {
|
||||||
|
/// `15` is the base two logarithm of the maximum window size (the size of the history buffer). It should be in the range 8..15 for this version of the library.
|
||||||
|
/// `32` enables automatic detection of gzip or zlib compression format with automatic header detection.
|
||||||
|
///
|
||||||
|
/// Documentation from https://www.zlib.net/manual.html:
|
||||||
|
/// The windowBits parameter is the base two logarithm of the maximum window size (the size of the history buffer).
|
||||||
|
/// It should be in the range 8..15 for this version of the library.
|
||||||
|
/// The default value is 15 if inflateInit is used instead.
|
||||||
|
/// windowBits must be greater than or equal to the windowBits value provided to deflateInit2() while compressing,
|
||||||
|
/// or it must be equal to 15 if deflateInit2() was not used.
|
||||||
|
/// If a compressed stream with a larger window size is given as input,
|
||||||
|
/// inflate() will return with the error code Z_DATA_ERROR instead of trying to allocate a larger window.
|
||||||
|
/// windowBits can also be zero to request that inflate use the window size in the zlib header of the compressed stream.
|
||||||
|
/// windowBits can also be –8..–15 for raw inflate.
|
||||||
|
/// In this case, -windowBits determines the window size.
|
||||||
|
/// inflate() will then process raw deflate data, not looking for a zlib or gzip header, not generating a check value,
|
||||||
|
/// and not looking for any check values for comparison at the end of the stream.
|
||||||
|
/// This is for use with other formats that use the deflate compressed data format such as zip.
|
||||||
|
/// Those formats provide their own check values.
|
||||||
|
/// If a custom format is developed using the raw deflate format for compressed data,
|
||||||
|
/// it is recommended that a check value such as an Adler-32 or a CRC-32 be applied to the uncompressed data as is done in the zlib,
|
||||||
|
/// gzip, and zip formats. For most applications, the zlib format should be used as is.
|
||||||
|
/// Note that comments above on the use in deflateInit2() applies to the magnitude of windowBits.
|
||||||
|
/// windowBits can also be greater than 15 for optional gzip decoding.
|
||||||
|
/// Add 32 to windowBits to enable zlib and gzip decoding with automatic header detection,
|
||||||
|
/// or add 16 to decode only the gzip format (the zlib format will return a Z_DATA_ERROR).
|
||||||
|
/// If a gzip stream is being decoded, strm->adler is a CRC-32 instead of an Adler-32.
|
||||||
|
/// Unlike the gunzip utility and gzread() (see below), inflate() will not automatically decode concatenated gzip members.
|
||||||
|
/// inflate() will return Z_STREAM_END at the end of the gzip member.
|
||||||
|
/// The state would need to be reset to continue decoding a subsequent gzip member.
|
||||||
|
/// This must be done if there is more data after a gzip member, in order for the decompression to be compliant with the gzip standard (RFC 1952).
|
||||||
|
static let windowBitsWithAutomaticCompressionFormatDetection: Int32 = 15 + 32
|
||||||
|
|
||||||
private let limit: NIOHTTPDecompression.DecompressionLimit
|
private let limit: NIOHTTPDecompression.DecompressionLimit
|
||||||
private var stream = z_stream()
|
private var stream = z_stream()
|
||||||
private var inflated = 0
|
private var inflated = 0
|
||||||
@ -125,13 +148,13 @@ public enum NIOHTTPDecompression {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
mutating func initializeDecoder(encoding: NIOHTTPDecompression.CompressionAlgorithm) throws {
|
mutating func initializeDecoder() throws {
|
||||||
self.stream.zalloc = nil
|
self.stream.zalloc = nil
|
||||||
self.stream.zfree = nil
|
self.stream.zfree = nil
|
||||||
self.stream.opaque = nil
|
self.stream.opaque = nil
|
||||||
self.inflated = 0
|
self.inflated = 0
|
||||||
|
|
||||||
let rc = CNIOExtrasZlib_inflateInit2(&self.stream, encoding.window)
|
let rc = CNIOExtrasZlib_inflateInit2(&self.stream, Self.windowBitsWithAutomaticCompressionFormatDetection)
|
||||||
guard rc == Z_OK else {
|
guard rc == Z_OK else {
|
||||||
throw NIOHTTPDecompression.DecompressionError.initializationError(Int(rc))
|
throw NIOHTTPDecompression.DecompressionError.initializationError(Int(rc))
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ public final class NIOHTTPRequestDecompressor: ChannelDuplexHandler, RemovableCh
|
|||||||
let length = head.headers[canonicalForm: "Content-Length"].first.flatMap({ Int($0) })
|
let length = head.headers[canonicalForm: "Content-Length"].first.flatMap({ Int($0) })
|
||||||
{
|
{
|
||||||
do {
|
do {
|
||||||
try self.decompressor.initializeDecoder(encoding: algorithm)
|
try self.decompressor.initializeDecoder()
|
||||||
self.compression = Compression(algorithm: algorithm, contentLength: length)
|
self.compression = Compression(algorithm: algorithm, contentLength: length)
|
||||||
} catch let error {
|
} catch let error {
|
||||||
context.fireErrorCaught(error)
|
context.fireErrorCaught(error)
|
||||||
|
@ -71,7 +71,7 @@ public final class NIOHTTPResponseDecompressor: ChannelDuplexHandler, RemovableC
|
|||||||
do {
|
do {
|
||||||
if let algorithm = algorithm {
|
if let algorithm = algorithm {
|
||||||
self.compression = Compression(algorithm: algorithm, compressedLength: 0)
|
self.compression = Compression(algorithm: algorithm, compressedLength: 0)
|
||||||
try self.decompressor.initializeDecoder(encoding: algorithm)
|
try self.decompressor.initializeDecoder()
|
||||||
}
|
}
|
||||||
|
|
||||||
context.fireChannelRead(data)
|
context.fireChannelRead(data)
|
||||||
|
@ -103,13 +103,20 @@ class HTTPRequestDecompressorTest: XCTestCase {
|
|||||||
try channel.pipeline.addHandler(NIOHTTPRequestDecompressor(limit: .none)).wait()
|
try channel.pipeline.addHandler(NIOHTTPRequestDecompressor(limit: .none)).wait()
|
||||||
|
|
||||||
let body = Array(repeating: testString, count: 1000).joined()
|
let body = Array(repeating: testString, count: 1000).joined()
|
||||||
|
let algorithms: [(actual: String, announced: String)?] = [
|
||||||
|
nil,
|
||||||
|
(actual: "gzip", announced: "gzip"),
|
||||||
|
(actual: "deflate", announced: "deflate"),
|
||||||
|
(actual: "gzip", announced: "deflate"),
|
||||||
|
(actual: "deflate", announced: "gzip"),
|
||||||
|
]
|
||||||
|
|
||||||
for algorithm in [nil, "gzip", "deflate"] {
|
for algorithm in algorithms {
|
||||||
let compressed: ByteBuffer
|
let compressed: ByteBuffer
|
||||||
var headers = HTTPHeaders()
|
var headers = HTTPHeaders()
|
||||||
if let algorithm = algorithm {
|
if let algorithm = algorithm {
|
||||||
headers.add(name: "Content-Encoding", value: algorithm)
|
headers.add(name: "Content-Encoding", value: algorithm.announced)
|
||||||
compressed = compress(ByteBuffer.of(string: body), algorithm)
|
compressed = compress(ByteBuffer.of(string: body), algorithm.actual)
|
||||||
} else {
|
} else {
|
||||||
compressed = ByteBuffer.of(string: body)
|
compressed = ByteBuffer.of(string: body)
|
||||||
}
|
}
|
||||||
|
@ -166,13 +166,20 @@ class HTTPResponseDecompressorTest: XCTestCase {
|
|||||||
for _ in 1...1000 {
|
for _ in 1...1000 {
|
||||||
body += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
body += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||||
}
|
}
|
||||||
|
let algorithms: [(actual: String, announced: String)?] = [
|
||||||
|
nil,
|
||||||
|
(actual: "gzip", announced: "gzip"),
|
||||||
|
(actual: "deflate", announced: "deflate"),
|
||||||
|
(actual: "gzip", announced: "deflate"),
|
||||||
|
(actual: "deflate", announced: "gzip"),
|
||||||
|
]
|
||||||
|
|
||||||
for algorithm in [nil, "gzip", "deflate"] {
|
for algorithm in algorithms {
|
||||||
let compressed: ByteBuffer
|
let compressed: ByteBuffer
|
||||||
var headers = HTTPHeaders()
|
var headers = HTTPHeaders()
|
||||||
if let algorithm = algorithm {
|
if let algorithm = algorithm {
|
||||||
headers.add(name: "Content-Encoding", value: algorithm)
|
headers.add(name: "Content-Encoding", value: algorithm.announced)
|
||||||
compressed = compress(ByteBuffer.of(string: body), algorithm)
|
compressed = compress(ByteBuffer.of(string: body), algorithm.actual)
|
||||||
} else {
|
} else {
|
||||||
compressed = ByteBuffer.of(string: body)
|
compressed = ByteBuffer.of(string: body)
|
||||||
}
|
}
|
||||||
@ -214,12 +221,20 @@ class HTTPResponseDecompressorTest: XCTestCase {
|
|||||||
body += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
body += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||||
}
|
}
|
||||||
|
|
||||||
for algorithm in [nil, "gzip", "deflate"] {
|
let algorithms: [(actual: String, announced: String)?] = [
|
||||||
|
nil,
|
||||||
|
(actual: "gzip", announced: "gzip"),
|
||||||
|
(actual: "deflate", announced: "deflate"),
|
||||||
|
(actual: "gzip", announced: "deflate"),
|
||||||
|
(actual: "deflate", announced: "gzip"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for algorithm in algorithms {
|
||||||
let compressed: ByteBuffer
|
let compressed: ByteBuffer
|
||||||
var headers = HTTPHeaders()
|
var headers = HTTPHeaders()
|
||||||
if let algorithm = algorithm {
|
if let algorithm = algorithm {
|
||||||
headers.add(name: "Content-Encoding", value: algorithm)
|
headers.add(name: "Content-Encoding", value: algorithm.announced)
|
||||||
compressed = compress(ByteBuffer.of(string: body), algorithm)
|
compressed = compress(ByteBuffer.of(string: body), algorithm.actual)
|
||||||
} else {
|
} else {
|
||||||
compressed = ByteBuffer.of(string: body)
|
compressed = ByteBuffer.of(string: body)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user