我在上传超过 6GB 的大文件时遇到一个非常奇怪的问题。我的流程是这样的:
- 文件通过 Ajax 上传到 PHP 脚本。
- PHP 上传脚本获取 $_FILE 并将其分块复制,如 this answer 所示。到临时位置。
- 文件的位置存储在数据库中
- cron 脚本稍后会将文件上传到 s3,再次使用 fopen 函数和缓冲来保持较低的内存使用量
我的 PHP(HHVM) 和 NGINX 配置都将其配置设置为允许最多 16GB 的文件,我的测试文件只有 8GB。
这是奇怪的部分,ajax总是超时。但是文件已成功上传,它被复制到 tmp 位置,即存储在 db、s3 等中的位置。但是即使在所有执行完成之后,AJAX 也会运行一个小时(这需要 10-15 分钟) )并且仅在超时时结束。
什么可能导致服务器不发送仅对大文件的响应?
服务器端的错误日志也是空的。
最佳答案
大文件上传是一项昂贵且容易出错的操作。 Nginx 和后端应该配置正确的超时来处理缓慢的磁盘 IO(如果发生)。理论上,使用多部分/表单数据编码 RFC 1867 管理文件上传非常简单。
根据developer.mozilla.org在 multipart/form-data 正文中,HTTP Content-Disposition 通用 header 是可在多部分正文的子部分上使用的 header ,以提供有关其适用的字段的信息。子部分由 Content-Type header 中定义的边界分隔。用于正文本身,Content-Disposition 没有任何效果。
让我们看看文件上传时发生了什么:
1) 客户端向网络服务器发送带有文件内容的 HTTP 请求
2) Web服务器接受请求并启动数据传输(如果文件大小超过限制,则返回错误413)
3) 网络服务器开始填充缓冲区(取决于文件和缓冲区大小)
4)网络服务器通过文件/网络套接字将文件内容发送到后端
5) 后端验证初始请求
6)后端读取文件并剪切头部(Content-Disposition、Content-Type)
7) 后端将结果文件转储到磁盘
8) 任何后续程序,例如数据库更改
在大文件上传过程中会出现一些问题:
- HTTP 正文请求转储到磁盘并传递到后端处理和复制文件
- 在 HTTP 请求内容上传到服务器之前无法验证请求
- 虽然上传大文件后端很少需要立即获取文件内容
让我们从配置新位置的 Nginx 开始 http://backend/upload 为了接收大文件上传,后端交互被最小化(Content-Legth:0),文件仅存储到磁盘。使用缓冲区 Nginx 将文件转储到磁盘(文件存储到临时目录中,名称随机,无法更改),然后向后端发送 POST 请求到位置 http://backend/file 与 X-File-Name header 中的文件名。
要保留额外信息,您可以在初始 POST 请求中使用 header 。例如,初始请求中的 X-Original-File-Name header 可帮助您匹配文件并将必要的映射信息存储到数据库。
让我们看看如何实现它:
1) 配置 Nginx 将 HTTP 正文内容转储到文件并将其存储在client_body_in_file_only;
2) 创建新的后端端点 http://backend/file处理临时文件名和 header X-File-Name
之间的映射4) 带有 header X-File-Name 的仪器 AJAX 查询,Nginx 将使用它来发送帖子上传请求
配置:
location /upload {
client_body_temp_path /tmp/;
client_body_in_file_only on;
client_body_buffer_size 1M;
client_max_body_size 7G;
proxy_pass_request_headers on;
proxy_set_header X-File-Name $request_body_file;
proxy_set_body off;
proxy_redirect off;
proxy_pass http://backend/file;
}
Nginx configuration option client_body_in_file_only is incompatible with multi-part data upload, but you can use it with AJAX i.e. XMLHttpRequest2 (binary data).
如果需要后端验证,唯一的处理方法是使用auth_request ,例如:
location = /upload {
auth_request /upload/authenticate;
...
}
location = /upload/authenticate {
internal;
proxy_set_body off;
proxy_pass http://backend;
}
无论初始 POST 内容长度大小如何,预上传身份验证逻辑都会防止未经身份验证的请求。
关于php - Nginx PHP 因大文件上传(超过 6 GB)而失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44371643/