无法多次读取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; } }
关键注意事项
- 过滤器顺序:必须保证自定义过滤器在Spring的
MultipartFilter之前执行,否则Spring会先解析请求流,导致包装器无法缓存完整请求体。 - 请求体完整性:确保在读取请求体时完整读取所有字节,避免缓存不完整导致后续解析失败。
- 兼容性:该实现基于Servlet 3.0+ API,兼容大多数现代Servlet容器(如Tomcat、Jetty)。
内容的提问来源于stack exchange,提问作者Gourav Kumar
相关产品推荐
相关产品推荐

