Implement format=auto (Accept-header content negotiation) #88

Closed
opened 2026-08-09 03:50:49 +02:00 by clawbot · 2 comments
Collaborator

Verified against main at 61f42e6. This is the "auto format selection (format=auto based on Accept header)" item in TODO.md; confirmed missing — parseFormat (internal/imgcache/urlparser.go:241-258) has no auto case, and the client's Accept header is never read anywhere.

This is the feature that makes a proxy like pixa worth deploying: serve WebP or AVIF to clients that accept them, JPEG to those that do not, from a single URL. Without it, callers must know each client's capabilities at URL-construction time, which is exactly what they were trying to avoid.

Definition of done

  1. format=auto selects the best format the client accepts, preferring AVIF, then WebP, then falling back to the source format (or JPEG). The preference order is documented.
  2. Accept parsing handles quality values (q=) and wildcards (image/*, */*) correctly. A client sending no Accept gets the fallback.
  3. Vary: Accept is set on every negotiated response. Without it a shared cache will serve AVIF to a client that cannot render it — a correctness bug, not an optimization. See the related response-headers issue.
  4. The cache key includes the resolved format, not the literal auto, so negotiated variants do not collide.
  5. Signature interaction is decided explicitly: the signature covers format, so a URL signed for auto must validate against auto (the literal requested value) rather than the resolved one — otherwise the signature depends on the client's headers and becomes unverifiable. Add a test pinning this, and coordinate with the issue that adds q/fit to the signed payload.
  6. Repo rule: format=auto must never silently produce a format the client explicitly rejected. If nothing acceptable can be produced, return an error rather than guessing.
  7. Failing tests first, table-driven over Accept values: AVIF-capable, WebP-capable, neither, wildcard-only, absent, and a malformed header.
  8. README documents auto in the format list. make check green.
Verified against `main` at `61f42e6`. This is the "auto format selection (format=auto based on Accept header)" item in `TODO.md`; confirmed missing — `parseFormat` (`internal/imgcache/urlparser.go:241-258`) has no `auto` case, and the client's `Accept` header is never read anywhere. This is the feature that makes a proxy like pixa worth deploying: serve WebP or AVIF to clients that accept them, JPEG to those that do not, from a single URL. Without it, callers must know each client's capabilities at URL-construction time, which is exactly what they were trying to avoid. ## Definition of done 1. `format=auto` selects the best format the client accepts, preferring AVIF, then WebP, then falling back to the source format (or JPEG). The preference order is documented. 2. `Accept` parsing handles quality values (`q=`) and wildcards (`image/*`, `*/*`) correctly. A client sending no `Accept` gets the fallback. 3. **`Vary: Accept` is set on every negotiated response.** Without it a shared cache will serve AVIF to a client that cannot render it — a correctness bug, not an optimization. See the related response-headers issue. 4. The cache key includes the *resolved* format, not the literal `auto`, so negotiated variants do not collide. 5. Signature interaction is decided explicitly: the signature covers `format`, so a URL signed for `auto` must validate against `auto` (the literal requested value) rather than the resolved one — otherwise the signature depends on the client's headers and becomes unverifiable. Add a test pinning this, and coordinate with the issue that adds `q`/`fit` to the signed payload. 6. Repo rule: `format=auto` must never silently produce a format the client explicitly rejected. If nothing acceptable can be produced, return an error rather than guessing. 7. Failing tests first, table-driven over `Accept` values: AVIF-capable, WebP-capable, neither, wildcard-only, absent, and a malformed header. 8. README documents `auto` in the format list. `make check` green.
clawbot added this to the 1.0.0 milestone 2026-08-09 03:50:49 +02:00
Author
Collaborator

Plan, checked against next at 01823d2. Still missing: parseFormat (internal/imgcache/urlparser.go) has no auto, and no response depends on Accept.

  • auto becomes a format value wherever a format is accepted: the /v1/image/ path suffix (<size>.auto), the format in an encrypted URL's token, and the generator page's format choice.
  • It is resolved per request from Accept: AVIF if acceptable, else WebP, else JPEG. Quality values (q=), q=0 refusals and wildcards (image/*, */*) are honoured; no Accept header means JPEG. If none of the three is acceptable, the answer is 406 with the usual JSON error; pixa never sends a format the client refused. The order is documented in README.md.
  • The signature and the encrypted token cover the literal auto, not the resolved format, so a signed URL stays valid whatever the client sends; a test pins this.
  • The cache key uses the resolved format, so the AVIF, WebP and JPEG variants of one auto URL are cached apart and a cache hit for one never answers another.
  • Every response to an auto URL carries Vary: Accept (also on 304 and HEAD), next to the Vary: Origin the CORS library already sends; responses to fixed-format URLs do not.
  • Tests first, table-driven over Accept: AVIF-capable, WebP-only, neither, wildcard only, absent, q=0 on AVIF, and a malformed header; plus the signature and cache-key tests above and Vary on both routes.

Model: opus-5-5

Plan, checked against `next` at `01823d2`. Still missing: `parseFormat` (`internal/imgcache/urlparser.go`) has no `auto`, and no response depends on `Accept`. - `auto` becomes a format value wherever a format is accepted: the `/v1/image/` path suffix (`<size>.auto`), the format in an encrypted URL's token, and the generator page's format choice. - It is resolved per request from `Accept`: AVIF if acceptable, else WebP, else JPEG. Quality values (`q=`), `q=0` refusals and wildcards (`image/*`, `*/*`) are honoured; no `Accept` header means JPEG. If none of the three is acceptable, the answer is 406 with the usual JSON error; pixa never sends a format the client refused. The order is documented in `README.md`. - The signature and the encrypted token cover the literal `auto`, not the resolved format, so a signed URL stays valid whatever the client sends; a test pins this. - The cache key uses the resolved format, so the AVIF, WebP and JPEG variants of one `auto` URL are cached apart and a cache hit for one never answers another. - Every response to an `auto` URL carries `Vary: Accept` (also on 304 and `HEAD`), next to the `Vary: Origin` the CORS library already sends; responses to fixed-format URLs do not. - Tests first, table-driven over `Accept`: AVIF-capable, WebP-only, neither, wildcard only, absent, `q=0` on AVIF, and a malformed header; plus the signature and cache-key tests above and `Vary` on both routes. Model: opus-5-5
Author
Collaborator

Built in #220. One deviation from the plan above: image/* and */* allow and refuse JPEG but do not make AVIF or WebP acceptable; only naming image/avif or image/webp does, because browsers that cannot show those formats send the wildcards too.

Model: opus-5-5

Built in https://git.eeqj.de/sneak/pixa/pulls/220. One deviation from the plan above: `image/*` and `*/*` allow and refuse JPEG but do not make AVIF or WebP acceptable; only naming `image/avif` or `image/webp` does, because browsers that cannot show those formats send the wildcards too. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/pixa#88