如果您打开一个新文件,写入它,然后调用 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() 的文件之一的关闭 ?> 标签之后出现了一个空格(不是空行,我通常会立即发现,而是一个空格字符)。奇怪的是,所有下载似乎都正常工作,但文件已损坏:该空格字符最终出现在每个文件的开头。
这可能为某人节省一些时间。我创建了一个程序来列出一些相当大的文件,并为最终用户创建链接以供点击以下载它们(使用 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 请求,并且客户端 MUST 在完成处理当前请求后关闭连接。
如果客户端(例如旧的 HTTP 代理)正在使用 HTTP/1.0,它可能无法识别此标头,并可能保持连接打开;Web 服务器应检测到这一点并关闭连接并忽略对该连接的任何进一步请求尝试。
HTTP/1.1 客户端 MUST 遵守此标头,并在检测到答案结束时立即关闭其连接。
无论如何,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 的
我还仔细阅读了这个示例列表,我确信它对那个人有效,但是,正如其他人在这里提到的那样,它对我和(其他人)都不起作用。
因此,我所做的就是尝试了所有这些示例,检查了其他信息来源,并整理了我认为在“不止少数”系统上有效的示例。以下示例对我在需要使用 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);
}
?>
玩得开心,感谢大家提供的帮助……
Simon(来自法国巴黎)
以下是您需要设置的用于使下载始终在 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-120 MB 之间)存储在 web 根目录之外来保护它们。下载视频文件需要用户单击 HTML 链接,该链接请求一个 PHP 脚本,例如 download-video.php?video_id=123。如果用户有效(从成功登录创建的会话变量),脚本将创建必要的标头以触发“另存为”下载框,从 web 根目录之外打开文件,并使用 fpassthru 发送它。
问题如下
用户应该能够在文件下载时单击网站上的其他链接。但是,当他们这样做时,请求的页面将不会加载,直到下载完成。
由于此下载脚本是一个单独的 PHP 请求,用户应该能够在文件下载时加载网站上的其他页面。
在撰写本文时,我已经尝试过几乎所有方法来消除此错误。使用 PHP 脚本而不是直接的 web 服务器链接来下载文件肯定存在问题。
回复
"3. 即使我用尽各种方法修改头信息,也无法在通过刷新(META 或头信息)启动实际传输时正确设置文件名。我不知道这是否是仅限于 MSIE 的问题。例如,如果 'download.php?dl=now'(例如)刷新回 'download.php',以便它打算显示一些信息(例如安装说明)以及启动下载,那么 MSIE 坚持认为下载的文件应该命名为 '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 的性能,并且通过在一秒钟内使用 1024*kb 数来非常准确地...
就是这样
致敬,omega2k.dynu.com
关于一起使用会话和 fpassthru。
尝试添加:session_write_close()
在下载脚本的顶部附近,在你开始发送视频之前,这应该可以解决问题。
我已经实施并测试了 session_write_close(),它运行得像梦一样。现在可以在传输大型文件时点击并加载其他链接,使用 fpassthru。
非常感谢 Greg 的这个提示。我们生活在一个多么有帮助的社区中:0)
在尝试了所有其他可以想象的方法来让 Explorer 通过 PHP 下载文件后,上述方法对我有用。但是,我不得不更改内容长度行。无需像上面帖子中那样对 $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 类型。这是一个小技巧,因为它依赖于“file”系统命令,但它应该可以很好地工作。
<?
// 文件命令的完整路径
$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. 如果文件名中有多个句点,你最终可能会得到一个文件名,其中包含方括号中的数字(例如,当你将 myfile-1.0-windows.zip 放入头信息时,将 myfile-1.0-windows.zip 变为 myfile-[1][0]-windows.zip),使用的是 MSIE。根据微软的知识库,这是一个“已知”错误,与 MSIE 的缓存有关,而且我无法找到任何解决方法。
3. 通过各种方法修改头信息,也无法在通过刷新(META 或头信息)启动实际传输时正确设置文件名。我不知道这是否是仅限于 MSIE 的问题。例如,如果 'download.php?dl=now'(例如)刷新回 'download.php',以便它打算显示一些信息(例如安装说明)以及启动下载,那么 MSIE 坚持认为下载的文件应该命名为 '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 状态代码,使用“Location:”头指定有效的下载 URL)。
这样可以防止您的 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()。但解决方案是强制另一种方式的 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);
?>
这只会更改调用它们的脚本的这些设置。(感谢(我不记得是谁)编写了一个使用这两行的表单邮件脚本)
找到了解决今晚刚刚出现的一个头痛问题的解决方法。显然,如果在 php.ini 中启用了 zlib.output_compression,那么运行在 Linux 上的 Opera 6.1(不确定其他版本/平台)在使用上述方法下载文件时会出现问题。
看起来,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(已测试为 CGI 的 4.4.4,Apache 2,Linux)中,在循环中使用 fpassthru() 和 fread() 会出现相同的内存“泄漏”。这表现为发送给客户端的所有数据也保存在 PHP 中,并且没有被释放。看起来好像是垃圾回收失败了。
在 PHP 5(已测试为 CGI 的 5.2.1,Apache 2,Linux)中,这两个情况下的缺陷都已解决。在循环中使用 fpassthru() 或 fread() 不会在执行期间出现内存“泄漏”。
使用哪一个的问题似乎不再是内存问题,因为它们在 PHP 4 中一样有缺陷,在 PHP 5 中也一样得到了修复。
速度,留待读者在最新的 PHP 中进行测试。