天天看點

PHP下載下傳檔案、限速、X-sendfile

一、普通檔案下載下傳

①laravel架構HTTP響應的download方法

$pathToFile = 'myfile.csv';//參數一:絕對路徑
$downloadName = 'downloadFile.csv';//參數二:下載下傳後的檔案名
//download 參數三:HTTP頭資訊
return response()->download($pathToFile, $downloadName);
           

②PHP實作

$pathToFile = 'myfile.csv';//檔案絕對路徑
 $downloadName = 'downloadFile.csv';//下載下傳後的檔案名

 //輸入檔案标簽
 Header("Content-type: application/octet-stream");
 Header("Accept-Ranges: bytes");
 Header("Accept-Length: " . filesize($pathToFile));
 Header("Content-Disposition: filename=" . $downloadName);

 //輸出檔案内容
 $file = fopen($pathToFile, "r");
 echo fread($file, filesize($pathToFile));
 fclose($file);
 //或
 //readfile($pathToFile);
 
           

其中fread()與readfile()的差別可以參考https://segmentfault.com/q/10...

但是有時候為了節省帶寬,避免瞬時流量過大而造成網絡堵塞,就要考慮下載下傳限速的問題

二、下載下傳檔案限速

$pathToFile = 'myfile.csv';//檔案絕對路徑
  $downloadName = 'downloadFile.csv';//下載下傳後的檔案名
  $download_rate = 30;// 設定下載下傳速率(30 kb/s)
  if (file_exists($pathToFile) && is_file($pathToFile)) {
      header('Cache-control: private');// 發送 headers
      header('Content-Type: application/octet-stream');
      header('Content-Length: ' . filesize($pathToFile));
      header('Content-Disposition: filename=' . $downloadName);
      flush();// 重新整理内容
      $file = fopen($pathToFile, "r");
      while (!feof($file)) {
          print fread($file, round($download_rate * 1024));// 發送目前部分檔案給浏覽者
          flush();// flush 内容輸出到浏覽器端
          sleep(1);// 終端1秒後繼續
      }
      fclose($file);// 關閉檔案流
  } else {
      abort(500, '檔案' . $pathToFile . '不存在');
  }
  
           

此時出現一個問題,當$download_rate>1kb時,檔案正常下載下傳;當$download_rate<1kb時,檔案要等一會兒才下載下傳,究其原因是因為buffer的問題。

  • buffer是一個記憶體位址空間,Linux系統預設大小一般為4096(1kb),即一個記憶體頁。主要用于存儲速度不同步的裝置或者優先級不同的裝置之間傳辦理資料的區域。舉個例子,你打開文本編輯器編輯一個檔案的時候,你每輸入一個字元,作業系統并不會立即把這個字元直接寫入到磁盤,而是先寫入到buffer,當寫滿了一個buffer的時候,才會把buffer中的資料寫入磁盤。同樣的道理,當執行echo,print的時候,輸出并沒有立即通過tcp傳給用戶端浏覽器顯示,而是将資料寫入php buffer。php output_buffering機制,意味在tcp buffer之前,建立了一新的隊列,資料必須經過該隊列。當一個php buffer寫滿的時候,腳本程序會将php buffer中的輸出資料交給系統核心交由tcp傳給浏覽器顯示。是以,資料會依次寫到這幾個地方echo/pring -> php buffer -> tcp buffer -> browser。資料:http://blog.csdn.net/superhos...
  • 在沒有開啟緩存時,腳本輸出的内容都在伺服器端處于等待輸出的狀态,flush()可以将等待輸出的内容立即發送到用戶端。
  • 開啟緩存後,腳本輸出的内容存入了輸出緩存中,這時沒有處于等待輸出狀态的内容,你直接使用flush()不會向用戶端發出任何内容。而ob_flush()的作用就是将本來存在輸出緩存中的内容取出來,設定為等待輸出狀态,但不會直接發送到用戶端,這時你就需要先使用ob_flush()再使用flush(),用戶端才能立即獲得腳本的輸出。
  • 以及這篇文章同樣講述了ob_flush()和flush()的差別http://www.laruence.com/2010/...

但是這種方法将檔案内容從磁盤經過一個固定的 buffer 去循環讀取到記憶體,再發送給前端 web 伺服器,最後才到達使用者。當需要下載下傳的檔案很大的時候,這種方式将消耗大量記憶體,甚至引發 php 程序逾時或崩潰,接下來就使用到X-Sendfile。

三、X-Sendfile

  • X-Sendfile 是一種将檔案下載下傳請求由後端應用轉交給前端 web

    伺服器處理的機制,它可以消除後端程式既要讀檔案又要處理發送的壓力,進而顯著提高伺服器效率,特别是處理大檔案下載下傳的情形下。

我是用的nginx,是以apache請參考https://tn123.org/mod_xsendfile/

①首先在配置檔案中添加

location /download/ {
  internal;
  root   /some/path;//絕對路徑
}
           
  • internal 表示這個路徑隻能在 Nginx 内部通路,不能用浏覽器直接通路防止未授權的下載下傳
  • 注意添加在location / {...}的前面
  • 這樣你在代碼中使用時,檔案路徑就可以寫成“/download/myfile.csv”

②重新開機Nginx,寫代碼

$pathToFile = 'myfile.csv';//檔案絕對路徑
$downloadName = 'downloadFile.csv';//下載下傳後的檔案名
$download_rate = 30;// 設定下載下傳速率(30 kb/s)
if (file_exists($pathToFile) && is_file($pathToFile)) {
    return (new Response())->withHeaders([
      'Content-Type'        => 'application/octet-stream',
      'Content-Disposition' => 'attachment;filename=' . $downloadName,
      'X-Accel-Redirect'    => $pathToFile,//讓Xsendfile發送檔案
      'X-Sendfile'          => $pathToFile,
      'X-Accel-Limit-Rate'  => $download_rate,
    ]);
}else {
    abort(500, '檔案' . $pathToFile . '不存在');
}
           
PHP下載下傳檔案、限速、X-sendfile

如果你還想了解更多關于X-sendfile,請自行查閱

記得關注我呦

PHP下載下傳檔案、限速、X-sendfile