You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()确保读取的内容立即输出,减少内存占用。

额外建议

  1. 不要盲目提高memory_limit,分块读取才是解决大文件下载内存问题的根本方案。
  2. 确保downloads/目录不在Web根目录下,或通过.htaccess禁止直接访问,提升安全性。
  3. 若无需精简WordPress加载,可直接使用官方函数wp_send_file(),内部已实现分块输出逻辑:
// 加载完整WordPress框架后可直接调用
if ($allowed) {
    wp_send_file($filePath);
}

内容的提问来源于stack exchange,提问作者M Hasan Nudin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 02:17:14