|
|
@@ -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<Response> { |
|
|
|
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..<offset+readSize) |
|
|
|
}, |
|
|
|
consumer: { data -> 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 |
|
|
|
} |
|
|
|
} |