Output a file with HTTP range header in PHP

Summary

The HTTP Range header is a crucial mechanism for efficiently handling large file downloads, enabling clients to request specific byte ranges and facilitating download resumption after network interruptions. Implementing this involves checking for the `HTTP_RANGE` request header, parsing the requested byte range, and then setting appropriate response headers such as `206 Partial Content`, `Content-Length`, and `Content-Range`. A PHP example demonstrates how to open the file, use `fseek` to navigate to the starting byte, and stream only the requested portion. This approach not only saves bandwidth but also optimizes server I/O pressure by transferring partial content, enhancing the robustness of file serving.

When downloading a large file, we may encounter some network issues which causes download termination and only part of the file is downloaded. When the network connection resumes next time, we may need to redownload the file again from the beginning. In order to save bandwidth, http provides a Range parameter in its header which can control the file transfer flow. With the range parameter in the header, we can resume the download from where we stop.

Here is a piece of PHP code snippet which uses the range header to control which part of the file to transfer:

    <?php
      $filename=$_GET['filename'];
      $location='media/'.$filename;
      
      $extension = substr(strrchr($filename,'.'),1);
      if ($extension == "mp3") {
        $mimeType = "audio/mpeg";
      } else if ($extension == "ogg") {
        $mimeType = "audio/ogg";
      }
      if (!file_exists($location))
      {
        header ("HTTP/1.1 404 Not Found");
        return;
      }
      
      $size  = filesize($location);
      $time  = date('r', filemtime($location));
      
      $fm = @fopen($location, 'rb');
      if (!$fm)
      {
        header ("HTTP/1.1 505 Internal server error");
        return;
      }
      
      $begin  = 0;
      $end  = $size - 1;
      
      if (isset($_SERVER['HTTP_RANGE']))
      {
        if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches))
        {
        $begin  = intval($matches[1]);
        if (!empty($matches[2]))
        {
          $end  = intval($matches[2]);
        }
        }
      }
      if (isset($_SERVER['HTTP_RANGE']))
      {
        header('HTTP/1.1 206 Partial Content');
      }
      else
      {
        header('HTTP/1.1 200 OK');
      }
      
      header("Content-Type: $mimeType");
      header('Cache-Control: public, must-revalidate, max-age=0');
      header('Pragma: no-cache');  
      header('Accept-Ranges: bytes');
      header('Content-Length:' . (($end - $begin) + 1));
      if (isset($_SERVER['HTTP_RANGE']))
      {
        
        header("Content-Range: bytes $begin-$end/$size");
      }
      header("Content-Disposition: inline; filename=$filename");
      header("Content-Transfer-Encoding: binary");
      header("Last-Modified: $time");
      
      $cur  = $begin;
      fseek($fm, $begin, 0);
      
      while(!feof($fm) && $cur <= $end && (connection_status() == 0))
      {
        print fread($fm, min(1024 * 16, ($end - $cur) + 1));
        $cur += 1024 * 16;
      }

Here is the document of range : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

The use of range : Usually it is used to resume file transfer process, also when you want to transfer a large file, you can use it to control partial file transfer to release the server IO pressure.

Source : http://cloudbbs.org/forum.php?mod=viewthread&tid=10764

PHP HTTP RANGE FILE TRANSFER

  RELATED

  COMMENTS

3
codefinder
Mar 1, 2015 at 1:12 pm

in line 38 there is one empty too much...

sonic0002
Mar 2, 2015 at 4:17 am

Thakn you. Have corrected it.

reminoco
Jun 16, 2017 at 8:33 am

thank you for this article but i get    this error stream does not support seeking