Compress our own responses #288

Open
opened 2026-09-06 06:29:17 +00:00 by jcoffey-dev · 2 comments
Owner

Follow-on to #287, which fixed the nginx example. This makes the default right
regardless of what sits in front.

The bundle went out uncompressed unless a proxy compressed it. Measured against
the built image, no proxy involved:

Wire Decoded
before 932,696 B 932,696 B
after 311,204 B 932,696 B

The main script alone goes 662 KB -> 209 KB.

What is deliberately not compressed

The proxy routes/api/blob, /api/image, /api/ics, /api/upload.
They forward upstream bytes under a content-length copied from Stalwart, and
#76 was a silent download truncation caused by exactly that header disagreeing
with its body. Re-encoding them would be safe in principle (the length is
dropped, the response goes out chunked) but they carry attachments and images
that are already compressed — nothing to win, and a scar to respect.

/api/events — Hono already skips text/event-stream by content type. It
is in the list anyway so that changing the push route's content type cannot
quietly start buffering the stream.

/api/health — for the opposite reason: at 47 bytes, gzip made it 73.
Hono's size threshold only applies when a response carries a content-length,
and c.json() does not set one. Every other JSON route stays compressed — a
JMAP response has just as unknown a length and can run to hundreds of KB.

Verified against the built image

  • assets return Content-Encoding: gzip with Vary: Accept-Encoding
  • /api/events returns text/event-stream, no Content-Encoding, and
    delivered a real StateChange event while messages were being written to the
    account
  • /api/jmap responses compress
  • health is 47 bytes with or without Accept-Encoding
  • a gzipped shell decodes byte-for-byte to the uncompressed one

Six tests added. A proxy that already compresses still wins: Hono declines
anything carrying Content-Encoding or Transfer-Encoding, so there is no
double-encoding.

No user-visible strings, so no work in any of the nine catalogues.

Merged 2026-09-05 as coffey-labs/ihasmail@5356e603fe

Rebuilt from: GH Archive, git history, session transcript.

Follow-on to #287, which fixed the nginx example. This makes the default right regardless of what sits in front. The bundle went out uncompressed unless a proxy compressed it. Measured against the built image, no proxy involved: | | Wire | Decoded | | --- | --- | --- | | before | 932,696 B | 932,696 B | | after | **311,204 B** | 932,696 B | The main script alone goes 662 KB -> 209 KB. ### What is deliberately *not* compressed **The proxy routes** — `/api/blob`, `/api/image`, `/api/ics`, `/api/upload`. They forward upstream bytes under a content-length copied from Stalwart, and #76 was a silent download truncation caused by exactly that header disagreeing with its body. Re-encoding them would be safe in principle (the length is dropped, the response goes out chunked) but they carry attachments and images that are already compressed — nothing to win, and a scar to respect. **`/api/events`** — Hono already skips `text/event-stream` by content type. It is in the list anyway so that changing the push route's content type cannot quietly start buffering the stream. **`/api/health`** — for the opposite reason: at 47 bytes, gzip made it 73. Hono's size threshold only applies when a response carries a content-length, and `c.json()` does not set one. Every other JSON route stays compressed — a JMAP response has just as unknown a length and can run to hundreds of KB. ### Verified against the built image - assets return `Content-Encoding: gzip` with `Vary: Accept-Encoding` - `/api/events` returns `text/event-stream`, no `Content-Encoding`, and delivered a real `StateChange` event while messages were being written to the account - `/api/jmap` responses compress - health is 47 bytes with or without `Accept-Encoding` - a gzipped shell decodes byte-for-byte to the uncompressed one Six tests added. A proxy that already compresses still wins: Hono declines anything carrying `Content-Encoding` or `Transfer-Encoding`, so there is no double-encoding. No user-visible strings, so no work in any of the nine catalogues. **Merged** 2026-09-05 as coffey-labs/ihasmail@5356e603fe3a <sub>Rebuilt from: GH Archive, git history, session transcript.</sub>
Author
Owner

Rebased onto main now that #287 has merged, and folded in the comment reword that belongs with this change.

Verified the two layers together. With this branch's app-side compression running behind the nginx config from #287, an asset comes back single-encoded — 209,355 bytes on the wire, decoding once to 662,021. nginx passes through what the upstream already encoded rather than re-compressing, so there is no double-encoding and neither layer has to be removed for the other to be correct.

The nginx comment no longer claims the bundle ships uncompressed, since this PR makes that false. It now describes what the directives do without asserting which side does the work.

148 tests pass.

Rebased onto main now that #287 has merged, and folded in the comment reword that belongs with this change. **Verified the two layers together.** With this branch's app-side compression running behind the nginx config from #287, an asset comes back **single-encoded** — 209,355 bytes on the wire, decoding once to 662,021. nginx passes through what the upstream already encoded rather than re-compressing, so there is no double-encoding and neither layer has to be removed for the other to be correct. The nginx comment no longer claims the bundle ships uncompressed, since this PR makes that false. It now describes what the directives do without asserting which side does the work. 148 tests pass.
Author
Owner

Rebased onto main now that #287 has merged, and folded in the comment reword that belongs with this change.

Verified the two layers together. With this branch's app-side compression running behind the nginx config from #287, an asset comes back single-encoded — 209,355 bytes on the wire, decoding once to 662,021. nginx passes through what the upstream already encoded rather than re-compressing, so there is no double-encoding and neither layer has to be removed for the other to be correct.

The nginx comment no longer claims the bundle ships uncompressed, since this PR makes that false. It now describes what the directives do without asserting which side does the work.

148 tests pass.

Rebased onto main now that #287 has merged, and folded in the comment reword that belongs with this change. **Verified the two layers together.** With this branch's app-side compression running behind the nginx config from #287, an asset comes back **single-encoded** — 209,355 bytes on the wire, decoding once to 662,021. nginx passes through what the upstream already encoded rather than re-compressing, so there is no double-encoding and neither layer has to be removed for the other to be correct. The nginx comment no longer claims the bundle ships uncompressed, since this PR makes that false. It now describes what the directives do without asserting which side does the work. 148 tests pass.
This repo is archived. You cannot comment on issues.