2017年2月14日 星期二

PHP 透過 xsendfile 下載檔案

原本使用 PHP 來做檔案下載控制,包括限制下載速率,同一IP的連線數目。可是,CPU 使用率會飆高,記憶體的使用率也不低。試過 Nginx,Apache 的 event 模式,結果都差不多。

 後來,看到可以設定 X-SendFile header,交由 Apache 控制下載,今天花了一天的時間,終於搞定。

需要安裝的模組,包括
  • mod_limitipconn: 限制個別IP的連線數
  • mod_ratelimit: 限制下載的速度,如 150 為 150KB/s
  • mod_xsendfile: X-Sendfile 模組
單純針對直接的檔案下載,設定如下,其中將 mp4 強制下載存檔,否則支援 mp4 的瀏覽器會直接播放。
<Directory "/var/www/html/vod_dl">
    <Files *.mp4>
        ForceType application/octet-stream
        Header set Content-Disposition attachment
    </Files>

    <IfModule mod_limitipconn.c>
        # limit concurrent connection for 2
        MaxConnPerIP 2
    </IfModule>

    <IfModule mod_ratelimit.c>
       SetOutputFilter RATE_LIMIT
        SetEnv rate-limit 850
    </IfModule>

</Directory>

有可能需要設定 XSendFilePath,例如 "XSendFilePath /home/video_data",不然會出現 "The given path was above the root path .... " 的錯誤。

因為我的下載程式的 url 是長的這樣,
http://10.161.81.118/dl_video.php?fn=105S101.mp4

設定時,需針對 dl_video.php 做設定,因此下面的設定也會套用到像 dl_videx_xxx.php 的程式。

<Files dl_video*>
    <IfModule mod_xsendfile.c>

        # 啟 xsendfile
        XSendFile On
        SetEnv MOD_X_SENDFILE_ENABLED 1
    </IfModule>

    <IfModule mod_limitipconn.c>
        # 限制每一IP同時連線的數目
        MaxConnPerIP 8
    </IfModule>

    <IfModule mod_ratelimit.c>

       # 限制下載速率 850KB/s
       SetOutputFilter RATE_LIMIT
       SetEnv rate-limit 850
    </IfModule>
</Files>
 


PHP 的程式長得像下面這樣,只要設定 header,就會轉由 Apache 控制下載。
// 權限檢查 .....
// 檔案檢查 .....
//  .....
header("X-Sendfile: {$fullname}");
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . basename($fname) . '"');

注意,$fullname 是檔案系統中的實際目錄名稱,例如
/var/www/html/mp4_files/105S101.mp4


這樣子,就算開到 1千多個檔,CPU 的使用率不高,記憶體大概也只在 500MB左右。

Nginx 也有類似功能,不過它是接受URI,正如一般的 request,而非目錄檔名,我個人覺得比較不盡理想。

沒有留言: