2018-10-06 20:24:47 +00:00
|
|
|
import HTTP
|
|
|
|
import Vapor
|
|
|
|
import Foundation
|
2018-10-06 22:43:44 +00:00
|
|
|
import ZIPFoundation
|
2018-10-06 20:24:47 +00:00
|
|
|
|
2018-10-06 23:32:19 +00:00
|
|
|
private extension Data {
|
|
|
|
mutating func appendLittleEndian(_ word: UInt32) {
|
|
|
|
self.append(UInt8(word & 0xFF))
|
|
|
|
self.append(UInt8((word >> 8) & 0xFF))
|
|
|
|
self.append(UInt8((word >> 16) & 0xFF))
|
|
|
|
self.append(UInt8((word >> 24) & 0xFF))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-06 20:24:47 +00:00
|
|
|
/// 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",
|
2018-10-06 22:43:44 +00:00
|
|
|
public struct GzipServerMiddleware: Middleware, ServiceType {
|
|
|
|
public static func makeService(for worker: Container) throws -> GzipServerMiddleware {
|
|
|
|
return .init()
|
|
|
|
}
|
2018-10-06 20:24:47 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-10-06 22:43:44 +00:00
|
|
|
public func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture<Response> {
|
|
|
|
let acceptsGzip = request.http.headers[.acceptEncoding].first?.contains("gzip") ?? false
|
2018-10-06 20:24:47 +00:00
|
|
|
guard acceptsGzip && shouldGzip(request) else {
|
|
|
|
return try next.respond(to: request)
|
|
|
|
}
|
|
|
|
|
|
|
|
let response = try next.respond(to: request)
|
2018-10-06 22:43:44 +00:00
|
|
|
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
|
2018-10-06 23:32:19 +00:00
|
|
|
var buffer = Data()
|
2018-10-06 22:43:44 +00:00
|
|
|
let header : [UInt8] = [0x1f, 0x8b, 0x08, 0x00]
|
|
|
|
let header2 : [UInt8] = [0x00, 0x03]
|
2018-10-06 23:32:19 +00:00
|
|
|
buffer.append(contentsOf: header)
|
|
|
|
buffer.appendLittleEndian(UInt32(Date().timeIntervalSince1970))
|
|
|
|
buffer.append(contentsOf: header2)
|
|
|
|
let crc32 = try Data.compress(size: data.count, bufferSize: 4096,
|
2018-10-06 22:43:44 +00:00
|
|
|
provider: {(offset, readSize) -> Data in
|
|
|
|
return data.subdata(in: offset..<offset+readSize)
|
|
|
|
},
|
|
|
|
consumer: { data -> Void in
|
2018-10-06 23:32:19 +00:00
|
|
|
buffer.append(data)
|
2018-10-06 22:43:44 +00:00
|
|
|
})
|
2018-10-06 23:32:19 +00:00
|
|
|
buffer.appendLittleEndian(crc32)
|
|
|
|
buffer.appendLittleEndian(UInt32(data.count))
|
|
|
|
let httpResponse = HTTPResponse(status: response.http.status, headers: headers, body: buffer)
|
2018-10-06 22:43:44 +00:00
|
|
|
return request.response(http: httpResponse)
|
|
|
|
}
|
2018-10-06 20:24:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|