trillium handler that rewrites html using
lol-html through the lol-async library.
HtmlRewriter wraps another handler and transforms its response body as it streams to the client,
using lol-html's CSS-selector-based rewriting API. It's well suited to
sitting in front of a Proxy or other handlers that you don't
have direct control over, letting you inject tags, rewrite attributes, or strip elements from an
outbound HTML body without buffering the whole response.
use ;
let handler = ;
use ;
block_on;
Behavior
-
Only HTML responses are rewritten. Before touching anything, the handler checks the outgoing
Content-Type: a response is rewritten only if its mime subtype ishtml(e.g.text/html). Responses with any other content type — or noContent-Typeat all — pass through untouched, so it's safe to place in front of a handler that serves a mix of HTML, JSON, and binary data. -
It transforms the response in
before_send, so composition order is forgiving. The rewriting runs in the handler'sbefore_sendphase, which trillium runs for every handler in a tuple regardless of whether an earlier handler halted. SoHtmlRewriterrewrites whatever HTML body the rest of the tuple produced. An example using trillium-proxy:use ; use Proxy; use ClientConfig; run; -
The response becomes a stream of unknown length. Because rewriting is streaming, the rewritten body's length isn't known up front, so any
Content-Lengthheader is removed and the response is sent with chunked transfer encoding (HTTP/1.1) or the equivalent for HTTP/2 and HTTP/3.
Constructing settings
HtmlRewriter is constructed from a settings-building function, not a Settings value, because
lol-html's content handlers are single-use: the function is invoked once per rewritten response
to build a fresh set of handlers. Three constructors are available, named for what the settings
may depend on:
HtmlRewriter::new(|| …)— the same rewrite for every responseHtmlRewriter::new_with_conn(|conn| …)— settings that depend on the request or responseHtmlRewriter::new_async(async |conn: &Conn| …)— settings that additionally await async work
Use Settings::new_send() as the base (its handlers are
Send, as required to move between trillium's tasks) and fill in element_content_handlers /
document_content_handlers. See the lol-html docs for the full
rewriting API — element!,
comments!,
text!, and the
Element API for
inspecting and mutating matched elements.
Rewriting per request
The settings function passed to HtmlRewriter::new_with_conn receives the
Conn whose response is about to be
rewritten, so the rewrite can depend on the request — its path, its headers, or state set by an
earlier handler:
use ;
new_with_conn;
Data read from the conn must be moved into the content handlers, which outlive the conn borrow, moving into the streaming body.
Async settings
HtmlRewriter::new_async accepts an async function, which can await a database query, an http
request, or any other async work while borrowing the conn:
use Conn;
use ;
async
new_async;
Two things to know about the closure forms new_async accepts:
- The
&Connparameter annotation is required — type inference cannot supply it. - An async closure must not capture its environment. To use captured state (a client handle,
configuration, …), write a plain closure that clones what it needs — from its environment and
from the conn — into an async block it returns:
move |conn: &Conn| { let client = client.clone(); async move { /* … */ } }. See theHtmlRewriter::new_asyncdocs for a complete example.
Whichever constructor is used, the settings function runs only for responses that are actually rewritten, so no work is done for non-HTML responses.