在PHP中可靠地下载大文件

时间:2009-02-28 00:12:22

标签: php download

我在服务器上有一个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

13 个答案:

答案 0 :(得分:11)

如果您要发送真正大的文件并担心它会产生的影响,您可以使用x-sendfile标头。

从SOQ using-xsendfile-with-apache-php开始,如何blog.adaniels.nl : how-i-php-x-sendfile/

答案 1 :(得分:8)

最好的解决方案是依赖lighty或apache,但如果在PHP中,我会使用PEAR's HTTP_Download(不需要重新发明轮子等),有一些不错的功能,如:

  • 基本限制机制
  • 范围(部分下载和恢复)

intro/usage docs

答案 2 :(得分:8)

如果你不能或不想在{{3}上cURLmod-xsendfile使用更专业的东西,那么分块文件是PHP中最快/最简单的方法。或某些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!';

dedicated script / richnetapps.com移植。在readfile()死亡的200 MB文件上进行了测试,即使允许的最大内存限制设置为1G,也是下载文件大小的五倍。

BTW:我也在文件>2GB上对此进行了测试,但PHP只设法编写了文件的第一个2GB,然后断开了连接。与文件相关的函数(fopen,fread,fseek)使用INT,因此最终达到2GB的限制。上面提到的解决方案(即mod-xsendfile)似乎是这种情况下的唯一选择。

编辑让自己 100%将您的文件保存在utf-8中。如果省略,则下载的文件将被破坏。这是因为此解决方案使用print将文件块推送到浏览器。

答案 3 :(得分:3)

对于下载文件,我能想到的最简单的方法是将文件放在临时位置,并为他们提供一个可以通过常规HTTP下载的唯一URL。

作为生成这些链接的部分,您还可以删除超过X小时的文件。

答案 4 :(得分:3)

创建指向实际文件的符号链接,并在符号链接上创建下载链接点。然后,当用户点击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作业以定期删除符号链接....

然后整个过程会将文件发送到浏览器,因为它不是脚本,所以运行的时间并不重要。

答案 5 :(得分:3)

我们已经在几个项目中使用它,到目前为止它工作得非常好:

/**
 * 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);
    }
}

答案 6 :(得分:1)

过去我做过这个时我就用过这个:

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()将流式传输指定给客户端的整个文件,并确保设置无限时间限制,否则您可能会在此之前耗尽时间文件已完成流式传输。

答案 7 :(得分:1)

如果您使用lighttpd作为网络服务器,安全下载的替代方法是使用ModSecDownload。它需要服务器配置,但您将让Web服务器处理下载本身而不是PHP脚本。

生成下载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);
?>

当然,根据文件的大小,使用Unkwntech提议的readfile()非常好。使用garrow提出的xsendfile是Apache支持的另一个好主意。

答案 8 :(得分:1)

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();

答案 9 :(得分:0)

我不确定这对大文件是个好主意。如果下载脚本的线程运行直到用户完成下载,并且您正在运行类似Apache的内容,则只有50个或更多并发下载可能会导致服务器崩溃,因为Apache不能运行大量长时间运行线程同时。当然我可能错了,如果apache线程以某种方式终止并且下载在下载过程中位于某个缓冲区中。

答案 10 :(得分:0)

我使用了php手册条目的注释中的以下代码:readfile:

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;
}

答案 11 :(得分:0)

我有同样的问题, 我的问题通过在开始会话之前添加这个来解决 session_cache_limiter(&#39;无&#39);

答案 12 :(得分:0)

在具有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();
}