文章目录
下载文件时文件名乱码和文件大小未知
这个问题引发自标准兼容问题
FireFox 对规范要求严格, 而其他浏览器相对宽容, 所以其他浏览器会兼容, 而FireFox则不做处理
解决方案是添加 Content-Disposition
响应头
快速解决文件名乱码
传统 File 类型文件写法( java 1.4 ~ java 6 ):
File file = getFile();
String filename = java.net.URLEncoder.encode(file.getName(), "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename*=utf-8'zh_cn'" + filename);
新IO Path 类型文件写法( java 7 ~ java 13 ):
Path file = Paths.get("E:\\Downloads\\测试文件.pdf");
String filename = file.getFileName().toString();
filename = URLEncoder.encode(filename, StandardCharsets.UTF_8.displayName());
String disposition = "attachment;filename*=utf-8'zh_cn'" + filename;
response.addHeader("Content-Disposition", disposition);
重点
重点在这一行:
"attachment;filename*=utf-8'zh_cn'" + filename
在 filename*=utf-8'zh_cn'
后面直接加文件名
快速解决文件大小未知
传统IO
File file = new File("E:\\Downloads\\测试文件.pdf");
long fileSize = file .length();
response.setContentLength((int) fileSize);
新IO
Path file = Paths.get("E:\\Downloads\\测试文件.pdf");
long fileSize = Files.size(file);
response.setContentLength((int) fileSize);
重点
给 response 设置 Content-Length
头就可以解决这个问题
response.setContentLength((int) fileSize);
完整类代码
package cc.momas;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/** * @author Sod-Momas * @since 2020.01.16 **/
@WebServlet(value = "/download")
public class DownloadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 解决乱码,一般写在过滤器 filter里
request.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
response.setContentType("text/html;charset=utf-8");
// 获取文件
Path file = Paths.get("E:\\Downloads\\测试文件.pdf");
// 检测文件是否存在
if (Files.notExists(file)) {
response.getWriter().write("指定文件不存在:" + file.toAbsolutePath());
return;
}
// 获取文件名
String filename = file.getFileName().toString();
// 获取文件大小,单位是byte
long fileSize = Files.size(file);
// 将文件名URL编码
filename = URLEncoder.encode(filename, StandardCharsets.UTF_8.displayName());
// 添加触发下载的前缀
// 规范参考 : Content-Disposition https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition
String disposition = "attachment;filename*=utf-8'zh_cn'" + filename;
// 添加 Content-Disposition 告诉浏览器数据展现的方式是附件下载,并且提供文件名
response.addHeader("Content-Disposition", disposition);
// 添加 Content-Length 告诉浏览器数据的长度,单位是 byte
response.setContentLength((int) fileSize);
// 添加 Content-Type 告诉浏览器这是一个二进制流
response.setContentType("application/octet-stream");
// 把文件从本地复制到 response 输出流输出给前端
Files.copy(file, response.getOutputStream());
}
}
完整项目示例
请访问我的码云仓库获取最新代码:
https://gitee.com/Sod-Momas/servlet-download-example
参考资料
博客
MDN
- Content-Disposition
Content-Disposition
响应头的作用 - Content-Type Content-Type 实体头的作用
- Content-Length Content-Length 是一个实体消息首部,用来指明发送给接收方的消息主体的大小,即用十进制数字表示的八位元组的数目。
RFC
- Media Types 不同文件的 MIME Types
- octet-stream – Media Type octet-stream 类型的规范