Skip to content
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

Merged
merged 13 commits into from Jan 7, 2021
Merged

Streaming files as partial content #2342

merged 13 commits into from Jan 7, 2021

Conversation

Akazm
Copy link
Contributor

@Akazm Akazm commented May 1, 2020

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.

@Akazm
Copy link
Contributor Author

Akazm commented May 2, 2020

I've noticed two bugs in there:

  • asByteBufferBounds() returnend incorrect values (fixed)
  • Incorrect parsing of range headers; fixed that, too.

@vzsg vzsg requested a review from tanner0101 May 2, 2020 13:49
@grosch
Copy link
Contributor

grosch commented May 2, 2020

Please add unit tests as well.

@Akazm
Copy link
Contributor Author

Akazm commented May 2, 2020

  • renamed asHeaderString() to serialize()
  • added tests for serialization of ranges and content ranges
  • added tests for deserialization of ranges and content ranges
  • resolved all code style issues mentioned by @grosch (except for "Please move this to the line right after you assign rangeStr", since I seriously don't get it :()

Copy link
Sponsor Member

@MrLotU MrLotU left a 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!

Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Copy link
Contributor Author

@Akazm Akazm left a 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/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
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)
Copy link
Contributor Author

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?

Copy link
Member

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

Copy link
Contributor

@mcritz mcritz left a 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.

@Joannis
Copy link
Member

Joannis commented May 6, 2020

Thanks so much for this PR. I'm afraid I don't have a quick solution for your question. But a .badRequest error would be an option in my book.

@Akazm
Copy link
Contributor Author

Akazm commented May 6, 2020

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.

@tanner0101 tanner0101 added the enhancement New feature or request label May 8, 2020
@tanner0101 tanner0101 added this to Awaiting Review in Vapor 4 via automation May 8, 2020
Copy link
Member

@tanner0101 tanner0101 left a 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.

Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved

public struct ContentRangeHeader : Equatable {
public let unit: RangeUnit
public let range: ResponseContentRange
Copy link
Member

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 *.

Copy link
Contributor Author

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]
Copy link
Member

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?

Copy link
Contributor Author

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!

Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/Utilities/FileIO.swift Outdated Show resolved Hide resolved
Sources/Vapor/Utilities/FileIO.swift Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Tests/VaporTests/HTTPHeaderTests.swift Outdated Show resolved Hide resolved
Copy link
Member

@tanner0101 tanner0101 left a 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.

Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/Utilities/FileIO.swift Outdated Show resolved Hide resolved
Sources/Vapor/Utilities/FileIO.swift Outdated Show resolved Hide resolved
Sources/Vapor/Utilities/FileIO.swift Outdated Show resolved Hide resolved
@tanner0101 tanner0101 moved this from Awaiting Review to Awaiting Updates in Vapor 4 May 26, 2020
@Akazm
Copy link
Contributor Author

Akazm commented May 28, 2020

Will take a look at it on Saturday.

@Akazm
Copy link
Contributor Author

Akazm commented May 31, 2020

Done, @tanner0101!

@tanner0101
Copy link
Member

tanner0101 commented Jun 24, 2020

@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.

@Akazm
Copy link
Contributor Author

Akazm commented Jun 25, 2020

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.

@Akazm
Copy link
Contributor Author

Akazm commented Jul 15, 2020

@tanner0101 Will take care of the tests on the weekend!

@Akazm
Copy link
Contributor Author

Akazm commented Jul 19, 2020

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.

@nerdsupremacist
Copy link

@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

@Akazm
Copy link
Contributor Author

Akazm commented Aug 26, 2020

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.

@0xTim 0xTim added the semver-minor Contains new API label Dec 22, 2020
Copy link
Member

@0xTim 0xTim left a 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/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
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)
Copy link
Member

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

Sources/Vapor/Utilities/FileIO.swift Outdated Show resolved Hide resolved
Vapor 4 automation moved this from Awaiting Updates to Awaiting Review Dec 22, 2020
@0xTim 0xTim linked an issue Dec 22, 2020 that may be closed by this pull request
Copy link
Member

@siemensikkema siemensikkema left a 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.

Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift Outdated Show resolved Hide resolved
Sources/Vapor/Utilities/FileIO.swift Outdated Show resolved Hide resolved
Sources/Vapor/Utilities/FileIO.swift Outdated Show resolved Hide resolved
0xTim and others added 10 commits January 5, 2021 17:07
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>
@0xTim
Copy link
Member

0xTim commented Jan 6, 2021

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?

Comment on lines +98 to +99
byteCount:
fileSize.intValue,
Copy link
Member

Choose a reason for hiding this comment

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

nit: extra newline?

Copy link
Contributor Author

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!

@0xTim
Copy link
Member

0xTim commented Jan 7, 2021

Confirmed I can play an MP4 in Safari so will merge, thank you everyone!

@0xTim 0xTim merged commit 1ccc4a2 into vapor:master Jan 7, 2021
Vapor 4 automation moved this from Awaiting Review to Done Jan 7, 2021
@tanner0101
Copy link
Member

These changes are now available in 4.37.0

@0xTim 0xTim mentioned this pull request Jan 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request semver-minor Contains new API
Projects
Vapor 4
  
Done
Development

Successfully merging this pull request may close these issues.

FileIO support range header
10 participants