Skip to content

Commit

Permalink
No default Content-Type when no content (#8858)
Browse files Browse the repository at this point in the history
(cherry picked from commit 26772ad)
  • Loading branch information
Dreamsorcerer committed Aug 23, 2024
1 parent 15d622c commit 6e306f4
Show file tree
Hide file tree
Showing 7 changed files with 25 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGES/8858.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Stopped adding a default Content-Type header when response has no content -- by :user:`Dreamsorcerer`.
6 changes: 4 additions & 2 deletions aiohttp/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,15 +762,17 @@ def content_type(self) -> str:
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]
assert self._content_type is not None
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)
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]:
Expand Down
6 changes: 3 additions & 3 deletions aiohttp/web_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class FileField:
filename: str
file: io.BufferedReader
content_type: str
headers: "CIMultiDictProxy[str]"
headers: CIMultiDictProxy[str]


_TCHAR: Final[str] = string.digits + string.ascii_letters + r"!#$%&'*+.^_`|~-"
Expand Down Expand Up @@ -169,7 +169,7 @@ def __init__(
self._payload_writer = payload_writer

self._payload = payload
self._headers = message.headers
self._headers: CIMultiDictProxy[str] = message.headers
self._method = message.method
self._version = message.version
self._cache: Dict[str, Any] = {}
Expand Down Expand Up @@ -493,7 +493,7 @@ def query_string(self) -> str:
return self._rel_url.query_string

@reify
def headers(self) -> "MultiMapping[str]":
def headers(self) -> CIMultiDictProxy[str]:
"""A case-insensitive multidict proxy with all headers."""
return self._headers

Expand Down
3 changes: 2 additions & 1 deletion aiohttp/web_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,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
2 changes: 0 additions & 2 deletions tests/test_client_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,6 @@ async def handler(request):
raw_headers = tuple((bytes(h), bytes(v)) for h, v in resp.raw_headers)
assert raw_headers == (
(b"Content-Length", b"0"),
(b"Content-Type", b"application/octet-stream"),
(b"Date", mock.ANY),
(b"Server", mock.ANY),
)
Expand Down Expand Up @@ -760,7 +759,6 @@ async def handler(request):
assert raw_headers == (
(b"X-Empty", b""),
(b"Content-Length", b"0"),
(b"Content-Type", b"application/octet-stream"),
(b"Date", mock.ANY),
(b"Server", mock.ANY),
)
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 @@ -148,6 +148,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
2 changes: 0 additions & 2 deletions tests/test_web_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -1139,7 +1139,6 @@ async def test_send_headers_for_empty_body(buf, writer) -> None:
Matches(
"HTTP/1.1 200 OK\r\n"
"Content-Length: 0\r\n"
"Content-Type: application/octet-stream\r\n"
"Date: .+\r\n"
"Server: .+\r\n\r\n"
)
Expand Down Expand Up @@ -1182,7 +1181,6 @@ async def test_send_set_cookie_header(buf, writer) -> None:
"HTTP/1.1 200 OK\r\n"
"Content-Length: 0\r\n"
"Set-Cookie: name=value\r\n"
"Content-Type: application/octet-stream\r\n"
"Date: .+\r\n"
"Server: .+\r\n\r\n"
)
Expand Down

0 comments on commit 6e306f4

Please sign in to comment.