Skip to content

Commit

Permalink
Avoid compressing empty body (#9108) (#9110)
Browse files Browse the repository at this point in the history
(cherry picked from commit 1d11241)
(cherry picked from commit b90dd1e)
  • Loading branch information
Dreamsorcerer authored and patchback[bot] committed Sep 10, 2024
1 parent 4b2ac73 commit 1f1bb8a
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGES/9108.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed compressed requests failing when no body was provided -- by :user:`Dreamsorcerer`.
4 changes: 2 additions & 2 deletions aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ class _RequestOptions(TypedDict, total=False):
auth: Union[BasicAuth, None]
allow_redirects: bool
max_redirects: int
compress: Union[str, None]
compress: Union[str, bool, None]
chunked: Union[bool, None]
expect100: bool
raise_for_status: Union[None, bool, Callable[[ClientResponse], Awaitable[None]]]
Expand Down Expand Up @@ -459,7 +459,7 @@ async def _request(
auth: Optional[BasicAuth] = None,
allow_redirects: bool = True,
max_redirects: int = 10,
compress: Optional[str] = None,
compress: Union[str, bool, None] = None,
chunked: Optional[bool] = None,
expect100: bool = False,
raise_for_status: Union[
Expand Down
8 changes: 5 additions & 3 deletions aiohttp/client_reqrep.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def __init__(
cookies: Optional[LooseCookies] = None,
auth: Optional[BasicAuth] = None,
version: http.HttpVersion = http.HttpVersion11,
compress: Optional[str] = None,
compress: Union[str, bool, None] = None,
chunked: Optional[bool] = None,
expect100: bool = False,
loop: Optional[asyncio.AbstractEventLoop] = None,
Expand Down Expand Up @@ -503,7 +503,9 @@ def update_cookies(self, cookies: Optional[LooseCookies]) -> None:

def update_content_encoding(self, data: Any) -> None:
"""Set request content encoding."""
if data is None:
if not data:
# Don't compress an empty body.
self.compress = None
return

enc = self.headers.get(hdrs.CONTENT_ENCODING, "").lower()
Expand Down Expand Up @@ -714,7 +716,7 @@ async def send(self, conn: "Connection") -> "ClientResponse":
)

if self.compress:
writer.enable_compression(self.compress)
writer.enable_compression(self.compress) # type: ignore[arg-type]

if self.chunked is not None:
writer.enable_chunking()
Expand Down
45 changes: 42 additions & 3 deletions tests/test_client_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import tarfile
import time
import zipfile
from typing import Any, AsyncIterator, Type
from typing import Any, AsyncIterator, Optional, Type
from unittest import mock

import pytest
Expand All @@ -31,6 +31,9 @@
SocketTimeoutError,
TooManyRedirects,
)
from aiohttp.client_reqrep import ClientRequest
from aiohttp.connector import Connection
from aiohttp.http_writer import StreamWriter
from aiohttp.pytest_plugin import AiohttpClient, AiohttpServer, TestClient
from aiohttp.test_utils import unused_port

Expand Down Expand Up @@ -1510,8 +1513,44 @@ async def handler(request):
assert 200 == resp.status


async def test_POST_DATA_DEFLATE(aiohttp_client) -> None:
async def handler(request):
@pytest.mark.parametrize("data", (None, b""))
async def test_GET_DEFLATE(
aiohttp_client: AiohttpClient, data: Optional[bytes]
) -> None:
async def handler(request: web.Request) -> web.Response:
return web.json_response({"ok": True})

write_mock = None
original_write_bytes = ClientRequest.write_bytes

async def write_bytes(
self: ClientRequest, writer: StreamWriter, conn: Connection
) -> None:
nonlocal write_mock
original_write = writer._write

with mock.patch.object(
writer, "_write", autospec=True, spec_set=True, side_effect=original_write
) as write_mock:
await original_write_bytes(self, writer, conn)

with mock.patch.object(ClientRequest, "write_bytes", write_bytes):
app = web.Application()
app.router.add_get("/", handler)
client = await aiohttp_client(app)

async with client.get("/", data=data, compress=True) as resp:
assert resp.status == 200
content = await resp.json()
assert content == {"ok": True}

assert write_mock is not None
# No chunks should have been sent for an empty body.
write_mock.assert_not_called()


async def test_POST_DATA_DEFLATE(aiohttp_client: AiohttpClient) -> None:
async def handler(request: web.Request) -> web.Response:
data = await request.post()
return web.json_response(dict(data))

Expand Down

0 comments on commit 1f1bb8a

Please sign in to comment.