8000 Fix FileResponse doing blocking I/O in the event loop by bdraco · Pull Request #8012 · aio-libs/aiohttp · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Fix FileResponse doing blocking I/O in the event loop #8012

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/8012.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix :py:class:`~aiohttp.web.FileResponse`. doing blocking I/O in the event loop
28 changes: 20 additions & 8 deletions aiohttp/web_fileresponse.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,19 +121,31 @@ async def _precondition_failed(
self.content_length = 0
return await super().prepare(request)

async def prepare(self, request: "BaseRequest") -> Optional[AbstractStreamWriter]:
def _get_file_path_stat_and_gzip(
self, check_for_gzipped_file: bool
) -> Tuple[pathlib.Path, os.stat_result, bool]:
"""Return the file path, stat result, and gzip status.

This method should be called from a thread executor
since it calls os.stat which may block.
"""
filepath = self._path

gzip = False
if "gzip" in request.headers.get(hdrs.ACCEPT_ENCODING, ""):
if check_for_gzipped_file:
gzip_path = filepath.with_name(filepath.name + ".gz")
try:
return gzip_path, gzip_path.stat(), True
except OSError:
# Fall through and try the non-gzipped file
pass

if gzip_path.is_file():
filepath = gzip_path
gzip = True
return filepath, filepath.stat(), False

async def prepare(self, request: "BaseRequest") -> Optional[AbstractStreamWriter]:
loop = asyncio.get_event_loop()
st: os.stat_result = await loop.run_in_executor(None, filepath.stat)
check_for_gzipped_file = "gzip" in request.headers.get(hdrs.ACCEPT_ENCODING, "")
filepath, st, gzip = await loop.run_in_executor(
None, self._get_file_path_stat_and_gzip, check_for_gzipped_file
)

etag_value = f"{st.st_mtime_ns:x}-{st.st_size:x}"
last_modified = st.st_mtime
Expand Down
8 changes: 4 additions & 4 deletions tests/test_web_sendfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ def test_using_gzip_if_header_present_and_file_available(loop: Any) -> None:
)

gz_filepath = mock.create_autospec(Path, spec_set=True)
gz_filepath.is_file.return_value = True
gz_filepath.stat.return_value.st_size = 1024
gz_filepath.stat.return_value.st_mtime_ns = 1603733507222449291

Expand All @@ -35,7 +34,8 @@ def test_gzip_if_header_not_present_and_file_available(loop: Any) -> None:
request = make_mocked_request("GET", "http://python.org/logo.png", headers={})

gz_filepath = mock.create_autospec(Path, spec_set=True)
gz_filepath.is_file.return_value = True
gz_filepath.stat.return_value.st_size = 1024
gz_filepath.stat.return_value.st_mtime_ns = 1603733507222449291

filepath = mock.create_autospec(Path, spec_set=True)
filepath.name = "logo.png"
Expand All @@ -57,7 +57,7 @@ def test_gzip_if_header_not_present_and_file_not_available(loop: Any) -> None:
request = make_mocked_request("GET", "http://python.org/logo.png", headers={})

gz_filepath = mock.create_autospec(Path, spec_set=True)
gz_filepath.is_file.return_value = False
gz_filepath.stat.side_effect = OSError(2, "No such file or directory")

filepath = mock.create_autospec(Path, spec_set=True)
filepath.name = "logo.png"
Expand All @@ -81,7 +81,7 @@ def test_gzip_if_header_present_and_file_not_available(loop: Any) -> None:
)

gz_filepath = mock.create_autospec(Path, spec_set=True)
gz_filepath.is_file.return_value = False
gz_filepath.stat.side_effect = OSError(2, "No such file or directory")

filepath = mock.create_autospec(Path, spec_set=True)
filepath.name = "logo.png"
Expand Down
0