Skip to content

Fix FileResponse fallback code #6726

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

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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/6726.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a bug in ``FileResponse`` fallback code when the requested range is smaller than the chunk size.
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ Martin Sucha
Mathias Fröjdman
Mathieu Dugré
Matt VanEseltine
Matthias Dellweg
Matthias Marquardt
Matthieu Hauglustaine
Matthieu Rigal
Expand Down
2 changes: 1 addition & 1 deletion aiohttp/web_fileresponse.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ async def _sendfile_fallback(
chunk_size = self._chunk_size
loop = asyncio.get_event_loop()
chunk = await loop.run_in_executor(
None, self._seek_and_read, fobj, offset, chunk_size
None, self._seek_and_read, fobj, offset, min(chunk_size, count)
)
while chunk:
await writer.write(chunk)
Expand Down
27 changes: 27 additions & 0 deletions tests/test_web_sendfile_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -1164,3 +1164,30 @@ async def handler(request: web.Request) -> web.FileResponse:

resp.release()
await client.close()


@pytest.mark.parametrize("chunk_size", (15, 31))
async def test_a_ranged_request_serves_no_additional_bytes(
aiohttp_client: AiohttpClient, sender: _Sender, chunk_size: int
) -> None:
filepath = pathlib.Path(__file__).parent / "aiohttp.jpg"

async def handler(request: web.Request) -> web.FileResponse:
return sender(filepath, chunk_size=chunk_size)

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

resp = await client.get("/", headers={"Range": "bytes=20-40"})
assert resp.status == 206
body = await resp.read()
Copy link
Member

@Dreamsorcerer Dreamsorcerer Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may also be important to use the context manager correctly if we want to test 2 requests in a row.

Suggested change
resp = await client.get("/", headers={"Range": "bytes=20-40"})
assert resp.status == 206
body = await resp.read()
async with client.get("/", headers={"Range": "bytes=20-40"}) as resp:
assert resp.status == 206
body = await resp.read()


# 21 bytes, because http range headers are including both fenceposts.
assert len(body) == 21

# Additionally verify that we are served the right bytes from that file.
with filepath.open("rb") as f:
f.seek(20)
content = f.read(21)
assert content == body
Loading