在过滤器中多次读取请求体内容

MUEDSA,Development

问题

HttpServletRequest接口定义了ServletInputStream getInputStream() throws IOException;来获取HttpBody的二进制流。但是由于ServletInputStream通常是不可重复的读取的Stream(inputStream.markSupported() === false),如果在Filter中读取了inputStream将导致过滤器链中的下一个Filter不能读取inputStream,Spring的HttpMessageConverter也不能读取inputStream,进一步会导致Controller层获取的参数对象(被@RequestBody@RequestPart注解)为null

1. 解决提出问题的人

不在Filter中获取请求体内容!这下压力来到了......

2. @ControllerAdvice

使用@ControllerAdvice继承RequestBodyAdviceAdapter或实现RequestBodyAdvice。可以在beforeBodyRead中读取HttpInputMessage但是记得要返回一个带着新InputStreamHttpInputMessage

注意:使用此方法对于不需要转换请求体到Controller层的请求无法捕获到,比如没有请求体的GET请求

3. 自己实现HttpServletRequest缓存下InputStream内容并在每次getInputStream都返回一个新的InputStream

构建一个新的可以重复读取InputStreamHttpServletRequest,并在filterChain.doFilter中使用此HttpServletRequest

Example:

public static class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
    private final byte[] cachedBody;
 
    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        InputStream requestInputStream = request.getInputStream();
        this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
    }
 
    @Override
    public ServletInputStream getInputStream() {
        return new CachedBodyServletInputStream(this.cachedBody);
    }
 
    @Override
    public BufferedReader getReader() {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
        return new BufferedReader(new InputStreamReader(byteArrayInputStream));
    }
 
    public byte[] getCachedBody() {
        return cachedBody;
    }
 
    public static class CachedBodyServletInputStream extends ServletInputStream {
 
        private InputStream cachedBodyInputStream;
 
        public CachedBodyServletInputStream(byte[] cachedBody) {
            this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
        }
 
        @Override
        public boolean isFinished() {
            boolean flag;
            try {
                flag =  cachedBodyInputStream.available() == 0;
            } catch (IOException e) {
                flag = true;
                log.error("CachedBodyServletInputStream.isFinished()", e);
            }
            return flag;
        }
 
        @Override
        public boolean isReady() {
            return true;
        }
 
        @Override
        public void setReadListener(ReadListener listener) {
 
        }
 
        @Override
        public int read() throws IOException {
            return cachedBodyInputStream.read();
        }
    }
}

但是此示例有个问题,因为super(request)中的request.inputStream已经被读取完成,会导致getParts()&getPart(name)等另一些与请求体内容有关的方法不可用,也会使Controller中的@RequestPart获取的对象为NULL

© MUEDSA BLOG.RSS