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
Streaming files as partial content #2342
Conversation
I've noticed two bugs in there:
|
Please add unit tests as well. |
|
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.
Overall looking good! Left a few style comments/suggestions. Also, saw you used a few force unwraps, and although all in a safe way, probably still best to try and avoid those :)
Thanks for the PR!
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.
Overall looking good! Left a few style comments/suggestions. Also, saw you used a few force unwraps, and although all in a safe way, probably still best to try and avoid those :)
Usually, I do. Just tried to not clutter the code here with stuff that is unnecessarily expressive. It's a tradeoff, so to speak.
Sources/Vapor/Utilities/FileIO.swift
Outdated
response.headers.add(name: .accept, value: contentRange.unit.serialize()) | ||
let range = contentRange.ranges[0].asResponseContentRange(limit: fileSize) | ||
response.headers.contentRange = ContentRangeHeader(unit: contentRange.unit, range: range) | ||
(offset, byteCount) = contentRange.ranges[0].asByteBufferBounds(withMaxSize: fileSize) |
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.
I notice undefined behaviour here if the requested range exceeds the file size. Any proposals here?
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.
I think a bad request would be appropriate in this case
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.
Love this. Thanks for putting in the work.
Thanks so much for this PR. I'm afraid I don't have a quick solution for your question. But a |
One option would be to return 416, another would be to just return the whole file instead. Both options would be valid, I'd prefer a configurable solution with a reasonable default. |
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.
+1 to this change, thanks! Comments inline.
|
||
public struct ContentRangeHeader : Equatable { | ||
public let unit: RangeUnit | ||
public let range: ResponseContentRange |
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.
Instead of an enum, what about something like:
public var range: Range<Int>?
public var size: Int?
Where nil
on either is serialized as *
.
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 actually has been my first approach on doing this, but I wanted to eliminate optionals here. Has the advantage of avoiding a lot of code cluttering.
If you insist on this change (same goes for Request Range), please confirm this!
|
||
public struct RangeHeader : Equatable { | ||
public let unit: RangeUnit | ||
public let ranges: [RequestedRange] |
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.
Could we use Swift.Range
here 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.
This actually has been my first approach on doing this, but I wanted to eliminate optionals here. Has the advantage of avoiding a lot of code cluttering.
If you insist on this change (same goes for Response Content Range), please confirm this!
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 latest changes look good, thanks! Most of my comments here are style nits. Please take a look at the other source files in this project for comparison and follow their style.
More importantly though, header value parsing should be handled by the HTTPHeaders.Directive
helpers. From what I can tell this should handle the Range and ContentRange header syntax. Simple string parsing is error prone and will not handle edge cases.
Will take a look at it on Saturday. |
Done, @tanner0101! |
@Akazm sorry for the late response. The latest updates look great, thank you! Would you mind adding some comments to the code? Public method docblocks and inline comments around terse code would be especially helpful. The header parsing tests are great, but we should have at least one test ensuring the actual partial file reading works. Ideally this would go through a real route. This file has some good examples: https://github.com/vapor/vapor/blob/master/Tests/VaporTests/FileTests.swift. Let me know if you need help writing that. |
Comments won't be a problem, writing test code for streaming files is a thing I've never done before and since I'm kind of occupied for the next couple of weeks, any help on this would be appreciated. |
@tanner0101 Will take care of the tests on the weekend! |
I'm sorry, but I simply couldn't do it this weekend, @tanner0101. I didn't forget about it and I'll take care of it as soon as I can, probably on Tuesday afternoon, CEST. |
@Akazm What's the status on this? I kinda need it right now. I'll do a fork of this for now, please reach out if you need any help |
It's a motivation for me that other people want this as well. I promise to solve this during the wekeend. Time is a serious issue for me right now and I'm sorry for not having been able to continue here within the past few months. Any help is appreciated. |
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.
Thanks for doing this (and poking me!)
I think this is mostly good to go, just a few small things to clarify. Also, if you could update the PR comments as they'll become the release notes
Sources/Vapor/Utilities/FileIO.swift
Outdated
response.headers.add(name: .accept, value: contentRange.unit.serialize()) | ||
let range = contentRange.ranges[0].asResponseContentRange(limit: fileSize) | ||
response.headers.contentRange = ContentRangeHeader(unit: contentRange.unit, range: range) | ||
(offset, byteCount) = contentRange.ranges[0].asByteBufferBounds(withMaxSize: fileSize) |
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.
I think a bad request would be appropriate in this case
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.
I left some suggestions to remove some force unwraps and to clarify the code here and there a bit.
Co-authored-by: Siemen Sikkema <siemensikkema@users.noreply.github.com>
Co-authored-by: Siemen Sikkema <siemensikkema@users.noreply.github.com>
Co-authored-by: Siemen Sikkema <siemensikkema@users.noreply.github.com>
Co-authored-by: Siemen Sikkema <siemensikkema@users.noreply.github.com>
Co-authored-by: Siemen Sikkema <siemensikkema@users.noreply.github.com>
Co-authored-by: Siemen Sikkema <siemensikkema@users.noreply.github.com>
I think this is finally ready - @siemensikkema @gwynne if you want to do a final review. @Akazm is there anything else that needs addressing before we merge? |
byteCount: | ||
fileSize.intValue, |
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.
nit: extra newline?
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.
I think this is finally ready - @siemensikkema @gwynne if you want to do a final review.
@Akazm is there anything else that needs addressing before we merge?
Hello 0xTim,
please don't ask me for this. I've been in trouble since last year, I'm unfortunately occupied since my last activity here (since Corona gave our company many oppurtinities).
Nevertheless: I'm happy to see that there's some progress here made by other people since this is my first contribution to Open Source ever!
Confirmed I can play an MP4 in Safari so will merge, thank you everyone! |
These changes are now available in 4.37.0 |
Better support for streaming files by returning
206 Partial Content
when a range header is supplied with the correct content. Required when streaming to some browsers.