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,而非目錄檔名,我個人覺得比較不盡理想。

使用 docker 

原先的環境是使用 CentOS,但其已於數年前被宣告死亡,只能再找其他途徑。使用 docker.hub 的 PHP 官方 image -- php:7.4.33-apache-bullseye,bullseye 為 Debian 當前的舊的穩定(oldstable)版。

Debian 官方沒有 mod_limitipconn,必須自己下載編譯安裝。變通的方式,是在 docker 中編譯好,把用到的檔案 copy 出來。

apt install apache2-dev wget
wget https://dominia.org/djao/limit/mod_limitipconn-0.24.tar.bz2
tar -xvf mod_limitipconn-0.24.tar.bz2
cd mod_limitipconn-0.24

make
make install
--
## 需要的檔案是下面兩個
/usr/lib/apache2/modules/mod_limitipconn.so
/etc/apache2/mods-available/limitipconn.load

Dockerfile 如下。

# Debian 當前的舊的穩定(oldstable)版
FROM php:7.4.33-apache-bullseye

# Install required extensions
# libicu-dev -- required by php ext: intl
# libpq-dev -- required by php ext: pgsql pdo_pgsql
# libonig-dev -- required by php ext: mbstring
RUN apt-get update && \
    apt-get -y install libapache2-mod-xsendfile \
        libicu-dev libpq-dev libonig-dev \
    && docker-php-ext-install intl pdo \
		mysqli pdo_mysql  pgsql pdo_pgsql \
        mbstring

COPY conf/ocw_vod_dl.conf /etc/apache2/conf-enabled
COPY conf/mod_limitipconn.so /usr/lib/apache2/modules
COPY conf/limitipconn.load /etc/apache2/mods-available

RUN a2enmod headers limitipconn ratelimit xsendfile

EXPOSE 80
CMD ["apache2-foreground"]

docker-compose_tst.yml 如下。

# version: '2'

services:
  ocw-dl:
    container_name: ocw-dl-server
    build: .
    domainname: ocw.aca
    hostname: ocw-dl-server
    working_dir: /var/www/html
    volumes:
        - ../web:/var/www/html
        - /ext_hd8t/ajax/wk_backup/ocw_bak/vod:/var/www/html/vod_dl
    ports:
        - 8380:80

以上為 2024-01-23 更新,陽明山下雪之日。







沒有留言:

張貼留言

網誌存檔