Content-range support for file_transfer()

IsoTeemu - October 30, 2006 - 00:48
Project:Drupal
Version:8.x-dev
Component:file system
Category:feature request
Priority:normal
Assigned:Darren Oh
Status:needs work
Issue tags:Needs tests
Description

A patch that adds file transfer resume support by accepting byte ranges and sending 'HTTP/1.1 206 Partial Content' in private file handling.

AttachmentSizeStatusTest resultOperations
drupal-file-range.diff.txt1.49 KBIgnoredNoneNone

#1

Steven - October 31, 2006 - 10:10
Version:4.7.4» x.y.z
Status:needs review» needs work
  • Feature requests go in head, not 4.7.
  • The code is broken: $r-1024;.
  • The code does not conform to Drupal code style.
  • The parsing of the HTTP header is messy and could be simplified using a simple regular expression (int verification, proper syntax, and more).
  • As per HTTP 1.1, the proper response in case the range extends beyond the size of the file is "416 Requested Range Not Satisfiable", instead of just adjusting it silently.
  • Overall, the coding style is messy and needs to be cleaned up. Macros such as min/max() can be used to make it more readable.

http://www.faqs.org/rfcs/rfc2616.html

#2

IsoTeemu - November 1, 2006 - 16:06
Status:needs work» closed

Thanks for feedback, but dang, comp^Wsuggestion list is bigger than my patch. Taking the hint.
True, I had missed the 416 header part, so I added it. Next I changed it to use preg_match(), but alas, I had been in delusion that regexes should be avoided in simple cases, as they're suppose to be a bit expensive to do. But call me a dumm, as what I can't understand where is that you would like to use min/max. In comparison?

And I think I was a bit rushing, as looking a "correct" way of handling ranges from apache source revealed that there is much more than meets the eye at first sight. So I'll close this and let it wait in my own tree for infinite future.

AttachmentSizeStatusTest resultOperations
drupal-file-range.diff_0.txt1.53 KBIgnoredNoneNone

#3

9802008 - December 12, 2006 - 13:11
Version:x.y.z» 4.7.2

Thanks! This solved the problem I was having with windows media player: http://drupal.org/node/102167

#4

Darren Oh - June 17, 2007 - 00:06
Version:4.7.2» 6.x-dev
Status:closed» needs work

Too often an imperfect contribution is criticized so strongly that it seems no contribution would have been preferred. Supporting pause and resume for downloads is an important feature. The flaws in this patch should motivate other developers to work on it.

#5

Darren Oh - July 3, 2007 - 05:20

Here's an up-to-date patch for Drupal 4.7.

AttachmentSizeStatusTest resultOperations
file.inc-4.7.patch1.87 KBIgnoredNoneNone

#6

Darren Oh - July 3, 2007 - 05:27
Version:6.x-dev» 7.x-dev
Assigned to:Anonymous» Darren Oh
Status:needs work» needs review

And one for HEAD. I set it to "needs review" for testing, not so that the previous criticisms could be repeated.

#7

Darren Oh - July 3, 2007 - 05:28

File was missing.

AttachmentSizeStatusTest resultOperations
file.inc-HEAD.patch1.86 KBIgnoredNoneNone

#8

Darren Oh - July 4, 2007 - 18:06

Cleaned up code and replaced header() with drupal_set_header().

AttachmentSizeStatusTest resultOperations
file.inc-HEAD_0.patch1.95 KBIgnoredNoneNone

#9

Darren Oh - July 5, 2007 - 19:27

Still trying to meet the vague "messiness" objection.

AttachmentSizeStatusTest resultOperations
file.inc-HEAD_1.patch1.78 KBIgnoredNoneNone

#10

Darren Oh - July 11, 2007 - 08:50

Completely rewritten patch to implement the full HTTP spec.

AttachmentSizeStatusTest resultOperations
file.inc-HEAD_2.patch2.68 KBIgnoredNoneNone

#11

Stefan Nagtegaal - July 11, 2007 - 10:44

How should this be tested?

#12

Darren Oh - July 11, 2007 - 14:04

Two ways: with a download manager that supports pause and resume, and by jumping past the downloaded portion of a song in a music player.

#13

Darren Oh - July 15, 2007 - 05:08

RealPlayer works well for testing the seek function. Cancelling and restarting a download in FireFox is a good test of the pause and resume function. Make sure to set downloads to private for a valid test.

After some testing on my own, I modified the patch.

AttachmentSizeStatusTest resultOperations
file.inc-HEAD_3.patch2.7 KBIgnoredNoneNone

#14

Darren Oh - July 15, 2007 - 05:27

Missed an error.

AttachmentSizeStatusTest resultOperations
file.inc-HEAD_4.patch2.77 KBIgnoredNoneNone

#15

Stefan Nagtegaal - July 16, 2007 - 08:15
Status:needs review» reviewed & tested by the community

Nice! Coding style looks good and functionality seems to work on FF win/OSX and Opera Win/OSX.

This is RTBC.

#16

Darren Oh - October 3, 2007 - 07:48
Version:7.x-dev» 6.x-dev

Adding support for a basic HTTP feature is a simple change that can be done now.

#17

Gábor Hojtsy - October 3, 2007 - 14:21
Status:reviewed & tested by the community» needs review

Hm, I looked through http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 which says that:

the server SHOULD return a response with a status of 206 (Partial Content) containing the satisfiable ranges of the entity-body.

Which means to me that if multiple ranges are requested, all ranges should be returned combined. This code does not seem like doing that from what I see. It does move the file pointer to the latest range and sets the length to the latest range requested. Seems to be printing out Content-Range headers for all satisfiable ranges but then only returning the last one. Do I misunderstand the standards or the code?

#18

Darren Oh - October 5, 2007 - 07:17

You caught a bug. Updated patch attached.

AttachmentSizeStatusTest resultOperations
file.inc-91934.patch3.16 KBIgnoredNoneNone

#19

Wim Leers - October 15, 2007 - 21:17

Patch wasn't created from root. Reroll.

AttachmentSizeStatusTest resultOperations
file.inc__2.patch3.19 KBIgnoredNoneNone

#20

Darren Oh - October 18, 2007 - 22:37
Status:needs review» reviewed & tested by the community

I'd call it ready.

#21

Dries - November 10, 2007 - 08:15
Version:6.x-dev» 7.x-dev

This is a new feature and needs to wait for Drupal 7. Thanks.

#22

Arancaytar - February 12, 2008 - 17:11

Patch still applies to HEAD, but with offset. Here's a reroll.

AttachmentSizeStatusTest resultOperations
file-content-range-91934-22.patch3.19 KBIdleFailed: Failed to apply patch.View details | Re-test

#23

drewish - April 15, 2008 - 20:14
Status:reviewed & tested by the community» needs work

looking at the RFC i'm still not sure this is correct: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.16

When an HTTP message includes the content of multiple ranges (for example, a response to a request for multiple non-overlapping ranges), these are transmitted as a multipart message. The multipart media type used for this purpose is "multipart/byteranges" as defined in appendix 19.2.

it doesn't seem like we'd be returning multipart media.

i'd like to see _file_transfer_range() take a starting offset rather than having to know that you need to fseek() before calling it.

also, the new string concatenation rules aren't followed.

#24

drewish - April 16, 2008 - 17:11

marked #49868: Download Resume support. as a duplicate

#25

Darren Oh - April 25, 2008 - 15:49
Status:needs work» needs review
AttachmentSizeStatusTest resultOperations
file.inc-91934.patch5.44 KBIdleFailed: Failed to apply patch.View details | Re-test

#26

Darren Oh - April 25, 2008 - 19:46

Set Last-Modified and ETag headers based on file modification time to support download pause/resume.

AttachmentSizeStatusTest resultOperations
file.inc-91934.patch5.73 KBIdleFailed: Failed to apply patch.View details | Re-test

#27

lilou - August 22, 2008 - 22:47
Status:needs review» needs work

The patch #26 cannot apply against CVS HEAD.

#28

Darren Oh - September 2, 2008 - 23:25
Status:needs work» needs review
AttachmentSizeStatusTest resultOperations
file.inc-91934-28_HEAD.patch5.5 KBIdleFailed: Failed to apply patch.View details | Re-test

#29

Darren Oh - October 21, 2008 - 18:10

Keeping up with HEAD.

AttachmentSizeStatusTest resultOperations
file.inc-91934-29_HEAD.patch5.4 KBIdlePassed: 8972 passes, 0 fails, 0 exceptionsView details | Re-test

#30

drewish - October 21, 2008 - 18:17

this code just seems scary and easy to screw up. i'm of the opinion that we could do this in contrib using the technique i've outlined here: http://drupal.org/node/74472#comment-1052308

#31

System Message - November 16, 2008 - 21:50
Status:needs review» needs work

The last submitted patch failed testing.

#32

lilou - November 17, 2008 - 04:53
Status:needs work» needs review

Test failure : #335122: Test clean HEAD after every commit

#33

catch - January 22, 2009 - 00:33
Status:needs review» needs work

This does look like something which might be better suited for contrib. Or at least it needs tests - should at least be able to check headers.

#34

edhel - March 18, 2009 - 08:28

I've made module based on last patch of Darren Oh (http://drupal.org/comment/reply/91934#comment-1070336) and advice of drewish (http://drupal.org/comment/reply/91934#comment-1070346). See attachment.

It seems work on my site, though it's not tested in detail yet.

AttachmentSizeStatusTest resultOperations
file_transfer_range.zip2.35 KBIgnoredNoneNone

#35

batje - March 25, 2009 - 08:55

subscribe

#36

grendzy - October 15, 2009 - 20:03
Version:7.x-dev» 8.x-dev
Priority:minor» normal

I think this would be a great core feature, and would make private transfers more consistent with public file behavior (Apache, at least, has the byteranges filter enabled by default). In addition to resumable downloads, byte range requests are used by the iPhone (and probably other mobile devices) to emulate video streaming over plain HTTP.

For those looking for a contrib solution, the X-Sendfile will also solve this problem.

#37

grendzy - October 27, 2009 - 16:32

+++ includes/file.inc 21 Oct 2008 18:07:33 -0000
@@ -1233,10 +1233,116 @@ function file_transfer($source, $headers
+            $ranges[$key] = array('from' => max(0, $end_file + $range), 'to' => $end_file);

If I'm reading the RFC correctly, this is off by one. $end_file + $range should use $filesize instead.

Regarding the "scariness" factor, it's quite easy to write tests for this code, and it's not something that will need to change very often (not until the HTTP spec is changed, probably). Here's a sample of what tests would look like:

<?php
   
// The first 500 bytes (byte offsets 0-499, inclusive): bytes=0-499
   
$this->drupalGet($filepath, array(), array('Range: bytes=0-499'));
   
$this->assertResponse(206, t('Received HTTP/1.1 206 Partial Content.'));
   
$headers = $this->drupalGetHeaders();
   
$this->assertEqual($headers['accept-ranges'], 'bytes', t('Received accept-ranges header.'));
   
$this->assertEqual($headers['content-length'], '500', t('Content-Length is 500 bytes.'));
   
$this->assertEqual($headers['content-type'], 'image/jpeg', t('Content-Type is image/jpeg.'));
   
$this->assertEqual(strlen($this->drupalGetContent()), 500, t('Received 500 bytes.'));
   
$this->assertTrue(substr($this->drupalGetContent(), 0, 1) === chr(255), t('First byte matched correctly.'));
?>

This review is powered by Dreditor.

#38

Cyberwolf - November 25, 2009 - 09:08

I took a look at this module. There seems to be some weird code path though:

  if (1) {
    _file_transfer_range($filepath, $headers);
  } else {
    file_transfer($filepath, $headers);
  }

There is no case in which the else block will get executed. Is the condition right? Or can the control structure be completely removed and just keep what is in the if block?

#39

Arancaytar - November 28, 2009 - 09:33

Either the "1" is supposed to become some variable or setting, or it's debug code and the condition can be safely removed.

Edit: Wait, if the patch enters core, we won't need the contrib module anyway.

 
 

Drupal is a registered trademark of Dries Buytaert.