基于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;
}
}