web-dev-qa-db-ja.com

大きなファイルを確実にダウンロードするPHP

サーバー上にファイルをレシピエントに送信するためのphpスクリプトがあります。固有のリンクを取得すると、大きなファイルをダウンロードできます。転送に問題があり、ファイルが破損しているか、完了しないことがあります。大きなファイルを送信するより良い方法があるかどうか私は思っています

コード:

$f = fopen(DOWNLOAD_DIR.$database[$_REQUEST['fid']]['filePath'], 'r');
while(!feof($f)){
    print fgets($f, 1024);
}
fclose($f);

私は次のような機能を見てきました

http_send_file
http_send_data

しかし、それらが機能するかどうかはわかりません。

この問題を解決する最良の方法は何ですか?

よろしく
erwing

24
Erwing

本当に大きなファイルを送信していて、これによる影響を心配している場合は、x-sendfileヘッダーを使用できます。

SOQから sing-xsendfile-with-Apache-php 、howto blog.adaniels.nl:how-i-php-x-sendfile /

11
garrow

cURLのようなもう少し専門的なものを利用できない、または利用したくない場合は、ファイルのチャンク化がPHPで最も速く/最も簡単な方法です。 _mod-xsendfile_ on Apache または一部の 専用スクリプト

_$filename = $filePath.$filename;

$chunksize = 5 * (1024 * 1024); //5 MB (= 5 242 880 bytes) per one chunk of file.

if(file_exists($filename))
{
    set_time_limit(300);

    $size = intval(sprintf("%u", filesize($filename)));

    header('Content-Type: application/octet-stream');
    header('Content-Transfer-Encoding: binary');
    header('Content-Length: '.$size);
    header('Content-Disposition: attachment;filename="'.basename($filename).'"');

    if($size > $chunksize)
    { 
        $handle = fopen($filename, 'rb'); 

        while (!feof($handle))
        { 
          print(@fread($handle, $chunksize));

          ob_flush();
          flush();
        } 

        fclose($handle); 
    }
    else readfile($path);

    exit;
}
else echo 'File "'.$filename.'" does not exist!';
_

richnetapps.com / NeedBee から移植されました。最大許容メモリ制限が_1G_に設定されていても、ダウンロードされたファイルサイズの5倍の200 MBファイルでテストされ、readfile()が終了しました。

ところで、私はこれをファイル_>2GB_でもテストしましたが、PHPは最初にファイルの_2GB_を書き込んでから接続を切断することができました。ファイル関連の関数(fopen、 fread、fseek)はINTを使用するため、最終的に_2GB_の制限に達します。上記の解決策(つまり、_mod-xsendfile_)がこの場合の唯一のオプションのようです。

[〜#〜] edit [〜#〜]自分を作る100% ファイルが_utf-8_に保存されていること。省略した場合、ダウンロードしたファイルが破損します。これは、このソリューションがprintを使用してファイルのチャンクをブラウザーにプッシュするためです。

8
trejder

最善の解決策は、lightyまたはApacheに依存することですが、PHPの場合は PEARのHTTP_Download を使用します(ホイールなどを再発明する必要はありません)。

  • 基本的なスロットルメカニズム
  • 範囲(部分的なダウンロードと再開)

intro/usage docs を参照してください。

8
PrettyCoder

実際のファイルへのシンボリックリンクを作成し、ダウンロードリンクがシンボリックリンクを指すようにします。次に、ユーザーがDLリンクをクリックすると、実際のファイルからファイルがダウンロードされますが、シンボリックリンクから名前が付けられます。シンボリックリンクの作成には数ミリ秒かかります。ファイルを新しい名前にコピーしてそこからダウンロードしようとしています。

例えば:

<?php

// validation code here

$realFile = "Hidden_Zip_File.Zip";
$id = "UserID1234";

if ($_COOKIE['authvalid'] == "true") {
    $newFile = sprintf("myzipfile_%s.Zip", $id); //creates: myzipfile_UserID1234.Zip

    system(sprintf('ln -s %s %s', $realFile, $newFile), $retval);

    if ($retval != 0) {
        die("Error getting download file.");
    }

    $dlLink = "/downloads/hiddenfiles/".$newFile;
}

// rest of code

?>

<a href="<?php echo $dlLink; ?>Download File</a>

Go Daddyが2分30秒ほど後にスクリプトの実行を停止するので、これは私がやったことです。

その後、CRONジョブを設定して、定期的にシンボリックリンクを削除できます。

このプロセス全体がファイルをブラウザに送信し、スクリプトではないため、実行時間は問題ではありません。

3
Hummdis

ファイルをダウンロードするための最も簡単な方法は、ファイルを一時的な場所に置き、通常のHTTP経由でダウンロードできる一意のURLを与えることです。

これらのリンクを生成する一部として、X時間以上経過したファイルを削除することもできます。

3
Andrew Grant

これをいくつかのプロジェクトで使用してきましたが、これまでのところ非常にうまく機能しています。

/**
 * Copy a file's content to php://output.
 *
 * @param string $filename
 * @return void
 */
protected function _output($filename)
{
    $filesize = filesize($filename);

    $chunksize = 4096;
    if($filesize > $chunksize)
    {
        $srcStream = fopen($filename, 'rb');
        $dstStream = fopen('php://output', 'wb');

        $offset = 0;
        while(!feof($srcStream)) {
            $offset += stream_copy_to_stream($srcStream, $dstStream, $chunksize, $offset);
        }

        fclose($dstStream);
        fclose($srcStream);   
    }
    else 
    {
        // stream_copy_to_stream behaves() strange when filesize > chunksize.
        // Seems to never hit the EOF.
        // On the other handside file_get_contents() is not scalable. 
        // Therefore we only use file_get_contents() on small files.
        echo file_get_contents($filename);
    }
}
3

Webサーバーとしてlighttpdを使用している場合、安全なダウンロードの代替手段は ModSecDownload を使用することです。サーバーの設定が必要ですが、PHPスクリプトではなく、Webサーバーにダウンロード自体を処理させます。

ダウンロードURLの生成は(ドキュメントから取得した)そのようになり、もちろん承認されたユーザーに対してのみ生成できます。

_<?php

  $secret = "verysecret";
  $uri_prefix = "/dl/";

  # filename
  # please note file name starts with "/" 
  $f = "/secret-file.txt";

  # current timestamp
  $t = time();

  $t_hex = sprintf("%08x", $t);
  $m = md5($secret.$f.$t_hex);

  # generate link
  printf('<a href="%s%s/%s%s">%s</a>',
         $uri_prefix, $m, $t_hex, $f, $f);
?>
_

もちろん、ファイルのサイズによっては、 nkwntech によって提案されているようなreadfile()の使用が優れています。そして、 garrow によって提案されたxsendfileを使用することも、Apacheによってサポートされている別の良いアイデアです。

1
lpfavreau
header("Content-length:".filesize($filename));
header('Content-Type: application/Zip'); // Zip file
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="downloadpackage.Zip"');
header('Content-Transfer-Encoding: binary');
ob_end_clean();
readfile($filename);
exit();
1
ahmed

私が過去にこれを行ったとき、私はこれを使用しました:

set_time_limit(0); //Set the execution time to infinite.
header('Content-Type: application/exe'); //This was for a LARGE exe (680MB) so the content type was application/exe
readfile($fileName); //readfile will stream the file.

これらの3行のコードは、ダウンロードのすべての作業を実行します readfile() は、指定されたファイル全体をクライアントにストリーミングします。ファイルのストリーミングが完了する前。

1
UnkwnTech

これが大きなファイルに適しているかどうかはわかりません。ユーザーがダウンロードを完了するまでダウンロードスクリプトのスレッドが実行され、Apacheのようなものを実行している場合、Apacheは長時間実行されるように設計されていないため、50以上の同時ダウンロードがサーバーをクラッシュさせる可能性があります。同時にスレッド。もちろん、Apacheスレッドが何らかの理由で終了し、ダウンロードが進行している間、ダウンロードがバッファのどこかに置かれている場合、私は間違っている可能性があります。

0
user257124

これは、256MBのメモリ制限があるサーバー上の200+ MBのサイズのファイルでテストされます。

header('Content-Type: application/Zip');
header("Content-Disposition: attachment; filename=\"$file_name\"");
set_time_limit(0);
$file = @fopen($filePath, "rb");
while(!feof($file)) {
  print(@fread($file, 1024*8));
  ob_flush();
  flush();
}
0
Alex

私は同じ問題を抱えていましたが、セッションsession_cache_limiter( 'none');を開始する前にこれを追加することで問題が解決しました。

0
Nawras

Readfileのphpマニュアルエントリのコメントにある次のスニペットを使用しました。

function _readfileChunked($filename, $retbytes=true) {
    $chunksize = 1*(1024*1024); // how many bytes per chunk
    $buffer = '';
    $cnt =0;
    // $handle = fopen($filename, 'rb');
    $handle = fopen($filename, 'rb');
    if ($handle === false) {
        return false;
    }
    while (!feof($handle)) {
        $buffer = fread($handle, $chunksize);
        echo $buffer;
        ob_flush();
        flush();
        if ($retbytes) {
            $cnt += strlen($buffer);
        }
    }
    $status = fclose($handle);
    if ($retbytes && $status) {
        return $cnt; // return num. bytes delivered like readfile() does.
    }
    return $status;
}
0
periklis