/ 日常 / 0浏览

【收藏】基于Nginx XSendfile+SpringMVC进行文件下载

 
@RequestMapping("/courseware/{id}")    

public void download(@PathVariable("id") String courseID, HttpServletResponse response) throws Exception {   

  

     ResourceFile file = coursewareService.downCoursewareFile(courseID);   

     response.setContentType(file.getType());   

     response.setContentLength(file.contentLength());   

     response.setHeader("Content-Disposition","attachment; filename=\"" + file.getFilename() +"\"");   

     //Reade File - > Write To response   

     FileCopyUtils.copy(file.getFile(), response.getOutputStream());   

}  





   @RequestMapping("/courseware/{id}") 

   public void download(@PathVariable("id") String courseID, HttpServletResponse response) throws Exception { 



        ResourceFile file = coursewareService.downCoursewareFile(courseID); 

        response.setContentType(file.getType()); 

        response.setContentLength(file.contentLength()); 

        response.setHeader("Content-Disposition","attachment; filename=\"" + file.getFilename() +"\""); 

        //Reade File - > Write To response 

        FileCopyUtils.copy(file.getFile(), response.getOutputStream()); 

    }
由于程序的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中加入以下配置
server {   

        listen 80;   

        server_name dl.mydomain.com;   

  

        location / {   

            proxy_pass  http://127.0.0.1:8080/;  #首先pass到应用服务器   

            proxy_redirect     off;   

            proxy_set_header   Host             $host;   

            proxy_set_header   X-Real-IP        $remote_addr;   

            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;   

  

            client_max_body_size       10m;   

            client_body_buffer_size    128k;   

  

            proxy_connect_timeout      90;   

            proxy_send_timeout         90;   

            proxy_read_timeout         90;   

  

            proxy_buffer_size          4k;   

            proxy_buffers              4 32k;   

            proxy_busy_buffers_size    64k;   

            proxy_temp_file_write_size 64k;   

  

        }   

  

        location /course/ {    

            charset utf-8;   

            alias       /nfs/files/; #文件的根目录(允许使用本地磁盘,NFS,NAS,NBD等)   

            internal;   

        }   

    }
其Spring代码为:
package com.xxxx.portal.web;   

  

import java.io.IOException;   

import java.io.UnsupportedEncodingException;   

  

import javax.servlet.http.HttpServletResponse;   

  

import org.springframework.beans.factory.annotation.Autowired;   

import org.springframework.stereotype.Controller;   

import org.springframework.web.bind.annotation.PathVariable;   

import org.springframework.web.bind.annotation.RequestMapping;   

  

import com.xxxx.core.io.ResourceFile;   

import com.xxxx.portal.services.CoursewareService;   

  

/**  

* File download controller, provide courseware download or other files. <br>  

* <br>  

* <i> download a course URL e.g:<br>  

* http://dl.mydomain.com/download/courseware/1 </i>  

*   

* @author denger  

*/  

@Controller  

@RequestMapping("/download/*")   

public class DownloadController {   

  

    private CoursewareService coursewareService;   

       

    protected static final String DEFAULT_FILE_ENCODING = "ISO-8859-1";   

  

    /**   

     * Under the courseware id to download the file.   

     *    

     * @param courseID The course id.   

     * @throws IOException    

     */   

    @RequestMapping("/courseware/{id}")   

    public void downCourseware(@PathVariable("id") String courseID, final HttpServletResponse response) throws IOException {   

        ResourceFile file = coursewareService.downCoursewareFile(courseID);   

        if (file != null && file.exists()){   

            // redirect file to x-accel-Redirect    

            xAccelRedirectFile(file, response);   

  

        } else { // If not found resource file, send the 404 code   

            response.sendError(404);   

        }   

    }   

  

    protected void xAccelRedirectFile(ResourceFile file, HttpServletResponse response)    

        throws IOException {   

        String encoding = response.getCharacterEncoding();   

  

        response.setHeader("Content-Type", "application/octet-stream");   

        //这里获取到文件的相对路径。其中 /course/ 为虚拟路径,主要用于nginx中进行拦截包含了/course/ 的URL, 并进行文件下载。   

        //在以上nginx配置的第二个location 中同样也设置了 /course/,实际的文件下载路径并不会包含 /course/   

        //当然,如果希望包含的话可以将以上的 alias 改为 root 即可。   

        response.setHeader("X-Accel-Redirect", "/course/"  

                + toPathEncoding(encoding, file.getRelativePath()));   

        response.setHeader("X-Accel-Charset", "utf-8");   

  

        response.setHeader("Content-Disposition", "attachment; filename="  

                + toPathEncoding(encoding, file.getFilename()));   

        response.setContentLength((int) file.contentLength());   

    }   

  

    //如果存在中文文件名或中文路径需要对其进行编码成 iSO-8859-1   

    //否则会导致 nginx无法找到文件及弹出的文件下载框也会乱码   

    private String toPathEncoding(String origEncoding, String fileName) throws UnsupportedEncodingException{  

        return new String(fileName.getBytes(origEncoding), DEFAULT_FILE_ENCODING);   

    }   

  

    @Autowired  

    public void setCoursewareService(CoursewareService coursewareService) {   

        this.coursewareService = coursewareService;   

    }   

}