在 Spring 中多次读取 InputStream
需求背景
系统需要对请求体内容进行校验计算是否合法,因此准备通过拦截器 Interceptor 来进行统一处理。
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String param = new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
// 进行校验处理
return check(param);
}
}
由于该拦截器读取了一次 inputStream, 导致 Spring框架在赋值给接口 @RequestBody 参数时无法再获取到 inputStream 导致程序出现错误:Required request body is missing
。
解决方法
- 扩写ServletInputStream
public class RequestBodyInputStreamWrapper extends ServletInputStream {
private ByteArrayInputStream bais;
private ServletInputStream wrapped;
public RequestBodyInputStreamWrapper(ServletInputStream wrapped) {
this.wrapped = wrapped;
}
// 缓存内容用于后期读取
public byte[] resetReading(byte[] bytes) {
this.bais = new ByteArrayInputStream(bytes);
return bytes;
}
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return wrapped.isFinished();
}
@Override
public boolean isReady() {
return wrapped.isReady();
}
@Override
public void setReadListener(ReadListener readListener) {
wrapped.setReadListener(readListener);
}
}
- 扩写请求类 ContentCachingRequestWrapper
public class RequestBodyContentWrapper extends ContentCachingRequestWrapper {
private RequestBodyInputStreamWrapper bodyProcessorIS;
public RequestBodyContentWrapper(ContentCachingRequestWrapper wrapped) throws IOException {
super(wrapped);
this.bodyProcessorIS = new RequestBodyInputStreamWrapper(wrapped.getInputStream());
}
@Override
public ServletInputStream getInputStream() {
return bodyProcessorIS;
}
public byte[] prepareInputStream() throws IOException {
return bodyProcessorIS.resetReading(super.getInputStream().readAllBytes());
}
}
- 过滤器中将 HttpServletRequest 替换为包装后的类 RequestBodyContentWrapper
public class RequestBodyWrapperFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(@Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ContentCachingRequestWrapper cacheRequest = new ContentCachingRequestWrapper(request);
RequestBodyContentWrapper readableRequest = new RequestBodyContentWrapper(cacheRequest);
// 调用该方法来缓存请求体的内容
readableRequest.prepareInputStream();
filterChain.doFilter(readableRequest, response);
}
}
- 拦截器中获取请求体内容
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
RequestBodyContentWrapper request1 = (RequestBodyContentWrapper) request;
String param = new String(request1.getContentAsByteArray());
// 进行校验处理
return check(param);
}
}
参考资料
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构