Apache2环境下PHP实现无阻塞带权限校验的媒体文件展示方案
解决PHP处理大文件阻塞的方案
最优方案:使用Apache X-Sendfile模块
这是最彻底的解决办法——让Apache负责实际的文件发送,PHP只完成权限校验、扩展名检查这些核心逻辑,校验通过后立刻结束进程,完全不会阻塞应用。
配置步骤:
- 启用Apache的
mod_xsendfile模块:- Debian/Ubuntu系统:执行命令
a2enmod xsendfile,然后重启Apache - RHEL/CentOS系统:编辑
httpd.conf,添加LoadModule xsendfile_module modules/mod_xsendfile.so(路径需根据实际安装情况调整)
- Debian/Ubuntu系统:执行命令
- 在Apache配置文件或
.htaccess中设置允许发送的文件根目录:XSendFile On XSendFilePath /foo/bar/ # 填写你的文件实际存储目录,确保Apache有读取权限 - 修改PHP代码:
保留所有校验逻辑,去掉readfile或fpassthru,改用X-Sendfile头让Apache接管文件发送:if (empty($_GET['filename'])) { http_response_code(500); die; } $filename = $_GET['filename']; $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); // 用原生函数获取扩展名更可靠 $file_path = '/foo/bar/' . $filename; // 扩展名校验 $ALLOWED_EXTENSIONS = ['jpg','png','mp4']; if (!in_array($ext, $ALLOWED_EXTENSIONS)) { http_response_code(403); die; } // 文件存在性与可读性检查 if (!file_exists($file_path) || !is_readable($file_path)) { http_response_code(404); die; } // 设置缓存相关头信息 $time = filemtime($file_path); $expires = gmdate('D, d M Y H:i:s \G\M\T', $time + 3600); $modified = gmdate('D, d M Y H:i:s \G\M\T', $time); $finfo = new Finfo(FILEINFO_MIME_TYPE); $mime = $finfo->file($file_path); header('Pragma: cache'); header('Cache-Control: max-age=31536000'); header('Expires: '.$expires); header('Content-Type: '.$mime); header('Last-Modified: '.$modified); // 关键:发送X-Sendfile头,让Apache处理文件发送 header("X-Sendfile: {$file_path}"); exit; // 立刻结束PHP进程,不再占用资源
备选方案:分块输出文件
如果暂时无法启用X-Sendfile模块,可以用分块读取输出的方式,降低PHP内存占用,缓解阻塞问题:
// 前面的校验逻辑不变 $chunk_size = 1024 * 1024; // 按1MB分块读取 $fp = fopen($file_path, 'rb'); while (!feof($fp)) { echo fread($fp, $chunk_size); ob_flush(); // 刷新PHP输出缓冲区 flush(); // 强制将内容发送到客户端 } fclose($fp);
这种方式不会一次性加载整个大文件到内存,阻塞时间会被分散,但效率仍不如X-Sendfile。
补充:现有代码的小问题修正
- 变量名不一致:
$extension和$ext需统一,建议用pathinfo获取扩展名 die(http_response_code(500))末尾的冒号应改为分号- 用
finfo类获取MIME类型比自定义函数更准确
内容的提问来源于stack exchange,提问作者user1427930
相关产品推荐
相关产品推荐

