PHP 實作 FTP 連線列表檔案以及 Streaming 下載大檔案

本文使用 PHP 8.1 實作。

用 Docker 準備 FTP 環境

先參考這篇 [Docker] 使用 Docker 建置 FTP(SFTP) 環境 建立 FTP 環境。

要注意它建立的 FTP Port 為 23,因為也沒有掛上 Volume,所以需要有一個 FTP 客戶端連進去測試,可以使用 Filezilla 或是 Cyberduck。

docker run -d --name ftpd_server -p 23:21 -p 30010-30019:30010-30019 -e "FTP_PASSIVE_PORTS=30010:30019" -e FTP_USER_HOME=/home/miles -e FTP_USER_NAME=miles -e FTP_USER_PASS=123456 -e "PUBLICHOST=localhost" stilliard/pure-ftpd

使用 MLSD 列出 FTP 項目

<?php
function listFtpFiles($hostname, $port, $ftpUsername, $ftpPassword) {

    $ftpConnection = ftp_connect($hostname, $port);

    if (!$ftpConnection) {
        echo "can not connect to ftp server<br>";
        return;
    }

    $login = ftp_login($ftpConnection, $ftpUsername, $ftpPassword);

    if (!$login) {
        echo "login error!<br>";
        return;
    }

    // pasv
    ftp_pasv($ftpConnection, true);

    $files = ftp_mlsd($ftpConnection, ".");

    ftp_close($ftpConnection);

    return $files;
}

實際呼叫:

$files = listFtpFiles(
    hostname: '127.0.0.1',
    port: 23,
    ftpUsername: 'miles',
    ftpPassword: '123456'
);

print_r($files);

FTP 連線後,透過 fread 下載檔案

<?php
function ftpDownloadFile($hostname, $port = 21, $ftpUsername, $ftpPassword, $file) {

    $ftpConnection = ftp_connect($hostname, $port);

    if (!$ftpConnection) {
        echo "can not connect to ftp server<br>";
        return;
    }

    $login = ftp_login($ftpConnection, $ftpUsername, $ftpPassword);

    if (!$login) {
        echo "login error!<br>";
        return;
    }

    // pasv
    ftp_pasv($ftpConnection, true);

    $remoteFile = fopen("ftp://$ftpUsername:$ftpPassword@$hostname:$port/$file", 'rb');

    if (!$remoteFile) {
        ftp_close($ftpConnection);
        echo "can not download file<br>";
        return;
    }

    // get extension name
    $fileExtension = pathinfo($file, PATHINFO_EXTENSION);

    switch( $fileExtension ) {
     case "pdf": $contentType="application/pdf"; break;
     case "exe": $contentType="application/octet-stream"; break;
     case "zip": $contentType="application/zip"; break;
     case "rar": $contentType="application/x-rar-compressed"; break;
     case "doc": $contentType="application/msword"; break;
     case "xls": $contentType="application/vnd.ms-excel"; break;
     case "ppt": $contentType="application/vnd.ms-powerpoint"; break;
     case "gif": $contentType="image/gif"; break;
     case "png": $contentType="image/png"; break;
     case "jpeg":
     case "jpg": $contentType="image/jpg"; break;
     case "mp3": $contentType="audio/mpeg"; break;
     case "wav": $contentType="audio/x-wav"; break;
     case "mpeg":
     case "mpg":
     case "mpe": $contentType="video/mpeg"; break;
     case "mov": $contentType="video/quicktime"; break;
     case "avi": $contentType="video/x-msvideo"; break;
     //禁止下面幾種類型的檔案被下載
     case "php":
     case "htm":
     case "html":
     case "txt": die("Cannot be used for ". $fileExtension ." files!"); break;

     default: $contentType="application/force-download";
   }

    header('Content-Type: ' . $contentType);
    header('Content-Disposition: attachment; filename="' . basename($file) . '"');

    // read file to download
    while (!feof($remoteFile)) {
        echo fread($remoteFile, 8192); // 8192
        ob_flush();
        flush();
    }

    // close
    fclose($remoteFile);
    ftp_close($ftpConnection);
    return;
}

實際使用

ftpDownloadFile('127.0.0.1', 23, 'miles', '123456', 'example.zip');