每一個可以努力的日子,都是一份厚禮。
使用 Nginx 的 X-Sendfile 機制提升 PHP 文件下載性能
很多時候用戶需要從網站下載文件,如果文件是可以通過一個固定鏈接公開獲取的,那麼我們只需將文件存放到 webroot 下的目錄里就好。但大多數情況下,我們需要做權限控制,例如下載 PDF 賬單,又例如下載網盤裡的檔案。這時,我們通常藉助於腳本代碼來實現,而這無疑會增加服務器的負擔。
例如下面的代碼:
<?php // 用戶身份認證,若驗證失敗跳轉 authenticate(); // 獲取需要下載的文件,若文件不存在跳轉 $file = determine_file(); // 讀取文件內容 $content=file_get_contents($file); // 發送合適的 HTTP 頭 header("Content-type: application/octet-stream"); header('Content-Disposition: attachment; filename="' . basename($file) . '"'); header("Content-Length: ". filesize($file)); echo $content; // 或者 readfile($file); ?> |
一、這樣做有什麼問題?
這樣做意味着我們的程序需要將文件內容從磁盤經過一個固定的 buffer 去循環讀取到內存,再發送給前端 web 服務器,最後才到達用戶。當需要下載的文件很大的時候,這種方式將消耗大量內存,甚至引發 php 進程超時或崩潰。Cache 也很頭疼,更不用說中斷重連的情況了。
一個理想的解決方式應該是,由 php 程序進行權限檢查等邏輯判斷,一切通過後,讓前台的 web 服務器直接將文件發送給用戶——像 Nginx 這樣的前台更善於處理靜態文件。這樣一來 php 腳本就不會被 I/O 阻塞了。
二、什麼是 X-Sendfile?
X-Sendfile 是一種將文件下載請求由後端應用轉交給前端 web 服務器處理的機制,它可以消除後端程序既要讀文件又要處理髮送的壓力,從而顯著提高服務器效率,特別是處理大文件下載的情形下。
X-Sendfile 通過一個特定的 HTTP header 來實現:在 X-Sendfile 頭中指定一個文件的地址來通告前端 web 服務器。當 web 服務器檢測到後端發送的這個 header 後,它將忽略後端的其他輸出,而使用自身的組件(包括 緩存頭 和 斷點重連 等優化)機制將文件發送給用戶。
不過,在使用 X-Sendfile 之前,我們必須明白這並不是一個標準特性,在默認情況下它是被大多數 web 服務器禁用的。而不同的 web 服務器的實現也不一樣,包括規定了不同的 X-Sendfile 頭格式。如果配置失當,用戶可能下載到 0 字節的文件。
使用 X-Sendfile 將允許下載非 web 目錄中的文件(例如/root/),即使文件在 .htaccess 保護下禁止訪問,也會被下載。
sendfile 頭 | 使用的 web 服務器 |
---|---|
X-Sendfile | Apache, Lighttpd v1.5, Cherokee |
X-LIGHTTPD-send-file | Lighttpd v1.4 |
X-Accel-Redirect | Nginx, Cherokee |
使用 X-SendFile 的缺點是你失去了對文件傳輸機制的控制。例如如果你希望在完成文件下載後執行某些操作,比如只允許用戶下載文件一次,這個 X-Sendfile 是沒法做到的,因為後台的 php 腳本並不知道下載是否成功。
三、怎樣使用?
Apache 請參考 mod_xsendfile 模塊。下面我介紹 Nginx 的用法。
Nginx 默認支持該特性,不需要加載額外的模塊。只是實現有些不同,需要發送的 HTTP 頭為 X-Accel-Redirect。另外,需要在配置文件中做以下設定
location /protected/ { internal; root /some/path; } |
internal 表示這個路徑只能在 Nginx 內部訪問,不能用瀏覽器直接訪問防止未授權的下載。
於是 PHP 發送 X-Accel-Redirect 給 Nginx:
<?php $filePath = '/protected/iso.img'; header('Content-type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . basename($file) . '"'); //讓Xsendfile發送文件 header('X-Accel-Redirect: '.$filePath); ?> |
這樣用戶就會下載到 /some/path/protected/iso.img 這個路徑下的文件。
如果你想發送的是 /some/path/iso.img 文件,那麼 Nginx 配置應該是
location /protected/ { internal; alias /some/path/; # 注意最後的斜杠 } |
參考鏈接:
Nginx 官方 XSendfile 文檔
讓PHP更快的提供文件下載
Yii Framework 對 XSendfile 的支持
這篇文章由lovelucy於2012-06-20 00:36發表在編程。你可以訂閱RSS 2.0 也可以發表評論或引用到你的網站。除特殊說明外文章均為本人原創,並遵從署名-非商業性使用-相同方式共享創作協議,轉載或使用請註明作者和來源,尊重知識分享。 |
批評不自由
則讚美無意義
Google Chrome 31.0.1650.63 Windows 7 大約9年前
您好,我在cnBeta上拜讀您發表的文章《使用 Nginx 的 X-Sendfile 機制提升 PHP 文件下載性能》,感覺您對這一方面比較有興趣。
目前我們也在做一款IT內容類產品,招募前期用戶試用,提意見。
不您是否有興趣?
Google Chrome 27.0.1453.116 Mac OS X Mountain Lion 10_8_4 大約11年前
location /protected/ {
internal;
alias /some/path/; # 注意最後的斜杠
}
alias 是什麼?是webserver用戶嘛?
Google Chrome 28.0.1500.72 Windows 7 大約11年前
alias 是 Nginx 配置文件的指令。這個詞不用改
Netscape Navigator 5.0 iPhone iPhone OS 4_3_5 like Mac OS X 大約12年前
Jitbit那個東西所有附件是用二進制存在mssql裡面,要下載的時候數據庫還會多做一次無用的緩存,性能更差了.另外從文中的描述,sendfile做了至少兩件事,一是開了多進程負責傳送文件,二是負責傳送文件的程序不是php因此不會timeout.覺得對於大文件的傳送是否應該託管給其他網絡io向的應用程序去完成,而且不應該繼續使用http端口.
Google Chrome 19.0.1084.56 Windows 7 大約12年前
搞笑么。。用戶是用瀏覽器訪問的網頁,不用 http 端口,怎麼下載