Envoy doesn’t actually decompress responses; it can’t without knowing the original content type.
Here’s a live example. Imagine a backend service that always returns Content-Type: application/json and sometimes compresses it with Brotli (which is becoming more common than gzip for web assets).
# Envoy Configuration Snippet
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 10000 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route:
cluster: some_service_cluster
http_filters:
- name: envoy.filters.http.router
typed_config: {}
- name: envoy.filters.http.compressor # This is the filter we'll configure
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor
compressor_library:
name: envoy.compression.brotli
typed_config:
"@type": type.googleapis.com/envoy.extensions.compression.brotli.v3.Brotli
params:
quality_level: 4 # Brotli quality level (0-11)
lgwin: 22 # Brotli window size
# Crucially, we need to tell it WHICH content types to compress.
# If this is missing, it won't compress anything.
content_type: application/json
# We can also specify minimum content length to avoid compressing small responses.
min_content_length:
value: 1024
clusters:
- name: some_service_cluster
connect_timeout: 0.25s
type: LOGICAL_DNS
# This is a dummy service that returns JSON.
# In a real scenario, this would be your actual backend.
# For testing, you can use httpbin.org/json
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: some_service_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: httpbin.org, port_value: 80 }
Now, let’s send a request to our Envoy listener on port 10000 and see what happens. We’ll use curl to demonstrate.
First, a request without Accept-Encoding that results in a response smaller than min_content_length:
curl -v http://localhost:10000/json
You’ll see a Content-Type: application/json header, but no Content-Encoding header. The response body is the raw JSON.
Now, let’s request a compressed response by including the Accept-Encoding header. We’ll also make sure the response is large enough. httpbin.org/json returns a small JSON, so we’ll use /anything and add a large payload.
curl -v -H "Accept-Encoding: br" http://localhost:10000/anything -d '{"data": "'$(head -c 2000 /dev/urandom | base64)'"}'
Here, curl sends Accept-Encoding: br. Envoy’s compressor filter sees this, checks if the Content-Type is application/json, and if the Content-Length is over 1024 bytes. If all conditions are met, it compresses the response body using Brotli and adds Content-Encoding: br to the response headers. The curl client, seeing Content-Encoding: br, automatically decompresses it.
The key takeaway is that Envoy doesn’t guess. The compressor filter needs to be explicitly told what Content-Types to compress, and the client needs to signal its support via Accept-Encoding.
The compressor filter is a powerful tool for reducing bandwidth and improving latency, but its configuration is sensitive to the Content-Type and Accept-Encoding headers. If you’re seeing responses that should be compressed but aren’t, the first place to check is the content_type field in the Compressor filter configuration.
If you configure the compressor filter with content_type: "text/html" and your backend returns Content-Type: application/json, no compression will occur, even if the client sends Accept-Encoding: br.
The next thing you’ll likely run into is wanting to compress multiple content types. The Compressor filter’s content_type field accepts a comma-separated list.