案例:

  • 文件下载需求:
    1. 页面显示超链接
    2. 点击超链接后弹出下载提示框
    3. 完成图片文件下载
  • 分析:
    1. 超链接指向的资源如果能够被浏览器解析,则在浏览器中展示,如果不能解析,则弹出下载提示框,不满足需求
    2. 需求:任何资源都必须弹出下载提示框
    3. 解决方式:使用响应头设置资源的打开方式:
      • content-disposition:attachment;filename=xxx
  • 步骤:
    1. 定义页面,编辑超链接 href 属性,指向 Servlet, 传递资源名称 filename
    2. 定义 Servlet
      1. 获取文件名称
      2. 使用字节输入流加载文件进内存
      3. 指定 response 的响应头:content-dispostion:attchment;filename=xxx
      4. 将数据写出到 response 输出流

案例代码:

  • HttpServlet 类
@WebServlet("/servletload")
public class Servletload extends HttpServlet {
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        //1. 获取请求参数,文件名称
        String file = req.getParameter("filename");
        //2. 使用字节输入流加载文件进内存
        //2.1 找到文件服务器路径,ServletContext: 代表整个 web 应用,可以对服务器通信
        ServletContext context = this.getServletContext();
        //realpath: 获取文件服务器路径:/opt/idea/ideaDemo/day12_1/out/artifacts/day12_1_war_exploded/image/tupian.jpg
        String filerealpath = context.getRealPath("/image/"+file);
        System.out.println(filerealpath);
        //2.2 使用字节流关联
        FileInputStream input = new FileInputStream(filerealpath);
        //3. 设置 response 的响应头
        //3.1 获取 MIME 类型
        String mime = context.getMimeType(file);
        //3.2 设置响应头类型 content-type: 服务器告诉客户端本次响应体格式以及编码
        resp.setHeader("content-type",mime);
        // 解决中文文件名乱码问题
        //1. 获取 user-agent 请求头 user-agent: 浏览器告诉服务器访问时使用的浏览器版本信息,可以解决浏览器的兼容性问题
        String user_agent = req.getHeader("user-agent");
        //2. 使用工具类编码即可
        file = DownLoadUtils.getFileName(user_agent,file);
        //3.3 设置响应头打开方式,解释如下 PS
        resp.setHeader("content-disposition","attachment;filename="+"/image/"+file);
        //4. 将输入流的数据写出到输出流中
        ServletOutputStream out = resp.getOutputStream();
        int i = 0;
        byte[] by = new byte[1024 * 8];
        while((i = input.read(by)) != -1){
            out.write(by,0,i);
        }
        // 关闭流
        input.close();
        out.close();
    }
    protected void doGet(HttpServletRequest req,HttpServletResponse resp) throws IOException {
        this.doPost(req,resp);
        int i = 0;
    }
}

PS: Content-Disposition 头字段的设置主要有两个部分:

  1. attachment :告诉浏览器这是一个附件,应该提示用户下载而不是直接显示
  2. filename :指定下载时保存的文件名
  • Html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="/dkx/image/风景.jpg">图片</a>
<hr>
<a href="/dkx/servletload?filename=风景.jpg">图片1</a>
</body>
</html>
  • 解决中文乱码问题

需要使用到 BASE64Encoder.jar 包来对火狐浏览器进行编码的处理

PS:URLEncoder.encode 的作用

由于文件名可能包含特殊符号 (例如 空格,中文字符等),使用 URLEncoder.encode 对文件名进行编码以确保它在 HTTP 头中能够被正确传输和解析。 URLEncoder.encode 会将这些特殊字符转换为 % 加上两位十六进制数的形式,这样可以避免传输过程中由于字符编码问题引起的错误。

编码原理

URL 编码的基本原则是将字符串中的非字母数字字符和一些特殊字符转换为 % 加上该字符的 ASCII 十六进制值,具体步骤如下:

  1. 非字母数字字符转换:将非字母字符 (例如 空格,中文字符,特殊符号等) 转换为 % 加上两位十六进制数字表示的形式。例如,空格转换为 %20 ,汉字 "文" 的 UTF-8 编码为 E6 96 87 ,所以 "文" 会被转换为 %E6%96%87
  2. 保留字符转换:某些字符在 URL 中有特殊意义,如 ?&= 等。这些字符也会被转换为百分号编码形式,以避免在 URL 中被误解
public class DownLoadUtils {
    public static String getFileName(String agent,String filename) throws UnsupportedEncodingException {
        if(agent.contains("MSIE")){
            //IE 浏览器
            filename = URLEncoder.encode(filename,"utf-8");
            filename = filename.replace("+"," ");
        }else if(agent.contains("Firefox")){
            // 火狐浏览器
            BASE64Encoder base64encoder = new BASE64Encoder();
            filename = "?utf-8?B?" + base64encoder.encode(filename.getBytes("utf-8"))+"?=";
        }else{
            // 其它浏览器
            filename = URLEncoder.encode(filename,"utf-8");
        }
        return filename;
    }
}

PS:服务端使用 URLEncoder.encode 对文件名进行编码,以确保在 HTTP 头中传输不出错。例如,测试 文件.txt 会被编码为:测试 + 文件.txt 或者测试 %20 文件.txt

浏览器处理:浏览器接收到响应后,会解析 Content-Disposition 头字段中的文件名,并自动解码 URL 编码的文件名,使其恢复为原始的用户友好的名称。这样用户在下载对话框中看到的文件名会是 测试 文件.txt 而不是编码后的形式。