import HTTPTypes import Hummingbird import HummingbirdCompression import Logging /// Compresses the responses that are not already encoded, and passes the ones that are through untouched. /// /// Hummingbird's `ResponseCompressionMiddleware` appends to `Content-Encoding` without checking for one, so a pre-compressed page (see /// ``CachedHTMLResponse``) would go out as `gzip, gzip` — a client decodes once and renders the inner gzip stream. This stands in for that /// middleware and delegates to it, so the threshold, the negotiation and the compressor stay its behaviour. /// /// - Note: `Context` is the request context the middleware is resolved against. public struct CompressionMiddleware: Sendable { // MARK: Properties /// The middleware the unencoded responses are handed to. private let compression: ResponseCompressionMiddleware // MARK: Initializers /// Creates a compression middleware. /// - Parameter minimumResponseSizeToCompress: the smallest response body, in bytes, that is compressed at all. public init( minimumResponseSizeToCompress: Int ) { self.compression = .init( minimumResponseSizeToCompress: minimumResponseSizeToCompress ) } } // MARK: - RouterMiddleware extension CompressionMiddleware: RouterMiddleware { // MARK: Functions /// Passes the request down the chain and compresses the response on the way back up, unless it already names an encoding. /// - Parameters: /// - request: the incoming request. /// - context: the context the request is resolved against. /// - next: the next responder in the middleware chain. /// - Returns: the downstream response, compressed when it was not already. /// - Throws: any error thrown downstream. public func handle( _ request: Request, context: Context, next: (Request, Context) async throws -> Response ) async throws -> Response { let response = try await next( request, context ) guard response.headers[.contentEncoding] == nil else { return response } // The response is already in hand, so the delegate gets it rather than the chain — `next` runs once. return try await compression.handle( request, context: context ) { _, _ in response } } }