import Elementary import HTTPTypes import Hummingbird import NIOCore /// A pre-rendered HTTP response for a fully static HTML page. /// /// The document is rendered to bytes once, at initialization, and every ``response()`` reuses those /// bytes — along with a fixed status and precomputed headers — instead of re-rendering. This suits /// pages whose markup never changes between requests, such as the landing page and the not-found /// page, avoiding a per-request Elementary render on hot paths. /// /// ``LocalizedHTMLCollectionResponse`` builds on this type, caching one instance per supported language. /// /// The body is written as an unsized stream (no `Content-Length`), mirroring `HTMLResponse`, so the /// response-compression middleware downstream treats it exactly as it would a freshly rendered page. struct CachedHTMLResponse: Sendable { // MARK: Properties /// The page rendered to bytes once. private let buffer: ByteBuffer /// The headers applied to every response, precomputed once. private let headers: HTTPFields /// The status applied to every response. private let status: HTTPResponse.Status // MARK: Initializers /// Renders the given document to bytes once. /// - Parameters: /// - status: the status applied to every response. Defaults to `.ok`. /// - additionalHeaders: extra headers merged onto every response, alongside the content type. /// Used to carry per-language signals such as `Content-Language` and `Vary`. /// - document: the static HTML document to render and cache. init( status: HTTPResponse.Status = .ok, additionalHeaders: HTTPFields = [:], document: some HTMLDocument ) { var headers: HTTPFields = [ .contentType: "text/html; charset=utf-8" ] for field in additionalHeaders { headers[field.name] = field.value } self.status = status self.headers = headers self.buffer = .init(string: document.render()) } // MARK: Methods /// Builds a response from the cached, pre-rendered bytes. /// /// Mirrors the `text/html; charset=utf-8` content type `HTMLResponse` produces, and leaves the /// `Content-Length` unset so small pages remain eligible for compression. /// - Returns: the response carrying the cached HTML body. func response() -> Response { Response( status: status, headers: headers, body: .init { [buffer] writer in try await writer.write(buffer) try await writer.finish(nil) } ) } }