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

转自:http://denger.iteye.com/blog/1014066

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

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

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

Java代码  收藏代码
  1. @RequestMapping("/courseware/{id}")   
  2. public void download(@PathVariable("id") String courseID, HttpServletResponse response) throws Exception {  
  3.   
  4.      ResourceFile file = coursewareService.downCoursewareFile(courseID);  
  5.      response.setContentType(file.getType());  
  6.      response.setContentLength(file.contentLength());  
  7.      response.setHeader("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中加入以下配置
     

Conf代码  收藏代码
  1. server {  
  2.         listen 80;  
  3.         server_name dl.mydomain.com;  
  4.   
  5.         location / {  
  6.             proxy_pass  http://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.             proxy_connect_timeout      90;  
  16.             proxy_send_timeout         90;  
  17.             proxy_read_timeout         90;  
  18.   
  19.             proxy_buffer_size          4k;  
  20.             proxy_buffers              4 32k;  
  21.             proxy_busy_buffers_size    64k;  
  22.             proxy_temp_file_write_size 64k;  
  23.   
  24.         }  
  25.   
  26.         location /course/ {   
  27.             charset utf-8;  
  28.             alias       /nfs/files/; #文件的根目录(允许使用本地磁盘,NFS,NAS,NBD等)  
  29.             internal;  
  30.         }  
  31.     }  



    其Spring代码为:
   

Java代码  收藏代码
  1. package com.xxxx.portal.web;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.UnsupportedEncodingException;  
  5.   
  6. import javax.servlet.http.HttpServletResponse;  
  7.   
  8. import org.springframework.beans.factory.annotation.Autowired;  
  9. import org.springframework.stereotype.Controller;  
  10. import org.springframework.web.bind.annotation.PathVariable;  
  11. import org.springframework.web.bind.annotation.RequestMapping;  
  12.   
  13. import com.xxxx.core.io.ResourceFile;  
  14. import com.xxxx.portal.services.CoursewareService;  
  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 DownloadController {  
  27.   
  28.     private CoursewareService coursewareService;  
  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 courseID The course id.  
  36.      * @throws IOException   
  37.      */  
  38.     @RequestMapping("/courseware/{id}")  
  39.     public void downCourseware(@PathVariable("id") String courseID, final HttpServletResponse response) throws IOException {  
  40.         ResourceFile file = coursewareService.downCoursewareFile(courseID);  
  41.         if (file != null && file.exists()){  
  42.             // redirect file to x-accel-Redirect   
  43.             xAccelRedirectFile(file, response);  
  44.   
  45.         } else { // If not found resource file, send the 404 code  
  46.             response.sendError(404);  
  47.         }  
  48.     }  
  49.   
  50.     protected void xAccelRedirectFile(ResourceFile file, HttpServletResponse response)   
  51.         throws IOException {  
  52.         String encoding = response.getCharacterEncoding();  
  53.   
  54.         response.setHeader("Content-Type", "application/octet-stream");  
  55.         //这里获取到文件的相对路径。其中 /course/ 为虚拟路径,主要用于nginx中进行拦截包含了/course/ 的URL, 并进行文件下载。  
  56.         //在以上nginx配置的第二个location 中同样也设置了 /course/,实际的文件下载路径并不会包含 /course/  
  57.         //当然,如果希望包含的话可以将以上的 alias 改为 root 即可。  
  58.         response.setHeader("X-Accel-Redirect", "/course/"  
  59.                 + toPathEncoding(encoding, file.getRelativePath()));  
  60.         response.setHeader("X-Accel-Charset", "utf-8");  
  61.   
  62.         response.setHeader("Content-Disposition", "attachment; filename="  
  63.                 + toPathEncoding(encoding, file.getFilename()));  
  64.         // response.setContentLength((int) file.contentLength());  // 经过实际测试,这里不设置 Content-Length 也是可以的
  65.     }  
  66.   
  67.     //如果存在中文文件名或中文路径需要对其进行编码成 iSO-8859-1  
  68.     //否则会导致 nginx无法找到文件及弹出的文件下载框也会乱码  
  69.     private String toPathEncoding(String origEncoding, String fileName) throws UnsupportedEncodingException{  
  70.         return new String(fileName.getBytes(origEncoding), DEFAULT_FILE_ENCODING);  
  71.     }  
  72.   
  73.     @Autowired  
  74.     public void setCoursewareService(CoursewareService coursewareService) {  
  75.         this.coursewareService = coursewareService;  
  76.     }  
原文地址:https://www.cnblogs.com/leoncfor/p/4754426.html