apple / swift-nio

Event-driven network application framework for high performance protocol servers & clients, non-blocking.
https://swiftpackageindex.com/apple/swift-nio/documentation
Apache License 2.0
7.98k stars 650 forks source link

NIOHTTP1Server can not serve video to Safari (Ranges: handling) #608

Closed bkrpub closed 6 years ago

bkrpub commented 6 years ago

Expected behavior

Be able to serve video files to any major browser.

(Handling ranged requests or at least respond in a way that the browser will accept)

Actual behavior

I have modified NIOHTTP1Server to return Content-type: video/mp4 to serve video files from a macOS app (I would love to replace CocoaHTTPServer with swift-nio).

Firefox and Chrome do show the video, Safari (11.1 on macOS 10.13.4) does show a broken play button.

Safari seems to use Ranges no matter if the server includes Accept-Range:. NIOHTTP1 responds with the whole file to a Range: bytes=0-1 request.

Safari first requests (GET) the file without a range - receives the whole file and then requests it again (probably via AVFoundation).

I tried to add code to send "Accept-Range: bytes" but it didn't change Safaris behaviour (which is - at least for iOS - documented to depend on working Range: headersI and I didn't find anything in the NIOHTTP1 code implementing them.

I would like to know if someone is already working on supporting Ranges on NIOHTTP1. If so, I am willing to help. If not I would appreciate pointers on where to best start implementing them as I am not yet familiar with the code.

NIOHTTP2 does not seem to be a viable option yet for production code, so I'd like to get it working with NIOHTTP1.

Below I attach a dump of the request and response headers for the Safari case. (The results do not change for fileio vs. sendfile).

Thank you very much, kind regards, Bjoern

Request: GET /fileio/rabbithole.mp4 Host: localhost:4200 Connection: keep-alive Upgrade-Insecure-Requests: 1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1 Safari/605.1.15 Accept-Language: de-de DNT: 1 Accept-Encoding: gzip, deflate

Response: Content-Length: 328686 Content-Type: video/mp4

Request: GET /fileio/rabbithole.mp4 Host: localhost:4200 DNT: 1 X-Playback-Session-Id: B4C41F17-7C7A-412B-8486-B6425BE036F5 Accept-Language: de-de Range: bytes=0-1 Accept: / User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1 Safari/605.1.15 Referer: http://localhost:4200/fileio/rabbithole.mp4 Accept-Encoding: identity Connection: keep-alive

Response: Content-Length: 328686 Content-Type: video/mp4

Steps to reproduce

If possible, minimal yet complete reproducer code (or URL to code)

SwiftNIO version/commit hash

bf8c64289654d57fd2a9e9405c99798f9515f3c4

Swift & OS version (output of swift --version && uname -a)

Apple Swift version 4.1.2 (swiftlang-902.0.54 clang-902.0.39.2) Target: x86_64-apple-darwin17.5.0 Darwin lucy.local 17.5.0 Darwin Kernel Version 17.5.0: Mon Mar 5 22:24:32 PST 2018; root:xnu-4570.51.1~1/RELEASE_X86_64 x86_64

helje5 commented 6 years ago

Yours responses carry multiple content-type headers? First text/plain, then video/mp4. Maybe you just need to fix that ...

bkrpub commented 6 years ago

Oops. Thank you, seems like I had a severe case of snow blindness. I fixed it and re-ran the test but it didn't change the result, I edited the issue accordingly.

weissi commented 6 years ago

@bkrpub SwiftNIO is really a framework to built networking libraries and applications. NIOHTTP1Server literally just serves as an example of how to deal with certain HTTP1 things (and as a server for our integration tests). There's lots of stuff missing that would make this example a real HTTP server. For your application I would suggest using one of the existing Swift webservers that are written with SwiftNIO. For example Vapor, Kitura (there's an option to use SwiftNIO), swift-web or some of the others.

If you prefer to go low-level and use NIO directly it would be your responsibility to handle the range requests, you will be able to implement them but it'll be more work than you'd like. Therefore I'd suggest using something higher level that's meant to be used as a HTTP server and obviously I'd recommend using one that's written with SwiftNIO ;).

Please let me know if I'm missing something, otherwise I'd close this issue.

helje5 commented 6 years ago

Does any one of those frameworks actually handle the issue?

bkrpub commented 6 years ago

I actually implemented the most simple form of range requests (only one range) by slightly extending FileRegion (to keep around the file size) and got video to work but also realized how much is missing, so basically I am done with the issue and thank you very much for the pointers.

What I would still be interested in are opinions on which HTTP implementation to use for running a server inside a macOS GUI app. The next experiment will be using Vapor. Is anyone here sharing that use case?

Thanks a lot, Bjoern

helje5 commented 6 years ago

SwiftNIO? http://www.alwaysrightinstitute.com/nio-on-the-client/