如果您打开一个新文件,写入它然后调用 fpassthru(),它将不起作用。您需要先调用 rewind() 将文件指针设置到文件开头。
(PHP 4, PHP 5, PHP 7, PHP 8)
fpassthru — 输出文件指针上所有剩余数据
从当前位置读取给定文件指针到 EOF,并将结果写入输出缓冲区。
如果您已经向文件写入数据,则可能需要调用 rewind() 将文件指针重置到文件开头。
如果您只想将文件的内容转储到输出缓冲区,而无需先修改它或跳转到特定的偏移量,则可以使用 readfile(),它可以为您节省 fopen() 调用。
返回从 stream
读取并传递到输出的字符数。
示例 #1 使用 fpassthru() 处理二进制文件
<?php
// 以二进制模式打开文件
$name = './img/ok.png';
$fp = fopen($name, 'rb');
// 发送正确的头部
header("Content-Type: image/png");
header("Content-Length: " . filesize($name));
// 转储图片并停止脚本
fpassthru($fp);
exit;
?>
注意:
在 Windows 系统上对二进制文件使用 fpassthru() 时,应确保以二进制模式打开文件,方法是在对 fopen() 的调用中使用的模式后附加一个
b
。即使您的系统不需要,也建议在处理二进制文件时使用
b
标志,以便您的脚本更具可移植性。
如果您打开一个新文件,写入它然后调用 fpassthru(),它将不起作用。您需要先调用 rewind() 将文件指针设置到文件开头。
对于大于大约 5MB 的文件,Passthru 对我来说不起作用。只需添加 "ob_end_clean()",现在一切正常,包括 > 50MB 的文件。
$ToProtectedFile=$pathUnder.$filename
$handle = @fopen($ToProtectedFile, "rb");
@header("Cache-Control: no-cache, must-revalidate");
@header("Pragma: no-cache"); //让 ie 满意
@header("Content-Disposition: attachment; filename= ".$NomFichier);
@header("Content-type: application/octet-stream");
@header("Content-Length: ".$SizeOfFile);
@header('Content-Transfer-Encoding: binary');
ob_end_clean();//此处需要,否则大文件将无法工作
@fpassthru($handle);//现在工作正常
如果您的下载文件已损坏,则下载脚本或页面中包含/需要的脚本之一可能在 <?php ?> 标记周围有空格。一个常见的问题,但最常在 header() 失败时被识别,因为标头已经发送,但值得在这里提及。
最近我的下载脚本遇到了这个问题。在某种程度上,在我的网站中添加功能时,我最终在一个 require()'d 文件中结束标记 ?> 后有一个空格(不是空白行,我通常会立即发现,而是一个空格字符)。奇怪的是,所有下载似乎都正常工作,但文件已损坏:该空格字符最终位于每个文件的开头。
这可能会为某些人节省一些时间。我创建了一个程序来列出一些相当大的文件,并为最终用户创建链接以供点击下载(使用 php 函数 fpassthru())。
我遇到的问题是它会进行一半的下载(大约 377 兆),然后脚本会终止,下载也会停止。
经过一些随机故障排除后,我发现了 php 配置选项 'max_execution_time = 30'。将其更改为 'max_execution_time = -1' 后,>370 兆的文件可以在脚本中止之前下载。
Jon
还可以使您的 php 脚本恢复下载,为此,您需要检查 $_SERVER['HTTP_RANGE'],它可能包含如下内容
"bytes=10-" - 从位置 10 恢复,到文件末尾
发送响应时,还需要使用标头发送
Accept-Ranges: bytes
Content-Length: {filesize}
Content-Range: bytes 10-{filesize-1}/{ffilesize}
希望它有用
请注意,上面关于“Connection: close”头的评论是不正确的:它并不能保证连接在传输完成后立即关闭。相反,它通知客户端它不能再使用现有的HTTP连接在同一服务器上执行其他HTTP请求,并且客户端必须在完成当前请求的处理后立即关闭连接。
如果客户端(例如旧的HTTP代理)正在使用HTTP/1.0,它可能无法识别此头,并且可能会保持连接打开;Web服务器应该检测到这种情况并关闭连接,并忽略对该连接的任何进一步请求尝试。
HTTP/1.1客户端必须遵守此头,并在检测到答案结束时立即关闭其连接。
无论如何,Web服务器将在脚本完成之后启动一个看门狗,如果客户端不遵守此头,则在大约15到30秒后强制断开连接。
等待“socket closed by remote”事件的确切时间可在Web服务器中配置。
当服务器发送了“Connection: close”头时,等待时间通常较短,而当没有发送“Connection:close”头时(在这种情况下,连接会持续更长时间,以允许客户端在服务器上导航,而无需承受新的连接成本,例如:连接延迟、最终等待状态下的套接字控制块数量、使用的端口数量)。
不要在服务器上的每个托管页面都滥用“Connection: close”:这会创建比必要更多的传入TCP连接尝试,并减慢您网站上的导航速度。仅当您的脚本无法在结果头中生成显式内容长度时才使用它,因为客户端难以确定结果的结尾。
如果您想节省服务器的连接资源,请始终在脚本中发送显式的“Content-Length”头,或者使用“chunked”传输编码以分隔片段显式发送结果(如果客户端使用HTTP/1.1,则根据规范,它必须支持此分块传输编码)。有关详细信息,请参阅RFC2616。
这是一个最终的工作副本,如果您使用会话,它不会让Microsoft Explorer崩溃。感谢之前的所有其他人。这不像我想象的那样简单。
用户将传递对页面的调用
http://mysite/getfile.php?file=products.pdf
include 'base.inc'; // 包含基本代码,启动会话并管理用户
// 这从post/get变量加载文件全局
// 出于安全原因,已禁用register globals
LoadPostGet('file');
$filename = '/data/files/' . $file;
if(file_exists($filename)){
$FILECMD = '/usr/bin/file';
$contentType = '';
$fp=popen("$FILECMD -bin $filename", 'r');
if (!$fp) $contentType='application/octet-stream';
else {
while($string=fgets($fp, 1024)) $contentType .= $string;
pclose($fp);
}
if(strpos($HTTP_SERVER_VARS['HTTP_USER_AGENT'], 'MSIE')){
// IE无法从没有缓存的会话中下载
header('Cache-Control: public');
}
header("Content-type: $contentType");
header("Content-Disposition:inline; filename=\"".$file."\"");
header("Content-length:".(string)(filesize($filename)));
$fd=fopen($filename,'rb');
while(!feof($fd)) {
print fread($fd, 4096);
}
fclose($fd);
}else{
print "File Not Found";
}
此代码可以与下载管理器一起正常使用……也许不是最佳解决方案,但它是唯一对IE有效的解决方案!
它强制下载,但gif文件不想被下载!因此我需要在浏览器中简单地显示它们……
注意 $file 是对文件表查询的结果……
require_once("auth.inc.php");
$attachment = (strstr($HTTP_USER_AGENT, "MSIE")) ? "" : " attachment"; // IE 5.5 修复。
// 文件内容
if (!headers_sent()){
$ficexp=explode('.',$file["orig_name"]);
$ext=$ficexp[sizeof($ficexp)-1];
if ($ext!='gif'){
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
header("Content-Type: application/force-download");
header("Content-Length: ".filesize("files/".$file["save_name"]));
header("Content-Disposition: ".$attachment."; filename=".$file["orig_name"]);
}
$fn=fopen("files/".$file["save_name"], "rb");
fpassthru($fn);
}
else {
MessageBox('Headers already sent, cannot force download!');
}
Min's
我也浏览过此示例列表,我相信这些示例对那个人有效,但正如其他人在此处提到的那样,对我或(其他任何人)都不起作用。
因此,我所做的是尝试了所有这些示例,检查了其他信息来源,并将我认为对“不仅仅是几个”系统有效的示例放在一起。以下示例对我在需要使用fpassthru()创建下载的任何地方都有效,它适用于IE6(以及其他浏览器)
<?
/*/
使用fpassthru()下载文件
/*/
$fileDir = "/home/pathto/myfiles"; // 提供路径名。
$fileName = "myfile.zip"; // 提供文件名。
$fileString=$fileDir.'/'.$fileName; // 组合路径和文件
// 为Internet Explorer正确转换文件名。
if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE")){
$fileName = preg_replace('/\./', '%2e', $fileName, substr_count($fileName, '.') - 1);
}
// 在发送标头之前确保文件存在
if(!$fdl=@fopen($fileString,'r')){
die("Cannot Open File!");
} else {
header("Cache-Control: ");// 留空以避免IE错误
header("Pragma: ");// 留空以避免IE错误
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"".$fileName."\"");
header("Content-length:".(string)(filesize($fileString)));
sleep(1);
fpassthru($fdl);
}
?>
所有需要编辑的应该是 $fileDir 和 $fileName 变量。上传文件并使用浏览器指向它以查看脚本是否会提示您下载。
注意:关于文件类型:保持“Content-type”标头原样应该允许您下载几乎任何文件。我已在一些更流行的文件类型上对其进行了测试,包括zip、css、php、inc、htm、png、gif和jpg。在这些测试期间,我确实注意到,如果我在提示下载gif或jpg时选择“取消”或“打开”,它确实会在我的图像浏览器中取消或打开,但随后尝试“仅下载”会产生图像的网页视图。关闭窗口并打开一个新窗口可以重置此操作,允许我直接将jpeg或gif保存到硬盘驱动器。我相信问题在于缓存标头的处理方式,因为如果在“cache-control”标头中指定了任何信息,则浏览器下载将完全失败(至少在IE中是这样)。
享受!如果它有效,请给我发邮件!;-)
来自ssharma脚本的一点补充(感谢他提供的巨大帮助……)
不要忘记将fopen与“rb”参数一起使用,而不仅仅是“r”
否则您将无法使脚本与所有pdf文件一起使用。
我的最终脚本(在1.9 Mb复杂PDF文件上工作以打开和保存)
<?php
// 文件名存储在我的脚本中的 $produitFilename 变量中(您唯一需要的东西)
// 您需要指定文件的真实路径,而不是URL
$fullPath = getcwd()."./directory_where_the_file_is/".$produitFilename;
if ($fd = fopen ($fullPath, "rb")) {
$fsize =filesize($fullPath);
$fname = basename ($fullPath);
header("Pragma: ");
header("Cache-Control: ");
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"".$fname."\"");
header("Content-length: $fsize");
fpassthru($fd);
}
?>
玩得开心,感谢大家提供的帮助……
西蒙(来自法国巴黎)
以下是您需要设置的不同标头的摘要,以使下载*始终*与IE和Mozilla一起使用
[SNIP]
$disposition = "inline"; // “inline”在浏览器中查看文件或“attachment”下载到硬盘
$mime = "image/jpeg"; // 或任何mime类型
$name = "foo.jpg"; // 文件名
$path = "/path/to/foo.jpg"; // 全路径和文件名
if (isset($_SERVER["HTTPS"])) {
/**
* 我们需要设置以下标头才能使下载在HTTPS模式下使用IE时工作。
*/
header("Pragma: ");
header("Cache-Control: ");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate"); // HTTP/1.1
header("Cache-Control: post-check=0, pre-check=0", false);
}
else if ($disposition == "attachment") {
header("Cache-control: private");
}
else {
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
}
header("Content-Type: $mime");
header("Content-Disposition:$disposition; filename=\"".trim(htmlentities($name))."\"");
header("Content-Description: ".trim(htmlentities($name)));
header("Content-Length: ".(string)(filesize($path)));
header("Connection: close");
[/SNIP]
这样所有类型的下载对我都有效。希望有帮助
如果您尝试在页面上输出用户编写的文件以进行验证、编辑等,您将希望使用fopen()、fread()、htmlentities() 来避免恶意代码。来自fpassthru的文本,虽然本身没有被解析,但仍然会弄乱页面的显示(或者至少对我来说是这样!)——mt。
如果有多个缓冲的可能性,请尝试在循环中运行以下示例中的ob_end_clean()
while (@ob_end_clean());
例如,在输出的自动gz压缩的情况下,这将有所帮助。
我相信以下问题是将会话和fpassthru一起使用导致的结果。
我有一个基于订阅的网站,它通过将大型视频文件(WMV格式,大小在100-120MB之间)存储在Web根目录下进行保护。下载视频文件需要用户单击一个HTML链接,该链接请求一个PHP脚本,例如download-video.php?video_id=123。如果用户有效(从成功登录创建会话变量),则脚本将创建必要的标头以触发“另存为”下载框,从Web根目录下打开文件并使用fpassthru发送它。
问题如下
用户应该能够在文件下载时单击网站上的其他链接。但是,当他们这样做时,请求的页面将不会加载,直到下载完成。
由于此下载脚本是一个单独的PHP请求,因此用户应该能够在文件下载时加载网站上的其他页面。
在撰写本文时,我几乎尝试了所有方法来消除这个错误。问题可能出在使用 PHP 脚本而不是直接的 Web 服务器链接来下载文件上。
回复
"3. 通过各种头信息设置的调整,我无法在通过刷新(META 或通过头信息)发起实际传输时正确设置文件名。我不知道这是否是仅限于 IE 的问题。例如,如果 'download.php?dl=now' 刷新回 'download.php',目的是显示一些信息(例如安装说明)并启动下载,那么 IE 坚持认为下载的文件应该命名为 'download.php?dl=now' 或 'download.php',忽略头信息中的文件名。"
我最近遇到了完全相同的问题。我发现这是由于页面上的会话初始化导致的。由于某种原因,执行 session_start() 会导致脚本尝试下载自身,而不是我通过各种 header() 调用指示的内容。
解决方案是将下载部分移到会话初始化之前。乍一看,这似乎很危险,但我只在存在 POST 变量并且脚本正在重新加载自身时处理它。这样,我知道表单是由该页面提交的,并且在他们提交表单之前,他们必须拥有一个会话!添加 .htaccess 规则以拒绝存储文件的目录的所有访问也有帮助,因为这样只有我的脚本才能访问这些文件。
在我的本地机器上托管的论坛中,使用以下方法可以很好地限制特定文件的下载速度
//#######################################
$big_file=filesize($completeFilePath)/1024; //文件大小(KB)
header('Content-Type: '.$mime_type);
header('Content-disposition: '.$content_disp.'filename="'.$attachment_name.'"');
header('Cache-Control: no-cache');
header('Pragma: no-cache');
header('Expires: 0');
header('Content-Length: '.(string)(filesize($completeFilePath)));
$fp=fopen($completeFilePath,'r');
while(!feof($fp)) {
$buffer = fread($fp, 1024*6); //速度限制 6kb/s
if ($big_file>32 &&
$extension!="jpg" &&
$extension!="jpeg" &&
$extension!="gif" &&
$extension!="png" &&
$extension!="txt")
sleep(1); //如果文件大小>32KB 且不是像 jpg、gif 等小文件 - 等待 1 秒
print $buffer;
}
fclose($fp);
header ("Connection: close");
//#######################################
我认为这是在不使用循环或 for-next 的情况下降低文件下载速度最简单的方法 - 这确实节省了 PHP 的性能,并且通过在 1 秒内使用 1024*kb 数来非常精确…
就是这样
问候,omega2k.dynu.com
关于一起使用会话和 fpassthru。
尝试添加:session_write_close()
在下载脚本的顶部附近,在你开始发送视频之前,这应该可以解决问题。
我已经实现了并测试了 session_write_close(),它运行得非常完美。现在,在使用 fpassthru 传输大文件时,可以点击并加载其他链接。
非常感谢 Greg 提供此提示。我们生活在一个多么有帮助的社区中:0)
在尝试了所有其他可以想象的方法来让 Explorer 通过 PHP 下载文件之后,上述方法对我有用。但是,我不得不更改 content-length 行。不需要像上面帖子中那样将 $size 变量“转换为字符串”。以下方法适用于小文件和大文件(在超过 30MB 的文件上测试过,没有问题)…
<?php
$distribution="/path/to/a/file.exe"
if ($fd = fopen ($distribution, "r")){
$size=filesize($distribution);
$fname = basename ($distribution);
//这是一些我用来在修复
//这个问题之前重定向到文件的非常弱的代码...它使浏览器通过 Apache 而不是 PHP 处理下载
//但随后很容易找到文件的真实位置
//header("Location: $distribution");
//fclose ($fd);
//exit;
//下面是一个更好的方法...
header("Pragma: ");
header("Cache-Control: ");
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"".$fname."\"");
header("Content-length: $size");
while(!feof($fd)) {
$buffer = fread($fd, 2048);
print $buffer;
}
fclose ($fd);
exit;
}
?>
祝你好运。
Brett Brewer。
请注意,如果你在发送文件到浏览器之前使用前面示例中的这两个头信息
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
Internet Explorer 文件下载对话框中的“打开”选项将无法正常工作。如果用户点击“打开”而不是“保存”,目标应用程序将打开一个空文件,因为下载的文件未被缓存。用户必须将文件保存到硬盘才能使用它。
如果你希望访问者能够使用 IE 的“打开”选项,请确保省略这些头信息。
更新上述内容。这还会设置正在发送文件的正确 MIME 类型。这是一个小技巧,因为它依赖于“文件”系统命令,但它应该可以正常工作。
<?
// 文件命令的完整路径
$FILECMD='/usr/bin/file';
// 文件所在的目录
$fileDir='/home/mcaserta';
// 完整的文件名
$fileName='test.sh';
// 配置结束
$completeFilePath=$fileDir.'/'.$fileName;
$fp=popen("$FILECMD -bin $completeFilePath", 'r');
if (! $fp) $contentType='application/octet-stream';
else {
while($string=fgets($fp, 1024)) $contentType .= $string;
pclose($fp);
}
header('Content-type: '.($contentType));
header('Content-Disposition: inline; filename="'.($fileName).'"');
header('Content-length: '.(string)(filesize($completeFilePath)));
$fd=fopen($completeFilePath,'r');
fpassthru($fd);
?>
关于使用 fpassthru() 为 PHP 驱动的下载链接弹出“另存为...”对话框的一些说明
1. 我发现传输完成后,下载进度对话框会保留几秒钟,然后才会告诉用户已完成。通过添加以下头信息解决了此问题
header ("Connection: close");
这将导致连接在传输完成后立即关闭,而不是等待超时。
2. 如果文件名中有多个句点,你可能会在 IE 中得到一个文件名,其中包含括号中的数字(例如,当你将 myfile-1.0-windows.zip 放入头信息中时,为 myfile-[1][0]-windows.zip)。根据 Microsoft 的知识库,这是一个与 IE 缓存相关的“已知”错误,而且我找不到任何解决方法。
3. 通过各种头信息设置的调整,我无法在通过刷新(META 或通过头信息)发起实际传输时正确设置文件名。我不知道这是否是仅限于 IE 的问题。例如,如果 'download.php?dl=now' 刷新回 'download.php',目的是显示一些信息(例如安装说明)并启动下载,那么 IE 坚持认为下载的文件应该命名为 'download.php?dl=now' 或 'download.php',忽略头信息中的文件名。"
这是我的代码,我尝试了几种组合,但大多数都不起作用,并且包含各种不必要的头信息等。它还具有其他良好的功能,例如,如果连接中断,它会停止发送文件(希望它无论如何都会这样做),并且通过使用简单的 preg_replace 修复了在发送包含多个句点文件时 IE 的文件名问题(IE 会终止文件名并弄乱所有内容)
<?
function send_file($path) {
session_write_close();
ob_end_clean();
if (!is_file($path) || connection_status()!=0)
return(FALSE);
//防止长文件因 //max_execution_time 而被截断
set_time_limit(0);
$name=basename($path);
//包含句点的 IE 文件名将弄乱
//文件名,除非我们添加此内容
if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE"))
$name = preg_replace('/\./', '%2e', $name, substr_count($name, '.') - 1);
//必需,否则它可能会尝试发送服务 //文档而不是文件
header("Cache-Control: ");
header("Pragma: ");
header("Content-Type: application/octet-stream");
header("Content-Length: " .(string)(filesize($path)) );
header('Content-Disposition: attachment; filename="'.$name.'"');
header("Content-Transfer-Encoding: binary\n");
if($file = fopen($path, 'rb')){
while( (!feof($file)) && (connection_status()==0) ){
print(fread($file, 1024*8));
flush();
}
fclose($file);
}
return((connection_status()==0) and !connection_aborted());
}
?>
我已经尝试了所有这些难以捉摸的任务的版本。没有一个对我有用。当我说有用时,我的意思是,我能否点击某种链接并在 MSIE 6.0 上弹出“另存为...”对话框。在我尝试过的所有其他浏览器(Safari、Firebird、Netscape pc 和 mac)中,它们都可以在我的桌面上下载文件或询问我是否要将其保存到特定位置。
在 MSIE 6.0 中,我尝试下载的文件会出现在它自己的窗口中。它是一张图片。但是,我唯一能做的事情就是将其另存为 BMP 格式。真是糟糕。
我使用 fpassthru 函数是因为我有一些文件不能由 Web 服务器提供服务。
找到了一种解决 MSIE 缓存错误的方法,该错误会在带点项目周围添加括号,我之前曾发布过关于此错误的信息(例如,“somefile1.0-xyz.zip”变成“somefile[1][0]-xyz.zip”)。
事实证明,如果您将除了最后一个点之外的所有点都编码为 %2e,那么 MSIE 就不会这样做。如果您将所有点(包括最后一个点)都编码,那么 MSIE 会在文件末尾添加一个额外的括号数字(例如,“somefile1.0-xyz.zip[1]”)。但是不幸的是,一些其他浏览器则希望使用文件名中的 %2e 而不是点来保存文件。
if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE"))
{
$fileName = preg_replace('/\./', '%2e', $fileName,
substr_count($fileName, '.') - 1);
}
瞧!文件名称正确了。这至少在 MSIE 6.0 中有效。
fpassthru() 最适合用于小文件。在下载管理器脚本中,最好确定要下载的文件的 URL(如果需要,您可以在本地会话数据中生成它),然后使用 HTTP **临时**重定向(302 状态代码,以及指定有效下载 URL 的“Location:” 标头)。
这样可以节省您的 Web 服务器在文件下载期间长时间维护 PHP 脚本的资源,而是由 Web 服务器直接管理下载,而无需脚本支持(结果:并行下载使用的内存资源更少)…
我编写了一个页面,该页面对用户进行身份验证,然后调用 fpassthru() 来下载 Acrobat 文档。它在 1MB 左右之前运行良好,但是对于较大的文件,脚本会在中间崩溃。我的 ISP 告诉我,他们终止了我的脚本,因为它占用太多内存。我尝试了 readfile(),但没有用。
我用此解决方法替换了 fpassthru()。它运行良好
while(!feof($fn)) {
$buffer = fread($fn, 4096);
print $buffer;
}
PHP 页面的生成方式(是否缓冲以及如何缓冲)会影响使用 fpassthru(或 fread 等)进行的下载功能。我的意思是,当从简单的 php 文件(此处不进行缓冲)中调用下载功能时,它可能运行良好
<?php
function download($file) { ... }
$filename = "/tmp/test.zip";
download($filename);
?>
但在“现实生活中”,当页面被缓冲时可能会失败
<?php
ob_start("ob_gzhandler");
...
require_once(download.php);
...
$filename = "/files/file.zip";
download($filename);
?>
在我的特定情况下,只有 Firefox 1.0 英文版没有执行下载,因为使用了 ob_start("ob_gzhandler")。将其替换为 ob_start() 解决了问题。
希望这有帮助
来自法国巴黎的 Laurent
我修改了 straz at -removethispart-mac dot com 给出的示例,以计算每个字节的文件输出。然后,可以将其与文件发送完成后的大小进行比较,以确定文件是否已成功发送。
当然,这并不能保证用户实际成功接收了文件,但它可以让我们知道在读取/发送文件过程中我们的端点是否出现错误。
<?
/* fpassthru 显然是一个内存消耗大户。请改用此方法 */
while(!feof($fp)) {
$buf = fread($fp, 4096);
echo $buf;
$bytesSent+=strlen($buf); /* 我们知道发送给用户的字节数 */
}
?>
然后,我使用此代码更新我的数据库,以表明文件已成功下载。
<?
if($bytesSent==filesize($file)) {
/* 在此处执行一些很酷的操作! */
}
?>
在尝试通过 PHP 将文件“passthru”到浏览器时,但使用 FEOF 循环,脚本尝试在将文件传递到浏览器之前缓冲整个文件。这是我的原始脚本。当使用 15M PHP 内存限制和 16M 文件调用它时,apache 终止了脚本。
<?php
$name = $tempDir . $_GET["file"];
$fd = fopen($name, 'rb');
if($fd == false)
die("<font color=red>ERROR: File not found.</font>");
// 发送正确的标头
header("Cache-Control: ");// 保留空白以避免 IE 错误
header("Pragma: ");// 保留空白以避免 IE 错误
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"" . $_GET["file"] . "\"");
header("Content-length:".(string)(filesize($name)));
sleep(1);
session_write_close();
ob_flush();
flush();
while(!feof($fd)) {
$buffer = fread($fd, 2048);
print $buffer;
}
fclose ($fd);
exit;
?>
Apache 错误日志读取
已用尽 15728640 字节的允许内存大小(尝试分配 10240 字节)
我尝试了所有方法,包括在循环中使用 flush()。但是解决方案是强制以另一种方式刷新
<?php
$buffer = fread($fd, 32 * 1024);
?>
瞧!对我来说,它运行良好。
回复 spam at flatwan dot net
这可能会为某些人节省一些时间。我创建了一个程序来列出一些相当大的文件,并为最终用户创建链接以供点击下载(使用 php 函数 fpassthru())。
我遇到的问题是它会进行一半的下载(大约 377 兆),然后脚本会终止,下载也会停止。
经过一些随机故障排除后,我发现了 php 配置选项 'max_execution_time = 30'。将其更改为 'max_execution_time = -1' 后,>370 兆的文件可以在脚本中止之前下载。
最好的方法是
<?php
@ignore_user_abort();
@set_time_limit(0);
?>
这只会更改调用它们的脚本的这些设置。(感谢(我不记得是谁)编写了一个使用这两行的表单邮件脚本)
找到了一种解决另一个今晚刚刚出现的难题的方法。显然,Linux 上的 Opera 6.1(不确定其他版本/平台)在您启用了 php.ini 中的 zlib.output_compression 进行压缩的情况下,使用上述方法下载文件时会出现问题。
Opera 似乎发现实际传输大小小于下载的“Content-length”标头中的大小,并认为传输不完整或已损坏。然后,它要么持续重试下载,要么为您留下损坏的文件。
解决方案:确保您的下载脚本/部分位于其自己的目录中。并为该目录的 .htaccess 文件添加以下内容
php_flag zlib.output_compression off
在 UNIX 下使用 fpassthru() 与 fread() 的有趣结果。
使用 fread(fp, length) 从有效的打开指针读取,其中文件名包含特殊字符(单引号、逗号、左括号等)会导致读取失败(在那之后没有写入调试语句)。但是,使用 fpassthru() 却运行良好。
感谢您关于 IE 会话信息的实用说明,我之前见过这种情况,但不知道是什么导致的。
我无法使上述示例正常工作。我改用了以下方法
header("Content-Disposition: attachment; filename=$file");
header("Content-Description: Image File");
$fd = fopen($file,'r');
fpassthru($fd);
(请勿删除此内容,这不是错误报告。这是对页面上关于 fpassthru() 使用过多内存的模糊评论的*后续*,以及一个用法提示,如果您想使用直通处理,强烈建议使用 PHP 5。它可能*看起来*像错误报告,因为与之前的提示不同,我尝试澄清了情况。它不是错误报告,因为问题已在 PHP 5 中*解决*。相反,任何仍在使用 PHP 4(例如,出于兼容性原因)的人只需知道问题现在已解决。)
在 PHP 4(已测试 4.4.4 作为 CGI,Apache 2,Linux)中,循环中使用 fpassthru() 和 fread() 都存在相同的内存“泄漏”。其特征是发送到客户端的所有数据也会保留在 PHP 内部且未释放。这似乎是垃圾回收失败。
在 PHP 5(已测试 5.2.1 作为 CGI,Apache 2,Linux)中,这两个情况下的缺陷都已解决。循环中的 fpassthru() 和 fread() 在执行期间都不会“泄漏”内存。
哪个函数更适合使用的问题似乎与内存无关,因为两者在 PHP 4 中都存在同样的缺陷,并且在 PHP 5 中都得到了修复。
速度方面,留给读者在最新的 PHP 版本中进行测试。