Compression, caching & headers¶
servery applies the cross-cutting web behaviors you'd expect from a real static server — most on by default, each with a flag.
On-the-fly compression (zstd / gzip)¶
Text-like responses (HTML/CSS/JS/JSON/SVG/XML — and the directory listing itself)
are compressed when the client accepts a coding. On by default; disable with
--no-compress.
servery prefers zstd — better ratio, much faster decode — when the
interpreter provides it (Python 3.14+, via the stdlib compression.zstd module,
PEP 784) and the client sends Accept-Encoding: zstd. Otherwise it uses gzip.
On Python 3.13 (no stdlib zstd) only gzip is offered — zstd is advertised only when
it can actually be produced, so a client never sees a coding the server can't make.
It's RFC 9110-correct:
- q-value-aware negotiation;
br(brotli) anddeflateare intentionally not used (brotli needs a third-party dependency; deflate is ambiguous). Vary: Accept-Encodingon every compressible response, and a distinct ETag per coding (-gz/-zstsuffix) so caches never mix representations.- Mutually exclusive with
Range: a range request is served identity, since a byte range over coded bytes is incoherent. - Already-compressed media (jpeg/png/mp4/zip/woff2/…) is never touched — preserving
the zero-copy
sendfilefast path.
A typical directory listing compresses ~18×. Compression is applied across HTTP/1.1, HTTP/2, and HTTP/3.
Integrity digests (RFC 9530)¶
A client that wants to verify a download can send Want-Repr-Digest; servery answers
with a Repr-Digest over the whole representation (the file on disk):
curl -sD- -o out.bin -H 'Want-Repr-Digest: sha-256' http://localhost:8000/big.bin
# ... Repr-Digest: sha-256=:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=:
This is the standardized (RFC 9530) replacement for an out-of-band .sha256 sidecar.
It's emitted only when asked, on identity responses — including 206 range
responses, where the digest still covers the full file, so a download reassembled
from several parallel range requests can be verified end-to-end. sha-256 and
sha-512 are offered (the client's Want-Repr-Digest preference picks). Because the
digest requires reading the whole file, it costs nothing on the default download path
(no header, no hashing).
Caching¶
By default file responses are Cache-Control: no-cache (revalidate every time,
using the strong ETag). --cache SECONDS sets an explicit max-age for serving
static assets that don't change often. Conditional requests
(If-None-Match/If-Modified-Since → 304) work either way.
CORS¶
Sends permissive CORS headers (Access-Control-Allow-Origin: *) and answers
preflight OPTIONS — handy when a separate front-end origin needs to fetch these
files.
Security headers¶
By default servery sends X-Content-Type-Options: nosniff on everything, a scoped
Content-Security-Policy + Referrer-Policy on its own generated pages (the
listing and error pages, never your files), and HSTS over TLS. Turn the defaults off
with --no-security-headers.
Access logging to a file¶
servery --access-log access.log
servery --access-log access.log --access-log-format combined
servery --access-log access.log --access-log-format json
Writes one line per response to a file, separate from the stderr request log:
| Format | Looks like |
|---|---|
clf (default) |
Common Log Format — 127.0.0.1 - - [date] "GET /f HTTP/1.1" 200 42 |
combined |
CLF + "referer" "user-agent" |
json |
one JSON object per line (method, path, status, size, …) |
It's thread-safe and records the real status and response size.
See also¶
- Serving files — listings, downloads, archives, SPA.
- HTTP/2 & HTTP/3 — modern transports + tuning concurrency.