Skip to content
Merged
Next Next commit
No default Content-Type when no content
  • Loading branch information
Dreamsorcerer committed Aug 23, 2024
commit c9c5e2550f7e0fca74910e46b0eba6299ee1adf7
17 changes: 10 additions & 7 deletions aiohttp/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,13 +753,15 @@ def ceil_timeout(
class HeadersMixin:
__slots__ = ("_content_type", "_content_dict", "_stored_content_type")

headers: CIMultiDict[str]

def __init__(self) -> None:
super().__init__()
self._content_type: Optional[str] = None
self._content_dict: Optional[Dict[str, str]] = None
self._stored_content_type: Union[str, _SENTINEL] = sentinel
self._stored_content_type: Union[str, None, _SENTINEL] = sentinel

def _parse_content_type(self, raw: str) -> None:
def _parse_content_type(self, raw: Optional[str]) -> None:
self._stored_content_type = raw
if raw is None:
# default value according to RFC 2616
Expand All @@ -774,23 +776,24 @@ def _parse_content_type(self, raw: str) -> None:
@property
def content_type(self) -> str:
"""The value of content part for Content-Type HTTP header."""
raw = self._headers.get(hdrs.CONTENT_TYPE) # type: ignore[attr-defined]
raw = self._headers.get(hdrs.CONTENT_TYPE)
if self._stored_content_type != raw:
self._parse_content_type(raw)
return self._content_type # type: ignore[return-value]
return self._content_type

@property
def charset(self) -> Optional[str]:
"""The value of charset part for Content-Type HTTP header."""
raw = self._headers.get(hdrs.CONTENT_TYPE) # type: ignore[attr-defined]
raw = self._headers.get(hdrs.CONTENT_TYPE)
if self._stored_content_type != raw:
self._parse_content_type(raw)
return self._content_dict.get("charset") # type: ignore[union-attr]
assert self._content_dict is not None
return self._content_dict.get("charset")

@property
def content_length(self) -> Optional[int]:
"""The value of Content-Length HTTP header."""
content_length = self._headers.get( # type: ignore[attr-defined]
content_length = self._headers.get(
hdrs.CONTENT_LENGTH
)

Expand Down
3 changes: 2 additions & 1 deletion aiohttp/web_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,8 @@ async def _prepare_headers(self) -> None:
# https://datatracker.ietf.org/doc/html/rfc9112#section-6.1-13
if hdrs.TRANSFER_ENCODING in headers:
del headers[hdrs.TRANSFER_ENCODING]
else:
elif self.content_length != 0:
# https://www.rfc-editor.org/rfc/rfc9110#section-8.3-5
headers.setdefault(hdrs.CONTENT_TYPE, "application/octet-stream")
headers.setdefault(hdrs.DATE, rfc822_formatted_time())
headers.setdefault(hdrs.SERVER, SERVER_SOFTWARE)
Expand Down
15 changes: 15 additions & 0 deletions tests/test_web_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,21 @@ async def handler(request):
assert resp.headers["Content-Length"] == "4"


@pytest.mark.parametrize("status", (201, 204, 404))
async def test_default_content_type_no_body(aiohttp_client: Any, status: int) -> None:
async def handler(request):
return web.Response(status=status)

app = web.Application()
app.router.add_get("/", handler)
client = await aiohttp_client(app)

async with client.get("/") as resp:
assert resp.status == status
assert await resp.read() == b""
assert "Content-Type" not in resp.headers


async def test_response_before_complete(aiohttp_client: Any) -> None:
async def handler(request):
return web.Response(body=b"OK")
Expand Down