【サーブレット】HttpサーブレットRequestWrapperによるrequest bodyの二次読み取りを実現し、ログの記録に使用できる

3022 ワード

最近のプロジェクトではapiで受信したリクエストがあり、springmvcがエンティティパラメータに変換される前にrequest bodyを読み出してログを記録する必要があります.
通常の応答フローではrequestが使用する.getInputStream()の後、このrequest bodyのストリームは1回しか読み取れないというストリームが無効になります.これもストリーム自体の特性によるものです(もちろん、入力ストリームPushbackInputStreamを戻す特殊なストリームもあります).
servlet apiにより、HttpServertRequestWrapperというクラスを継承し、filterでrequestを一度カプセル化してchainに転送することで、ログの記録を実現できます.
 
まず、HttpServiceletRequestWrapperのサブクラスです.
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private final byte[] bytes;

    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        try (BufferedInputStream bis = new BufferedInputStream(request.getInputStream());
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) > 0) {
                baos.write(buffer, 0, len);
            }
            bytes = baos.toByteArray();
            String body = new String(bytes);
            System.out.println(body);
        } catch (IOException ex) {
            throw ex;
        }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

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

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

}

 
次はfilterの実装です
@WebFilter(filterName = "httpServletRequestWrapperFilter", urlPatterns = "/*")
public class HttpServletRequestWrapperFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {
            ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request);
            chain.doFilter(requestWrapper, response);
        } else {
            chain.doFilter(request, response);
        }

    }

    @Override
    public void destroy() {

    }


}

ここではspring bootを使用した注記@WebFilterです.spring bootで@WebFilter付きのクラスをスキャンする場合は、Applicationエントリクラスに@サーブレットComponentScanという注記を付けてから正常に動作することを覚えておいてください.