Servlet如何实现文件上传和下载?

wen java案例 67

本文目录导读:

Servlet如何实现文件上传和下载?

  1. 目录导读
  2. 文件上传与下载基础原理
  3. Servlet处理文件上传的核心组件
  4. 文件上传实战代码解析
  5. 文件下载的两种实现方式
  6. 安全防护与性能优化
  7. 常见问题解答(Q&A)

Servlet实现文件上传与下载:从原理到实战的完整指南

目录导读

  1. 文件上传与下载基础原理
  2. Servlet处理文件上传的三大组件(Part、MultipartConfig、Apache Commons FileUpload)
  3. 文件上传实战代码解析(单文件/多文件)
  4. 文件下载的两种实现方式(直接流输出与断点续传)
  5. 安全防护与性能优化
  6. 常见问题解答(Q&A)

文件上传与下载基础原理

文件上传本质上是通过HTTP协议的POST请求,将客户端二进制数据(文件内容)以multipart/form-data格式发送到服务器端,Servlet容器解析请求体,将文件内容提取为InputStream流,文件下载则相反,服务器读取本地文件(或数据库中的BLOB),通过设置响应头(Content-Disposition: attachment)将二进制流写回客户端。

关键点:文件传输的核心是处理输入/输出流(I/O Stream),以及合理的内存管理。


Servlet处理文件上传的核心组件

在Servlet 3.0+规范中,官方引入了@MultipartConfig注解和Part接口,极大简化了文件上传实现,若使用旧版或特殊需求,可选择Apache Commons FileUpload库。

组件 适用版本 特点
@MultipartConfig + Part Servlet 3.0+ 原生支持,无需额外JAR
Commons FileUpload 所有版本 功能丰富,支持内存/磁盘阈值

核心思路:通过request.getPart()request.getParts()获取上传文件对象,然后调用write()方法保存到服务器目录。


文件上传实战代码解析

1 使用Servlet原生API实现单文件上传

@WebServlet("/upload")
@MultipartConfig(
    location = "/tmp",  // 临时目录
    fileSizeThreshold = 1024 * 1024, // 1MB内存阈值
    maxFileSize = 1024 * 1024 * 10,  // 10MB单文件大小
    maxRequestSize = 1024 * 1024 * 50 // 50MB总请求大小
)
public class FileUploadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 1. 获取上传文件Part
        Part filePart = req.getPart("file"); // 表单name属性
        String fileName = filePart.getSubmittedFileName();
        // 2. 防止路径穿越攻击
        fileName = new File(fileName).getName();
        // 3. 存储到上传目录
        String uploadPath = getServletContext().getRealPath("/uploads");
        File uploadDir = new File(uploadPath);
        if (!uploadDir.exists()) uploadDir.mkdirs();
        filePart.write(uploadPath + File.separator + fileName);
        resp.getWriter().write("文件上传成功:" + fileName);
    }
}

关键点@MultipartConfig必须配置在Servlet上;getSubmittedFileName()在部分容器中可能返回完整路径,需提取文件名。

2 多文件上传处理

HTML表单需设置multiple属性:<input type="file" name="files" multiple>

protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException {
    Collection<Part> parts = req.getParts();
    for (Part part : parts) {
        if (part.getContentType() != null) { // 仅处理文件域
            String fileName = part.getSubmittedFileName();
            part.write(uploadPath + File.separator + fileName);
        }
    }
}

核心逻辑:遍历getParts(),通过getContentType()getSize()判断是否为文件域(普通表单域ContentType为null)。


文件下载的两种实现方式

1 基础下载(标准流输出)

@WebServlet("/download")
public class FileDownloadServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        String fileName = req.getParameter("file");
        String filePath = getServletContext().getRealPath("/uploads") + File.separator + fileName;
        File downloadFile = new File(filePath);
        if (!downloadFile.exists()) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        // 设置响应头
        resp.setContentType("application/octet-stream");
        resp.setHeader("Content-Disposition", 
            "attachment; filename=\"" + URLEncoder.encode(fileName, "UTF-8") + "\"");
        resp.setContentLengthLong(downloadFile.length());
        // 流复制
        try (InputStream is = new FileInputStream(downloadFile);
             OutputStream os = resp.getOutputStream()) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
        }
    }
}

关键配置

  • Content-Disposition: attachment 强制浏览器下载
  • URLEncoder.encode解决中文文件名乱码
  • 使用try-with-resources自动关闭流

2 支持断点续传的高级下载

String rangeHeader = req.getHeader("Range");
if (rangeHeader != null) {
    long rangeStart = Long.parseLong(rangeHeader.replace("bytes=", "").split("-")[0]);
    resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
    resp.setHeader("Content-Range", "bytes " + rangeStart + "-" + (fileSize-1) + "/" + fileSize);
    // 从指定位置开始读取
}

适用场景:大文件下载、视频流播放。


安全防护与性能优化

1 安全防护要点

  • 文件类型校验:检查Content-Type和文件魔数(Magic Number),而非仅依赖扩展名
  • 路径穿越防护:使用new File(fileName).getName()过滤目录符号
  • 大小限制:通过@MultipartConfigmaxFileSize限制
  • 存储隔离:文件存储路径不要放在Web根目录下,避免直接访问

2 性能优化建议

  • 内存阈值设置fileSizeThreshold设置为1-4MB,小文件直接在内存处理
  • 缓冲区大小:流复制时使用8KB以上缓冲区
  • 异步处理:大文件上传可采用AsyncContext
  • 避免重复读取:上传时如需获取文件内容,先将Part保存到临时文件再读取

常见问题解答(Q&A)

Q1: 上传文件时出现"Request exceeds the configured size limit"错误怎么办?

A: 增大@MultipartConfig中的maxRequestSize值,或调整容器配置(如Tomcat的maxSwallowSize),若上传文件超过50MB,建议采用分片上传方案。

Q2: 下载文件时中文名变成乱码如何解决?

A: 使用URLEncoder.encode(fileName, "UTF-8")编码文件名,并确保Content-Disposition头中包含filename*=UTF-8''格式:

resp.setHeader("Content-Disposition", 
    "attachment; filename=\"" + encodedName + "\"; filename*=UTF-8''" + encodedName);

Q3: Servlet原生API和Commons FileUpload哪个更好?

A: 如果使用Servlet 3.0+容器,优先选择原生API(无需额外依赖、代码简洁),Commons FileUpload更适合需要底层控制(如自定义缓冲区、多Part处理策略)的场景。

Q4: 上传的文件如何防止被恶意执行?

A: 设置存储目录的权限为只读(不可执行);文件上传后重命名为UUID格式;将文件存储在与Web应用分离的目录(如/data/uploads),通过专用下载Servlet提供访问。

Q5: 如何实现文件上传的进度条?

A: Servlet原生API不支持进度跟踪,可通过以下方式实现:

  • 前端使用XMLHttpRequest的upload.onprogress事件
  • 后端使用WebSocket或Session存储进度状态
  • 使用Spring的MultipartFileCommonsMultipartResolver的监听器

通过以上六个维度的解析,您可以掌握Servlet处理文件上传与下载的完整技术栈,核心在于理解HTTP协议的多部分数据封装、输入输出流的生命周期管理,以及在实现中始终考虑安全性和性能边界,实际生产环境建议结合Spring MVC的MultipartFile封装,可进一步简化代码逻辑。

抱歉,评论功能暂时关闭!