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
Add support for range headers to FileResponse
#1999
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
|
||
from starlette import status | ||
from starlette.background import BackgroundTask | ||
from starlette.datastructures import Headers | ||
from starlette.requests import Request | ||
from starlette.responses import ( | ||
FileResponse, | ||
|
@@ -241,6 +242,31 @@ async def app(scope, receive, send): | |
assert filled_by_bg_task == "6, 7, 8, 9" | ||
|
||
|
||
def test_file_response_with_range(tmpdir, test_client_factory): | ||
path = os.path.join(tmpdir, "xyz") | ||
content = b"<file content>" | ||
with open(path, "wb") as file: | ||
file.write(content) | ||
|
||
async def app(scope, receive, send): | ||
range_header = Headers(scope=scope)["range"] | ||
start, end = (int(v) for v in range_header[len("bytes=") :].split("-")) | ||
response = FileResponse(path=path, filename="example.png", range=(start, end)) | ||
Comment on lines
+252
to
+254
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is what a developer would need to do by its own with this solution. It doesn't look good. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about something like... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... No... But you actually gave me an idea: range_header = Headers(scope=scope)["range"]
FileResponse(path=path, filename=..., range_header=range_header) Still... I'm not comfortable yet... 🤔 |
||
await response(scope, receive, send) | ||
|
||
client = test_client_factory(app) | ||
response = client.get("/", headers={"range": "bytes=1-12"}) | ||
expected_disposition = 'attachment; filename="example.png"' | ||
assert response.status_code == status.HTTP_206_PARTIAL_CONTENT | ||
assert response.content == content[1:13] | ||
assert response.headers["content-type"] == "image/png" | ||
assert response.headers["content-disposition"] == expected_disposition | ||
assert response.headers["content-range"] == "bytes 1-12/14" | ||
assert "content-length" in response.headers | ||
assert "last-modified" in response.headers | ||
assert "etag" in response.headers | ||
|
||
|
||
def test_file_response_with_directory_raises_error(tmpdir, test_client_factory): | ||
app = FileResponse(path=tmpdir, filename="example.png") | ||
client = test_client_factory(app) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The status code is ignored in case of range being sent.
This doesn't look cool.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you change
status_code: int = 200
tostatus_code: Optional[int] = None
then here set the default to 200 if there is no range and 206 if there is a range? If it's explicitly set to something other than 206 and there is a range then throw an error.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would make the
FileResponse
status_code type differ from the other Response classes.But indeed, I can do the following instead:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well the
Response.status_code
type would always be an integer still, the default value is just inferred andNone
is used in to signal that it was not passed explicitly.Throwing an exception seems much better than ignoring the code though :)