如果您開啟一個新檔案,寫入資料,然後呼叫 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())。
我遇到的問題是,它會在下載過程中途停止(大約 377MB),腳本會終止,下載也會停止。
經過一些試錯排除後,我發現了 php 設定選項 'max_execution_time = 30'。將其更改為 'max_execution_time = -1' 後,即可下載 >370MB 的檔案,而不會中止腳本。
Jon
也可以讓您的 php 腳本恢復下載,要做到這一點,您需要檢查 $_SERVER['HTTP_RANGE'],其中可能包含如下內容
"bytes=10-" - 從位置 10 恢復到檔案結尾
發送回應時,也需要在標頭中發送
Accept-Ranges: bytes
Content-Length: {檔案大小}
Content-Range: bytes 10-{filesize-1}/{ffilesize}
希望這有用
請注意,以上關於「Connection: close」標頭的註釋是不正確的:它不保證連線會在傳輸完成後立即關閉。 相反,它會通知客戶端,它不能再使用現有的 HTTP 連線在同一伺服器上執行其他 HTTP 請求,並且客戶端必須在處理完當前請求後立即關閉連線。
如果客戶端(例如舊的 HTTP 代理伺服器)使用的是 HTTP/1.0,它可能無法辨識此標頭,並可能使連線保持開啟;網路伺服器應偵測到此情況並關閉連線,並忽略在該連線上進一步的請求嘗試。
HTTP/1.1 客戶端必須遵守此標頭,並在偵測到回應結束後立即關閉其連線。
無論如何,網路伺服器將在腳本完成後啟動一個看門狗,如果客戶端不遵守此標頭,將會在約 15 到 30 秒後強制斷開連線。
等待「socket closed by remote」事件的確切時間可在網路伺服器中設定。
當伺服器已發送「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 "找不到檔案";
}
這段程式碼可以與下載管理器正常運作... 也許不是最佳解決方案,但卻是唯一適用於 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('標頭已發送,無法強制下載!'); // 或:訊息方塊('標頭已發送,無法強制下載!')
}
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("無法開啟檔案!");// 或:die("開啟檔案失敗!");
} 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 變數中(您唯一需要的東西)
// 您需要指定檔案的實際路徑,而不是網址
$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 兼容所需設定的不同標頭的摘要
[略]
$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");
[/略]
這樣,所有類型的下載對我來說都適用。希望這有幫助
如果您試圖在頁面上輸出使用者編寫的檔案以進行驗證、編輯等,您需要使用 fopen()、fread()、htmlentities() 來避免惡意程式碼。來自 fpassthru 的文字雖然本身不會被解析,但仍然會弄亂頁面的顯示(或者至少對我來說是這樣!)--mt。
如果可能有多個緩衝區,請嘗試在迴圈中執行以下範例中的 ob_end_clean()
while (@ob_end_clean());
例如,在自動 gz 壓縮輸出的情況下,這會有所幫助。
我認為以下問題是由於同時使用 session 和 fpassthru 造成的。
我有一個基於訂閱的網站,它通過將大型影片檔案(WMV 格式,大小介於 100-120MB 之間)儲存在網站根目錄下來保護它們。下載影片檔案需要使用者點擊一個 HTML 連結,該連結會請求一個 PHP 腳本,例如 download-video.php?video_id=123。如果使用者有效(通過成功登錄建立的 session 變數),腳本就會建立必要的標頭來觸發「另存新檔」下載框,從網站根目錄下打開檔案,並使用 fpassthru 發送它。
問題如下:
使用者應該能夠在檔案下載時點擊網站上的其他連結。但是當他們這樣做時,請求的頁面在下載完成之前不會載入。
由於此下載腳本是一個單獨的 PHP 請求,因此使用者應該能夠在檔案下載時載入網站上的其他頁面。
在撰寫本文時,我幾乎嘗試了所有方法來消除這個錯誤。使用 PHP 腳本而不是直接的網路伺服器連結來下載檔案肯定存在問題。
回覆
「3. 無論如何調整標頭,我都無法在通過重新整理(META 或通過標頭)啟動實際傳輸時正確設定檔名。我不知道這是否也是僅限 MSIE 的問題。如果 'download.php?dl=now'(例如)重新整理回 'download.php',以便它既顯示一些資訊(例如安裝說明),又啟動下載,那麼 MSIE 堅持認為下載的檔案應該命名為 'download.php?dl=now' 或 'download.php',而忽略標頭中的檔名。」
我最近遇到了完全相同的問題。我發現這是由於頁面上的 session 初始化造成的。由於某些原因,執行 session_start() 會導致腳本嘗試下載自身,而不是我通過各種 header() 呼叫指示的內容。
解決方案是將下載部分移到 session 初始化之上。乍一看,這似乎很危險,但我只在存在 POST 變數且腳本正在重新載入自身時才處理它。這樣我就知道表單是由該頁面提交的,並且在他們提交表單之前,他們必須有一個 session!新增一個 .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
關於同時使用 session 和 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 類型。這是一個小技巧,因為它依賴「file」系統指令,但它應該可以正常運作。
<?
// 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. 如果檔名中有多個句點,在使用 MSIE 時,您可能會得到檔名中帶有括號數字的檔案(例如,當您在標頭中放入 myfile-1.0-windows.zip 時,會變成 myfile-[1][0]-windows.zip)。根據微軟的知識庫,這是一個「已知」的錯誤,與 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 函式是因為我有一些檔案不能由網路伺服器提供服務。
我找到了解決 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)。
這樣可以避免您的網路伺服器在檔案下載期間長時間運行 PHP 腳本,而下載將直接由網路伺服器管理,無需腳本支援(結果:平行下載使用的記憶體資源更少)...
我寫了一個網頁,用於驗證使用者身份,然後調用 fpassthru() 下載 Acrobat 文件。它在大小約為 1MB 的檔案上運作良好,但對於較大的檔案,腳本會在執行過程中中止。我的網路服務供應商告訴我,他們終止了我的腳本,因為它佔用了太多記憶體。我嘗試使用 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 使用 FEOF 迴圈將檔案「passthru」到瀏覽器時,腳本會嘗試先將整個檔案緩衝,然後再傳送到瀏覽器。這是我原本的腳本。當使用 15MB 的 PHP 記憶體限制和 16MB 的檔案呼叫它時,Apache 就中止了腳本。
<?php
$name = $tempDir . $_GET["file"];
$fd = fopen($name, 'rb');
if($fd == false)
die("<font color=red>錯誤:找不到檔案。</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())。
我遇到的問題是,它會在下載過程中途停止(大約 377MB),腳本會終止,下載也會停止。
經過一些試錯排除後,我發現了 php 設定選項 'max_execution_time = 30'。將其更改為 'max_execution_time = -1' 後,即可下載 >370MB 的檔案,而不會中止腳本。
最好的方法是
<?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(測試版本為 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 版本中自行測試。