This repository contains a minimal reproduction of a bug in httpx's ZStandardDecoder.
When an HTTP server sends a response with:
Content-Encoding: zstdTransfer-Encoding: chunked
And each HTTP chunk contains a complete, independent zstd frame (rather than a continuous zstd stream), httpx's ZStandardDecoder crashes with:
DecodingError: cannot use a decompressobj multiple times
The bug is in httpx/_decoders.py in the ZStandardDecoder.decode() method:
def decode(self, data: bytes) -> bytes:
output.write(self.decompressor.decompress(data)) # crashes on 2nd call
while self.decompressor.eof and self.decompressor.unused_data:
# This handles multiple frames WITHIN a single chunk,
# but NOT when new chunks arrive with their own complete frames
...The code handles multiple zstd frames within a single chunk (via unused_data), but does not handle the case where multiple HTTP chunks arrive, each containing a complete zstd frame.
When the first chunk is decompressed, self.decompressor.eof becomes True. When the second chunk arrives, the decompressor tries to continue but fails because it's already finished.
Reset the decompressor when the previous frame is complete:
def decode(self, data: bytes) -> bytes:
# Reset decompressor if previous frame was complete
if self.decompressor.eof:
self.decompressor = zstandard.ZstdDecompressor().decompressobj()
output.write(self.decompressor.decompress(data))
...Yes. The HTTP spec allows:
- Compressing the entire response body as one continuous stream
- Compressing each chunk independently (what this server does)
Both are valid, but httpx only handles case #1 for zstd.
# Install dependencies
pip install httpx zstandard
# Run the single-file reproduction
python repro.pyExpected output:
============================================================
HTTPX ZSTD DECODER BUG REPRODUCTION
============================================================
Bug: httpx's ZStandardDecoder crashes when receiving
multiple HTTP chunks, each containing a complete zstd frame.
Testing UNPATCHED httpx ZStandardDecoder...
FAILED: DecodingError: cannot use a decompressobj multiple times
Applied patch to ZStandardDecoder.decode()
Testing PATCHED httpx ZStandardDecoder...
SUCCESS: Received 160 bytes
Content: First chunk of data - this is a complete zstd frameSecond chunk of data - another independent zstd frameThird chunk of data - yet another independent zstd frame
============================================================
RESULTS:
Unpatched httpx: FAIL (BUG!)
Patched httpx: PASS
============================================================
Bug successfully reproduced!
The patch fixes the issue.
| File | Description |
|---|---|
repro.py |
Single-file reproduction (server + client) |
server.py |
Standalone server that sends zstd-chunked responses |
client.py |
Client using unpatched httpx |
patched_client.py |
Client with monkey-patched decoder that fixes the bug |
- Python 3.14.0
- httpx 0.28.1
- zstandard 0.25.0