iron / staticfile

Static file-serving middleware for the Iron web framework.
MIT License
63 stars 56 forks source link

Support HTTP Byte Serving via Ranges header #93

Open ojensen5115 opened 7 years ago

ojensen5115 commented 7 years ago

Currently, if you have a <video> tag whose src is served through staticfile, seeking doesn't work. This can be fixed by supporting Byte Serving.

Hoverbear commented 7 years ago

This would be awesome! :) If anyone wants to pick this up please let us know and we can try to help!

Cobrand commented 7 years ago

I'd like to pick this up. If I run into trouble I'll let you know.

Funnily enough, video playback doesn't work at all with staticfile, let alone seeking. Any video just refuses to play, either via firefox or via mpv. The same video works if played with firefox or mpv and if the video is hosted with an apache. I wonder if it's related ?

I will try to dig this up and come back with some news.

untitaker commented 7 years ago

I suspect usage of X-Sendfile will fix this too.

ojensen5115 commented 7 years ago

@Cobrand on my end, video playback works fine (in Chrome), it's just seeking that doesn't work. (note if you don't set autoplay and/or controls in the <video> tag, you'll need to tell the video to play via context menu.

Cobrand commented 7 years ago

Turns out that I tested with mp4, and it doesn't work in iron at all (probably because it can't seek metadata ?). webm either works without seeking in mpv, or hangs in the middle for long videos (> 30s). So that's definitely our issue here.

For info, the difference between Iron and Apache :

Iron

HTTP/1.1 200 OK
Content-Length: 29271156
Content-Type: video/webm
Date: Mon, 06 Feb 2017 19:20:56 GMT

Apache

HTTP/1.1 200 OK
Accept-Ranges: bytes
Connection: Keep-Alive
Content-Length: 29271156
Content-Type: video/webm
Date: Mon, 06 Feb 2017 19:21:57 GMT
ETag: "1bea474-54227ffe25f38"
Keep-Alive: timeout=5, max=100
Last-Modified: Fri, 25 Nov 2016 22:54:07 GMT
Server: Apache/2.4.25 (Unix) PHP/7.1.1
Cobrand commented 7 years ago

So I dug a little bit into how this stuff works, and I have some questions.

Basically if you answer with the Accept-Range: bytes header, you must be able to receive and process the Range header (event though you can still ignore the Range header). Basically a client can request things like these (one line = one different request) :

Range: bytes=0-
Range: bytes=200-
Range: bytes=200-1000, 2000-3000, 4000-

First one is dead simple: just send a "File" like we used to do.

Second one is less simple but feasible: Answer with the proper Content-Range, and offset the file by 200 bytes (from a File struct using a Bytes Iterator I guess)

Third one is way less simple, you have to answer something like this according to RFC 2046 :

Content-type: multipart/byteranges; boundary="xxironstaticfileboundaryxx"

--xxironstaticfileboundaryxx
Content-type: application/octet-stream;

200 to 1000 bytes here
--xxironstaticfileboundaryxx
Content-type: application/octet-stream;

2000 to 3000 bytes here...
--xxironstaticfileboundaryxx
Content-type: application/octet-stream;

4000 bytes to end of file here

--xxironstaticfileboundaryxx--

This is technically what we must answer, but unfortunately there is no proper way to answer a multipart body in Iron as of now, so for now I guess I'll pass.

I guess there might also be a way to only reply bytes 200 to 1000, but not only this isn't mentioned as allowed or forbidden in the RFCs, but I don't really know what to answer as well. Do we just answer as if only one range was asked, and we just set the Content-Length and Content-Range accordingly ?

For info, Apache will answer this to a Range: bytes=1000-2000, 3000-4000 request:

Content-Length: 2208
Content-Type: multipart/byteranges; boundary=beb42d06bc54e552

(2208 instead of 2000 is because the delimiter + "Content-type: video/webm" + "Content-range: bytes 3000-4000/29271156" are added as well, twice)

Since we can choose to ignore the "Range" header, I guess we can choose to ignore it when we got more than 1 range as well.

So yeah if someone knows If I can answer only bytes 1000 to 2000 for a request "Range: bytes=1000-2000, 3000-4000", that'd be great.

untitaker commented 7 years ago

You could just ignore the range request if it's composed of multiple ranges for now.

Cobrand commented 7 years ago

Can someone have a look at PR #95 ?

ojensen5115 commented 7 years ago

I'm very excited to see this -- it turns a simple "serve this folder" webserver into something significantly more useful than python's equivalent, especially in the case of e.g. a home media server.

Cobrand commented 7 years ago

I totally forgot that I used to have a PR open about that ... I'll try to find some time to get this PR fixed and merged once for all