2009-02-28 103 views
22

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

+1

这个问题的一部分可以通过支持`Range`头来解决,所以浏览器可以暂停和恢复下载。这是一个问题处理:http://stackoverflow.com/questions/157318/resumable-downloads-when-using-php-to-send-the-file – grossvogel 2010-11-08 19:01:15

+0

也请看看[this](http:// stackoverflow.com/a/6527829/1469208)和[this](http://stackoverflow.com/a/21354337/1469208)SO答案。 – trejder 2014-02-21 13:06:43

+0

在这里看到 - https://stackoverflow.com/questions/47827768/how-to-download-large-files-with-php/47827769#47827769 – Aplextor 2017-12-15 07:40:56

回答

2

要下载文件,我能想到的将是将文件放在一个临时位置,并给他们一个独特的网址,他们可以通过普通的HTTP下载的最简单方法。

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

+0

我不喜欢这个答案,因为它需要额外使用crons或者这样删除旧文件,这给系统增加了另一层复杂性和另一个故障点,但我不打算把它投下来,因为这是一个有效的答案。 – UnkwnTech 2009-02-28 00:26:44

+0

@Unkwntech - 不需要cron,正如上面提到的那样,在生成新文件的时候你也可以放弃旧的文件。许多网站执行类似cron的任务作为另一个呼叫的一部分。 – 2009-02-28 00:41:35

+0

@安德鲁格兰特,你说的没错,没有CRON就是对的,但我仍然觉得它增加了一个额外的失败点,而我的回答并不认为它会增加额外的失败点。 – UnkwnTech 2009-02-28 00:54:54

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()的一切工作都要流指定到客户端的整个文件,请务必设置无限的时间限制,否则在文件完成流式传输之前您可能会耗尽时间。

+0

当然,如果这不是一个exe,MIME类型应该是不同的。 – configurator 2009-02-28 00:30:10

1

如果您正在使用的lighttpd作为Web服务器,用于安全下载的替代方法是使用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); 
?> 

当然,这取决于文件的大小,使用readfile()Unkwntech建议的非常好。并且使用garrow建议的xsendfile是Apache也支持的另一个好主意。

8

最好的办法是依靠莱蒂或Apache,但如果在PHP中,我会用PEAR's HTTP_Download(无需推倒重来等),有一些不错的功能,如:

  • 基本节流机构
  • 范围(部分下载和恢复)

intro/usage docs见。

0

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

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> 

这就是我所做的,因为去爸爸杀死了2分30秒左右后,运行该脚本....这防止问题和隐藏实际的文件。

然后,您可以设置一个cron作业来删除定期符号链接....

这整个过程,然后将文件发送到浏览器并不要紧运行多长时间,因为它的不是脚本。

0

我已经使用在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; 
} 
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); 
    } 
} 
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(); 
0

我有同样的问题, 我的问题,加入这个会议开始 session_cache_limiter(“无”)之前得到解决;

6

分块文件是在PHP中最快/最简单的方法,如果你不能或不想使用一些更专业的像cURLmod-xsendfileApache或一些dedicated script

$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移植。在200 MB的文件上测试,其中readfile()死亡,即使最大允许内存限制设置为1G,这也是下载文件大小的五倍。

顺便说一句:我也测试了这个文件>2GB,但PHP只管理写第一个2GB的文件,然后打破了连接。与文件相关的功能(fopen,fread,fseek)使用INT,因此您最终达到了2GB的限制。上述解决方案(即mod-xsendfile)似乎是这种情况下唯一的选择。

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

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