Spring中如何创建读取请求体且不破坏请求的过滤器?
解决方案:读取SOAP请求体同时保证后续流程正常使用
你的核心问题是HttpServletRequest的输入流仅能读取一次,直接读取后后续SOAP处理器无法获取请求体;而默认的ContentCachingRequestWrapper缓存容量过小(默认2048字节),若SOAP请求体超过这个大小会被截断,导致解析失败返回400错误。
以下是两种可行的解决方法:
方法一:调整ContentCachingRequestWrapper缓存容量
直接使用Spring提供的ContentCachingRequestWrapper,但设置足够大的缓存容量以容纳完整SOAP请求体:
@Component @RequiredArgsConstructor public class AddHashFilter extends OncePerRequestFilter { private final RequestHash hash; // 根据实际业务设置缓存大小,例如10MB private static final int MAX_CACHE_SIZE = 10 * 1024 * 1024; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 创建带大缓存的请求包装类 ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request, MAX_CACHE_SIZE); // 从缓存中读取完整请求体,无需直接操作输入流 String requestBody = new String(wrappedRequest.getContentAsByteArray(), StandardCharsets.UTF_8); hash.hash(requestBody); // 将包装后的请求传递给后续流程 filterChain.doFilter(wrappedRequest, response); } }
说明
getContentAsByteArray()会直接返回缓存的完整请求体,避免了重复读取输入流的问题- 自定义
MAX_CACHE_SIZE确保不会因请求体过大被截断
方法二:自定义可重复读取的请求包装类
如果需要更灵活的控制,可以自己实现HttpServletRequestWrapper,将请求体一次性缓存到字节数组中:
1. 实现包装类
public class RepeatableRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public RepeatableRequestWrapper(HttpServletRequest request) throws IOException { super(request); // 一次性读取原始请求体并缓存 body = StreamUtils.copyToByteArray(request.getInputStream()); } @Override public ServletInputStream getInputStream() throws IOException { // 返回基于缓存字节数组的输入流,支持重复读取 return new ServletInputStream() { private final ByteArrayInputStream byteStream = new ByteArrayInputStream(body); @Override public int read() throws IOException { return byteStream.read(); } @Override public boolean isFinished() { return byteStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener listener) { // 同步场景下无需实现该方法 } }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8)); } // 提供直接获取请求体的方法 public String getRequestBody() { return new String(body, StandardCharsets.UTF_8); } }
2. 在过滤器中使用
@Component @RequiredArgsConstructor public class AddHashFilter extends OncePerRequestFilter { private final RequestHash hash; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { RepeatableRequestWrapper wrappedRequest = new RepeatableRequestWrapper(request); // 获取缓存的请求体计算哈希 hash.hash(wrappedRequest.getRequestBody()); // 传递包装后的请求给后续流程 filterChain.doFilter(wrappedRequest, response); } }
额外注意事项
- 确保你的过滤器执行顺序在SOAP相关处理器之前,可以通过
@Order注解指定优先级 - 如果SOAP请求使用非UTF-8编码,需替换代码中的
StandardCharsets.UTF_8为request.getCharacterEncoding()(需确保请求头正确设置了编码)
内容的提问来源于stack exchange,提问作者dermoritz
相关产品推荐
相关产品推荐

