PHP下載文件、限速、X-sendfile

1、普通文件下載

①laravel框架HTTP響應的download方法php

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

②PHP實現html

$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...
可是有時候爲了節省帶寬,避免瞬時流量過大而形成網絡堵塞,就要考慮下載限速的問題前端

2、下載文件限速

$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的問題。nginx

  • 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。laravel

3、X-Sendfile

  • X-Sendfile 是一種將文件下載請求由後端應用轉交給前端 web
    服務器處理的機制,它能夠消除後端程序既要讀文件又要處理髮送的壓力,從而顯著提升服務器效率,特別是處理大文件下載的情形下。

我是用的nginx,因此apache請參考https://tn123.org/mod_xsendfile/
①首先在配置文件中添加web

location /download/ {
  internal;
  root   /some/path;//絕對路徑
}
  • internal 表示這個路徑只能在 Nginx 內部訪問,不能用瀏覽器直接訪問防止未受權的下載
  • 注意添加在location / {...}的前面
  • 這樣你在代碼中使用時,文件路徑就能夠寫成「/download/myfile.csv」

②重啓Nginx,寫代碼apache

$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 . '不存在');
}

clipboard.png

若是你還想了解更多關於X-sendfile,請自行查閱segmentfault

記得關注我呦
圖片描述後端