Implement GzipServerMiddleware
This commit is contained in:
parent
e4d1659244
commit
6b2983cbbb
|
@ -1,47 +1,15 @@
|
||||||
import HTTP
|
import HTTP
|
||||||
import Vapor
|
import Vapor
|
||||||
import gzip
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import ZIPFoundation
|
||||||
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:
|
/// Server Gzip middlere:
|
||||||
/// 1. checks if the "Accept-Encoding" header contains "gzip"
|
/// 1. checks if the "Accept-Encoding" header contains "gzip"
|
||||||
/// 2. if so, compresses the body and sets the response header "Content-Encoding" to "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
|
private let shouldGzip: (_ request: Request) -> Bool
|
||||||
|
|
||||||
|
@ -51,26 +19,48 @@ public struct GzipServerMiddleware: Middleware {
|
||||||
self.shouldGzip = shouldGzip
|
self.shouldGzip = shouldGzip
|
||||||
}
|
}
|
||||||
|
|
||||||
public func respond(to request: Request, chainingTo next: Responder) throws -> Response {
|
public func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture<Response> {
|
||||||
|
let acceptsGzip = request.http.headers[.acceptEncoding].first?.contains("gzip") ?? false
|
||||||
let acceptsGzip = request.headers["Accept-Encoding"]?.contains("gzip") == true
|
|
||||||
guard acceptsGzip && shouldGzip(request) else {
|
guard acceptsGzip && shouldGzip(request) else {
|
||||||
return try next.respond(to: request)
|
return try next.respond(to: request)
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = try next.respond(to: request)
|
let response = try next.respond(to: request)
|
||||||
response.headers["Content-Encoding"] = "gzip"
|
return response.flatMap { response in
|
||||||
|
var headers = response.http.headers
|
||||||
let unzipped = response.body
|
headers.replaceOrAdd(name: .contentEncoding, value: "gzip")
|
||||||
switch unzipped {
|
return response.http.body.consumeData(on: request).map { data in
|
||||||
case .data(let bytes):
|
let stream = HTTPChunkedStream(on: request)
|
||||||
response.body = .data(Array(try Data(bytes: bytes).gzipCompressed()))
|
let bufSize = 4096
|
||||||
case .chunked(let chunker):
|
var buffer = ByteBufferAllocator().buffer(capacity: bufSize)
|
||||||
response.body = .chunked({ (stream: ChunkStream) in
|
let header : [UInt8] = [0x1f, 0x8b, 0x08, 0x00]
|
||||||
let gzipStream = try GzipStream(mode: .compress, stream: stream.raw)
|
let header2 : [UInt8] = [0x00, 0x03]
|
||||||
try chunker(ChunkStream(stream: gzipStream))
|
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user