Java SpringBoot下使用X-SendFile实现文件下载 - 行万里路才能回到内心深处,读万卷书才能看得清皓月繁星
Java SpringBoot下使用X-SendFile实现文件下载
- Lokie.Wang
- 2022-10-10 22:26:19
- 笔记
- SpringBoot nginx
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-Sendfile | Apache, Lighttpd v1.5, Cherokee |
X-LIGHTTPD-send-file | Lighttpd v1.4 |
X-Accel-Redirect | Nginx, 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.zipStirng 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.Wang原创文章,转载无需和我联系,但请注明来自lokie博客http://lokie.wang
Nginx与X-Sendfile
Nginx与X-Sendfile_ITPUB博客
Nginx与X-Sendfile
X-accel模块允许由后台通过返回的头来决定投递静态文件。为什么要这么做呢?试想一下我们经常碰到的这种情况,在有的论坛中,下载资源需要登录认证、权限核查、积分扣除或者是积分增加等等,对于Nginx来说这些细粒度的控制其本身无法完成,因此需要应用程序来完成,当应用程序完成这些操作后,根据实际情况会做出选择,如果条件满足那么开始下载所要获取的静态资源(文件),注意,这是由动态的程序提供下载,对于动态程序来说,这是一个弱点,而对于Nginx来说,这是它的强项,那么在这种形式下能不能让Nginx来完成静态资源的下载呢?答案是可以的。但我们为什么有必要这么做呢?答案在于Nginx在打开静态文件上使用了sendfile(2),因此其IO效率非常之高。
处理流程
具体的处理流程是:
-------------------
|"GET /dd/filename |
| |
∨ |
客户端请求----------->Nginx----->Backend(Apache、Tomcat、FastCGI,……)
∧ |
| |
| X-Accel-Redirect: /files/filename |
|_______________________________________|
我们从这个流程图中不难看出在客户端的请求被转向后台服务器时,服务器并没有为客户端返回实际要下载的资源(而是去做了其它的验证或者是其它的工作)而是使用了X-Accel-Redirect头将下载的资源又传递给了Nginx,最后又是通过Nginx服务器处理该请求发送给客户端。
这种功能就是我们说的X-Sendfile,在Nginx中由X-Accel-Redirect来完成,在后台服务器将下载的请求抛给Nginx后,那么后台的服务器又可以承接其它的活进而处理其它的请求了,因此大大的减轻了后端服务器的压力。
相对于其它的Nginx来说,X-accel模块与其它标准的Nginx模块有所不同,它的实现不是依赖于指令而是依赖于在特定方式下后台(或者叫上游)服务器发回的请求头,它的方法我们在前面也了解到了,就是通过发送一个带有URI的x-accel-redirect头,Nginx将会将这个请求作为正常(这里的正常就是指就像是使用浏览器一样的请求)的请求来处理这个请求,然后根据这个URI进行location匹配,然后是请求文件的匹配,最终实现的是在后端服务器返回的请求头中:“root + URI”与Nginx中location匹配,这里的“root”,我们以PHP程序为例:
header("X-Accel-Redirect: /files/" . $path); |
就是我们这个PHP程序中的“/files/”部分,而URI则是“$path”部分。在这里我们就了解到这里,在后面的例子会证实这一点。
另外,还需要注意一点,由于Nginx服务器只认识从后端服务器发来的X-Accel-Redirect头,而从客户端发来的这种头,它并不理睬。
配置示例
# Will serve /var/www/files/myfile.tar.gz # When passed URI /protected_files/myfile.tar.gz location /protected_files { internal; alias /var/www/files; }
# Will serve /var/www/protected_files/myfile.tar.gz # When passed URI /protected_files/myfile.tar.gz location /protected_files { internal; root /var/www; }
You can also proxy to another server.
location /protected_files { internal; proxy_pass }
|