Ajax + Spring MVC上传文件失败的问题的排查解决方案

最近在作一个文件上传需求,发现当上传文件大于60M时,前端ajax文件上传后,服务请求不到服务端。前端

排查过程:     java

第一步,页面请求返回NS_BINDING_ABORTED,先查了下Nginx日志,Nginx 400 Bad Request 猜想是由于客户端Post请求Packet在网络传输过程当中部分丢失致使到服务端没法正常响应,客户端10s超时断开链接,这时候nginx记录了400。这种状况下,nginx实际未反馈400的response,只是在链接断开时记录了400的日志。nginx

因而排查前端代码以下:前端在上传文件请求后,设置10S的请求超时时间,初步猜想是上传文件较大,前端请求包尚未发送完毕就中断请求了,因而把timeout时间设置加大,增长到20秒,发现60多M的文件上传成功。web

 1      $.ajax({
 2                     url: '/upload_url',
 3                     type: 'post',
 4                     data: data,
 5                     timeout: 10000, // 超时
 6                     cache: false,
 7                     processData: false,
 8                     contentType: false,
 9                     success: function(json) {
10                       .......
11                         }
12                     },
13                     error: function() {
14                       
15                     },
16                     complete: function(XMLHttpRequest, status) {
17                         if (status === 'timeout') {
18                             // 超时直接提示
19                            //   '上传时间较长,请稍后查看结果',           
20                             });
21                         }
22                     }
23                 })

 

 

第二步,测试上传文件大于100M时的状况,发现服务端不会接收到请求,加大了超时时间也没有任何效果,排查Nginx配置上传文件大小限制是300M,即 client_max_body_size 300m。ajax

而且发现Nginx以下日志:spring

。。。。。。。。。。。a client request body is buffered to a temporary file /dev/。。。。。。。/client_body/XXXXXXX, clientapache

。。。。。。。。。。。pwrite() "/dev/。。。。。。。/client_body/XXXXXXX" failed (28: No space left on device), clientjson

 因此,优化nginx配置,client_max_body_size 300m;   client_body_buffer_size 300m; 加大上传缓存Buffer size文件,reload nginx生效。后端

 ps :    浏览器

     client_max_body_size
    此指令设置NGINX能处理的最大请求主体大小。 若是请求大于指定的大小,则NGINX发回HTTP 413(Request Entity too large)错误。 若是服务器处理大文件上传,则该指令很是重要。
     client_body_buffer_size
   
此指令设置用于请求主体的缓冲区大小。 若是主体超过缓冲区大小,则完整主体或其一部分将写入临时文件。 若是NGINX配置为使用文件而不是内存缓冲区,则该指令会被忽略。 默认状况下,该指令为32位系统设置一个8k缓冲区,为64位系统设置一个16k缓冲区。 该指令在NGINX配置的http,server和location区块使用。

 

第三步,继续测试100M以上的文件,仍然不成功,浏览器前端请求结果返回 net::ERR_INCOMPLETE_CHUNKED_ENCODING,因而排查nginx日志以下:

  。。。。。。。。。    错误日志所有是104: Connection reset by peer) while reading upstream

  显然,因为upstream重置链接了,就是说后端主动断开了链接,而后发现链接里有不少TIME-WAIT,一直 等待远程TCP接收到链接中断请求的确认。因而分析nginx配置,nginx做为反向代理既然是客户端又是服务端,当和后端服务创建链接时并无默认开启长链接,开启长链接后性能应该会提高不少,因而优化nginx配置开启nginx长连接【说明:(从client到nginx的链接是长链接,对于客户端来讲,nginx长链接是默认开启的。从nginx到server的链接是长链接,须要本身开启】。
配置以下:
           proxy_connect_timeout      120;   
           proxy_send_timeout         200; 
           proxy_read_timeout         300;  
           proxy_http_version 1.1;    ##开启后端,长链接
           proxy_set_header Connection "";  ##开启后端,长链接
 
nginx reload 生效。
 
第四步,   从新上传大于100M的文件,此次上面的nginx报错问题解决,但仍然没有成功,由于上一步,已经能够确认上传文件请求已经发送到服务端,因而排查服务端日志,发现异常日志以下:
org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size of 104857600 bytes exceeded; nested exception is org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (240330252) exceeds the configured maximum (104857600)
    org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.java:162)
    org.springframework.web.multipart.commons.CommonsMultipartResolver.resolveMultipart(CommonsMultipartResolver.java:142)
    org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1099)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:932)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
 
经过上面的报错日志,定位到 CommonsMultipartResolvermaxUploadSize配置设置成100M大小,因而加大此配置,以下参考。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="314572800" /> <!--300M-->
<property name="maxInMemorySize" value="314572800" /> <!--300M 设置multipart请求所容许的最大大小,默认不限制 -->
<property name="defaultEncoding" value="UTF-8"></property> <!-- 设置一个大小,multipart请求小于这个大小时会存到内存中,大于这个内存会存到硬盘中 -->
</bean>
修改服务端文件大小校检,从新编辑发布服务端服务,问题解决。经测试300M之内文件上传没有问题。
固然,若是有业务需求上传文件的大小更大的,须要同时修改nginx配置及 maxUploadSize配置,而后会报以下错误:
。。。。 682 client intended to send too large body:。。。。。。。。。
 
至此,问题得以解决,真相大白于天下,因为是解决问题以后理解的,可能会有些遗漏,及忽略掉一些本人以为不必的其它的排查过程及说明,敬请见谅。
 
 
备注:

一、Nginx 400 Bad Request

  通常致使400异常的场景:

(1)请求头过大
(2)空请求
(3)URLConnection发起HTTPS请求通过代理400异常
(4)网络传输丢包致使的400异常

 

二、Nginx 499 / ClientClosed Request

     upstream在如下几种状况下会返回499:

  (1)upstream 在收到读写事件处理以前时,会检查链接是否可用:
  ngx_http_upstream_check_broken_connection,
    if (c->error) { //connecttion错误      …… if (!u->cacheable) { //upstream的cacheable为false,这个值跟http_cache模块的设置有关。指示内容是否缓存。 ngx_http_upstream_finalize_request(r, u, NGX_HTTP_CLIENT_CLOSED_REQUEST); }   }

  如上代码,当链接错误时会返回499。

  (2)server处理请求未结束,而client提早关闭了链接,此时也会返回499。

  (3)在一个upstream出错,执行next_upstream时也会判断链接是否可用,不可用则返回499。

总之,这个错误的比例升高可能代表服务器upstream处理过慢,致使用户提早关闭链接。而正常状况下有一个小比例是正常的。