像圖片或者HTML文件這樣的靜態資源,在瀏覽器中打開正確的URL便可下載,只要該資源是放在應用程序的目錄下,或者放在應用程序目錄的子目錄下,而不是放在WEB-INF下,tomcat服務器就會將該資源發送到瀏覽器。然而,有時靜態資源是保存在應用程序目錄以外,或者是保存在某一個數據庫中,或者有時須要控制它的訪問權限,防止其餘網站交叉引用它。若是出現以上任意一種狀況,都必要經過編程來發送資源。css
簡言之,經過編程進行的文件下載,使你能夠有選擇地將文件發送到瀏覽器。本篇博客將介紹若是經過編程把資源發送到瀏覽器,並經過兩個案例進行示範。html
爲了將像文件這樣的資源發送到瀏覽器,須要在控制器中完成如下工做:java
文件下載的流程具體以下:web
例如,如下代碼將一個文件發送到瀏覽器:spring
//下載文件:須要設置消息頭 response.setCharacterEncoding("UTF-8"); response.addHeader("content-Type", "application/octet-stream"); //指定文件類型 MIME類型:二進制文件(任意文件) String encodeFileName = null; if(userAgent.contains("MSIE") || userAgent.contains("Trident") || (userAgent.contains("GECKO") && userAgent.contains("RV:11"))) { //處理IE瀏覽器下載中文文件名亂碼問題 encodeFileName = URLEncoder.encode( filename,"UTF-8"); }else { encodeFileName = "=?UTF-8?B?" + new String(Base64.encodeBase64(filename.getBytes("UTF-8"))) + "?="; //encodeFileName = new String(filename.getBytes("UTF-8"),"ISO-8859-1"); } System.out.println(filename + ":" + encodeFileName); //若是有換行,對於文本文件沒有什麼問題,可是對於其餘格式:好比AutoCAD,Word,Excel等文件下載下來的文件中就會多出來一些換行符//0x0d和0x0a,這樣可能致使某些格式的文件沒法打開 response.reset(); response.addHeader("content-Disposition", "attachement;filename="+encodeFileName); //告訴瀏覽器該文件以附件方式處理,而不是去解析 //經過文件地址,將文件轉換爲輸入流 InputStream in = request.getServletContext().getResourceAsStream(filename); //經過輸出流將剛纔已經轉爲輸入流的文件,輸出給用戶 ServletOutputStream out= response.getOutputStream(); byte[] bs = new byte[1000]; int len = -1; while((len=in.read(bs)) != -1) { out.write(bs,0,len); } out.close(); in.close();
爲了編程將一個文件發送到瀏覽器,首先要讀取該文件做爲InputStream ,隨後,獲取HttpServletResponse的OutputStream;循環從in中讀取1000個字節,寫入out中,直至文件讀取完畢。數據庫
注意:這裏將文件轉換爲輸入流使用的是:apache
request.getServletContext().getResourceAsStream(filename);
filename指的是相對當前應用根路徑下的文件。若是給定的路徑是絕對路徑,能夠採用以下函數將文件轉換爲輸入流:編程
FileInputStream in = new FileInputStream(filename) ;
將文件發送到HTTP客戶端的更好方法是使用Java NIO的Files.copy()方法:瀏覽器
//將文件的虛擬路徑轉爲在文件系統中的真實路徑 String realpath = request.getServletContext().getRealPath(filename); System.out.print(realpath); Path file = Paths.get(realpath); Files.copy(file,response.getOutputStream());
代碼更短,運行速度更快。spring-mvc
咱們建立一個download應用程序,用於展現如何向瀏覽器發送文件。
Login類有兩個屬性,登陸名和登陸密碼:
package domain; import java.io.Serializable; //登陸實體類 public class Login implements Serializable { private static final long serialVersionUID = 1L; //用戶名 private String userName; //用戶密碼 private String password; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
在這個應用程序中,由ResourceController類處理用戶登陸,並將一個secret.pdf文件發送給瀏覽器。secret.pdf文件放在/WEB-INF/data目錄下,所以不能直接方法。只能獲得受權的用戶,才能看到它,若是用戶沒有登陸,應用程序就會跳轉到登陸頁面。
package controller; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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 domain.Login; @Controller public class ResourceController { private static final Log logger = LogFactory.getLog(ResourceController.class); //請求URL:/login @RequestMapping(value="/login") public String login(@ModelAttribute Login login, HttpSession session, Model model) { model.addAttribute("login", new Login()); //校驗用戶名和密碼 if ("paul".equals(login.getUserName()) && "secret".equals(login.getPassword())) { //設置sessopm屬性"loggedIn" session.setAttribute("loggedIn", Boolean.TRUE); //校驗經過 請求轉發到Main.jsp頁面 return "Main"; } else { //校驗失敗 請求轉發到LoginForm.jsp頁面 return "LoginForm"; } } //請求URL:/download-resource @RequestMapping(value="/download-resource") public String downloadResource(HttpSession session, HttpServletRequest request, HttpServletResponse response, Model model) { //若是用戶沒有登陸 if (session == null || session.getAttribute("loggedIn") == null) { model.addAttribute("login", new Login()); //請求轉發到LoginForm.jsp頁面 等待用戶登陸 return "LoginForm"; } //用戶已經登陸 獲取待下載文件夾/WEB-INF/data在文件系統的真實路徑 String dataDirectory = request. getServletContext().getRealPath("/WEB-INF/data"); //建立Path對象 文件爲/WEB-INF/data/secret.pdf Path file = Paths.get(dataDirectory, "secret.pdf"); //若是文件存在 下載文件 if (Files.exists(file)) { //指定文件類型 pdf類型 response.setContentType("application/pdf"); //告訴瀏覽器該文件以附件方式處理,而不是去解析 response.addHeader("Content-Disposition", "attachment; filename=secret.pdf"); try { Files.copy(file, response.getOutputStream()); } catch (IOException ex) { } } return null; } }
控制器的第一個請求處理方法是login(),將用戶請求轉發到登陸表單LoginForm.jsp:
<%@ 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 modelAttribute="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>
當咱們輸入用戶名"paul",密碼"secret",將會成功登陸,而後請求轉發到Main.jsp頁面,該頁面包含一個連接,點擊它能夠將secret.pdf文件下載下來:
<%@ 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="download-resource">Download</a> </p> </div> </body> </html>
控制器的第二個方法downloadResource(),它經過驗證session屬性loggedIn是否存在,來覈實用戶是否已經成功登陸。若是找到該屬性,就會將文件發送到瀏覽器。不然,用戶就會跳轉到登陸頁面。
main.css:
#global { text-align: left; border: 1px solid #dedede; background: #efefef; width: 560px; padding: 20px; margin: 100px auto; } form { font:100% verdana; min-width: 500px; max-width: 600px; width: 560px; } form fieldset { border-color: #bdbebf; border-width: 3px; margin: 0; } legend { font-size: 1.3em; } form label { width: 250px; display: block; float: left; text-align: right; padding: 2px; } #buttons { text-align: right; } #errors, li { color: red; } .error { color: red; font-size: 9pt; }
下面給出springmvc-config.xml文件的全部內容:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="controller" /> <mvc:annotation-driven/> <mvc:resources mapping="/css/**" location="/css/" /> <mvc:resources mapping="/*.html" location="/" /> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> </beans>
部署描述符(web.xml文件):
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/config/springmvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
將應用程序部署到tomcat服務器,並在網頁輸入如下URL:
http://localhost:8008/download/login
將會看到一個表單:
輸入用戶名"paul",密碼"secret",將會跳轉到文件下載頁面:
點擊超連接,下載secret.pdf文件。
心懷叵測的競爭對手有可能經過交叉引用「竊取」你的網站資產,好比,將你的資料公然放在他的資源上,好像那些東西本來就屬於他的同樣。若是經過編程控制,使其只有當referer中包含你的域名時才發出資源,就能夠防止那種狀況發生。固然,那些心意堅定的竊賊仍有辦法下載到你的東西,只不過不像以前那麼簡單罷了。
download應用提供了ResourceController類,使其僅當referer不爲null且包含你的域名時時,纔將圖片發送給瀏覽器,這樣能夠防止僅在瀏覽器中輸入網址就能下載的狀況發生:
package controller; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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 { private static final Log logger = LogFactory.getLog(ImageController.class); //請求URL:/get-image 使用了路徑變量id @RequestMapping(value="/get-image/{id}", method = RequestMethod.GET) public void getImage(@PathVariable String id, HttpServletRequest request, HttpServletResponse response, @RequestHeader(value = "referer") String referer, @RequestHeader(value = "User-Agent", required = true, defaultValue = "-999") String userAgent) { System.out.println(referer); System.out.println(userAgent); //判斷referer標題是否爲null 而且是從本身的域名發出的資源請求? if (referer != null && referer.contains("http://localhost:8008")) { //獲取待下載文件夾/WEB-INF/image在文件系統的真實路徑 String imageDirectory = request.getServletContext().getRealPath("/WEB-INF/image"); //建立Path對象 文件爲/WEB-INF/image/id.jpg Path file = Paths.get(imageDirectory, id + ".jpg"); //文件存在 則下載文件 if (Files.exists(file)) { //指定文件類型 img類型 response.setContentType("image/jpg"); //告訴瀏覽器該文件以附件方式處理,而不是去解析 //response.addHeader("Content-Disposition", "attachment; filename="+id + ".jpg"); try { Files.copy(file, response.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } } } } }
咱們在請求處理方法getImage()的簽名中使用了@RequestHeader(value = "referer") String referer參數,referer用來獲取獲取來訪者地址。只有經過連接訪問當前頁的時候,才能獲取上一頁的地址;不然referer的值爲null,經過window.open打開當前頁或者直接輸入地址,也爲null。
利用images.html,能夠對這個應用進行測試:
<!DOCTYPE html> <html> <head> <title>Photo Gallery</title> </head> <body> <img src="get-image/1"/> <img src="get-image/2"/> <img src="get-image/3"/> <img src="get-image/4"/> <img src="get-image/5"/> <img src="get-image/6"/> <img src="get-image/7"/> <img src="get-image/8"/> <img src="get-image/9"/> <img src="get-image/10"/> </body> </html>
將應用程序部署到tomcat服務器,並在網頁輸入如下URL:
http://localhost:8008/download/images.html
將會看到以下效果:
相反,若是直接在瀏覽器輸入以下URL將會獲取圖片失敗:
http://localhost:8008/get-image/1
參考文章
[2]SpringMVC(六):@RequestMapping下使用@RequestHeader綁定請求報頭的屬性值、@CookieValue綁定請求中的Cookie值
[3]Spring MVC學習指南