How to download large files with PHP?

后端 未结 1 1275
不知归路
不知归路 2021-01-23 16:30

I spent a week to find right answer on this question. \'Right\' I mean absolutely conform to existing web-standards, reliable and performance effective. Finally, I\'ve

1条回答
  •  陌清茗
    陌清茗 (楼主)
    2021-01-23 16:55

    const STDOUT_CHUNK_SIZE = 128 * 1024; // Buffer size to send data to browser. MUST be less then 1/3 of PHP memory size
    const CACHE_EXP_SEC = 1800;  // Cache expire time is 30 min.
    
    $fileName = "large_video.mp4";
    $contentSize = filesize($fileName);
    $isAttachment = false;  // false allows to use a file as inline element of web page 
    
    // Parse range request. Browser asks for part of file
    if (isset($_SERVER["HTTP_RANGE"])) {
      list($units, $range) = explode("=", $_SERVER["HTTP_RANGE"], 2);
      if ($units !== "bytes") {
        http_response_code(416); // Requested Range Not Satisfiable
        exit;
      }
      $range = explode(",", $range, 2)[0]; // Get only first range. You can improve this ;)
      list($from, $to) = explode("-", $range, 2);
      $to = empty($to) ? ($contentSize - 1) : min(abs((int)$to), ($contentSize - 1));
      $from = (empty($from) || $to < abs((int)$from)) ? 0 : max(abs((int)$from), 0);
    }
    else {
      // Request for whole content
      $from = 0;
      $to = $contentSize - 1;
    }
    
    // Set response headers
    if ($from > 0 || $to < ($contentSize - 1))
    {
      http_response_code(206); // Partial Content
      header("Content-Type: video/mp4"));
      header("Content-Range: bytes $from-$to/$contentSize");
      header("Content-Length: " . ($from - $to + 1));
    }
    else {
      $etag = md5($file);  // Content is immutable but file name can be changed
      if (isset($_SERVER["HTTP_IF_NONE_MATCH"]) && trim($_SERVER["HTTP_IF_NONE_MATCH"]) === $etag) {
        http_response_code(304); // Not Modified
        setCacheHeaders($etag);
        exit;
      }
    
      http_response_code(200);  // Ok
      header("Content-Type: video/mp4"));
      header("Content-Length: $contentSize");
      if ($isAttachment) header("Content-Disposition: attachment; filename=\"$fileName\"");
      else header("Content-Disposition: inline");
    
      header("Accept-Ranges: bytes");
      setCacheHeaders($etag);
    }
    
    // Send response to client
    if ($file = fopen($fileName, "rb")) {
      fseek($file, $from);
      $counter = $from;
      set_time_limit(0);
      while (!feof($file) && $counter <= $to) {
        $bytesToRead = STDOUT_CHUNK_SIZE;
        if ($counter + $bytesToRead > $to) $bytesToRead = $to - $counter + 1;
        $data = fread($file, $bytesToRead);
        $counter += $bytesToRead;
        echo $data;
        flush();
      }
    fclose($file);
    
    function setCacheHeaders(string $etag, bool $cacheEnabled = true, bool $public = true)
    {
      if ($cacheEnabled) {
        header("ETag: $etag");
        $scope = $public ? "public" : "private";
        $sec = CACHE_EXP_SEC;
        $age = ($sec >= 0) ? ", max-age=$sec, s-maxage=$sec" : "";
        header("Cache-Control: $scope$age, no-transform");
      }
      else header("Cache-Control: no-cache, no-store, must-revalidate");
    }
    

    0 讨论(0)
提交回复
热议问题