Servlet 原生 Response 实现文件下载
在 Web 开发中,有时候我们需要控制文件的下载行为,比如增加权限校验、记录日志或者处理中文文件名乱码等问题。这时候直接通过 HTML <a> 标签指向资源就不够用了,得靠后端 Servlet 配合 HttpServletResponse 来接管响应流。
前端测试页面
先准备一个简单的 HTML 页面,模拟用户点击下载链接的场景。这里对比了两种调用方式:一种是直接访问静态资源路径,另一种是走 Servlet 接口。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件下载测试</title>
</head>
<body>
<h1>使用 a 标签直接指向服务器上的资源</h1>
<a href="/download/a.flv">a.flv</a><br>
<a href="/download/a.jpg">a.jpg</a><br>
<a href="/download/a.mp3">a.mp3</a><br>
<a href="/download/a.mp4">a.mp4</a><br>
<a href="/download/a.txt">a.txt</a><br>
<a href="/download/a.zip">a.zip</a><br>
<h1>使用服务器端编码的方式实现文件下载</h1>
<a href="/web1/downloadServlet?filename=a.flv">a.flv</a><br>
<a href="/web1/downloadServlet?filename=a.jpg">a.jpg</a><br>
<a href="/web1/downloadServlet?filename=a.mp3">a.mp3</a><br>
<a href="/web1/downloadServlet?filename=a.mp4">a.mp4</a><br>
<a href="/web1/downloadServlet?filename=a.txt">a.txt</a><br>
<a href="/web1/downloadServlet?filename=a.zip">a.zip</a><br>
<!-- 注意中文文件名需要 URL 编码 -->
<a href="/web1/downLoadServlet2?filename=%E7%BE%8E%E5%A5%B3.jpg">美女.jpg</a><br>
</body>
</html>
Servlet 核心实现
接下来看核心的 Java 代码。这里用到了 ServletOutputStream 将文件字节流写入响应体,并通过设置 Header 告诉浏览器以附件形式打开。
package com.xiangshuai.servlet;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DownloadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
// 生产环境建议从配置文件或环境变量读取,避免硬编码路径
String con = "F:/download";
String filename = request.getParameter("filename");
// 设置请求和响应的字符编码,防止中文乱码
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("UTF-8");
// 关键:Content-Disposition 头信息
// attachment 表示强制下载,inline 表示尝试在浏览器内打开
// filename 参数指定下载后的文件名,中文需进行 URL 编码
try {
String encodedFilename = URLEncoder.encode(filename, "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + encodedFilename);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
ServletOutputStream os = response.getOutputStream();
try {
// 构建文件对象并创建输入流
File file = new File(con + "/" + filename);
if (!file.exists()) {
throw new IOException("File not found: " + filename);
}
bis = new BufferedInputStream(new FileInputStream(file));
bos = new BufferedOutputStream(os);
byte[] b = new byte[1024];
int len = -1;
while ((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
bos.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// IO 流必须关闭,否则可能导致连接池耗尽或内存泄漏
if (bos != null) {
try { bos.close(); } catch (IOException e) { e.printStackTrace(); }
}
if (bis != null) {
try { bis.close(); } catch (IOException e) { e.printStackTrace(); }
}
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
关键点解析
1. Content-Disposition 的作用
这个响应头是控制浏览器行为的关键。当设置为 attachment 时,浏览器会弹出保存对话框而不是直接在窗口渲染内容。如果类型是图片但设置了这个头,浏览器也会提示下载而不是显示图片。
2. 中文文件名处理
代码中使用了 URLEncoder.encode 对文件名进行了编码。如果不处理,包含中文的文件名在某些浏览器下会变成乱码(如 %E7%BE%8E...),导致下载后无法识别。虽然现代浏览器兼容性好了很多,但在企业级应用中加上这一步更稳妥。
3. 资源清理
IO 流操作最忌讳的就是忘记关闭。我们在 finally 块中检查流是否为空并执行 close()。这不仅能释放系统资源,还能确保缓冲区数据被彻底刷新到客户端。
4. 路径问题
示例中写死了 F:/download,这在 Windows 上没问题,但换到 Linux 服务器就会报错。实际项目中建议使用 ServletContext.getRealPath("/upload") 或者配置类注入的路径常量,保证跨平台兼容性。
通过这种方式,我们不仅实现了文件下载,还预留了后续添加权限验证、流量统计等扩展功能的入口。

