像图片或者HTML文件这样的静态资源,在浏览器中打开正确的URL即可下载,只要该资源是放在应用程序的目录下,或者放在应用程序目录的子目录下,而不是放在WEB-INF下,tomcat服务器就会将该资源发送到浏览器。然而,有时静态资源是保存在应用程序目录之外,或者是保存在某一个数据库中,或者有时需要控制它的访问权限,防止其他网站交叉引用它。如果出现以上任意一种情况,都必要通过编程来发送资源。

简言之,通过编程进行的文件下载,使你可以有选择地将文件发送到浏览器。本篇博客将介绍如果通过编程把资源发送到浏览器,并通过两个案例进行示范。

一 文件下载概览

为了将像文件这样的资源发送到浏览器,需要在控制器中完成以下工作:

  • 对请求处理方法添加HttpServletResponse、HttpServletRequest参数;
  • 将相应的内容类型设为文件的内容类型。content-Type标题在某个实体的body中定义数据的类型,并包含媒体类型和子类型标识符。如果不清楚内容类型,并且希望浏览器式中显示Save As(另存为)对话框,则将它设为application/octet-stream。这个值是不区分大小写的(HTTP Content-type 对照表)。
  • 添加一个属性为content-Disposition的HTTP的响应标题,并赋予attachement;filename=fileName,这里的fileName是默认文件名,应该出现在File Download(文件下载)对话框中,它通常与文件同名,但是也并非一定如此。

文件下载的流程具体如下:

  • 通过浏览器,输入URL请求控制器的请求处理函数;
  • 请求处理方法根据文件路径,将文件转换为输入流;
  • 通过输出流将刚才已经转为输入流的文件,输出给用户(浏览器);

例如,以下代码将一个文件发送到浏览器:

  1. //下载文件:需要设置消息头
  2. response.setCharacterEncoding("UTF-8");
  3. response.addHeader("content-Type", "application/octet-stream"); //指定文件类型 MIME类型:二进制文件(任意文件)
  4.  
  5. String encodeFileName = null;
  6.  
  7. if(userAgent.contains("MSIE") || userAgent.contains("Trident") || (userAgent.contains("GECKO") && userAgent.contains("RV:11"))) {
  8. //处理IE浏览器下载中文文件名乱码问题
  9. encodeFileName = URLEncoder.encode( filename,"UTF-8");
  10. }else {
  11. encodeFileName = "=?UTF-8?B?" + new String(Base64.encodeBase64(filename.getBytes("UTF-8"))) + "?=";
  12. //encodeFileName = new String(filename.getBytes("UTF-8"),"ISO-8859-1");
  13. }
  14.  
  15. System.out.println(filename + ":" + encodeFileName);
  16. //如果有换行,对于文本文件没有什么问题,但是对于其他格式:比如AutoCAD,Word,Excel等文件下载下来的文件中就会多出来一些换行符//0x0d和0x0a,这样可能导致某些格式的文件无法打开
  17. response.reset();
  18. response.addHeader("content-Disposition", "attachement;filename="+encodeFileName); //告诉浏览器该文件以附件方式处理,而不是去解析
  19.  
  20. //通过文件地址,将文件转换为输入流
  21. InputStream in = request.getServletContext().getResourceAsStream(filename);
  22.  
  23. //通过输出流将刚才已经转为输入流的文件,输出给用户
  24. ServletOutputStream out= response.getOutputStream();
  25.  
  26. byte[] bs = new byte[1000];
  27. int len = -1;
  28. while((len=in.read(bs)) != -1) {
  29. out.write(bs,0,len);
  30. }
  31. out.close();
  32. in.close();

为了编程将一个文件发送到浏览器,首先要读取该文件作为InputStream ,随后,获取HttpServletResponse的OutputStream;循环从in中读取1000个字节,写入out中,直至文件读取完毕。

注意:这里将文件转换为输入流使用的是:

  1. request.getServletContext().getResourceAsStream(filename);

filename指的是相对当前应用根路径下的文件。如果给定的路径是绝对路径,可以采用如下函数将文件转换为输入流:

  1. FileInputStream in = new FileInputStream(filename) ;

将文件发送到HTTP客户端的更好方法是使用Java NIO的Files.copy()方法:

  1. //将文件的虚拟路径转为在文件系统中的真实路径
  2. String realpath = request.getServletContext().getRealPath(filename);
  3. System.out.print(realpath);
  4. Path file = Paths.get(realpath);
  5. Files.copy(file,response.getOutputStream());

代码更短,运行速度更快。

二 范例1:隐藏资源

我们创建一个download应用程序,用于展示如何向浏览器发送文件。

1、目录结构

2、 Login类

Login类有两个属性,登录名和登录密码:

  1. package domain;
  2.  
  3. import java.io.Serializable;
  4.  
  5. //登录实体类
  6. public class Login implements Serializable {
  7. private static final long serialVersionUID = 1L;
  8.  
  9. //用户名
  10. private String userName;
  11. //用户密码
  12. private String password;
  13.  
  14. public String getUserName() {
  15. return userName;
  16. }
  17. public void setUserName(String userName) {
  18. this.userName = userName;
  19. }
  20. public String getPassword() {
  21. return password;
  22. }
  23. public void setPassword(String password) {
  24. this.password = password;
  25. }
  26. }

3、ResourceController类

在这个应用程序中,由ResourceController类处理用户登录,并将一个secret.pdf文件发送给浏览器。secret.pdf文件放在/WEB-INF/data目录下,因此不能直接方法。只能得到授权的用户,才能看到它,如果用户没有登录,应用程序就会跳转到登录页面。

  1. package controller;
  2.  
  3. import java.io.IOException;
  4. import java.nio.file.Files;
  5. import java.nio.file.Path;
  6. import java.nio.file.Paths;
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9. import javax.servlet.http.HttpSession;
  10. import org.apache.commons.logging.Log;
  11. import org.apache.commons.logging.LogFactory;
  12. import org.springframework.stereotype.Controller;
  13. import org.springframework.ui.Model;
  14. import org.springframework.web.bind.annotation.ModelAttribute;
  15. import org.springframework.web.bind.annotation.RequestMapping;
  16. import domain.Login;
  17.  
  18. @Controller
  19. public class ResourceController {
  20.  
  21. private static final Log logger = LogFactory.getLog(ResourceController.class);
  22.  
  23. //请求URL:/login
  24. @RequestMapping(value="/login")
  25. public String login(@ModelAttribute Login login, HttpSession session, Model model) {
  26. model.addAttribute("login", new Login());
  27. //校验用户名和密码
  28. if ("paul".equals(login.getUserName()) &&
  29. "secret".equals(login.getPassword())) {
  30. //设置sessopm属性"loggedIn"
  31. session.setAttribute("loggedIn", Boolean.TRUE);
  32. //校验通过 请求转发到Main.jsp页面
  33. return "Main";
  34. } else {
  35. //校验失败 请求转发到LoginForm.jsp页面
  36. return "LoginForm";
  37. }
  38. }
  39.  
  40. //请求URL:/download-resource
  41. @RequestMapping(value="/download-resource")
  42. public String downloadResource(HttpSession session, HttpServletRequest request,
  43. HttpServletResponse response, Model model) {
  44. //如果用户没有登录
  45. if (session == null ||
  46. session.getAttribute("loggedIn") == null) {
  47. model.addAttribute("login", new Login());
  48. //请求转发到LoginForm.jsp页面 等待用户登录
  49. return "LoginForm";
  50. }
  51. //用户已经登录 获取待下载文件夹/WEB-INF/data在文件系统的真实路径
  52. String dataDirectory = request.
  53. getServletContext().getRealPath("/WEB-INF/data");
  54. //创建Path对象 文件为/WEB-INF/data/secret.pdf
  55. Path file = Paths.get(dataDirectory, "secret.pdf");
  56. //如果文件存在 下载文件
  57. if (Files.exists(file)) {
  58. //指定文件类型 pdf类型
  59. response.setContentType("application/pdf");
  60. //告诉浏览器该文件以附件方式处理,而不是去解析
  61. response.addHeader("Content-Disposition", "attachment; filename=secret.pdf");
  62. try {
  63. Files.copy(file, response.getOutputStream());
  64. } catch (IOException ex) {
  65. }
  66. }
  67. return null;
  68. }
  69. }

4、视图

控制器的第一个请求处理方法是login(),将用户请求转发到登录表单LoginForm.jsp:

  1. <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
  2. <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
  3. <!DOCTYPE html>
  4. <html>
  5. <head>
  6. <title>Login</title>
  7. <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
  8. </head>
  9. <body>
  10. <div id="global">
  11. <form:form modelAttribute="login" action="login" method="post">
  12. <fieldset>
  13. <legend>Login</legend>
  14. <p>
  15. <label for="userName">User Name: </label>
  16. <form:input id="userName" path="userName" cssErrorClass="error"/>
  17. </p>
  18. <p>
  19. <label for="password">Password: </label>
  20. <form:password id="password" path="password" cssErrorClass="error"/>
  21. </p>
  22. <p id="buttons">
  23. <input id="reset" type="reset" tabindex="4">
  24. <input id="submit" type="submit" tabindex="5"
  25. value="Login">
  26. </p>
  27. </fieldset>
  28. </form:form>
  29. </div>
  30. </body>
  31. </html>

当我们输入用户名"paul",密码"secret",将会成功登录,然后请求转发到Main.jsp页面,该页面包含一个链接,点击它可以将secret.pdf文件下载下来:

  1. <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <title>Download Page</title>
  6. <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
  7. </head>
  8. <body>
  9. <div id="global">
  10. <h4>Please click the link below.</h4>
  11. <p>
  12. <a href="download-resource">Download</a>
  13. </p>
  14. </div>
  15. </body>
  16. </html>

控制器的第二个方法downloadResource(),它通过验证session属性loggedIn是否存在,来核实用户是否已经成功登录。如果找到该属性,就会将文件发送到浏览器。否则,用户就会跳转到登录页面。

main.css:

  1. #global {
  2. text-align: left;
  3. border: 1px solid #dedede;
  4. background: #efefef;
  5. width: 560px;
  6. padding: 20px;
  7. margin: 100px auto;
  8. }
  9.  
  10. form {
  11. font:100% verdana;
  12. min-width: 500px;
  13. max-width: 600px;
  14. width: 560px;
  15. }
  16.  
  17. form fieldset {
  18. border-color: #bdbebf;
  19. border-width: 3px;
  20. margin:;
  21. }
  22.  
  23. legend {
  24. font-size: 1.3em;
  25. }
  26.  
  27. form label {
  28. width: 250px;
  29. display: block;
  30. float: left;
  31. text-align: right;
  32. padding: 2px;
  33. }
  34.  
  35. #buttons {
  36. text-align: right;
  37. }
  38. #errors, li {
  39. color: red;
  40. }
  41. .error {
  42. color: red;
  43. font-size: 9pt;
  44. }

5、配置文件

下面给出springmvc-config.xml文件的所有内容:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
  4. xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="
  6. http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/mvc
  9. http://www.springframework.org/schema/mvc/spring-mvc.xsd
  10. http://www.springframework.org/schema/context
  11. http://www.springframework.org/schema/context/spring-context.xsd">
  12.  
  13. <context:component-scan base-package="controller" />
  14. <mvc:annotation-driven/>
  15. <mvc:resources mapping="/css/**" location="/css/" />
  16. <mvc:resources mapping="/*.html" location="/" />
  17.  
  18. <bean id="viewResolver"
  19. class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  20. <property name="prefix" value="/WEB-INF/jsp/" />
  21. <property name="suffix" value=".jsp" />
  22. </bean>
  23. </beans>

部署描述符(web.xml文件):

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app version="3.1"
  3. xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  6. http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
  7. <servlet>
  8. <servlet-name>springmvc</servlet-name>
  9. <servlet-class>
  10. org.springframework.web.servlet.DispatcherServlet
  11. </servlet-class>
  12. <init-param>
  13. <param-name>contextConfigLocation</param-name>
  14. <param-value>/WEB-INF/config/springmvc-config.xml</param-value>
  15. </init-param>
  16. <load-on-startup>1</load-on-startup>
  17. </servlet>
  18.  
  19. <servlet-mapping>
  20. <servlet-name>springmvc</servlet-name>
  21. <url-pattern>/</url-pattern>
  22. </servlet-mapping>
  23. </web-app>

6、测试

将应用程序部署到tomcat服务器,并在网页输入以下URL:

  1. http://localhost:8008/download/login

将会看到一个表单:

输入用户名"paul",密码"secret",将会跳转到文件下载页面:

点击超链接,下载secret.pdf文件。

三 范例2:防止交叉引用

心怀叵测的竞争对手有可能通过交叉引用“窃取”你的网站资产,比如,将你的资料公然放在他的资源上,好像那些东西原本就属于他的一样。如果通过编程控制,使其只有当referer中包含你的域名时才发出资源,就可以防止那种情况发生。当然,那些心意坚决的窃贼仍有办法下载到你的东西,只不过不像之前那么简单罢了。

1、ImageController类

download应用提供了ResourceController类,使其仅当referer不为null且包含你的域名时时,才将图片发送给浏览器,这样可以防止仅在浏览器中输入网址就能下载的情况发生:

  1. package controller;
  2.  
  3. import java.io.IOException;
  4. import java.nio.file.Files;
  5. import java.nio.file.Path;
  6. import java.nio.file.Paths;
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9. import org.apache.commons.logging.Log;
  10. import org.apache.commons.logging.LogFactory;
  11. import org.springframework.stereotype.Controller;
  12. import org.springframework.web.bind.annotation.PathVariable;
  13. import org.springframework.web.bind.annotation.RequestHeader;
  14. import org.springframework.web.bind.annotation.RequestMapping;
  15. import org.springframework.web.bind.annotation.RequestMethod;
  16.  
  17. @Controller
  18. public class ImageController {
  19.  
  20. private static final Log logger = LogFactory.getLog(ImageController.class);
  21.  
  22. //请求URL:/get-image 使用了路径变量id
  23. @RequestMapping(value="/get-image/{id}", method = RequestMethod.GET)
  24. public void getImage(@PathVariable String id,
  25. HttpServletRequest request,
  26. HttpServletResponse response,
  27. @RequestHeader(value = "referer") String referer,
  28. @RequestHeader(value = "User-Agent", required = true, defaultValue = "-999") String userAgent) {
  29. System.out.println(referer);
  30. System.out.println(userAgent);
  31. //判断referer标题是否为null 并且是从自己的域名发出的资源请求?
  32. if (referer != null && referer.contains("http://localhost:8008")) {
  33. //获取待下载文件夹/WEB-INF/image在文件系统的真实路径
  34. String imageDirectory = request.getServletContext().getRealPath("/WEB-INF/image");
  35. //创建Path对象 文件为/WEB-INF/image/id.jpg
  36. Path file = Paths.get(imageDirectory, id + ".jpg");
  37. //文件存在 则下载文件
  38. if (Files.exists(file)) {
  39. //指定文件类型 img类型
  40. response.setContentType("image/jpg");
  41. //告诉浏览器该文件以附件方式处理,而不是去解析
  42. //response.addHeader("Content-Disposition", "attachment; filename="+id + ".jpg");
  43. try {
  44. Files.copy(file, response.getOutputStream());
  45. } catch (IOException e) {
  46. e.printStackTrace();
  47. }
  48. }
  49. }
  50. }
  51. }

我们在请求处理方法getImage()的签名中使用了@RequestHeader(value = "referer") String referer参数,referer用来获取获取来访者地址。只有通过链接访问当前页的时候,才能获取上一页的地址;否则referer的值为null,通过window.open打开当前页或者直接输入地址,也为null。

2、images.html

利用images.html,可以对这个应用进行测试:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Photo Gallery</title>
  5. </head>
  6. <body>
  7.   <img src="get-image/1"/>
  8.   <img src="get-image/2"/>
  9.   <img src="get-image/3"/>
  10.   <img src="get-image/4"/>
  11.   <img src="get-image/5"/>
  12.   <img src="get-image/6"/>
  13.   <img src="get-image/7"/>
  14.   <img src="get-image/8"/>
  15.   <img src="get-image/9"/>
  16.   <img src="get-image/10"/>
  17. </body>
  18. </html>

3、测试

将应用程序部署到tomcat服务器,并在网页输入以下URL:

  1. http://localhost:8008/download/images.html

将会看到如下效果:

相反,如果直接在浏览器输入如下URL将会获取图片失败:

  1. http://localhost:8008/get-image/1

参考文章

[1]spring项目获取ServletContext

[2]SpringMVC(六):@RequestMapping下使用@RequestHeader绑定请求报头的属性值、@CookieValue绑定请求中的Cookie值

[3]Spring MVC学习指南

[4]request.getHeader("referer")的作用

[5]http请求头中Referer的含义和作用

Spring MVC -- 下载文件的更多相关文章

  1. spring MVC 下载文件(转)

    springle MVC中如何下载文件呢? 比struts2 下载文件简单得多 先看例子: @ResponseBody @RequestMapping(value = "/download& ...

  2. spring mvc 下载文件链接

    http://www.blogjava.net/paulwong/archive/2014/10/29/419177.html http://www.iteye.com/topic/1125784 h ...

  3. Spring mvc 下载文件处理

    @RequestMapping(value = "downFile") public void downFile(HttpServletResponse response, Str ...

  4. Spring mvc下载文件java代码

    /** * 下载模板文件 * @author cq */ @RequestMapping("/downloadExcel.do") public ResponseEntity< ...

  5. Spring MVC的文件上传和下载

    简介: Spring MVC为文件上传提供了直接的支持,这种支持使用即插即用的MultipartResolver实现的.Spring MVC 使用Apache Commons FileUpload技术 ...

  6. Spring MVC 实现文件的上传和下载

    前些天一位江苏经贸的学弟跟我留言问了我这样一个问题:“用什么技术来实现一般网页上文件的上传和下载?是框架还是Java中的IO流”.我回复他说:“使用Spring MVC框架可以做到这一点,因为Spri ...

  7. spring mvc ajaxfileupload文件上传返回json下载问题

    问题:使用spring mvc ajaxfileupload 文件上传在ie8下会提示json下载问题 解决方案如下: 服务器代码: @RequestMapping(value = "/ad ...

  8. 0062 Spring MVC的文件上传与下载--MultipartFile--ResponseEntity

    文件上传功能在网页中见的太多了,比如上传照片作为头像.上传Excel文档导入数据等 先写个上传文件的html <!DOCTYPE html> <html> <head&g ...

  9. Java Web 学习(8) —— Spring MVC 之文件上传与下载

    Spring MVC 之文件上传与下载 上传文件 表单: <form action="upload" enctype="multipart/form-data&qu ...

随机推荐

  1. Mincut 最小割 (BZOJ1797+最小割+tarjan)

    题目链接 传送门 思路 根据题目给定的边跑一边最大流,然后再在残留网络上跑\(tarjan\). 对于每一条边有: 如果它是非满边,那么它一定不是最小割集里面的边: 如果\(c[u[i]] \not= ...

  2. thrift rpc通信

    thrift rpc通信 框架 别人的简历: 负责抓取程序的开发和维护,对抓取内容进行数据提取.整理.1.定向数据抓取程序的维护和开发,了解了Sqlite数据库.Thrift服务和多线程的开发调试.2 ...

  3. laravel 配置路由 api和web定义的路由的区别详解

    1.路由经过中间件方面不同 打开kerenl.php就可以看到区别 protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware ...

  4. 29、Python程序中的进程操作(multiprocess.process)

    一.multiprocess模块 multiprocess不是一个模块而是python中一个操作.管理进程的包. 子模块分为四个部分: 创建进程部分 进程同步部分 进程池部分 进程之间数据共享 二.m ...

  5. docker 空间清理

    https://blog.csdn.net/qq_28001193/article/details/79555177 清理之后,重要的是找到原因,如上连接所示,其中一个占空间比较大的是日志文件,除了考 ...

  6. 信噪比(signal-to-noise ratio)

    SNR或S/N,又称为讯噪比.是指一个电子设备或者电子系统中信号与噪声的比例.这里面的信号指的是来自设备外部需要通过这台设备进行处理的电子信号,噪声是指经过该设备后产生的原信号中并不存在的无规则的额外 ...

  7. 洛谷 P2634 [国家集训队]聪聪可可-树分治(点分治,容斥版) +读入挂+手动O2优化吸点氧才过。。。-树上路径为3的倍数的路径数量

    P2634 [国家集训队]聪聪可可 题目描述 聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃.两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一 ...

  8. 如何设置网站的robots.txt

    做过网站优化的朋友都知道,搜索引擎蜘蛛爬行抓取网站时首先会去访问根目录下的robots.txt文件,如果robots文件存在,则会根据robots文件内设置的规则进行爬行抓取,如果文件不存在则会顺着首 ...

  9. 实现mysql的读写分离(mysql-proxy)____2

    mysql-proxy简介 MySQL读写分离是指让master处理写操作,让slave处理读操作,非常适用于读操作量比较大的场景,可减轻master的压力. 使用mysql-proxy实现mysql ...

  10. Ubuntu 19.04 国内更新源

    2019年4月18日, Ubuntu 19.04 正式发布. Ubuntu 19.04 的 Codename 是"disco(迪斯科舞厅)": zkf@ubuntu:~$ lsb_ ...