import Foundation import HTTPTypes import Hummingbird /// Appends header names to the `Vary` header of every response passing through. /// /// Placed just above the response-compression middleware, it marks each response as varying on `Accept-Encoding`: the static files and pre-rendered /// pages are served with `Cache-Control: public`, so without the signal a shared cache could store a compressed body and hand it to a client that /// never advertised support for the encoding. /// /// Names already present on a response's `Vary` header — such as the `Accept-Language` the localized pages carry — are kept, and duplicates are not /// added. public struct VaryMiddleware: Sendable { // MARK: Properties /// The header names appended to every response's `Vary` header. private let names: [String] // MARK: Initializers /// Creates a vary middleware. /// - Parameter fields: the header names appended to every response's `Vary` header. Defaults to `Accept-Encoding`, the request header /// the response-compression middleware acts on. public init( fields: [HTTPField.Name] = [.acceptEncoding] ) { self.names = fields.map(\.rawName) } } // MARK: - RouterMiddleware extension VaryMiddleware: RouterMiddleware { // MARK: Functions /// Passes the request down the chain and appends the configured names to the response's `Vary` header on the way back up. /// - 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 with the `Vary` names applied. /// - Throws: any error thrown downstream. public func handle( _ request: Request, context: Context, next: (Request, Context) async throws -> Response ) async throws -> Response { var response = try await next( request, context ) var vary = response.headers[.vary]? .split(separator: ",") .map { $0.trimmingCharacters(in: .whitespaces) } ?? [] for name in names where !vary.contains(where: { $0.caseInsensitiveCompare(name) == .orderedSame }) { vary.append(name) } response.headers[.vary] = vary.joined(separator: ", ") return response } }