swoole不能直接用$_files,必须手动解析multipart/form-data原始数据。因swoole http服务不经过php-fpm,$request->files仅提供元信息(如文件名、大小、类型),真实二进制内容需从$request->rawcontent()提取,并按boundary分割解析;误将$tmp_name当有效路径是常见错误。

不能直接用 $_FILES,必须手动解析 multipart/form-data 原始数据。 Swoole HTTP 服务不经过 PHP-FPM,$_FILES 始终为空,$request->files 只提供元信息(如文件名、大小、类型),真实二进制内容藏在请求体里。
为什么 $request->files 有数据但文件没保存成功
这是最常踩的坑:误以为 $request->files 包含可直接写盘的文件内容。实际上它只是解析出的表单字段描述,类似这样:
Array
(
[upload_file] => Array
(
[name] => test.jpg
[type] => image/jpeg
[tmp_name] => /tmp/phpabc123 ← 无效!Swoole 不生成临时文件
[error] => 0
[size] => 20480
)
)
其中 tmp_name 是占位字符串,不是真实路径,file_get_contents($file['tmp_name']) 必然失败。
-
$request->files的作用仅是帮你快速拿到上传字段名、原始文件名、MIME 类型和大小 - 真实文件内容必须从
$request->rawcontent()或$request->getData()(Swoole ≥4.4)中提取 - 你需要按
boundary分割原始 body,定位到对应 part,再剥离 headers 提取二进制流
如何安全解析 multipart/form-data 请求体
手动解析容易出错且存在安全风险(如 boundary 注入、内存爆炸)。推荐两种方式:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 用
Symfony\Mime\Part\MultipartBody+Symfony\Component\HttpFoundation\File\UploadedFile组合:兼容性强,自动处理编码、边界、嵌套结构,支持流式读取大文件 - 轻量级自解析:先用
strtok($request->rawcontent(), "\r\n")找到Content-Type: multipart/form-data; boundary=...中的 boundary 字符串,再用explode("--{$boundary}", $body)切分 parts,逐个跳过 header 行(遇到空行即为 body 起始) - 注意:
package_max_length配置必须大于预期最大上传体积,否则请求被截断;建议设为10 * 1024 * 1024(10MB)起步,超大文件需配合分片或 TCP 服务
$request->getData() 和 $request->rawcontent() 怎么选
$request->getData() 是 Swoole 4.4+ 提供的「半解析」接口:它会自动解析 application/x-www-form-urlencoded 和 multipart/form-data 的字段值(文本类),但对文件内容仍只返回空数组或 null —— 它不帮你提取文件二进制流,只省掉解析普通表单字段的活。
- 如果你只需要读取文本字段(如
title、desc),用$request->getData()更简洁 - 如果你要存文件,必须用
$request->rawcontent()获取完整原始 body,再自行或借助组件解析 multipart 结构 - 二者不可混用:调用过
$request->getData()后,$request->rawcontent()可能返回空(底层已消费流)
上传路径与权限控制的关键细节
文件落地前,这几个检查点常被忽略:
- 校验
$file['size']是否超出业务限制(不要只信前端传来的 hidden input) - 用
pathinfo($file['name'], PATHINFO_EXTENSION)提取扩展名,再白名单比对(禁用.php、.phar等) - 保存路径必须用
realpath()+is_dir()确认可写,且不能拼接用户可控的目录名(防路径遍历) - 生成唯一文件名(如
spl_object_hash(new stdClass()) . '.' . $ext),避免覆盖或竞争条件 - 大文件场景下,别一次性
file_put_contents():改用fopen(..., 'wb')+fwrite()分块写入,防止内存溢出
真正麻烦的从来不是“怎么存”,而是“怎么确认存得既快又安全”。multipart 解析本身没有魔法,但绕过 PHP-FPM 意味着所有边界检查、编码还原、恶意 payload 过滤都得自己扛。










