X-Sendfile - Resumable Downloads?


I want to implement resumable file downloads (typically 1GB filesizes) in PHP on our Dreamhost dedicated server running Apache. From what I have read, X-Sendfile seems to be the most efficient way to do it.

Has anyone got X-Sendfile working on an Apache server so that file downloads can be resumed? There is very little information about X-Sendfile around - the brief documentation says to use ranges in the header but the few reports I’ve found report lack of success with this.

Any other experiences (good or bas) with X-Sendfile (mod_xsendfile) are welcome too.

Thanks very much!



function serve_file_resumable ($file, $contenttype = ‘application/octet-stream’) {

// Avoid sending unexpected errors to the client - we should be serving a file,
// we don't want to corrupt the data we send

// Make sure the files exists, otherwise we are wasting our time
if (!file_exists($file)) {
  header("HTTP/1.1 404 Not Found");

// Get the 'Range' header if one was sent
if (isset($_SERVER['HTTP_RANGE'])) $range = $_SERVER['HTTP_RANGE']; // IIS/Some Apache versions
else if ($apache = apache_request_headers()) { // Try Apache again
  $headers = array();
  foreach ($apache as $header => $val) $headers[strtolower($header)] = $val;
  if (isset($headers['range'])) $range = $headers['range'];
  else $range = FALSE; // We can't get the header/there isn't one set
} else $range = FALSE; // We can't get the header/there isn't one set

// Get the data range requested (if any)
$filesize = filesize($file);
if ($range) {
  $partial = true;
  list($param,$range) = explode('=',$range);
  if (strtolower(trim($param)) != 'bytes') { // Bad request - range unit is not 'bytes'
    header("HTTP/1.1 400 Invalid Request");
  $range = explode(',',$range);
  $range = explode('-',$range[0]); // We only deal with the first requested range
  if (count($range) != 2) { // Bad request - 'bytes' parameter is not valid
    header("HTTP/1.1 400 Invalid Request");
  if ($range[0] === '') { // First number missing, return last $range[1] bytes
    $end = $filesize - 1;
    $start = $end - intval($range[0]);
  } else if ($range[1] === '') { // Second number missing, return from byte $range[0] to end
    $start = intval($range[0]);
    $end = $filesize - 1;
  } else { // Both numbers present, return specific range
    $start = intval($range[0]);
    $end = intval($range[1]);
    if ($end >= $filesize || (!$start && (!$end || $end == ($filesize - 1)))) $partial = false; // Invalid range/whole file specified, return whole file
  $length = $end - $start + 1;
} else $partial = false; // No range requested

// Send standard headers
header("Content-Type: $contenttype");
header("Content-Length: $filesize");
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Accept-Ranges: bytes');

// if requested, send extra headers and part of file...
if ($partial) {
  header('HTTP/1.1 206 Partial Content'); 
  header("Content-Range: bytes $start-$end/$filesize"); 
  if (!$fp = fopen($file, 'r')) { // Error out if we can't read the file
    header("HTTP/1.1 500 Internal Server Error");
  if ($start) fseek($fp,$start);
  while ($length) { // Read in blocks of 8KB so we don't chew up memory on the server
    $read = ($length > 8192) ? 8192 : $length;
    $length -= $read;
} else readfile($file); // ...otherwise just send the whole file

// Exit here to avoid accidentally sending extra content on the end of the file



Source: http://stackoverflow.com/questions/157318/resumable-downloads-when-using-php-to-send-the-file

Range header field definition : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35

Thanks for the reply. The protocol on ranges is very useful. Regarding that sample code, I want to avoid fread/readfile as for large files (mine are 1GB) as they won’t be as fast as x-sendfile should be. Plus that code uses CPU time servicing the PHP loop whereas x-sendfile would not. That’s why I was asking about x-sendfile.

Anyone using x-sendfile with resumable downloads? Your experience/advise very welcome, thanks!