middleware

command
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2026 License: BSD-3-Clause Imports: 10 Imported by: 0

Documentation

Overview

Command middleware wraps an http.Handler so that its HTML output is rewritten on the way to the client, without giving up streaming.

$ middleware
handler writes 5 chunks, 40ms apart
  streaming middleware   first byte after 42µs, last after 207ms
  buffering middleware   first byte after 209ms, last after 209ms

Both produce the same bytes. The difference is when the client gets them, and that is the whole reason to rewrite in a middleware rather than in a template: a page that takes two hundred milliseconds to generate can start rendering after forty.

What makes it stream

The rewriter writes to the destination as output becomes available, so the middleware's job is to not get in the way. Three things do:

Buffering. Wrapping the ResponseWriter in a bytes.Buffer, rewriting at the end and writing once is the shape everyone writes first, and it turns a streaming handler into a batch one. The measurement above is what it costs.

Losing http.Flusher. A wrapped ResponseWriter that does not implement Flush stops the handler's own flushes from reaching the socket, and Go's chunked writer will hold up to 4 KB before it sends anything. This wrapper forwards Flush; http.ResponseController is the standard way to find it through however many layers of wrapping there are.

Writing the header too late. Content-Length has to be deleted before WriteHeader, because after it the header map has already gone out. The wrapper does it in its own WriteHeader, which is also where it decides whether this response is HTML at all - the handler sets its Content-Type before writing, so that is the first moment the decision can be made.

Closing

The rewriter has to be closed after the handler returns and before the middleware does, or the tail of the document never reaches the client. That is one deferred call, and it is the one thing in here that has no equivalent in an ordinary middleware: an io.Writer chain does not usually need finishing.

A rewrite that fails has already sent a prefix, headers included, so there is no 500 to send - see the package documentation on stopping early. The middleware logs it and closes the response.

What it does not do is carry on. The obvious recovery - stop rewriting, pass the handler's remaining bytes straight through - produces a worse response than sending nothing more: the rewriter had already consumed part of the failing chunk and was holding an unfinished token, so the raw suffix does not resume where the rewritten prefix stopped. The client gets a rewritten prefix, a hole, and then raw markup spliced on at whatever point the failure fell - which can be mid-tag or mid-attribute. Everything sent before the failure is a whole number of tokens (lolhtml.Writer.Write says so), so stopping leaves a short page a parser accepts. A middleware that cannot accept a truncated page has to buffer and forward only on success, which is what the buffering half of this file does.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL