前端需注意的文件下载上传的 8 个问题

在一般 webapp 中,文件的下载上传是使用频度相对较少的功能。一些常见的问题汇总如下。

  1. 触发下载

为了触发浏览器的下载行为,而非用其内置方式处理文件(例如打开 pdf 文件),可以为超链接加上 download 属性。例如

<a download href="test.pdf">下载</a>

  1. 客户端内容下载

如果希望下载客户端数据(例如当前 canvas 图像),需要将数据编码为 data:URLblob:URL,再由代码生成 <a download href="data:xxx"></a> 并触发其点击事件,触发浏览器的下载行为。

  1. 传送大量参数进行下载

下载请求往往是浏览器触发的 GET 请求,无法将大量参数编码为 body 进行传输。这时可以使用 POST 请求来传送参数进行下载。利用表单而非 <a> 可以将数据发送到服务器,服务器需响应 Content-Disposition: attachment header 来触发浏览器的下载行为。

  1. 反向代理的负载

上传下载文件时,代理服务不能 down 掉或重启会导致上传下载失败。需要注意上传下载文件时代理服务的负载,避免因负载过高导致重启。

  1. 反向代理 nginx 的 proxy_max_temp_file_size

在使用 nginx 作为代理服务器下载超过 1GiB 的文件,需要注意 proxy_max_temp_file_size 指令的配置(参考)。默认情况下 nginx 会使用文件缓冲,默认缓冲大小为 1GiB。使用文件缓冲时,nginx 会尽量快的向后端服务器请求数据。当缓冲写满时停止请求,直到所有缓冲数据都发送给客户端。由于 nginx 部署往往离后端服务器更近,数据传输速度也更快,离客户端远,传输速度慢,所以在这种模式下 nginx 与后端服务器之间会有一段时间无数据交换。如果连接没有保活机制,无数据交换的时间可能超过系统配置的 TCP Idle Timeout,这时 nginx 与后端服务器的连接将会断开,导致下载失败。

  1. 反向代理的流式处理

代理服务器必须流式地处理文件请求,不能将响应数据大量积压在内存中,否则可能引起 OOM 导致下载上传失败,同时造成客户端下载触发推迟甚至超时。koa-proxy 作为 koa 的代理中间件,有个非常吸引人的特性:允许其他中间件对代理响应做修改。这导致 koa-proxy 需要将响应数据全部缓冲在内存中,最后由其他中间件处理后一并发送给客户端。这使得 koa-proxy 不适合用于文件下载的代理。

  1. 上传进度

若需显示上传进度,目前应采用 xhr 而非 fetch 来实现上传。xhr 采用事件机制,其本质上是可以输出多次值的 observer,而 fetch 采用 Promise 风格,其本质上是只能输出一次值的 observer。所以 xhr 有能力也更适合用于上传进度的报告。理论上,可以在 fetch 之外实现进度监听,例如使用 stream api 来获取文件已传输的字节数。

  1. 分片上传

在后端支持的情况下,使用分片上传可以提高上传的可靠性,也有并行上传提高上传性能的潜力。需要利用 Content-Range header 和 Blob.slice api。