Compression Middleware¶
Cello provides built-in gzip compression for HTTP responses, implemented in Rust for high throughput. Compression reduces bandwidth usage and improves load times for clients, especially for JSON and HTML responses.
Quick Start¶
from cello import App
app = App()
# Enable with defaults (compress responses > 1024 bytes)
app.enable_compression()
# Custom minimum size
app.enable_compression(min_size=512)
How It Works¶
Handler returns response body
│
â–¼
Is body size >= min_size?
│
Yes │ No
▼ │ ▼
Gzip │ Pass through
compress│ uncompressed
│ │
â–¼ â–¼
Add Content-Encoding: gzip
Send response
The middleware:
- Checks the response body size against
min_size. - Checks the
Accept-Encodingrequest header forgzipsupport. - Compresses the body in Rust using native gzip.
- Sets the
Content-Encoding: gzipresponse header. - Updates the
Content-Lengthheader.
Configuration¶
Default Settings¶
| Setting | Default | Description |
|---|---|---|
min_size | 1024 | Minimum body size in bytes to trigger compression |
Custom Minimum Size¶
# Compress responses larger than 256 bytes
app.enable_compression(min_size=256)
# Compress responses larger than 4KB
app.enable_compression(min_size=4096)
Choosing min_size
Small responses (under 1KB) often do not benefit from compression because the gzip overhead can make the output the same size or larger. The default of 1024 bytes is a good starting point for most APIs.
What Gets Compressed¶
The middleware compresses responses that meet all of these criteria:
- Body size is greater than or equal to
min_size - The client sends
Accept-Encoding: gzipin the request - The response content type is compressible (text, JSON, HTML, XML, etc.)
Compressible Content Types¶
| Content Type | Compressed |
|---|---|
application/json | Yes |
text/html | Yes |
text/plain | Yes |
text/css | Yes |
application/javascript | Yes |
application/xml | Yes |
image/png | No (already compressed) |
image/jpeg | No (already compressed) |
application/gzip | No (already compressed) |
application/octet-stream | No |
Example¶
from cello import App
app = App()
app.enable_compression(min_size=512)
@app.get("/large-data")
def large_data(request):
# This response is ~2KB of JSON -- will be compressed
return {
"items": [{"id": i, "name": f"Item {i}", "description": "A" * 100}
for i in range(10)]
}
@app.get("/small-data")
def small_data(request):
# This response is ~30 bytes -- will NOT be compressed
return {"status": "ok"}
Verifying Compression¶
Check the response headers to confirm compression is active:
# With compression
curl -H "Accept-Encoding: gzip" -v http://localhost:8000/large-data 2>&1 | grep Content-Encoding
# Content-Encoding: gzip
# Without Accept-Encoding header
curl -v http://localhost:8000/large-data 2>&1 | grep Content-Encoding
# (no Content-Encoding header -- response sent uncompressed)
Performance¶
Cello's compression is implemented in Rust using a native gzip encoder:
| Metric | Value |
|---|---|
| Compression speed | ~1us per KB |
| Typical ratio (JSON) | 5:1 to 10:1 |
| Overhead (skip) | ~10ns (size check only) |
When to Skip Compression
If you are serving binary data, pre-compressed files, or images, compression adds overhead with no benefit. These content types are automatically excluded by the middleware.
Interaction with Other Middleware¶
Compression should be one of the last middleware in the chain, so it compresses the final response:
app = App()
app.enable_cors() # 1. CORS headers first
app.enable_logging() # 2. Log before compression
app.enable_compression() # 3. Compress last
Caching and compression work well together -- cached responses store the compressed version:
Next Steps¶
- Middleware Overview - Full middleware system
- Caching - Cache compressed responses
- Logging - Request/response logging