Skip to content

maczikasz/httpx-zstd-bug

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

httpx ZSTD Decoder Bug Reproduction

This repository contains a minimal reproduction of a bug in httpx's ZStandardDecoder.

The Bug

When an HTTP server sends a response with:

  • Content-Encoding: zstd
  • Transfer-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

Root Cause

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.

The Fix

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))
    ...

Is This Valid HTTP?

Yes. The HTTP spec allows:

  1. Compressing the entire response body as one continuous stream
  2. Compressing each chunk independently (what this server does)

Both are valid, but httpx only handles case #1 for zstd.

Running the Reproduction

# Install dependencies
pip install httpx zstandard

# Run the single-file reproduction
python repro.py

Expected 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.

Files

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

Environment

  • Python 3.14.0
  • httpx 0.28.1
  • zstandard 0.25.0

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors