以前接触到的文件下载基本上都是实时读取的,比方咱们间接将一些服务器端的文件做输入,或是导出一些计算量不太大的excel,所以没有太注意文件下载的细节。

昨天坐了一个数据导出,因为导出的数据量还不算小,而后在导出的过程中还须要做少许的运行,导致下载的时候大概须要5分钟左右。这时候以前没有留神到的细节便浮出了水面。

本次的问题次要呈现在浏览器端未及时弹出文件下载框,这给了用户一种下载页面没法关上的假象。

文件流

HTTP在进行连贯时,会接管到响应头与响应主体。依据HTTP协定的形容,响应头与主体间应用空行来分隔。当响应的内容比拟大时,服务器先把响应的内容由上至下的发送给客户端。这更像数据结构中的队列,header头信息发入队,body的主体的信息后入队,而后因为网速的限度,没有方法一次性将队列中的内容全副发送结束,所以在发送时便应用了先进先出的准则,将位于队头的header的信息先发送给客户端。

浏览器做为客户端,接管到http的header头信息后,便能够得悉后盾将发送一个大的文件给咱们,而后弹出保留文件操作的对话框。

弹出文件下载框

所以下载大的文件时,如果想让浏览器及时的弹出下载对话框,最要害的就是让浏览器及时的收到相干的header信息。

故以下的代码是谬误的:

    ServletOutputStream outputStream = httpServletResponse.getOutputStream();   // 设置响应头 httpServletResponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;");    httpServletResponse.setHeader("Content-Disposition", "attachment;filename=text.xlsx"    // 模仿耗时的下载    Thread.sleep(10000);    // 发送数据并敞开链接    httpServletResponse.flush(); ➊    httpServletResponse.close();

上述代码将导致执行到➊时,浏览器端才可能接管到响应的header信息。也就说是:只有后盾的代码执行到➊时,才会触发浏览器弹出保留文件的对话框。

正确的办法如下:

    ServletOutputStream outputStream = httpServletResponse.getOutputStream();   // 设置响应头 httpServletResponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;");    httpServletResponse.setHeader("Content-Disposition", "attachment;filename=text.xlsx"    httpServletResponse.flush(); ➋    // 耗时的下载    Thread.sleep(10000);    // 发送数据并敞开链接    httpServletResponse.flush();    httpServletResponse.close();

此时代码执行到➋时,浏览器便接管到了必要的header头信息,进而触发其弹出对话框。

本认为曾经高枕无忧,然而用很多老手的话说:浏览器就是不弹出对话框。其实,浏览器不弹出对话框必然是咱们还没有弄明确的,不存在就是一说。咱们在发问时,如果退出了就是,往往阐明本人的心态曾经解体了。

NGINX

排除这种就是的问题,往往还须要简化环境,一层层的把一些环境扔掉,看看是否依然报错。通过尝试我发现原来是本人应用nginx反向代理的起因导致header的信息没有被浏览器及时的接管,所以我大胆的猜想应该是nginx做了数据缓存。

因为发送的header的数据量比拟小,而后NGINX出于某些效率的起因,并没有抉择实时地将数据转发给浏览器,这导致了尽管后盾将HEADER头信息发送了进去,但却没有被浏览器接管到,所以浏览器便没有实时的弹出下载的对话框。

有了大略的方向后,通过google查问发现nginx确实默认有缓存性能。而后找到了相应的官网文档中对于proxy_buffer的一节。内容如下:


Syntax:    proxy_buffering on | off;Default:    proxy_buffering on;Context:    http, server, location

Enables or disables buffering of responses from the proxied server.

When buffering is enabled, nginx receives a response from the proxied server as soon as possible, saving it into the buffers set by the proxy_buffer_size and proxy_buffers directives. If the whole response does not fit into memory, a part of it can be saved to a temporary file on the disk. Writing to temporary files is controlled by the proxy_max_temp_file_size and proxy_temp_file_write_size directives.

When buffering is disabled, the response is passed to a client synchronously, immediately as it is received. nginx will not try to read the whole response from the proxied server. The maximum size of the data that nginx can receive from the server at a time is set by the proxy_buffer_size directive.

Buffering can also be enabled or disabled by passing “yes” or “no” in the “X-Accel-Buffering” response header field. This capability can be disabled using the proxy_ignore_headers directive.


通过浏览官网文档咱们发现有两种禁用该缓存的办法:

  1. 设置proxy_buffering的值为off
  2. 在返回header时,减少一项X-Accel-Buffering,设置值为no

通过测试发现两种状况均能够失常工作。思考到nginx的缓存机制必然有它本人的情理,所以在这里咱们采纳第二种:在返回header时,减少一项X-Accel-Buffering,设置值为no

    ServletOutputStream outputStream = httpServletResponse.getOutputStream();    httpServletResponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");    httpServletResponse.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));    httpServletResponse.setHeader("X-Accel-Buffering", "no");    outputStream.flush();

至此,便能够欢快的出工了。

总结

下载大文件未及时弹出对话框的问题,使我意识到了flush()办法的作用,也如同明确了为什么response中只有set办法,而没有clear办法。同时这还使我大胆地猜想:一旦调用了response中的write()办法后再调用setHeader办法,则应该会报一个谬误或是异样。同时理解了nginx为了某些不晓得的起因,主动启用了缓存,这应该是一种优良的机制,所以在解决办法中咱们并没有间接将其敞开。而是抉择了另一种自定义header值的办法,该办法来告之nginx:这里的数据不须要缓存,请间接发送给客户端。从而达到了在nginx转发的前提下,浏览器实时的弹出保留文件对话框的目标。

心愿能对你有所帮忙。