基于 Nginx XSendfile + SpringMVC 进行文件下载


PS:经过实际测试,通过 nginx 提供文件下载功能的时候,在 Application Server(Java/RoR/Go...) 端不设置 Content-Length 也是可以的


在平常我们实现文件下载通常是通过普通 read-write方式,如下代码所示。



springboot配置nginx代理的nacos nginx spring_java


1. @RequestMapping("/courseware/{id}")   
2. public void download(@PathVariable("id") String courseID, HttpServletResponse response) throws
3.   
4.      ResourceFile file = coursewareService.downCoursewareFile(courseID);  
5.      response.setContentType(file.getType());  
6.      response.setContentLength(file.contentLength());  
7. "Content-Disposition","attachment; filename=\"" + file.getFilename() +"\"");  
8. //Reade File - > Write To response
9.      FileCopyUtils.copy(file.getFile(), response.getOutputStream());  
10.  }


    由于程序的IO都是调用系统底层IO进行文件操作,于是这种方式在read和write时系统都会进行两次内存拷贝(共四次)。linux 中引入的

sendfile 的实际就为了更好的解决这个问题,从而实现"零拷贝",大大提升文件下载速度。

   

使用 sendfile() 提升网络文件发送性能

   

RoR网站如何利用lighttpd的X-sendfile功能提升文件下载性能

  



    在apache,nginx,lighttpd等web服务器当中,都有sendfile feature。下面就对 nginx 上的XSendfile与SpringMVC文件下载及访问控制进行说明。我们这里的大体流程为:



     1.用户发起下载课件请求; (http://dl.mydomain.com/download/courseware/1)

     2.nginx截获到该(dl.mydomain.com)域名的请求;

     3.将其proxy_pass至应用服务器;

     4.应用服务器根据课件id获取文件存储路径等其它一些业务逻辑(如增加下载次数等);

     5.如果允许下载,则应用服务器通过setHeader -> X-Accel-Redirect 将需要下载的文件转发至nginx中);

     6.Nginx获取到header以sendfile方式从NFS读取文件并进行下载。



     其nginx中的配置为:


     在location中加入以下配置


     


springboot配置nginx代理的nacos nginx spring_java



1. server {  
2. 80;  
3.         server_name dl.mydomain.com;  
4.   
5.         location / {  
6. 127.0.0.1:8080/;  #首先pass到应用服务器  
7.             proxy_redirect     off;  
8.             proxy_set_header   Host             $host;  
9.             proxy_set_header   X-Real-IP        $remote_addr;  
10.             proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;  
11.   
12.             client_max_body_size       10m;  
13.             client_body_buffer_size    128k;  
14.   
15. 90;  
16. 90;  
17. 90;  
18.   
19.             proxy_buffer_size          4k;  
20. 4
21.             proxy_busy_buffers_size    64k;  
22.             proxy_temp_file_write_size 64k;  
23.   
24.         }  
25.   
26.         location /course/ {   
27. 8;  
28.             alias       /nfs/files/; #文件的根目录(允许使用本地磁盘,NFS,NAS,NBD等)  
29.             internal;  
30.         }  
31.     }




    其Spring代码为:


   


springboot配置nginx代理的nacos nginx spring_java


1. package
2.   
3. import
4. import
5.   
6. import
7.   
8. import
9. import
10. import
11. import
12.   
13. import
14. import
15.   
16. /**
17.  * File download controller, provide courseware download or other files. <br>
18.  * <br>
19.  * <i> download a course URL e.g:<br>
20.  * http://dl.mydomain.com/download/courseware/1 </i>
21.  * 
22.  * @author denger
23.  */
24. @Controller
25. @RequestMapping("/download/*")  
26. public class
27.   
28. private
29.       
30. protected static final String DEFAULT_FILE_ENCODING = "ISO-8859-1";  
31.   
32.     /**  
33.      * Under the courseware id to download the file.  
34.      *   
35. @param
36. @throws
37.      */  
38. @RequestMapping("/courseware/{id}")  
39. public void downCourseware(@PathVariable("id") String courseID, final HttpServletResponse response) throws
40.         ResourceFile file = coursewareService.downCoursewareFile(courseID);  
41. if (file != null
42. // redirect file to x-accel-Redirect 
43.             xAccelRedirectFile(file, response);  
44.   
45. else { // If not found resource file, send the 404 code
46. 404);  
47.         }  
48.     }  
49.   
50. protected void
51. throws
52.         String encoding = response.getCharacterEncoding();  
53.   
54. "Content-Type", "application/octet-stream");  
55. //这里获取到文件的相对路径。其中 /course/ 为虚拟路径,主要用于nginx中进行拦截包含了/course/ 的URL, 并进行文件下载。
56. //在以上nginx配置的第二个location 中同样也设置了 /course/,实际的文件下载路径并不会包含 /course/
57. //当然,如果希望包含的话可以将以上的 alias 改为 root 即可。
58. "X-Accel-Redirect", "/course/"
59.                 + toPathEncoding(encoding, file.getRelativePath()));  
60. "X-Accel-Charset", "utf-8");  
61.   
62. "Content-Disposition", "attachment; filename="
63.                 + toPathEncoding(encoding, file.getFilename()));  
64. int) file.contentLength());  // 经过实际测试,这里不设置 Content-Length 也是可以的
65.     }  
66.   
67. //如果存在中文文件名或中文路径需要对其进行编码成 iSO-8859-1
68. //否则会导致 nginx无法找到文件及弹出的文件下载框也会乱码
69. private String toPathEncoding(String origEncoding, String fileName) throws
70. return new
71.     }  
72.   
73. @Autowired
74. public void
75. this.coursewareService = coursewareService;  
76.     }  
77. }