PHP动态下载触发内存耗尽错误,如何优化文件读取方式?
WordPress动态文件下载内存耗尽问题解决方案
问题场景
用户在WordPress环境中使用以下PHP代码实现动态文件下载:
<?php include '../wp-config.php'; global $wpdb; $allowed=false; $user_login=wp_get_current_user(); $usersn=explode(".",str_replace("http://","",$user_login->user_url)); $usermesin=$usersn[0]; if(strtoupper(trim($usermesin))=='ALLN' || strtoupper(trim($usermesin))=='TES'){ $allowed=true; } // Define the directory where your files are stored $fileDirectory = 'downloads/'; // Get the file name from a query parameter (e.g., ?file=example.txt) $fileName = isset($_GET['file']) ? $_GET['file'] : ''; // Check if the file exists in the directory if (!empty($fileName) && file_exists($fileDirectory . $fileName)) { $filePath = $fileDirectory . $fileName; // Set the appropriate headers for the download header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . (($allowed)? basename($filePath) :'permission-denied.txt') . '"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($filePath)); // Output the file for download if($allowed){ readfile($filePath); }else { echo"You don't have permission to download this file"; } exit; // echo 'dapat file '.(($allowed)?'software' :'permission-denied.txt'); } else { // Handle file not found, e.g., display an error message echo 'File not found.'; } ?>
线上环境触发致命错误:
Fatal error: Allowed memory size of 536870912 bytes exhausted (tried to allocate 427106304 bytes) in /home/bengke28/public_html/wp-includes/functions.php on line 5349
问题原因
readfile()函数会将整个文件内容加载到内存中再输出,当下载大文件时,内存占用会直接超过PHP的memory_limit限制,导致内存耗尽错误。
正确的文件读取方式
采用分块读取输出的方式,每次读取一小段文件内容并输出,避免一次性加载整个文件到内存。同时结合WordPress环境做针对性优化:
优化后的代码
<?php // 仅加载WordPress必要的用户功能,减少框架内存占用 define('SHORTINIT', true); include '../wp-config.php'; require ABSPATH . WPINC . '/pluggable.php'; $allowed = false; $current_user = wp_get_current_user(); // 安全处理用户URL,兼容http/https前缀 $user_url = preg_replace('#^https?://#', '', $current_user->user_url); $usersn = explode(".", $user_url); $usermesin = isset($usersn[0]) ? trim($usersn[0]) : ''; if(strtoupper($usermesin) === 'ALLN' || strtoupper($usermesin) === 'TES'){ $allowed = true; } $fileDirectory = 'downloads/'; $fileName = isset($_GET['file']) ? $_GET['file'] : ''; // 过滤文件名,防止路径遍历攻击 $fileName = basename($fileName); $filePath = $fileDirectory . $fileName; if (!empty($fileName) && file_exists($filePath) && is_file($filePath)) { if ($allowed) { // 设置标准下载响应头 header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . basename($filePath) . '"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($filePath)); // 分块读取文件,每次读取8KB(可根据需求调整) $chunkSize = 8192; $fileHandle = fopen($filePath, 'rb'); if ($fileHandle) { while (!feof($fileHandle)) { echo fread($fileHandle, $chunkSize); // 强制输出缓冲区内容,避免内存堆积 ob_flush(); flush(); } fclose($fileHandle); } } else { header('HTTP/1.1 403 Forbidden'); echo "You don't have permission to download this file"; } exit; } else { header('HTTP/1.1 404 Not Found'); echo 'File not found.'; } ?>
关键优化点
- 分块读取:使用
fopen()+fread()每次读取固定大小的文件片段,避免一次性加载大文件到内存。 - SHORTINIT模式:开启
SHORTINIT常量,仅加载WordPress核心必要功能,降低框架本身的内存消耗。 - 安全过滤:用
basename()过滤文件名,防范路径遍历攻击(如?file=../wp-config.php这类恶意请求)。 - 规范HTTP状态码:给无权限、文件不存在的场景返回对应状态码,符合Web规范。
- 强制缓冲区输出:通过
ob_flush()和flush()确保读取的内容立即输出,减少内存占用。
额外建议
- 不要盲目提高
memory_limit,分块读取才是解决大文件下载内存问题的根本方案。 - 确保
downloads/目录不在Web根目录下,或通过.htaccess禁止直接访问,提升安全性。 - 若无需精简WordPress加载,可直接使用官方函数
wp_send_file(),内部已实现分块输出逻辑:
// 加载完整WordPress框架后可直接调用 if ($allowed) { wp_send_file($filePath); }
内容的提问来源于stack exchange,提问作者M Hasan Nudin
相关产品推荐
相关产品推荐

