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

评:

Java代码

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

publicvoiddownload(@PathVariable("id")StringcourseID,HttpServletResponseresponse)throwsException{

ResourceFilefile=coursewareService.downCoursewareFile(courseID);

response.setContentType(file.getType());

response.setContentLength(file.contentLength());

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

//ReadeFile->WriteToresponse

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

}

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

publicvoiddownload(@PathVariable("id")StringcourseID,HttpServletResponseresponse)throwsException{

ResourceFilefile=coursewareService.downCoursewareFile(courseID);

response.setContentType(file.getType());

response.setContentLength(file.contentLength());

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

//ReadeFile->WriteToresponse

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

}

由于程序的IO都是调用系统底层IO进行文件操作,于是这种方式在read和write时系统都会进行两次内存拷贝(共四次)。linux中引入的sendfile的实际就为了更好的解决这个问题,从而实现"零拷贝",大大提升文件下载速度。

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

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

在apache,nginx,lighttpd等web服务器当中,都有sendfilefeature。下面就对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代码

server{

listen80;

server_namedl.mydomain.com;

location/{

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

proxy_redirectoff;

proxy_set_headerHost$host;

proxy_set_headerX-Real-IP$remote_addr;

proxy_set_headerX-Forwarded-For$proxy_add_x_forwarded_for;

client_max_body_size10m;

client_body_buffer_size128k;

proxy_connect_timeout90;

proxy_send_timeout90;

proxy_read_timeout90;

proxy_buffer_size4k;

proxy_buffers432k;

proxy_busy_buffers_size64k;

proxy_temp_file_write_size64k;

}

location/course/{

charsetutf-8;

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

internal;

}

}

server{

listen80;

server_namedl.mydomain.com;

location/{

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

proxy_redirectoff;

proxy_set_headerHost$host;

proxy_set_headerX-Real-IP$remote_addr;

proxy_set_headerX-Forwarded-For$proxy_add_x_forwarded_for;

client_max_body_size10m;

client_body_buffer_size128k;

proxy_connect_timeout90;

proxy_send_timeout90;

proxy_read_timeout90;

proxy_buffer_size4k;

proxy_buffers432k;

proxy_busy_buffers_size64k;

proxy_temp_file_write_size64k;

}

location/course/{

charsetutf-8;

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

internal;

}

}

其Spring代码为:

Java代码

packagecom.xxxx.portal.web;

importjava.io.IOException;

importjava.io.UnsupportedEncodingException;

importjavax.servlet.http.HttpServletResponse;

importorg.springframework.beans.factory.annotation.Autowired;

importorg.springframework.stereotype.Controller;

importorg.springframework.web.bind.annotation.PathVariable;

importorg.springframework.web.bind.annotation.RequestMapping;

importcom.xxxx.core.io.ResourceFile;

importcom.xxxx.portal.services.CoursewareService;

/**

*Filedownloadcontroller,providecoursewaredownloadorotherfiles.<br>

*<br>

*<i>downloadacourseURLe.g:<br>

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

*

*@authordenger

*/

@Controller

@RequestMapping("/download/*")

publicclassDownloadController{

privateCoursewareServicecoursewareService;

protectedstaticfinalStringDEFAULT_FILE_ENCODING="ISO-8859-1";

/**

*Underthecoursewareidtodownloadthefile.

*

*@paramcourseIDThecourseid.

*@throwsIOException

*/

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

publicvoiddownCourseware(@PathVariable("id")StringcourseID,finalHttpServletResponseresponse)throwsIOException{

ResourceFilefile=coursewareService.downCoursewareFile(courseID);

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

//redirectfiletox-accel-Redirect

xAccelRedirectFile(file,response);

}else{//Ifnotfoundresourcefile,sendthe404code

response.sendError(404);

}

}

protectedvoidxAccelRedirectFile(ResourceFilefile,HttpServletResponseresponse)

throwsIOException{

Stringencoding=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无法找到文件及弹出的文件下载框也会乱码

privateStringtoPathEncoding(StringorigEncoding,StringfileName)throwsUnsupportedEncodingException{

returnnewString(fileName.getBytes(origEncoding),DEFAULT_FILE_ENCODING);

}

@Autowired

publicvoidsetCoursewareService(CoursewareServicecoursewareService){

this.coursewareService=coursewareService;

}

}

packagecom.xxxx.portal.web;

importjava.io.IOException;

importjava.io.UnsupportedEncodingException;

importjavax.servlet.http.HttpServletResponse;

importorg.springframework.beans.factory.annotation.Autowired;

importorg.springframework.stereotype.Controller;

importorg.springframework.web.bind.annotation.PathVariable;

importorg.springframework.web.bind.annotation.RequestMapping;

importcom.xxxx.core.io.ResourceFile;

importcom.xxxx.portal.services.CoursewareService;

/**

*Filedownloadcontroller,providecoursewaredownloadorotherfiles.<br>

*<br>

*<i>downloadacourseURLe.g:<br>

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

*

*@authordenger

*/

@Controller

@RequestMapping("/download/*")

publicclassDownloadController{

privateCoursewareServicecoursewareService;

protectedstaticfinalStringDEFAULT_FILE_ENCODING="ISO-8859-1";

/**

*Underthecoursewareidtodownloadthefile.

*

*@paramcourseIDThecourseid.

*@throwsIOException

*/

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

publicvoiddownCourseware(@PathVariable("id")StringcourseID,finalHttpServletResponseresponse)throwsIOException{

ResourceFilefile=coursewareService.downCoursewareFile(courseID);

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

//redirectfiletox-accel-Redirect

xAccelRedirectFile(file,response);

}else{//Ifnotfoundresourcefile,sendthe404code

response.sendError(404);

}

}

protectedvoidxAccelRedirectFile(ResourceFilefile,HttpServletResponseresponse)

throwsIOException{

Stringencoding=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无法找到文件及弹出的文件下载框也会乱码

privateStringtoPathEncoding(StringorigEncoding,StringfileName)throwsUnsupportedEncodingException{

returnnewString(fileName.getBytes(origEncoding),DEFAULT_FILE_ENCODING);

}

@Autowired

publicvoidsetCoursewareService(CoursewareServicecoursewareService){

this.coursewareService=coursewareService;

}

}

相关推荐