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 的文件

大文件上传失败的php

谷歌云存储 - 上传大文件 (4GB)

解决nginx和php使用ckfinder无法上传大文件的问题

解决nginx和php使用ckfinder无法上传大文件的问题

上传文件失败,具体原因:上传的文件超过大小限制,请上传小于 1024k的文件?