概述
像静态资源,我们在浏览器中打开正确的URL即可下载,只要该资源不是放在WEB-INF目录下,Servlet/JSP容器就会将该资源发送到浏览器。 然而有的时候静态资源是保存在应用程序目录外或者存在数据库中,或者有的时候需要控制它的访问权限,防止其他网站交叉引用它。 如果出现上述任意一种情况,都必须通过编程来发送资源。
文件下载概览
为了将像文件这样的资源发送到浏览器,需要在控制器中完成以下工作
1. 队请求处理方法使用void返回类型,并在方法中添加HttpServletRespinse参数
2. 将响应的内容设置为文件的内容类型。 Content-Type标题在某个实体的body中定义数据的类型,并包含没提类型和子类型标示符。如果不清楚内容类型,并且希望浏览器始终显示Save As(另存为)对话框,则将它设置为APPLICATION/OCTETPSTREAM ,不区分大小写
3. 添加一个名为Content-Dispositionde HTTP响应标题,并赋值attachment;filename=fileName.这里的fileName是默认文件名,应该出现在File Download对话框中,它通常与文件名同名,但是也并非一定如此
下面的代码是将一个文件发送到浏览器
FileInputStream fis = new FileInputStream(); BufferedInputStream bis = new BufferedInputStream(fis); byte[] bytes = new byte[bis.available()]; response.setContentType(contentType); OutputStream os = response.getOutputStream(); bis.read(bytes); os.write(bytes);
为了通过编程将一个文件发送到浏览器,首先要读取该文件作为FileInputStream,并将内容加载到一个字节数组。 随后,获取HttpServletResponse的OutputStream,并调用其write方法传入字节数组。
隐藏资源
该示例演示如何向浏览器发送文件,由ResourceController类处理用户登录请求,并将WEB-INF/data目录下的artisan.pdf发送给浏览器。因为文件放到了WEB-INF目录下,所以不能够直接访问,只有得到授权的用户才能看到,如果未登录,返回登录页面。
ResourceController, 这里模拟下用户登录,只有当用户的HttpSession中包含一个loggedIn属性时,表明登录成功。
package com.artisan.controller; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.OutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import com.artisan.domain.Login; @Controller public class ResourceController { @RequestMapping(value = "/login") public String login(@ModelAttribute Login login, HttpSession httpSession, Model model) { model.addAttribute("login", new Login()); if ("artisan".equals(login.getUserName()) && "artisan123".equals(login.getPassword())) { httpSession.setAttribute("loggedIn", Boolean.TRUE); return "Main"; } else { return "LoginForm"; } } @RequestMapping(value = "/resource_download") public String downLoadResource(HttpSession session, HttpServletRequest request, HttpServletResponse response) { if (session == null && session.getAttribute("loggedIn") == null) { return "LoginForm"; } String dataDirectory = request.getServletContext().getRealPath("/WEB-INF/data"); File file = new File(dataDirectory, "artisan.pdf"); if (file.exists()) { response.setContentType("application/pdf"); response.addHeader("Content-Disposition", "attachment; filename=artisan.pdf"); byte[] buffer = new byte[1024]; FileInputStream fis = null; BufferedInputStream bis = null; // JDK7 以前的写法 // try { // fis = new FileInputStream(file); // bis = new BufferedInputStream(fis); // OutputStream os = response.getOutputStream(); // int i = bis.read(buffer); // while (i != -1) { // os.write(buffer, 0, i); // i = bis.read(buffer); // } // } catch (IOException ex) { // // do something, // // probably forward to an Error page // } finally { // if (bis != null) { // try { // bis.close(); // } catch (IOException e) { // } // } // if (fis != null) { // try { // fis.close(); // } catch (IOException e) { // } // } // } // // Java 7, use try-with-resources,自动释放资源 try (OutputStream os = response.getOutputStream();) { fis = new FileInputStream(file); bis = new BufferedInputStream(fis); int i = bis.read(buffer); while (i != -1) { os.write(buffer, 0, i); i = bis.read(buffer); } } catch (Exception e) { // do something, // probably forward to an Error page } } return null; } }
login方法,将用户带到登录表单页面
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE HTML> <html> <head> <title>Login</title> <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style> </head> <body> <div id="global"> <form:form commandName="login" action="login" method="post"> <fieldset> <legend>Login</legend> <p> <label for="userName">User Name: </label> <form:input id="userName" path="userName" cssErrorClass="error"/> </p> <p> <label for="password">Password: </label> <form:password id="password" path="password" cssErrorClass="error"/> </p> <p id="buttons"> <input id="reset" type="reset" tabindex="4"> <input id="submit" type="submit" tabindex="5" value="Login"> </p> </fieldset> </form:form> </div> </body> </html>
用户名和密码在login方法中使用硬编码的方式模拟用户登录,成功后跳转到Main.jsp页面,该页面包含一个超链接,点击下载文件。
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE HTML> <html> <head> <title>Download Page</title> <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style> </head> <body> <div id="global"> <h4>Please click the link below.</h4> <p> <a href="resource_download">Download</a> </p> </div> </body> </html>
测试
点击链接
查看下载的文件
防止交叉引用
为了防止他人引用我们网站的资源,可以通过编程的方式,只有当请求的报头referer标题中包含你的域名时才发出资源,当然了这种方式也不能完全阻止。
该示例中,ImageController类中,只有referer标题不为空时,才将图片发送给浏览器
package com.artisan.controller; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class ImageController { @RequestMapping("/imageList") public String getImageList(){ return "Image"; } @RequestMapping(value = "/image_get/{id}", method = RequestMethod.GET) public void getImage(@PathVariable String id, HttpServletRequest request, HttpServletResponse response, @RequestHeader String referer) { // 判断请求头中的Referer if (referer != null) { String imageDirectory = request.getServletContext().getRealPath("/WEB-INF/image"); File file = new File(imageDirectory, id + ".jpg"); if (file.exists()) { response.setContentType("image/jpg"); byte[] buffer = new byte[1024]; FileInputStream fis = null; BufferedInputStream bis = null; // if you're using Java 7, use try-with-resources try { fis = new FileInputStream(file); bis = new BufferedInputStream(fis); OutputStream os = response.getOutputStream(); int i = bis.read(buffer); while (i != -1) { os.write(buffer, 0, i); i = bis.read(buffer); } } catch (IOException ex) { // do something here } finally { if (bis != null) { try { bis.close(); } catch (IOException e) { } } if (fis != null) { try { fis.close(); } catch (IOException e) { } } } } } } }
前台页面
<!DOCTYPE HTML> <html> <head> <title>Images</title> </head> <body> <img src="image_get/1"/> <img src="image_get/2"/> <img src="image_get/3"/> <img src="image_get/4"/> <img src="image_get/5"/> <img src="image_get/6"/> <img src="image_get/7"/> <img src="image_get/8"/> <img src="image_get/9"/> <img src="image_get/10"/> </body> </html>
测试:
源码
代码已提交到github