소개
Request로 들어온 데이터는 Filter, Interceptor에서 사용하게 되면,
그 이후 사용이 불가능하므로 Caching 할 수 있는 Wrapper를 새로 만들기 위한 코드다.
소스 코드
MultiReadRequestWrapper
javapublic class MultiReadRequestWrapper extends HttpServletRequestWrapper { private byte[] cachedBody; public MultiReadRequestWrapper(HttpServletRequest request) throws IOException { super(request); // 생성 시점에 바디를 미리 읽어서 보관합니다. InputStream is = request.getInputStream(); this.cachedBody = StreamUtils.copyToByteArray(is); } @Override public ServletInputStream getInputStream() { // 호출될 때마다 보관된 바이트 배열로 새 스트림을 만듭니다. ByteArrayInputStream bais = new ByteArrayInputStream(this.cachedBody); return new ServletInputStream() { @Override public int read() { return bais.read(); } @Override public boolean isFinished() { return bais.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) { } }; } @Override public BufferedReader getReader() { return new BufferedReader(new InputStreamReader(this.getInputStream())); } // 캐싱된 응답 바디를 꺼내보는 메서드 public byte[] getContentAsByteArray() throws IOException { return StreamUtils.copyToByteArray(getInputStream()); } }
MultiReadResponseWrapper
javapublic class MultiReadResponseWrapper extends HttpServletResponseWrapper { private final ByteArrayOutputStream capture = new ByteArrayOutputStream(); private ServletOutputStream output; private PrintWriter writer; public MultiReadResponseWrapper(HttpServletResponse response) { super(response); } @Override public ServletOutputStream getOutputStream() { if (writer != null) throw new IllegalStateException("Writer already in use"); if (output == null) { output = new ServletOutputStream() { @Override public void write(int b) { capture.write(b); } @Override public void setWriteListener(WriteListener writeListener) {} @Override public boolean isReady() { return true; } }; } return output; } @Override public PrintWriter getWriter() { if (output != null) throw new IllegalStateException("Stream already in use"); if (writer == null) { writer = new PrintWriter(new OutputStreamWriter(capture, StandardCharsets.UTF_8)); } return writer; } @Override public void flushBuffer() throws IOException { if (writer != null) writer.flush(); if (output != null) output.flush(); } // 캐싱된 응답 바디를 꺼내보는 메서드 public byte[] getContentAsByteArray() { return capture.toByteArray(); } // 최종적으로 클라이언트에게 데이터를 전송하는 메서드 (필수!) public void copyBodyToResponse() throws IOException { flushBuffer(); byte[] content = capture.toByteArray(); ServletOutputStream out = getResponse().getOutputStream(); out.write(content); out.flush(); } }
사용법
- Filter에서 사용 예시
java@Component public class LoggingFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // mutipart 요청은 래핑하지 않도록 변경 String contentType = request.getContentType(); if(contentType != null && contentType.startsWith("multipart/")) { filterChain.doFilter(request, response); return; } // 여기서 딱 한 번만 래핑합니다. MultiReadRequestWrapper requestWrapper = new MultiReadRequestWrapper(request); MultiReadResponseWrapper responseWrapper = new MultiReadResponseWrapper(response); try { filterChain.doFilter(requestWrapper, responseWrapper); } finally { logRequestAndResponse(requestWrapper, responseWrapper); responseWrapper.copyBodyToResponse(); } } private void logRequestAndResponse(MultiReadRequestWrapper request, MultiReadResponseWrapper response) throws IOException { String requestBody = new String(request.getContentAsByteArray(), StandardCharsets.UTF_8); String responseBody = new String(response.getContentAsByteArray(), StandardCharsets.UTF_8); log.info("Request: [{} {}] | Body: {}", request.getMethod(), request.getRequestURI(), requestBody); log.info("Response: [Status {}] | Body: {}", response.getStatus(), responseBody); } }