From 6813456bdfb7a26f3b33c1016dbd41a5baf71662 Mon Sep 17 00:00:00 2001 From: Kashif Khan Date: Fri, 31 Jul 2026 09:33:37 -0500 Subject: [PATCH 1/9] update changelog --- sdk/core/azure-core/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md index 759a911cace5..9453dce37653 100644 --- a/sdk/core/azure-core/CHANGELOG.md +++ b/sdk/core/azure-core/CHANGELOG.md @@ -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` via the `Accept-Encoding` header (unless the caller sets it), preventing services from returning `br` (Brotli) or `zstd` responses that `azure-core` cannot decompress. #47186 + ## 1.41.0 (2026-05-07) ### Features Added From b660d50b16258679daedf5e41ebb1a6a06795082 Mon Sep 17 00:00:00 2001 From: Kashif Khan Date: Fri, 31 Jul 2026 09:34:46 -0500 Subject: [PATCH 2/9] explicitly support gzip and deflate; move body helper method here --- .../azure/core/pipeline/transport/_aiohttp.py | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/sdk/core/azure-core/azure/core/pipeline/transport/_aiohttp.py b/sdk/core/azure-core/azure/core/pipeline/transport/_aiohttp.py index 8af2829b7a86..5639243465c5 100644 --- a/sdk/core/azure-core/azure/core/pipeline/transport/_aiohttp.py +++ b/sdk/core/azure-core/azure/core/pipeline/transport/_aiohttp.py @@ -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, @@ -80,6 +77,7 @@ # Matching requests, because why not? CONTENT_CHUNK_SIZE = 10 * 1024 +_SUPPORTED_ACCEPT_ENCODING = "gzip, deflate" _LOGGER = logging.getLogger(__name__) try: @@ -344,6 +342,7 @@ async def send( # and that break services like storage signature if not request.data and not request.files: config["skip_auto_headers"] = ["Content-Type"] + request.headers.setdefault("Accept-Encoding", _SUPPORTED_ACCEPT_ENCODING) try: stream_response = stream timeout = config.pop("connection_timeout", self.connection_config.timeout) @@ -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"): + 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. From e3366c354a7f56dcf4d2c3685b14600058735c52 Mon Sep 17 00:00:00 2001 From: Kashif Khan Date: Fri, 31 Jul 2026 09:35:21 -0500 Subject: [PATCH 3/9] update import --- sdk/core/azure-core/azure/core/rest/_aiohttp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/core/azure-core/azure/core/rest/_aiohttp.py b/sdk/core/azure-core/azure/core/rest/_aiohttp.py index 9ffb173c74d2..4b951a91383b 100644 --- a/sdk/core/azure-core/azure/core/rest/_aiohttp.py +++ b/sdk/core/azure-core/azure/core/rest/_aiohttp.py @@ -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, From d0bd54f5aa84c0c6028e0da5538771a77faa2495 Mon Sep 17 00:00:00 2001 From: Kashif Khan Date: Fri, 31 Jul 2026 09:35:56 -0500 Subject: [PATCH 4/9] remove aiohttp specific helper from here --- .../utils/_pipeline_transport_rest_shared.py | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/sdk/core/azure-core/azure/core/utils/_pipeline_transport_rest_shared.py b/sdk/core/azure-core/azure/core/utils/_pipeline_transport_rest_shared.py index fb46f0f9949c..4eb6fd285bdd 100644 --- a/sdk/core/azure-core/azure/core/utils/_pipeline_transport_rest_shared.py +++ b/sdk/core/azure-core/azure/core/utils/_pipeline_transport_rest_shared.py @@ -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, @@ -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 From 05067cff78e1e743f685514563014efbabde6511 Mon Sep 17 00:00:00 2001 From: Kashif Khan Date: Fri, 31 Jul 2026 09:36:17 -0500 Subject: [PATCH 5/9] update tests --- .../async_tests/test_universal_http_async.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/sdk/core/azure-core/tests/async_tests/test_universal_http_async.py b/sdk/core/azure-core/tests/async_tests/test_universal_http_async.py index 93dd0b9c0ecc..2b9df138a001 100644 --- a/sdk/core/azure-core/tests/async_tests/test_universal_http_async.py +++ b/sdk/core/azure-core/tests/async_tests/test_universal_http_async.py @@ -51,13 +51,29 @@ 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 From 79b182e1247c47955dc6e47ecff74fc52ecc0182 Mon Sep 17 00:00:00 2001 From: Kashif Khan Date: Fri, 31 Jul 2026 09:36:44 -0500 Subject: [PATCH 6/9] update changelog --- sdk/core/azure-core/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md index 9453dce37653..5a4d2a135ade 100644 --- a/sdk/core/azure-core/CHANGELOG.md +++ b/sdk/core/azure-core/CHANGELOG.md @@ -8,7 +8,7 @@ ### Bugs Fixed -- `AioHttpTransport` now explicitly requests only `gzip` and `deflate` via the `Accept-Encoding` header (unless the caller sets it), preventing services from returning `br` (Brotli) or `zstd` responses that `azure-core` cannot decompress. #47186 +- `AioHttpTransport` now explicitly requests only `gzip` and `deflate` as the supported encodings via the `Accept-Encoding` header. #47186 ## 1.41.0 (2026-05-07) From b39b2b6ec368a488f8fc2314fb65884732cb1123 Mon Sep 17 00:00:00 2001 From: Kashif Khan Date: Fri, 31 Jul 2026 10:00:36 -0500 Subject: [PATCH 7/9] remove space in changelog --- sdk/core/azure-core/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md index 5a4d2a135ade..18c85a87789e 100644 --- a/sdk/core/azure-core/CHANGELOG.md +++ b/sdk/core/azure-core/CHANGELOG.md @@ -8,7 +8,7 @@ ### Bugs Fixed -- `AioHttpTransport` now explicitly requests only `gzip` and `deflate` as the supported encodings via the `Accept-Encoding` header. #47186 +- `AioHttpTransport` now explicitly requests only `gzip` and `deflate` as the supported encodings via the `Accept-Encoding` header. #47186 ## 1.41.0 (2026-05-07) From c0dfaf1cc498af99df2fe7608b32a53c93df67e4 Mon Sep 17 00:00:00 2001 From: Kashif Khan Date: Mon, 3 Aug 2026 09:34:39 -0500 Subject: [PATCH 8/9] fix mypy error by casting --- sdk/core/azure-core/azure/core/rest/_aiohttp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/core/azure-core/azure/core/rest/_aiohttp.py b/sdk/core/azure-core/azure/core/rest/_aiohttp.py index 4b951a91383b..8d6bed45543b 100644 --- a/sdk/core/azure-core/azure/core/rest/_aiohttp.py +++ b/sdk/core/azure-core/azure/core/rest/_aiohttp.py @@ -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.""" From 2bdb1e5221cdbf77b072850505ca8778ef7e49c8 Mon Sep 17 00:00:00 2001 From: Kashif Khan Date: Mon, 3 Aug 2026 10:45:22 -0500 Subject: [PATCH 9/9] ensure user provided overrides are honored --- .../azure/core/pipeline/transport/_aiohttp.py | 2 +- .../async_tests/test_universal_http_async.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/sdk/core/azure-core/azure/core/pipeline/transport/_aiohttp.py b/sdk/core/azure-core/azure/core/pipeline/transport/_aiohttp.py index 5639243465c5..c21e4c8a5ecd 100644 --- a/sdk/core/azure-core/azure/core/pipeline/transport/_aiohttp.py +++ b/sdk/core/azure-core/azure/core/pipeline/transport/_aiohttp.py @@ -188,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 @@ -342,7 +343,6 @@ async def send( # and that break services like storage signature if not request.data and not request.files: config["skip_auto_headers"] = ["Content-Type"] - request.headers.setdefault("Accept-Encoding", _SUPPORTED_ACCEPT_ENCODING) try: stream_response = stream timeout = config.pop("connection_timeout", self.connection_config.timeout) diff --git a/sdk/core/azure-core/tests/async_tests/test_universal_http_async.py b/sdk/core/azure-core/tests/async_tests/test_universal_http_async.py index 2b9df138a001..de359a135e95 100644 --- a/sdk/core/azure-core/tests/async_tests/test_universal_http_async.py +++ b/sdk/core/azure-core/tests/async_tests/test_universal_http_async.py @@ -76,6 +76,21 @@ async def test_aiohttp_preserves_accept_encoding_header(port, http_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 @pytest.mark.parametrize("http_request", HTTP_REQUESTS) async def test_basic_async_requests(port, http_request):