解决HttpServletRequest inputStream只能读取一次的问题

最近打算在之前的项目APP接口里面加入验证签名的功能,实现思路很简单,就是通过添加filter的方式,在filter中读取所有的请求参数,然后验证客户端传过来的SIGN值跟服务端生成的SIGN值是否一致。

目前的接口里面参数有K=V格式的,也有JSON格式的,对于前者在filter中通过HttpServletRequest.getParameterMap就可以直接获取。但是对于JSON参数,我们需要从request的inputStream中读取,当然这也不复杂,几行代码就可以搞定了。

但是当我完成filter中的代码,在进行调试的时候,发现原来controller中通过@RequestBody获取JSON参数的接口抛出Required request body is missing的错误。刚开始还以为参数传的有问题,查阅相关资料才明白inputStream的数据只能读取一次,从inputStream中读取过数据之后,后续再从inputStream中就不能再读取到数据了。

 

@Slf4j
@WebFilter(filterName = "myFilter", urlPatterns = {"/*"}, initParams = {@WebInitParam(name = "author", value = "Jaemon")})
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("过滤器初始化={}", filterConfig.getFilterName());
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("执行过滤器...");
        // 对 request 和 response 进行一些预处理
        servletRequest.setCharacterEncoding("UTF-8");
        servletResponse.setCharacterEncoding("UTF-8");
        servletResponse.setContentType("text/html;charset=UTF-8");
        // 此处读取了 inputStream
        String body = IOUtils.toString(servletRequest.getInputStream(), servletRequest.getCharacterEncoding());
        log.info("request body={}", body);

        // 做一些业务相关的请求验证处理,: 验签

        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        log.info("过滤器销毁");
    }
}

 

@Slf4j
@WebFilter(filterName = "myFilter", urlPatterns = {"/*"}, initParams = {@WebInitParam(name = "author", value = "Jaemon")})
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("过滤器初始化={}", filterConfig.getFilterName());
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    	// 封装类, 将 inputStream 读取到 body 中之后再 写到 inputStream 中
        BodyCachingRequestWrapper requestWrapper = new BodyCachingRequestWrapper((HttpServletRequest) servletRequest);
        log.info("执行过滤器...");
        // 对 request 和 response 进行一些预处理
        servletRequest.setCharacterEncoding("UTF-8");
        servletResponse.setCharacterEncoding("UTF-8");
        servletResponse.setContentType("text/html;charset=UTF-8");
        String body = new String(requestWrapper.getBody(), servletRequest.getCharacterEncoding());
        log.info("request body={}", body);

        // 做一些业务相关的请求验证处理,: 验签

        filterChain.doFilter(requestWrapper, servletResponse);
    }

    @Override
    public void destroy() {
        log.info("过滤器销毁");
    }
}

 

public class BodyCachingRequestWrapper extends HttpServletRequestWrapper {
    private byte[] body;
    private BufferedReader reader;
    private ServletInputStream inputStream;

    public BodyCachingRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        loadBody(request);
    }

    private void loadBody(HttpServletRequest request) throws IOException {
        // 读取 inputStream 中的内容到 body 之后,再将数据写回 inputStream 中
        body = IOUtils.toByteArray(request.getInputStream());
        inputStream = new RequestCachingInputStream(body);
    }

    public byte[] getBody() {
        return body;
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (inputStream != null) {
            return inputStream;
        }
        return super.getInputStream();
    }

    @Override
    public BufferedReader getReader() throws IOException {
        if (reader == null) {
            reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding()));
        }
        return reader;
    }

    private static class RequestCachingInputStream extends ServletInputStream {
        private final ByteArrayInputStream inputStream;

        public RequestCachingInputStream(byte[] bytes) {
            inputStream = new ByteArrayInputStream(bytes);
        }
        @Override
        public int read() throws IOException {
            return inputStream.read();
        }

        @Override
        public boolean isFinished() {
            return inputStream.available() == 0;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener readlistener) {
        }

    }
}

 

在SpringBootApplication上使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册,无需其他代码。

你可能感兴趣的:(Spring,开发笔记)