Java SpringBoot下使用X-SendFile实现文件下载

1.起因

在项目中有一个文件是服务器端打包生成后的文件放在服务器中某一个目录内,文件的特点如下:

  • 体积比较大,超过1G家常便饭
  • 数据比较敏感,业务上不能匿名下载,必须有严格的权限和鉴权体系
  • 终端用户使用的环境网络不稳定或者网速非常缓慢

2.传统做法

传统做法很容易,采用SpringBoot建立API,采用Token等方式对用户权限鉴定后,读取文件流,由Java发送给客户端。这样不暴露静态HTTP链接给用户,解决了鉴权问题。但是由于文件大用户网速慢并且不稳定等各种原因实际效果并不理想。主要问题如下:

  • 由于工作在nginx代理后,有超时现象发生来源
  • 用户希望有断点续传功能,无法轻易实现
  • 读文件Java开销比较大

3 解决方案

思路:能否结合让静态文件下载由类似nginx这样的web服务器去进行,并有一鉴权功能。方案当然是有的,比如采用lua或者自己编写nginx插件,但是改动都比较大成本非常高,查阅文档后发现可以采用如下x-sendfile这中方式来实现。

3.1 什么是X-SENDFILE

X-Sendfile 通过一个特定的 HTTP header 来实现:在 X-Sendfile 头中指定一个文件的地址来通告前端 web 服务器。当 web 服务器检测到后端发送的这个 header 后,它将忽略后端的其他输出,而使用自身的组件(包括 缓存头 和 断点重连 等优化)机制将文件发送给用户。

在使用 X-Sendfile 之前,我们必须明白这并不是一个标准特性,在默认情况下它是被大多数 web 服务器禁用的。而不同的 web 服务器的实现也不一样,包括规定了不同的 X-Sendfile 头格式。如果配置失当用户可能会下载到错误的文件。

SENDFILE 头使用的 WEB 服务器
X-SendfileApache, Lighttpd v1.5, Cherokee
X-LIGHTTPD-send-fileLighttpd v1.4
X-Accel-RedirectNginx, Cherokee

缺点:由于发送下载文件由nginx做了,所以Java是无法知道文件下载完成的

3.2 Nginx的X-SENDFILE

这里主要说明nginx的X-SENDFILE配置。

3.2.1 相关特殊的HTTP头

  • X-Accel-Redirect:设置URI给nginx进行内部的跳转
  • X-Accel-Buffering:下载使用Cache,默认YES
  • X-Accel-Expires: 缓存超时时间。
  • X-Accel-Limit-Rate:限速

3.2.2 相关配置

在nginx配置文件中设置如下:

location /protected/ {
  internal;
  root   /some/path;
}

internal指令保证了这个链接只能被nginx自己访问,root可以映射目录。

4 示例

Nginx 配置

# 动态权限
locaton /api/ {
    proxy_pass http://upstream:8080/;
}
#内部静态地址
location /download/ {
  internal;
  root   /some/path;
}

注意: /some/path/donwload文件夹必须存在

这里以Springboot Java代码作为例子

@RequestMapping(value = "/api/download", method = RequestMethod.GET)
    public void plistDownLoad(HttpServletResponse response, 
    @RequestParam("bidSectionID") String bidSectionID,
    @RequestParam("token") String token,
    HttpServletResponse response) { 
        // 权限代码
        if(authService.auth(token)) {
            String filePath = fileService.getFilte(bidSetionID)
            // 上面返回 /download/test.zip
            Stirng outFileName = FileNameUitls.getFileName(filePath);
             String contentDisposition = "attachment" + ";filename*=UTF-8''" + URLEncoder.encode(outFileName, "UTF-8");
        response.setHeader("Content-Type", "application/octet-stream;charset=utf-8");
        response.setHeader("Content-Disposition", contentDisposition);
        response.setHeader("X-Accel-Redirect", filePath);
        } else  {
           // 报错
             response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json;charset=UTF-8");
            PrintWriter pw = response.getWriter();
            pw.write("非法访问");
            pw.flush();
            pw.close();
        }
    }

Lokie博客
请先登录后发表评论
  • 最新评论
  • 总共0条评论
  • 本博客使用免费开源的 laravel-bjyblog v5.5.1.1 搭建 © 2014-2018 lokie.wang 版权所有 ICP证:沪ICP备18016993号
  • 联系邮箱:kitche1985@hotmail.com