diff --git a/Package.swift b/Package.swift index a217898..3592bc0 100644 --- a/Package.swift +++ b/Package.swift @@ -1,19 +1,19 @@ // swift-tools-version:4.0 import PackageDescription -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) -let dependencies: [Package.Dependency] = [] -#else -let dependencies: [Package.Dependency] = [.package(url: "https://github.com/IBM-Swift/CZlib.git", .exact("0.1.2"))] -#endif - let package = Package( name: "GzipMiddleware", products: [ .library(name: "GzipMiddleware", targets: ["GzipMiddleware"]) ], - dependencies: dependencies, + dependencies: [ + // 💧 A server-side Swift web framework. + .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), + + // 🤐 Unzip archives + .package(url: "https://github.com/microtherion/ZIPFoundation.git", .upToNextMajor(from: "0.9.7-µ")) + ], targets: [ - .target(name: "GzipMiddleware"), + .target(name: "GzipMiddleware", dependencies: ["Vapor", "ZIPFoundation"]), ] ) diff --git a/Sources/GzipMiddleware/GzipMiddleware.swift b/Sources/GzipMiddleware/GzipMiddleware.swift index e69de29..f6fe38c 100644 --- a/Sources/GzipMiddleware/GzipMiddleware.swift +++ b/Sources/GzipMiddleware/GzipMiddleware.swift @@ -0,0 +1,76 @@ +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 + } +} + +/// 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 { + + private let shouldGzip: (_ request: Request) -> Bool + + /// The `shouldGzip` closure is asked for every request whether that request + /// should allow response gzipping. Returns `true` always by default. + public init(shouldGzip: @escaping (_ request: Request) -> Bool = { _ in true }) { + self.shouldGzip = shouldGzip + } + + public func respond(to request: Request, chainingTo next: Responder) throws -> Response { + + let acceptsGzip = request.headers["Accept-Encoding"]?.contains("gzip") == true + 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 + } +} diff --git a/Sources/GzipMiddleware/GzipStream.swift b/Sources/GzipMiddleware/GzipStream.swift deleted file mode 100644 index e69de29..0000000