You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无法多次读取multipart/form-data类型HttpServletRequest请求体求助

解决Spring Boot过滤器中读取multipart/form-data请求体并兼容后续处理的问题

问题根源

你遇到的核心问题是:普通请求包装器(如ContentCachingRequestWrapper)仅缓存了请求体字节流,但未处理multipart/form-data类型请求的Part解析逻辑。当过滤器读取完请求体后,后续Spring MVC调用getPart()或getParts()时,原始请求流已被消费,且包装器未提供缓存的Part实例,因此抛出MissingServletRequestPartException。

解决方案:自定义支持Multipart的请求包装器

我们需要实现一个既能缓存请求体,又能缓存解析后的Part对象的请求包装器,并重写getPart()、getParts()方法,确保过滤器和后续Spring MVC都能获取到正确的请求数据。

1. 自定义请求包装器实现

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.Part;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class MultiReadMultipartHttpServletRequest extends HttpServletRequestWrapper {

    private final byte[] cachedBody;
    private Map<String, Part> partMap;
    private Collection<Part> parts;

    public MultiReadMultipartHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        // 读取并缓存完整请求体
        cachedBody = readRequestBody(request);
        // 解析并缓存Multipart Parts
        parseMultipartParts();
    }

    private byte[] readRequestBody(HttpServletRequest request) throws IOException {
        try (InputStream inputStream = request.getInputStream();
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            return outputStream.toByteArray();
        }
    }

    private void parseMultipartParts() throws IOException {
        if (!isMultipartContent()) {
            return;
        }
        // 利用重写后的getInputStream(返回缓存流)解析Parts
        try {
            parts = super.getParts();
            partMap = new HashMap<>();
            for (Part part : parts) {
                partMap.put(part.getName(), part);
            }
        } catch (Exception e) {
            throw new IOException("解析Multipart Parts失败", e);
        }
    }

    private boolean isMultipartContent() {
        String contentType = getContentType();
        return contentType != null && contentType.startsWith("multipart/form-data");
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return new ByteArrayInputStream(cachedBody);
    }

    @Override
    public Part getPart(String name) throws IOException {
        if (partMap == null) {
            parseMultipartParts();
        }
        return partMap.get(name);
    }

    @Override
    public Collection<Part> getParts() throws IOException {
        if (parts == null) {
            parseMultipartParts();
        }
        return parts;
    }

    // 供过滤器获取缓存的请求体
    public byte[] getCachedBody() {
        return cachedBody;
    }
}

2. 实现Servlet过滤器

创建过滤器,将请求包装为自定义的MultiReadMultipartHttpServletRequest,并在其中读取缓存的请求体:

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;

public class MultipartRequestHandlingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        // 仅处理multipart/form-data请求
        if (isMultipartRequest(httpRequest)) {
            MultiReadMultipartHttpServletRequest wrappedRequest = new MultiReadMultipartHttpServletRequest(httpRequest);
            
            // 在这里处理缓存的请求体,比如日志记录、内容校验等
            byte[] cachedBody = wrappedRequest.getCachedBody();
            // do something with cachedBody...
            
            chain.doFilter(wrappedRequest, response);
        } else {
            // 非multipart请求可使用ContentCachingRequestWrapper处理
            chain.doFilter(request, response);
        }
    }

    private boolean isMultipartRequest(HttpServletRequest request) {
        String contentType = request.getContentType();
        return contentType != null && contentType.startsWith("multipart/form-data");
    }
}

3. 配置过滤器在Spring Boot中生效

通过配置类注册过滤器,确保其执行顺序在Spring自带的MultipartFilter之前:

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<MultipartRequestHandlingFilter> multipartRequestFilter() {
        FilterRegistrationBean<MultipartRequestHandlingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new MultipartRequestHandlingFilter());
        registrationBean.addUrlPatterns("/*"); // 按需调整拦截路径
        registrationBean.setOrder(1); // 确保在Spring MultipartFilter之前执行
        return registrationBean;
    }
}

关键注意事项

  1. 过滤器顺序:必须保证自定义过滤器在Spring的MultipartFilter之前执行,否则Spring会先解析请求流,导致包装器无法缓存完整请求体。
  2. 请求体完整性:确保在读取请求体时完整读取所有字节,避免缓存不完整导致后续解析失败。
  3. 兼容性:该实现基于Servlet 3.0+ API,兼容大多数现代Servlet容器(如Tomcat、Jetty)。

内容的提问来源于stack exchange,提问作者Gourav Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 03:25:22