Skip to content
Open
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
4 changes: 4 additions & 0 deletions sdk/core/azure-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- Added `azure-deprecating` to the default allowed headers list in `HttpLoggingPolicy`, so deprecation notification headers are logged without redaction.

### Bugs Fixed

- `AioHttpTransport` now explicitly requests only `gzip` and `deflate` as the supported encodings via the `Accept-Encoding` header. #47186

## 1.41.0 (2026-05-07)

### Features Added
Expand Down
43 changes: 39 additions & 4 deletions sdk/core/azure-core/azure/core/pipeline/transport/_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@

from ._base import HttpRequest
from ._base_async import AsyncHttpTransport, AsyncHttpResponse, _ResponseStopIteration
from ...utils._pipeline_transport_rest_shared import (
_aiohttp_body_helper,
get_file_items,
)
from ...utils._pipeline_transport_rest_shared import get_file_items
from .._tools import is_rest as _is_rest
from .._tools_async import (
handle_no_stream_rest_response as _handle_no_stream_rest_response,
Expand All @@ -80,6 +77,7 @@

# Matching requests, because why not?
CONTENT_CHUNK_SIZE = 10 * 1024
_SUPPORTED_ACCEPT_ENCODING = "gzip, deflate"
_LOGGER = logging.getLogger(__name__)

try:
Expand Down Expand Up @@ -190,6 +188,7 @@ async def open(self):
"trust_env": self._use_env_settings,
"cookie_jar": jar,
"auto_decompress": False,
"headers": {"Accept-Encoding": _SUPPORTED_ACCEPT_ENCODING},
}
if self._loop is not None:
clientsession_kwargs["loop"] = self._loop
Expand Down Expand Up @@ -400,6 +399,42 @@ async def send(
return response


def _aiohttp_body_helper(
response: Union["AioHttpTransportResponse", "RestAioHttpTransportResponse"],
) -> bytes:
# pylint: disable=protected-access
"""Helper for body method of Aiohttp responses.

Since aiohttp body methods need decompression work synchronously,
need to share this code across old and new aiohttp transport responses
for backcompat.

:param response: The response to decode
:type response: ~azure.core.pipeline.transport.AioHttpTransportResponse
:rtype: bytes
:return: The response's bytes
"""
if response._content is None:
raise ValueError("Body is not available. Call async method load_body, or do your call with stream=False.")
if not response._decompress:
return response._content
if response._decompressed_content:
return response._content
enc = response.headers.get("Content-Encoding")
if not enc:
return response._content
enc = enc.lower()
if enc in ("gzip", "deflate"):
Comment thread
l0lawrence marked this conversation as resolved.
import zlib

zlib_mode = (16 + zlib.MAX_WBITS) if enc == "gzip" else -zlib.MAX_WBITS
decompressor = zlib.decompressobj(wbits=zlib_mode)
response._content = decompressor.decompress(response._content)
response._decompressed_content = True
return response._content
return response._content


class AioHttpStreamDownloadGenerator(AsyncIterator):
"""Streams the response body data.

Expand Down
6 changes: 3 additions & 3 deletions sdk/core/azure-core/azure/core/rest/_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
AsyncHttpResponseImpl,
AsyncHttpResponseBackcompatMixin,
)
from ..pipeline.transport._aiohttp import AioHttpStreamDownloadGenerator
from ..utils._pipeline_transport_rest_shared import _pad_attr_name, _aiohttp_body_helper
from ..pipeline.transport._aiohttp import AioHttpStreamDownloadGenerator, _aiohttp_body_helper
from ..utils._pipeline_transport_rest_shared import _pad_attr_name
from ..exceptions import (
ResponseNotReadError,
IncompleteReadError,
Expand Down Expand Up @@ -167,7 +167,7 @@ def body(self) -> bytes:
:return: The response's bytes
:rtype: bytes
"""
return _aiohttp_body_helper(self)
return _aiohttp_body_helper(cast("RestAioHttpTransportResponse", self))

async def _load_body(self) -> None:
"""Load in memory the body, so it could be accessible from sync methods."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
from ..pipeline.policies import SansIOHTTPPolicy
from azure.core.pipeline.transport import ( # pylint: disable=non-abstract-transport-import
HttpResponse as PipelineTransportHttpResponse,
AioHttpTransportResponse as PipelineTransportAioHttpTransportResponse,
)
from azure.core.pipeline.transport._base import (
_HttpResponseBase as PipelineTransportHttpResponseBase,
Expand Down Expand Up @@ -378,42 +377,6 @@ def _format_data_helper(
return (filename, cast(str, file_bytes))


def _aiohttp_body_helper(
response: "PipelineTransportAioHttpTransportResponse",
) -> bytes:
# pylint: disable=protected-access
"""Helper for body method of Aiohttp responses.

Since aiohttp body methods need decompression work synchronously,
need to share this code across old and new aiohttp transport responses
for backcompat.

:param response: The response to decode
:type response: ~azure.core.pipeline.transport.AioHttpTransportResponse
:rtype: bytes
:return: The response's bytes
"""
if response._content is None:
raise ValueError("Body is not available. Call async method load_body, or do your call with stream=False.")
if not response._decompress:
return response._content
if response._decompressed_content:
return response._content
enc = response.headers.get("Content-Encoding")
if not enc:
return response._content
enc = enc.lower()
if enc in ("gzip", "deflate"):
import zlib

zlib_mode = (16 + zlib.MAX_WBITS) if enc == "gzip" else -zlib.MAX_WBITS
decompressor = zlib.decompressobj(wbits=zlib_mode)
response._content = decompressor.decompress(response._content)
response._decompressed_content = True
return response._content
return response._content


def get_file_items(files: "FilesType") -> Sequence[Tuple[str, "FileType"]]:
if isinstance(files, Mapping):
# casting because ItemsView technically isn't a Sequence, even
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,44 @@ async def test_basic_aiohttp(port, http_request):

@pytest.mark.asyncio
@pytest.mark.parametrize("http_request", HTTP_REQUESTS)
async def test_aiohttp_auto_headers(port, http_request):
async def test_aiohttp_auto_headers(port, http_request, monkeypatch):

monkeypatch.setitem(aiohttp.ClientRequest.DEFAULT_HEADERS, "Accept-Encoding", "gzip, deflate, br")
request = http_request("POST", "http://localhost:{}/basic/string".format(port))
async with AioHttpTransport() as sender:
response = await sender.send(request)
auto_headers = response.internal_response.request_info.headers
assert "Content-Type" not in auto_headers
assert auto_headers["Accept-Encoding"] == "gzip, deflate"


@pytest.mark.asyncio
@pytest.mark.parametrize("http_request", HTTP_REQUESTS)
async def test_aiohttp_preserves_accept_encoding_header(port, http_request):

request = http_request(
"GET",
"http://localhost:{}/basic/string".format(port),
headers={"Accept-Encoding": "identity"},
)
async with AioHttpTransport() as sender:
response = await sender.send(request)
assert response.internal_response.request_info.headers["Accept-Encoding"] == "identity"


@pytest.mark.asyncio
@pytest.mark.parametrize("http_request", HTTP_REQUESTS)
async def test_aiohttp_preserves_injected_session_accept_encoding(port, http_request):
# A user-provided session may configure its own default Accept-Encoding (for example
# "identity", or an encoding handled by a custom decompressor). The transport must not
# override that default with its own supported-encodings value.
session = aiohttp.ClientSession(headers={"Accept-Encoding": "identity"}, auto_decompress=False)
request = http_request("GET", "http://localhost:{}/basic/string".format(port))
transport = AioHttpTransport(session=session, session_owner=False)
async with transport:
response = await transport.send(request)
assert response.internal_response.request_info.headers["Accept-Encoding"] == "identity"
await session.close()


@pytest.mark.asyncio
Expand Down
Loading