diff --git a/CHANGES/4559.feature.rst b/CHANGES/4559.feature.rst new file mode 100644 index 00000000000..02e04efcf6b --- /dev/null +++ b/CHANGES/4559.feature.rst @@ -0,0 +1 @@ +Added an optional ``charset`` parameter to :class:`~aiohttp.web.FileResponse` so callers can set an explicit charset on the response content-type for text-like MIME types. diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py index b09bfe109d4..3c227d9794a 100644 --- a/aiohttp/web_fileresponse.py +++ b/aiohttp/web_fileresponse.py @@ -91,11 +91,13 @@ def __init__( status: int = 200, reason: str | None = None, headers: LooseHeaders | None = None, + charset: str | None = None, ) -> None: super().__init__(status=status, reason=reason, headers=headers) self._path = pathlib.Path(path) self._chunk_size = chunk_size + self._charset = charset def _seek_and_read(self, fobj: BinaryIO, offset: int, chunk_size: int) -> bytes: fobj.seek(offset) @@ -383,6 +385,9 @@ async def _prepare_open_file( guesser = CONTENT_TYPES.guess_type self.content_type = guesser(self._path)[0] or FALLBACK_CONTENT_TYPE + if self._charset is not None and self.content_type.startswith("text/"): + self.charset = self._charset + if file_encoding: self._headers[hdrs.CONTENT_ENCODING] = file_encoding self._headers[hdrs.VARY] = hdrs.ACCEPT_ENCODING diff --git a/docs/web_reference.rst b/docs/web_reference.rst index 6b6d6aa06c3..aafbb5c51b4 100644 --- a/docs/web_reference.rst +++ b/docs/web_reference.rst @@ -941,7 +941,7 @@ and :ref:`aiohttp-web-signals` handlers:: :attr:`~aiohttp.StreamResponse.body`, represented as :class:`str`. -.. class:: FileResponse(*, path, chunk_size=256*1024, status=200, reason=None, headers=None) +.. class:: FileResponse(*, path, chunk_size=256*1024, status=200, reason=None, headers=None, charset=None) :canonical: aiohttp.web_fileresponse.FileResponse The response class used to send files, inherited from :class:`StreamResponse`. @@ -966,6 +966,9 @@ and :ref:`aiohttp-web-signals` handlers:: response's ones. The ``Content-Type`` response header will be overridden if provided. + :param str charset: Charset to append to the ``Content-Type`` header for + text-like MIME types (e.g. ``text/plain``). + .. class:: WebSocketResponse(*, timeout=10.0, receive_timeout=None, \ autoclose=True, autoping=True, heartbeat=None, \ diff --git a/tests/test_web_sendfile_functional.py b/tests/test_web_sendfile_functional.py index 93d505720c7..8a5c25b4795 100644 --- a/tests/test_web_sendfile_functional.py +++ b/tests/test_web_sendfile_functional.py @@ -261,6 +261,61 @@ async def handler(request: web.Request) -> web.FileResponse: await client.close() +@pytest.mark.parametrize( + ("filename", "content", "expected_type"), + [ + ("hello.txt", b"Hello", "text/plain"), + ("hello.html", b"