diff --git a/Sources/GzipMiddleware/GzipMiddleware.swift b/Sources/GzipMiddleware/GzipMiddleware.swift index f6fe38c..e7d5550 100644 --- a/Sources/GzipMiddleware/GzipMiddleware.swift +++ b/Sources/GzipMiddleware/GzipMiddleware.swift @@ -1,47 +1,15 @@ import HTTP import Vapor -import gzip import Foundation - -public enum GzipMiddlewareError: Error { - case unsupportedStreamType -} - -/// Client Gzip middlere: -/// 1. sets the "Accept-Encoding" header to "gzip" -/// 2. if the response has "Content-Encoding" == "gzip", uncompresses the body -public struct GzipClientMiddleware: Middleware { - - public init() { } - - public func respond(to request: Request, chainingTo next: Responder) throws -> Response { - - request.headers["Accept-Encoding"] = "gzip" - - let response = try next.respond(to: request) - - guard response.headers["Content-Encoding"] == "gzip" else { - return response - } - - let zipped = response.body - switch zipped { - case .data(let bytes): - response.body = .data(Array(try Data(bytes: bytes).gzipUncompressed())) - case .chunked(let chunker): - response.body = .chunked({ (stream: ChunkStream) in - let gzipStream = try GzipStream(mode: .uncompress, stream: stream.raw) - try chunker(ChunkStream(stream: gzipStream)) - }) - } - return response - } -} +import ZIPFoundation /// Server Gzip middlere: /// 1. checks if the "Accept-Encoding" header contains "gzip" /// 2. if so, compresses the body and sets the response header "Content-Encoding" to "gzip", -public struct GzipServerMiddleware: Middleware { +public struct GzipServerMiddleware: Middleware, ServiceType { + public static func makeService(for worker: Container) throws -> GzipServerMiddleware { + return .init() + } private let shouldGzip: (_ request: Request) -> Bool @@ -51,26 +19,48 @@ public struct GzipServerMiddleware: Middleware { self.shouldGzip = shouldGzip } - public func respond(to request: Request, chainingTo next: Responder) throws -> Response { - - let acceptsGzip = request.headers["Accept-Encoding"]?.contains("gzip") == true + public func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture { + let acceptsGzip = request.http.headers[.acceptEncoding].first?.contains("gzip") ?? false guard acceptsGzip && shouldGzip(request) else { return try next.respond(to: request) } let response = try next.respond(to: request) - response.headers["Content-Encoding"] = "gzip" - - let unzipped = response.body - switch unzipped { - case .data(let bytes): - response.body = .data(Array(try Data(bytes: bytes).gzipCompressed())) - case .chunked(let chunker): - response.body = .chunked({ (stream: ChunkStream) in - let gzipStream = try GzipStream(mode: .compress, stream: stream.raw) - try chunker(ChunkStream(stream: gzipStream)) - }) + return response.flatMap { response in + var headers = response.http.headers + headers.replaceOrAdd(name: .contentEncoding, value: "gzip") + return response.http.body.consumeData(on: request).map { data in + let stream = HTTPChunkedStream(on: request) + let bufSize = 4096 + var buffer = ByteBufferAllocator().buffer(capacity: bufSize) + let header : [UInt8] = [0x1f, 0x8b, 0x08, 0x00] + let header2 : [UInt8] = [0x00, 0x03] + buffer.write(bytes: header) + buffer.write(integer: UInt32(Date().timeIntervalSince1970), endianness: .little) + buffer.write(bytes: header2) + var write = stream.write(.chunk(buffer)) + let crc32 = try Data.compress(size: data.count, bufferSize: bufSize, + provider: {(offset, readSize) -> Data in + return data.subdata(in: offset.. Void in + write = write.flatMap { + buffer.clear() + buffer.write(bytes: data) + return stream.write(.chunk(buffer)) + } + }) + write.always { + buffer.clear() + buffer.write(integer: crc32, endianness: .little) + buffer.write(integer: UInt32(data.count), endianness: .little) + stream.write(.chunk(buffer)).always { + _ = stream.write(.end) + } + } + let httpResponse = HTTPResponse(status: response.http.status, headers: headers, body: stream) + return request.response(http: httpResponse) + } } - return response } }