2014-04-08 76 views
8

我正在使用PHP腳本來控制對下載文件的訪問。這適用於2Gb以下的任何內容,但對較大文件無效。Apache/PHP大文件下載(> 2Gb)失敗

  • Apache和PHP都是64位
  • 阿帕奇允許若是直接(我不能讓)

的PHP的膽量(忽略訪問要下載的文件訪問控制):

if (ob_get_level()) ob_end_clean(); 

error_log('FILETEST: '.$path.' : '.filesize($path)); 
header('Content-Description: File Transfer'); 
header('Content-Type: application/octet-stream'); 
header('Content-Disposition: attachment; filename='.basename($path)); 
header('Expires: 0'); 
header('Cache-Control: must-revalidate'); 
header('Pragma: public'); 
header('Content-Length: ' . filesize($path)); 
readfile($path); 
exit; 

錯誤日誌顯示文件大小細

[Tue Apr 08 11:01:16 2014] [error] [client *.*.*.*] FILETEST: /downloads/file.name : 2251373807, referer: http://myurl/files/ 

但訪問日誌具有負尺寸:

*.*.*.* - - [08/Apr/2014:11:01:16 +0100] "GET /files/file.name HTTP/1.1" 200 -2043593489 "http://myurl/files/" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0" 

所以瀏覽器拒絕下載該文件。事實上,使用wget,它不發送任何內容:

$ wget -S -O - http://myurl/files/file.name 
--2014-04-08 11:33:38-- http://myurl/files/file.name 
HTTP request sent, awaiting response... No data received. 
Retrying. 
+1

這通常是由於大內存消耗,使用fread一次保持2GB的大流而不是2GB。 –

+0

這是在您的主機上還是在您的家用電腦上?底層文件系統和操作系統是什麼? –

+0

我真的會嘗試傑森的建議。想象一下8個人一次請求2GB = 16GB內存..使用'fread'(和'fopen'和'b'標誌)也可以讓你控制下載的最大速度。 – DanFromGermany

回答

6

嘗試讀取數據塊文件,並將它們暴露在瀏覽器而不是填你擁有2GB本地內存和沖洗的一次。

更換readfile($path);由:

@ob_end_flush(); 
flush(); 

$fileDescriptor = fopen($file, 'rb'); 

while ($chunk = fread($fileDescriptor, 8192)) { 
    echo $chunk; 
    @ob_end_flush(); 
    flush(); 
} 

fclose($fileDescriptor); 
exit; 

8192字節是在某些情況下一個臨界點,要參考,php.net/fread

添加一些microtime變量(並與文件描述符的指針位置進行比較)也將允許您控制下載的最大速度。

* (沖洗輸出緩衝器也略微依賴於Web服務器,使用這些命令,以確保它至少會嘗試刷新儘可能。)

+2

使用這個而不是readfile來開始下載的速度要快得多。在這段時間裏掙扎着:我欠你一杯啤酒......! – BSUK

0

我碰到這個問題前,用下面的腳本下載文件時,將文件分解成塊下載,而不是試圖把整個大文件文件一次。該腳本還考慮到瀏覽器被用作一些瀏覽器(即IE)可以稍微不同地處理標題。

private function outputFile($file, $name, $mime_type='') { 
    $fileChunkSize = 1024*30; 

    if(!is_readable($file)) die('File not found or inaccessible!'); 

    $size = filesize($file); 
    $name = rawurldecode($name); 

    $known_mime_types=array(
     "pdf" => "application/pdf", 
     "txt" => "text/plain", 
     "html" => "text/html", 
     "htm" => "text/html", 
     "exe" => "application/octet-stream", 
     "zip" => "application/zip", 
     "doc" => "application/msword", 
     "xls" => "application/vnd.ms-excel", 
     "ppt" => "application/vnd.ms-powerpoint", 
     "gif" => "image/gif", 
     "png" => "image/png", 
     "jpeg"=> "image/jpg", 
     "jpg" => "image/jpg", 
     "php" => "text/plain" 
    ); 

    if($mime_type=='') 
    { 
     $file_extension = strtolower(substr(strrchr($file,"."),1)); 
     if(array_key_exists($file_extension, $known_mime_types)) 
      $mime_type=$known_mime_types[$file_extension]; 
     else 
      $mime_type="application/force-download"; 
    } 

    @ob_end_clean(); 

    if(ini_get('zlib.output_compression')) 
     ini_set('zlib.output_compression', 'Off'); 

    header('Content-Type: ' . $mime_type); 
    header('Content-Disposition: attachment; filename="'.$name.'"'); 
    header("Content-Transfer-Encoding: binary"); 
    header('Accept-Ranges: bytes'); 
    header("Cache-control: private"); 
    header('Pragma: private'); 
    header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); 

    if(isset($_SERVER['HTTP_RANGE'])) 
    { 
     list($a, $range) = explode("=",$_SERVER['HTTP_RANGE'],2); 
     list($range) = explode(",",$range,2); 
     list($range, $range_end) = explode("-", $range); 
     $range=intval($range); 
     if(!$range_end) 
      $range_end=$size-1; 
     else 
      $range_end=intval($range_end); 

     $new_length = $range_end-$range+1; 
     header("HTTP/1.1 206 Partial Content"); 
     header("Content-Length: $new_length"); 
     header("Content-Range: bytes $range-$range_end/$size"); 
    } 
    else 
    { 
     $new_length=$size; 
     header("Content-Length: ".$size); 
    } 

    $chunksize = 1*($fileChunkSize); 
    $bytes_send = 0; 
    if ($file = fopen($file, 'r')) 
    { 
     if(isset($_SERVER['HTTP_RANGE'])) 
     fseek($file, $range); 

     while(!feof($file) && 
      (!connection_aborted()) && 
      ($bytes_send<$new_length) 
     ) 
     { 
      $buffer = fread($file, $chunksize); 
      print($buffer); 
      flush(); 
      $bytes_send += strlen($buffer); 
     } 
    fclose($file); 
    } 
    else die('Error - can not open file.'); 

    die(); 
} 
0

在readfile($ path)之前添加代碼;

ob_clean(); 
flush(); 

我使用此代碼下載:

if (file_exists($file)) { 


     header('Content-Description: File Transfer'); 
     header('Content-Type: application/octet-stream'); 
     header('Content-Disposition: attachment; filename='.basename($file)); 
     header('Content-Transfer-Encoding: binary'); 
     header('Expires: 0'); 
     header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); 
     header('Pragma: public'); 
     header('Content-Length: ' . filesize($file)); 

     ob_clean(); 
     flush(); 
     readfile($file); 
     exit; 

    } 
0

你最好的選擇是強制Apache在HTTP帶有這樣的功能的分塊模式。您將以這種方式節省大量PHP內存。

function readfile_chunked($filename, $retbytes = TRUE) { 
    $CHUNK_SIZE=1024*1024; 
    $buffer = ''; 
    $cnt =0; 
    $handle = fopen($filename, 'rb'); 
    if ($handle === false) { 
    return false; 
    } 
    while (!feof($handle)) { 
    $buffer = fread($handle, $CHUNK_SIZE); 
    echo $buffer; 
    @ob_flush(); 
    flush(); 
    if ($retbytes) { 
     $cnt += strlen($buffer); 
    } 
    } 
    $status = fclose($handle); 
    if ($retbytes && $status) { 
    return $cnt; // return num. bytes delivered like readfile() does. 
    } 
    return $status; 
}