Compression, content negotiation and media types
Accept and Accept-Encoding with quality values, gzip, Brotli and zstd, charset handling, multipart uploads, and byte-range requests.
Negotiating the representation
GET /report HTTP/1.1
Host: api.example.com
Accept: text/html, application/json;q=0.9, */*;q=0.1
Accept-Language: en-GB, en;q=0.8, fr;q=0.5
Accept-Charset: utf-8
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Language: en
Vary: Accept, Accept-Language- Quality values run from
0to1and express relative preference, not an absolute ordering:text/htmlwith noqmeans 1.0. */*is the catch-all catchphrase of a client that does not care; it must rank lowest or a specific type is never chosen.- The server picks one representation and must say which with
Content-Type. A406 Not Acceptableis legal but rarely useful — return something sensible instead. Varytells caches which request headers produced this response. Omit it and a cached French page is served to an English client.
Compression in practice
GET /app.js HTTP/1.1
Accept-Encoding: br, gzip, zstd;q=0.9, identity;q=0.1
HTTP/1.1 200 OK
Content-Type: application/javascript; charset=utf-8
Content-Encoding: br
Vary: Accept-Encoding
Content-Length: 41230| Encoding | Ratio on text | Cost | Notes |
|---|---|---|---|
gzip | Good | Low | Universal support; the safe default |
br | Better | Higher CPU on slow dynamic content | Browser support is broad; pre-compress static assets |
zstd | Comparable to Brotli | Fast | Needs explicit client support; good for APIs and internal traffic |
identity | None | None | Always acceptable unless you forbid it |
# pre-compress static assets at build time, then serve by name
brotli -q 11 -k app.js # produces app.js.br
gzip -9 -k app.js # produces app.js.gz
# verify what a server actually sent
curl -sS -o /dev/null -H 'Accept-Encoding: br,gzip' -w '%{size_download} %{content_type}\n' https://example.com/app.js
curl -sSI -H 'Accept-Encoding: br,gzip' https://example.com/app.js | grep -iE 'content-encoding|vary'- Never compress what is already compressed: images, video, and archives gain a few bytes at best and burn CPU.
- Compression is a confidentiality problem too: compressing a response that mixes a secret with attacker-controlled input enables BREACH-style attacks. Disable compression on endpoints that reflect tokens.
- Decompressing is cheap for the client but not free — very small bodies (under about a kilobyte) are often better sent uncompressed.
- A proxy that strips
Accept-Encodingmust also stripContent-Encoding, or the client receives bytes it cannot decode.
Charsets, multipart and ranges
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----XyZ123
Content-Length: 481
------XyZ123
Content-Disposition: form-data; name="title"
Q3 report
------XyZ123
Content-Disposition: form-data; name="file"; filename="q3.png"
Content-Type: image/png
<binary bytes>
------XyZ123--GET /video.mp4 HTTP/1.1
Range: bytes=0-1048575
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Range: bytes 0-1048575/10485760
Content-Length: 1048576| Situation | Response | Why |
|---|---|---|
| Range satisfied | 206 with Content-Range | Video seeking and resumable downloads |
| Range outside the file | 416 with Content-Range: bytes */size | Tells the client the real size |
| Range header ignored | 200 with the whole body | Legal — servers may ignore ranges |
| Conditional range | 206 only if If-Range matches | Stops a resumed download appending bytes from a different version |
⚠️
Forgetting
Vary: Accept-Encoding is the classic compression bug: a CDN caches the Brotli version and then serves it to a client that never asked for it, producing a corrupted page with no obvious error. Send it on every response whose encoding depends on the request.FAQ
Why is my JSON still uncompressed?
Either the client did not send
Accept-Encoding, the server's compression middleware excludes that content type, or the body is below the minimum size threshold. Check the response headers before changing code.Does <code>charset</code> matter in <code>Content-Type</code>?
Yes for text media types. Without it, a browser or library may fall back to a locale default and mangle non-ASCII characters. Send
charset=utf-8 and be consistent end to end.Related
Message anatomy and framing Proxies, load balancers and connection management
Last refreshed 2026-09-18.