zoukankan      html  css  js  c++  java
  • Spring MVC -- 下载文件

    像图片或者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请求控制器的请求处理函数;
    • 请求处理方法根据文件路径,将文件转换为输入流;
    • 通过输出流将刚才已经转为输入流的文件,输出给用户(浏览器);

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

            //下载文件:需要设置消息头
            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中,直至文件读取完毕。

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

    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());

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

    二 范例1:隐藏资源

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

    1、目录结构

    2、 Login类

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

    3、ResourceController类

    在这个应用程序中,由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;
        }
    }

    4、视图

    控制器的第一个请求处理方法是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;    
    }
    View Code

    5、配置文件

    下面给出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>
    View Code

    6、测试

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

    http://localhost:8008/download/login

    将会看到一个表单:

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

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

    三 范例2:防止交叉引用

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

    1、ImageController类

    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。

    2、images.html

    利用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>

    3、测试

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

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

    将会看到如下效果:

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

    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的含义和作用

  • 相关阅读:
    toj 2819 Travel
    toj 2807 Number Sort
    zoj 2818 Prairie dogs IV
    zoj 1276 Optimal Array Multiplication Sequence
    toj 2802 Tom's Game
    toj 2798 Farey Sequence
    toj 2815 Searching Problem
    toj 2806 Replace Words
    toj 2794 Bus
    css截取字符
  • 原文地址:https://www.cnblogs.com/zyly/p/10888656.html
Copyright © 2011-2022 走看看