Nginx PHP 上传大文件失败(超过 6 GB)
Posted
技术标签:
【中文标题】Nginx PHP 上传大文件失败(超过 6 GB)【英文标题】:Nginx PHP Failing with Large File Uploads (Over 6 GB) 【发布时间】:2017-11-06 09:16:27 【问题描述】:我在上传超过 6GB 的大型文件时遇到了一个非常奇怪的问题。我的流程是这样的:
-
文件通过 Ajax 上传到 php 脚本。
PHP 上传脚本获取 $_FILE 并将其复制到块中,如 this answer 到 tmp 位置。
文件的位置存储在db中
cron 脚本稍后会将文件上传到 s3,再次使用 fopen 函数和缓冲以保持低内存使用率
我的 PHP(HHVM) 和 nginx 配置都将它们的配置设置为允许最多 16GB 的文件,我的测试文件只有 8GB。
这是奇怪的部分,ajax 会总是超时。但是文件已成功上传,它被复制到 tmp 位置,存储在 db、s3 等中的位置。但是 AJAX 运行了一个小时,即使 在所有执行完成后(需要 10-15 分钟) ) 并且仅在超时时结束。
什么可能导致服务器不发送响应只为大文件?
服务器端的错误日志也是空的。
【问题讨论】:
如果您需要帮助找出可能出现的问题,您需要发布代码。 您是否在 ajax 上传脚本中重复使用 session_write_close() 和 session_start()?这可能会导致问题。 【参考方案1】:大文件上传是一项昂贵且容易出错的操作。 Nginx 和后端应该配置正确的超时来处理慢速磁盘 IO(如果发生)。从理论上讲,使用多部分/表单数据编码 RFC 1867 管理文件上传很简单。
根据 multipart/form-data 正文中的developer.mozilla.org,HTTP Content-Disposition 通用标头是一个标头,可用于多部分正文的子部分,以提供有关其适用的字段的信息。子部分由 Content-Type 标头中定义的边界分隔。用于 body 本身,Content-Disposition 无效。
让我们看看上传文件时会发生什么:
1) 客户端向网络服务器发送带有文件内容的 HTTP 请求
2) 网络服务器接受请求并启动数据传输(如果文件大小超过限制,则返回错误 413)
3) 网络服务器开始填充缓冲区(取决于文件和缓冲区大小)
4) 网络服务器通过文件/网络套接字将文件内容发送到后端
5) 后端验证初始请求
6) 后端读取文件并切头(Content-Disposition,Content-Type)
7) 后端将结果文件转储到磁盘上
8) 任何后续程序,例如数据库更改
在大文件上传过程中会出现几个问题:
HTTP 正文请求转储到磁盘并传递到后端哪个进程和复制文件 在 HTTP 请求内容上传到服务器之前无法对请求进行身份验证 虽然上传大文件后端很少需要文件内容本身立即让我们从配置新位置 http://backend/upload 的 Nginx 开始,以接收大文件上传,最小化后端交互(Content-Legth:0),文件仅存储到磁盘。使用缓冲区 Nginx 将文件转储到磁盘(以随机名称存储到临时目录中的文件,不能更改),然后向后端发送 POST 请求到位置 http://backend/file,文件名在 X-File-Name 标题。
为了保留额外信息,您可以在初始 POST 请求中使用标头。例如,初始请求中的 X-Original-File-Name 标头可帮助您匹配文件并将必要的映射信息存储到数据库中。
让我们看看如何实现它:
1) 配置 Nginx 以将 HTTP 正文内容转储到文件并保持存储在 client_body_in_file_only 上;
2) 创建新的后端端点http://backend/file 来处理临时文件名和头文件之间的映射X-File-Name
4) 带有 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 配置选项client_body_in_file_only 是 与多部分数据上传不兼容,但可以与AJAX一起使用 即 XMLHttpRequest2(二进制数据)。
如果需要后端认证,只能使用auth_request,例如:
location = /upload
auth_request /upload/authenticate;
...
location = /upload/authenticate
internal;
proxy_set_body off;
proxy_pass http://backend;
无论初始 POST Content-Length 大小如何,预上传身份验证逻辑都可以防止未经身份验证的请求。
【讨论】:
太棒了!您将如何配置它以在没有代理的情况下运行?改为添加return 201 $request_body_file;
行会起作用吗?
@bcattle proxy_pass 不一定是后端上游 URL,它可以是一个命名的内部位置,你想达到什么目的?
在js中上传文件:const file = document.getElementById('file').files[0]; const xhr = new XMLHttpRequest(); xhr.open('POST', '/upload', false); xhr.send(file);
html:<input id="file" type="file" onchange="upload()" />
只是一个例子,在生产中使用asnyc
@bcattle 和其他任何想知道他们的问题的人:如果您在 location
指令的末尾使用 return
,它就不起作用。经过几个小时尝试不同的配置选项后,我发现它不起作用。但是,您可以使用 nginx 本身作为代理目标。有关设置服务器以侦听端口 4000 并将 proxy_pass
值设置为 127.0.0.1:4000
的示例,请参阅 spielwiese.fontein.de/2017/04/23/simple-file-uploading-in-nginx。
我使用相同的代码,但在网络浏览器中显示错误 405。你能帮帮我吗?以上是关于Nginx PHP 上传大文件失败(超过 6 GB)的主要内容,如果未能解决你的问题,请参考以下文章
PHP AWS Elastic Beanstalk - 不能发布超过 2GB 的文件
解决nginx和php使用ckfinder无法上传大文件的问题