Servlet 实现文件下载

Servlet 实现文件下载

在生活或者工作中,总是避免不了下载文件,下载的东西有各种类型,譬如图片,视频,文本,文档,音乐等等,这些东西有大有小,大的有几G到几十G,小的或许只有几K。这些东西究竟是如何从服务器下载到我们本地的呢?现在就利用 Servlet 来实现一个的文件下载功能。

前提

要想完成这个案例,需要如下技术:

  • Java 基础
  • HTML 基础
  • 会使用 Tomcat 服务器
  • 会使用 Servlet 进行开发
  • 了解 HTTP 协议
  • 会使用 Response 设置响应头、响应消息行

准备工作

所谓工欲善其事,必先利其器。要完成这个案例,我们得做好准备工作。

  1. 首先,打开 Eclipse,新建一个 Dynamic Web Project
  2. 键入项目名,选择运行时环境为 Apache Tomcat v7.0,选择 web 版本为 2.5,然后点击 Finished
  3. 在项目的 WebContent 目录下,新建一个文件夹 download,然后将可以供给用户下载的文件存放在这个文件夹里。
  4. WebContent 目录下新建一个 index.html

    代码如下:
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Insert title here</title>
    </head>
    
    <body>
        准备工作完成!
    </body>
</html>
  1. WebContent -> WEB-INF 目录下新建一个 web.xml 文件,这是 web2.5 版本的规范,一个 web.xml 文件可以对 web 项目进行基本的配置。

    代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    <display-name>download</display-name>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-app>
  1. 使用 Tomcat 将次项目发布,并在浏览器中预览。

    将服务器启动,然后在浏览器中输入:http://localhost:8080/download。如果出现以下界面,那么恭喜你,准备工作完美完成!

完成案例

超链接下载文件

也许有人会说,下载文件有什么难的,在 index.html 中编写几个 a 标签,将 href 属性设置为供给用户下载的文件的路径不就行了?

那么,我就按照上述所说试验:

首先,在 index.html 中编写3个 a 标签,将 href 属性分别指向了供给用户下载的文件的路径。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>下载</title>
    </head>
    
    <body>
        <a href="http://localhost:8080/download/download/1.jpg">图片</a><br>
        <a href="http://localhost:8080/download/download/2.txt">文本</a><br>
        <a href="http://localhost:8080/download/download/3.doc">文档</a><br>
    </body>
</html>

然后,在浏览器中打开 index.html

这样看似乎并没有什么问题,那么来点击超链接试验一下。



结果,有的文件直接在浏览器打开了,而有的文件则是下载了。这究竟是什么原因呢?原来,当浏览器能够解析该文件的 MIME类型,则直接打开该文件;若不能,则下载。

如此,看来使用超链接下载文件是行不通了。

编码下载

从以上分析可以看出,要实现文件下载并不是设置 a 标签那么简单的。那么,究竟该怎样实现文件下载功能呢?此时,就需要使用 Servlet 编码读取要下载的文件,然后写到响应流中以达到用户下载文件的目的。

在这里,我们使用 get 请求方式,将文件名放在 url 中传送给服务器,但在很多情况下使用 post 发送数据给服务器更加合适。通过 get 发送大量数据是不现实的,它有一定的局限性,在这里使用 get 方式只是为了方便。

首先,新建一个 Servlet,并编写 web.xml 设置它的访问路径:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    <display-name>download</display-name>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
    <servlet>
        <servlet-name>DownloadServlet</servlet-name>
        <servlet-class>club.luckylight.servlet.DownloadServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>DownloadServlet</servlet-name>
        <url-pattern>/DownloadServlet</url-pattern>
    </servlet-mapping>
</web-app>

然后,需要修改 index.html 页面中 a 标签的 href 属性:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>下载</title>
    </head>
    
    <body>
        <a href="http://localhost:8080/download/DownloadServlet?name=1.txt">图片</a><br>
        <a href="http://localhost:8080/download/DownloadServlet?name=2.txt">文本</a><br>
        <a href="http://localhost:8080/download/DownloadServlet?name=3.doc">文档</a><br>
    </body>
</html>

接下来,就可以开始编写 Servlet 了:

  1. 首先,从 url 中获取文件名
String filename = request.getParameter("name");
  1. 接下来,获取根据文件名获取文件的 MIME类型
String mimeType = getServletContext().getMimeType(filename);
  1. 然后,就需要设置文件的 MIME类型
response.setContentType(mimeType);

上条语句的作用就是使客户端浏览器,区分不同种类的数据,并根据不同的 MIME类型 调用浏览器内不同的程序嵌入模块来处理相应的数据。例如浏览器就是通过 MIME类型 来判断文件是 gif 图片,还是 doc 文档的。

  1. 光是这些还不行,下面还要告诉浏览器 “我要下载东西”
response.setHeader("content-disposition", "attachment;filename=" + filename);
  1. 最后,把文件从服务器拷贝到浏览器,完成下载
// 定义输入流
InputStream is = getServletContext().getResourceAsStream("/download/" + filename);
// 定义输出流
ServletOutputStream os = response.getOutputStream();
// 从文件中读入数据并写到输出字节流中
int len = -1;
byte[] bytes = new byte[1024];
while ((len = is.read(bytes)) != -1) {
    os.write(bytes, 0, len);
}
// 关闭资源
os.close();
is.close();

下面,我来测试一下:




如此看来,似乎也并没有什么问题,但是我设置的文件名都是数字,如果我将文件名都设置为中文,那么又会怎样呢?

此时,对于 get 请求,参数追加到地址栏,会使用 utf-8 编码,服务器(Tomcat)接受到请求之后,使用 iso-8859-1 解码,所以会出现乱码,导致找不到资源。

因此,我们在获取文件名时,必须将文件名用 utf-8 解码,然后才能使用。

String filename = new String(request.getParameter("name").getBytes("iso-8859-1"), "utf-8");

经过上述处理后,虽然能够下载了,但又出现了:浏览器无法识别文件名。




此时,我们需要编写一个工具类,以解决这个问题。

新建一个 class,设置类名为 DownloadUtils,编写一个静态方法,作用是根据浏览器的不同把传递过来的文件名编码。

package club.luckylight.util;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;

public class DownloadUtils {

    public static String getFilename(HttpServletRequest request, String filename) throws UnsupportedEncodingException {
        String userAgent = request.getHeader("user-agent");
        // 针对以IE或者Edge为内核的浏览器
        if (userAgent.contains("MSIE") || userAgent.contains("Trident") || userAgent.contains("Edge")) {
            filename = URLEncoder.encode(filename, "UTF-8");
        } else {
            // 非IE浏览器的处理
            filename = new String(filename.getBytes("UTF-8"), "ISO-8859-1");
        }

        return filename;
    }
}

然后,在告诉浏览器 “我要下载东西” 之前,把文件名编码。

String newFilename = DownloadUtils.getFilename(request, filename);
response.setHeader("content-disposition", "attachment;filename=" + newFilename);

根据测试,以上工具类可以解决主流浏览器(IE内核,Edge内核,Chrom,FireFox等)文件下载时文件名乱码问题。

完整代码

DownloadUtils.java

package club.luckylight.util;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;

public class DownloadUtils {

    public static String getFilename(HttpServletRequest request, String filename) throws UnsupportedEncodingException {
        String userAgent = request.getHeader("user-agent");
        // 针对以IE或者Edge为内核的浏览器
        if (userAgent.contains("MSIE") || userAgent.contains("Trident") || userAgent.contains("Edge")) {
            filename = URLEncoder.encode(filename, "UTF-8");
        } else {
            // 非IE浏览器的处理
            filename = new String(filename.getBytes("UTF-8"), "ISO-8859-1");
        }

        return filename;
    }
}

DownloadServlet.java

package club.luckylight.servlet;

import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import club.luckylight.util.DownloadUtils;

public class DownloadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取文件名,并设置编码
        String filename = new String(request.getParameter("name").getBytes("iso-8859-1"), "utf-8");

        // 获取文件的mime类型
        String mimeType = getServletContext().getMimeType(filename);

        // 设置文件的mime类型
        response.setContentType(mimeType);

        // 文件名编码
        String newFilename = DownloadUtils.getFilename(request, filename);

        // 设置下载头信息
        response.setHeader("content-disposition", "attachment;filename=" + newFilename);

        // 对拷流
        InputStream is = getServletContext().getResourceAsStream("/download/" + filename);
        ServletOutputStream os = response.getOutputStream();
        int len = -1;
        byte[] bytes = new byte[1024];
        while ((len = is.read(bytes)) != -1) {
            os.write(bytes, 0, len);
        }

        // 关闭资源
        os.close();
        is.close();
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

总结

至此,文件下载案例就完成了。其中有许多小细节并没有处理,如发现有什么错误,请多多指点。

这也是我第一次写一个案例,希望大家喜欢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,358评论 6 343
  • 0 系列目录# WEB请求处理 WEB请求处理一:浏览器请求发起处理 WEB请求处理二:Nginx请求反向代理 本...
    七寸知架构阅读 13,773评论 22 190
  • 这部分主要是与Java Web和Web Service相关的面试题。 96、阐述Servlet和CGI的区别? 答...
    杂货铺老板阅读 1,344评论 0 10
  • 我的世界不復雜只是帶着一絲苦味,委屈後、受傷後,告訴自己沒關系,堅持! 老天沒卷顾過,世界好大好精彩,但從不屬...
    栁圊薇楊阅读 83评论 0 1