Yii2 RESTful API大文件上传需绕过$_FILES,改用php://input流式读取,同时调高PHP及Nginx/Apache等中间件的上传限制,否则易触发500或413错误。

RESTful 接口本身不支持流式上传大文件,必须绕过 Yii 默认的 $_FILES 全局变量加载方式,否则 2MB 以上就可能直接 500 或空请求 —— 这是绝大多数人卡住的第一步。
为什么 UploadedFile::getInstance() 对大文件失效
Yii(尤其是 1.x 和 2.x 的默认行为)在接收到 multipart/form-data 请求时,会把整个请求体先交给 PHP 的 $_FILES 处理。而 PHP 的 upload_max_filesize、post_max_size、max_input_time 等 ini 设置会直接截断或拒绝请求,getInstance() 根本拿不到对象,返回 null。
- 即使你调大了 PHP 配置,内存中一次性加载几百 MB 文件也会导致 OOM 或超时
-
getInstance()和getInstances()都依赖$_FILES,本质是“全量解析”,不是流式 - RESTful 场景下常走
PUT或POST带原始 body(非 form),此时$_FILES根本为空
用 php://input 手动读取原始 body 实现流式上传
适用于 PUT 请求上传单个大文件(如视频、备份包),绕过 $_FILES,边读边写,内存占用恒定。
- 前端需用
fetch或curl发送原始二进制:fetch('/api/upload', { method: 'PUT', body: fileBlob }) - 后端控制器中禁用 Yii 的 multipart 解析(避免干扰):
'parsers' => []或移除multipart/form-data解析器 - 用
fopen('php://input', 'rb')打开只读流,配合fwrite(fopen($target, 'wb'))分块写入 - 示例关键逻辑:
public function actionUpload()
{
$targetPath = Yii::getAlias('@webroot/uploads/') . uniqid() . '.bin';
$input = fopen('php://input', 'rb');
$output = fopen($targetPath, 'wb');
while (!feof($input)) {
fwrite($output, fread($input, 8192));
}
fclose($input);
fclose($output);
return ['path' => '/uploads/' . basename($targetPath)];
}
注意:该方式不校验文件名、MIME、扩展名,所有校验需自行基于流内容做(如读前 4 字节判断 magic number)。
下载大文件必须用 response->sendStreamAsFile() 而非 sendFile()
sendFile() 会把整个文件读进内存再输出,100MB 文件就可能耗尽内存;sendStreamAsFile() 则打开文件句柄,分块 fpassthru(),适合任意大小。
- 确保文件路径真实存在且 Web 服务器有读权限(别用
@web别名路径,用@webroot绝对路径) - 手动设置
Content-Length和Content-Type可提升兼容性(尤其 Safari) - 示例:
public function actionDownload($name)
{
$filePath = Yii::getAlias('@webroot/uploads/') . $name;
if (!is_file($filePath)) {
throw new NotFoundHttpException('File not found');
}
return Yii::$app->response->sendStreamAsFile(
fopen($filePath, 'rb'),
$name,
['mimeType' => 'application/octet-stream']
);
}
别漏掉 fopen(..., 'rb') 的 b 模式,Windows 下缺它可能损坏二进制文件。
真正容易被忽略的点:Nginx/Apache 的中间层限制
就算 PHP 层搞定了,Nginx 默认 client_max_body_size 1m,Apache 有 LimitRequestBody,CDN 或反向代理也可能截断。这些配置比 Yii 代码更早生效,错误表现是直接 413 Request Entity Too Large,连 Yii 入口都进不去。
- Nginx:在
server或location块加client_max_body_size 512m; - Apache:在虚拟主机或 .htaccess 加
LimitRequestBody 536870912(单位字节) - 如果用了 Cloudflare,还要检查其“最大上传大小”设置(免费版默认 100MB)
调试时先用 curl -v -X PUT --data-binary @test.zip http://yoursite/api/upload 直连后端,排除中间件干扰。











