swoole / rfc

Swoole 提案
116 stars 3 forks source link

RFC-1027 HTTP 服务器支持超大文件上传 #83

Closed matyhtf closed 2 years ago

matyhtf commented 2 years ago

背景

当前版本中,HTTP 服务器在处理上传文件时,需要将所有内容保存在内存中。最大上传文件尺寸受限于 package_max_length 参数设置。基于此,底层无法支持超大文件上传,强行修改package_max_length 为一个巨大的值,会导致大量内存占用。

因此需要更换一个方式支持超大文件上传,在master进程中读取到文件上传请求,应当将文件内容直接写入磁盘,dispatch 时只传递文件名。

新增配置

$server->set([
    'upload_max_filesize' => 1 * 1024 * 1024 * 1024, // 最大允许上传 1G 
    'package_max_length' => 4 * 1024 * 1024, // 最大请求尺寸,所占内存的尺寸
]);

默认 upload_max_filesize0 ,表示不开启大文件上传,上传文件的 form-data 保存在内存中,受到 package_max_length 限制。 设置 upload_max_filesize 参数后,将开启大文件上传模块,客户端发送的 form-data 数据可以超过 package_max_length 限制,在master进程中会解析form-data,包含 filenamepart 的内容直接写入磁盘文件,目录为 upload_tmp_dir 配置。

实现细节

需要在 master 进程的 HTTP 解析部分进行处理,在检测到 Content-Typemultipart/form-data ,并且 upload_max_filesize 不为 0 并且 Content-Length 超过 package_max_length 时,进入超大文件上传模块。

需要解析 multipart,不包含 filename 的部分仍然保存在内存中,包含 filename 的部分,则将内容直接写入临时文件,并修改属性值,追加文件上传信息。

处理前:

-----------------------------7e196ceb17435e72200
Content-Disposition: form-data; name="file"; filename="test.jpg"
Content-Type: text/plain

$file_content  ... 1G 数据 
-----------------------------7e196ceb17435e72200

处理后

-----------------------------7e196ceb17435e72200
Content-Disposition: swoole-upload-file; name="testfile"; filename="test.jpg"
Content-Type: text/plain

tmp_file=$tmp_file; size=$file_size
-----------------------------7e196ceb17435e72200

worker 进程中读取到 Content-Disposition: swoole-upload-file 后,将其转换为 Response::$files[testfile] 的值

写入方式

需要使用 async write file 异步写入文件,在线程池中完成文件的写操作,避免磁盘等待导致 reactor 线程阻塞。应使用pwrite,记录每次写入的 offset,保证数据一致性。

错误处理

处理失败时,需要处理错误

超过限制

offset 超过了 upload_max_filesize,则认为请求存在问题,返回 413 错误,进入错误处理流程

写入失败

写磁盘失败时,返回 500 错误,进入错误处理流程

matyhtf commented 2 years ago

https://github.com/swoole/swoole-src/tree/rfc-1027