Files
ccn/Packages/Infrastructure/Sources/Public/Middlewares/CompressionMiddleware.swift
T
javierandClaude Fable 5.1 916df7e2f0 Project updates from Template
This commit contains the latest updates from the generic Website template, which rework the compression and localization:

- Reworked the compression and localization in the Infrastructure package. (3c568e4)
- Adopted the reworked compression and localization in the Website service. (08cf3e3)

The template commit that only touched the root README (53676ed) was left out, as this project no longer carries that file.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 12:45:58 +02:00

71 lines
2.4 KiB
Swift

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<Context: RequestContext>: Sendable {
// MARK: Properties
/// The middleware the unencoded responses are handed to.
private let compression: ResponseCompressionMiddleware<Context>
// 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
}
}
}