PHP 怎么PHP流式上传

wen PHP项目 4

本文目录导读:

PHP 怎么PHP流式上传

  1. 流式接收(服务器端处理上传)
  2. 流式发送(API 请求转发)
  3. 流式下载(将文件流式输出到浏览器)
  4. 使用现成的库(推荐)
  5. 注意事项与最佳实践
  6. 总结建议

在 PHP 中实现流式上传(Streaming Upload),主要目的是减少内存占用,特别是在处理大文件时,不必将整个文件一次性读入内存。

PHP 提供了多种方式实现流式上传,核心思路是读取一段,处理/发送一段,然后丢弃

以下是几种常见的实现方式,按场景分类:


流式接收(服务器端处理上传)

如果你是在写服务器端代码,接收客户端上传的大文件,核心是配合 php://input 流进行读取。

<?php
// 处理 PUT 请求或非标准表单上传的场景
// 目标文件路径
$targetFile = '/path/to/save/uploaded_file.bin';
// 打开输入流(只读)
$input = fopen('php://input', 'r');
// 打开输出流(写入目标文件)
$output = fopen($targetFile, 'w');
if ($input && $output) {
    // 每次读取 1MB(1024 * 1024 字节)
    $chunkSize = 1024 * 1024;
    while (!feof($input)) {
        // 从输入流读取一个块
        $chunk = fread($input, $chunkSize);
        // 写入到输出流
        fwrite($output, $chunk);
        // 可选:清空缓冲区,避免阻塞
        flush();
    }
    fclose($input);
    fclose($output);
    echo "文件上传成功";
} else {
    http_response_code(500);
    echo "无法打开输入/输出流";
}

注意:如果使用 multipart/form-data 标准表单上传,PHP 会处理到临时文件,此时用 move_uploaded_file() 即可,不涉及流式读取。


流式发送(API 请求转发)

如果你需要把上传的文件从 PHP 转发到另一个服务(例如第三方 API),避免在中间层占用大量内存。

<?php
// 假设文件已经上传到服务器临时目录 $temp_path
$temp_path = $_FILES['file']['tmp_name'];
$target_url = 'https://example.com/api/upload'; // 目标 API
$curl = curl_init($target_url);
$cfile = new CURLFile($temp_path, $_FILES['file']['type'], $_FILES['file']['name']);
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => ['file' => $cfile],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HEADER => false,
    // 关键:设置超时为较长时间,避免大文件超时
    CURLOPT_TIMEOUT => 300,
]);
$response = curl_exec($curl);
curl_close($curl);
// 处理 $response...

关键点:在使用 CURLFile 时,cURL 扩展会自己处理文件流的发送,不会把整个文件加载到 PHP 内存中。


流式下载(将文件流式输出到浏览器)

如果你需要将一个大文件(如视频、备份文件)流式输出到浏览器,而不占用内存:

<?php
$filepath = '/path/to/large/video.mp4';
// 处理 Range 请求(支持视频拖拽)略...
header('Content-Type: video/mp4');
header('Content-Length: ' . filesize($filepath)); // 注意:如果支持 Range,需要动态计算长度
// 关键:关闭输出缓冲
while (ob_get_level() > 0) {
    ob_end_flush();
}
$handle = fopen($filepath, 'rb');
while (!feof($handle)) {
    // 读取 1MB
    echo fread($handle, 1024 * 1024);
    // 强制将缓冲区输出给客户端
    flush();
    // 如果客户端断开了连接
    if (connection_aborted()) {
        break;
    }
}
fclose($handle);

使用现成的库(推荐)

如果项目允许引入第三方库,使用 Guzzle 是个好选择,它原生支持流。

<?php
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;
$client = new Client();
// 方式一:使用 Psr7\Utils::streamFor 包装资源
$stream = fopen('/path/to/large/file.txt', 'r');
$request = new Request('POST', 'https://api.example.com/upload', [], 
    \GuzzleHttp\Psr7\Utils::streamFor($stream)
);
$response = $client->send($request);
fclose($stream);

注意事项与最佳实践

要点 说明
内存限制 调整 memory_limit 可能会限制,但流式上传不应该依赖它。
超时设置 大文件处理务必设置较长的 max_execution_time(CLI 下默认不限)。
错误处理 检查 fopen 返回,处理好权限问题。
安全 不要使用 $_FILES 中的原始文件名直接保存,需进行过滤,防路径注入。
move_uploaded_file 例外 如果空间允许,官方推荐的 move_uploaded_file() 性能最高,因为它本质是 rename() 操作,不涉及流复制,仅在需要处理时用流。
fcgi 环境 在使用 fread + echo 时,flush() 可能不生效,需要配合 fastcgi_finish_request() 或在框架层处理。

总结建议

  • 如果是接收标准 POST 上传:不需要流式,直接用 move_uploaded_file
  • PHP 是中间代理:推荐 cURL + CURLFile
  • PHP 是接收方:使用 php://input 流。
  • 如果是纯 CLI 脚本:使用 fopen + fread 循环,并注意 yield 或进程管理。

好的编码习惯是:能用流操作,就不用一次性加载,这是处理大文件的不二法门。

抱歉,评论功能暂时关闭!