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

  • 相关阅读:
    贡献15本经典C、C++、MFC、VC++教程,都是pdf完整版的
    雪花
    孙鑫C++视频教程 rmvb格式 全20CD完整版 精品分享
    mac上用VMWare虚拟机装win7
    阿里云如何解析域名,搭建云服务器环境
    2. Windows编程基础
    复制指定目录下的全部文件到另一个目录中
    Select查询命令
    使用OneNote2016发送博客
    Linux数字雨
  • 原文地址:https://www.cnblogs.com/zyly/p/10888656.html
Copyright © 2011-2022 走看看