Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions httpx/_decoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@
brotli = None


BROTLI_INSTALLED = brotli is not None

# Zstandard support is optional
try:
import zstandard
except ImportError: # pragma: no cover
zstandard = None # type: ignore

ZSTANDARD_INSTALLED = zstandard is not None

class ContentDecoder:
def decode(self, data: bytes) -> bytes:
Expand Down Expand Up @@ -387,7 +390,7 @@ def flush(self) -> list[str]:
}


if brotli is None:
if not BROTLI_INSTALLED:
SUPPORTED_DECODERS.pop("br") # pragma: no cover
if zstandard is None:
if not ZSTANDARD_INSTALLED:
SUPPORTED_DECODERS.pop("zstd") # pragma: no cover
22 changes: 18 additions & 4 deletions httpx/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
import re
import typing
import urllib.request
import warnings
from collections.abc import Mapping
from http.cookiejar import Cookie, CookieJar

from ._content import ByteStream, UnattachedStream, encode_request, encode_response
from ._decoders import (
BROTLI_INSTALLED,
SUPPORTED_DECODERS,
ByteChunker,
ContentDecoder,
Expand Down Expand Up @@ -704,13 +706,25 @@ def _get_content_decoder(self) -> ContentDecoder:
if not hasattr(self, "_decoder"):
decoders: list[ContentDecoder] = []
values = self.headers.get_list("content-encoding", split_commas=True)
warned_missing_brotli = False
for value in values:
value = value.strip().lower()
try:
decoder_cls = SUPPORTED_DECODERS[value]
decoders.append(decoder_cls())
except KeyError:
decoder_cls = SUPPORTED_DECODERS.get(value)
if decoder_cls is None:
if (
value == "br"
and not BROTLI_INSTALLED
and not warned_missing_brotli
):
warned_missing_brotli = True
warnings.warn(
"Received 'Content-Encoding: br' but Brotli support is disabled. "
"Install httpx[brotli] to decode this response.",
UserWarning,
stacklevel=3,
)
continue
decoders.append(decoder_cls())

if len(decoders) == 1:
self._decoder = decoders[0]
Expand Down
16 changes: 16 additions & 0 deletions tests/models/test_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,22 @@ def test_value_error_without_request(header_value):
httpx.Response(200, headers=headers, content=broken_compressed_body)


def test_warns_when_brotli_support_missing(monkeypatch):
monkeypatch.setattr(httpx._decoders, "BROTLI_INSTALLED", False, raising=False)
monkeypatch.setattr(httpx._models, "BROTLI_INSTALLED", False, raising=False)
if "br" in httpx._decoders.SUPPORTED_DECODERS:
monkeypatch.delitem(httpx._decoders.SUPPORTED_DECODERS, "br", raising=False)

with pytest.warns(UserWarning, match="Content-Encoding: br"):
response = httpx.Response(
200,
headers={"Content-Encoding": "br"},
content=b"brotli-payload",
)

assert response.content == b"brotli-payload"


def test_response_with_unset_request():
response = httpx.Response(200, content=b"Hello, world!")

Expand Down
Loading