在 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

解决方法

  1. 扩写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);
    }
}
  1. 扩写请求类 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());
    }
}
  1. 过滤器中将 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);
    }
}
  1. 拦截器中获取请求体内容
@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);
    }
}

参考资料

posted @   红枫星空  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示