RE: [PHP] handling large files w/readfile
| From: | Robin Getz | Date: | Tue, 04 Jan 2005 14:04:03 +0000 |
| Subject: | RE: [PHP] handling large files w/readfile | ||
| Groups: | php.general | ||
| Request: | Send a blank email to php-general+get-205583@lists.php.net to get a copy of this message | ||
Jason Wong wrote:
Are you using the above code on its own (ie not within some other code that may affect the memory usage)?Well, herethe entire file (It is pretty short - only a 2 pages, but sorry in advance if anyone considers this bad form). site is called with something like http://blackfin.uclinux.org/frs/download.php/123/STAMP.jpg Files are stored in: $sys_upload_dir.$group_name.'/'.$filename -- frs/download.php --------------------------------------------- <?php /** * GForge FRS Facility * * Copyright 1999-2001 (c) VA Linux Systems * The rest Copyright 2002-2004 (c) GForge Team * http://gforge.org/ * * @version $Id: download.php,v 1.6 2004/10/08 23:05:29 gsmet Exp $ * * This file is part of GForge. * * GForge is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * GForge is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GForge; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ $no_gz_buffer=true; require_once('pre.php'); $arr=explode('/',$REQUEST_URI); $file_id=$arr[3]; $res=db_query("SELECT frs_file.filename,frs_package.is_public,
frs_file.file_id,groups.unix_group_name,groups.group_id
FROM frs_package,frs_release,frs_file,groups
WHERE frs_release.release_id=frs_file.release_id
AND groups.group_id=frs_package.group_id
AND frs_release.package_id=frs_package.package_id
AND frs_file.file_id='$file_id'");
if (db_numrows($res) < 1) {
Header("Status: 404");
exit;
}
$is_public =db_result($res,0,'is_public'); $group_name=db_result($res,0,'unix_group_name');
$filename = db_result($res,0,'filename'); $release_id=db_result($res,0,'release_id');
$group_id = db_result($res,0,'group_id');
$Group =& group_get_object($group_id);
if (!$Group || !is_object($Group) || $Group->isError()) {
exit_no_group();} if(!$Group->isPublic()) {
session_require(array('group' => $group_id)); }
// Members of projects can see all packages // Non-members can only see public packages
if(!$is_public) {
if (!session_loggedin() || (!user_ismember($group_id) && !user_ismember(1,'A'))) {
exit_permission_denied();
}
}
/*
echo $group_name.'|'.$filename.'|'.$sys_upload_dir.$group_name.'/'.$filename;
if (file_exists($sys_upload_dir.$group_name.'/'.$filename)) {
echo '<br />file exists';
passthru($sys_upload_dir.$group_name.'/'.$filename);
}
*/
if (file_exists($sys_upload_dir.$group_name.'/'.$filename)) {
Header('Content-disposition: filename="'.str_replace('"', '', $filename).'"');
Header("Content-type: application/binary");
length = filesize($sys_upload_dir.$group_name.'/'.$filename);
Header("Content-length: $length");
# Here is where all the problems start
readfile($sys_upload_dir.$group_name.'/'.$filename);
if (session_loggedin()) {
s =& session_get_user();
us=$s->getID();
} else {
us=100;
}
res=db_query("INSERT INTO frs_dlstats_file (ip_address,file_id,month,day,user_id)
VALUES ('$REMOTE_ADDR','$file_id','".date('Ym')."','".date('d')."','$us')");
} else {
Header("Status: 404");
}
?>
=============================================
If this runs for awhile things go very bad. This seems to be related to a specific download manager called NetAnts that seems to be popular in China.
http://www.netants.com/
Which attempts to open the same url for downloading 10-15 times at the same instant.
If I replace things with:
==== snip =====
if (file_exists($sys_upload_dir.$group_name.'/'.$filename)) {
# if the file is too big to download (10Meg) - use a different method than php
$length = filesize($sys_upload_dir.$group_name.'/'.$filename);
Header('Content-disposition: filename="'.str_replace('"', '', $filename).'"');
Header("Content-type: application/binary");
Header("Content-length: $length");
fp = fopen($sys_upload_dir.$group_name.'/'.$filename,'rb');
buff="0";
while (!feof($fp)) {
buff = fread($fp, 4096);
print $buff;
}
unset($buff);
fclose ($fp);
=== snip - rest is the same =====
I get exactly the same problem - I come back and there are 2-3-4 apache processes that are consuming memory the size of the largest downloads.
The only way I can make things work with large downloads is to use this:
==== snip ====
if (file_exists($sys_upload_dir.$group_name.'/'.$filename)) {
# if the file is too big to download (10Meg) - use a different method than php
$length = filesize($sys_upload_dir.$group_name.'/'.$filename);
if ($length >= 10485760 ) {
out = "http://downloads.".$sys_default_domain."/".$group_name."/".$filename;
Header("Location: ".$out);
} else {
# less than 10Meg - download with php
Header('Content-disposition: filename="'.str_replace('"', '', $filename).'"');
Header("Content-type: application/binary");
Header("Content-length: $length");
fp = fopen($sys_upload_dir.$group_name.'/'.$filename,'rb');
buff="0";
while (!feof($fp)) {
buff = fread($fp, 4096);
print $buff;
}
unset($buff);
fclose ($fp);
}
==== snip - rest is the same =====
Someone suggested apache_child_terminate - but this function doesn't seem to be available to me.
Thanks in advance.
-Robin