본문 바로가기
개발기록/Spring Boot

Spring Boot에서 Request 이력 저장하기

by niliso 2023. 1. 9.

클라이언트에서 호출한 Api 이력을 저장해야 하는 요구사항이 있었다.

시스템 관리 화면에서 호출하는 API의 호출 Url, 요청 파라미터, 요청 body를 저장해서 누가 어떤 이벤트를 발생시켰는지 이력을 확인할 수 있어야 했다.

시스템 관리 api 모든 요청에 대해서 저장해야했기 때문에 각 컨트롤러 내부가 아닌 공통 처리가 필요했다. 

 

Interceptor가 적용되어 있었기 때문에 Interceptor에서 호출 정보를 저장하면 되겠지 했는데 문제가 있었으니,, 

인터셉터에서 바디를 꺼내서 저장 후 내부에서 다시 읽으려고 하면 에러가 팡팡 터졌다는 것

찾아보니 HttpServletRequest의 InputStream 은 한번 읽으면 다시 못 읽기 때문에 다시 읽으려고 하면 에러가 난다고 한다..

 

구글링의 결과로 아래와 같이 해결하였다. 좋은 방법인지는 모르지만 굴러갔기 때문에 우선은 남겨둔다.

우선 filter와 spring boot의 interceptor에 대해 알아보려면 요기를 참고 요기!

 

1. HttpServletRequestWrapper를 상속받은 Wrapper 클래스를 작성한다

 

public class RequestBodyWrapper extends HttpServletRequestWrapper {
	class ServletInputStreamImpl extends ServletInputStream {
		private InputStream inputStream;
        
		public ServletInputStreamImpl(final InputStream inputStream){
			this.inputStream = inputStream;
		}

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

		@Override
		public boolean isReady() {
			return false;
		}
        
		@Override
		public int read() throws IOException {
			return this.inputStream.read();
		}

		@Override
		public int read(final byte[] b) throws IOException {
			return this.inputStream.read(b);
		}
	}
    
	private byte[] bytes;
	private String requestBody;

	public RequestBodyWrapper(final HttpServletRequest request) throws IOException{
		super(request);

		InputStream in = super.getInputStream();
		bytes = IOUtils.toByteArray(in);
		requestBody = new String(this.bytes);
	}

	@Override
	public ServletInputStream getInputStream() throws IOException {
		final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.bytes);
		return new ServletInputStreamImpl(byteArrayInputStream);
	}

	public String getRequestBody() {
		return this.requestBody;
	}
}

 

2. doFilter 메소드에서 request를 위에서 작성한 Wrapper로 감싸고 attribute로 set 한다.

 

public class RequestBodyFilter implements Filter {
	@Override
	public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
		try {
			RequestBodyWrapper wrapper = new RequestBodyWrapper((HttpServletRequest)request);
			wrapper.setAttribute("requestBody", wrapper.getRequestBody());
			chain.doFilter(wrapper, response);
		} catch (Exception e) {
			chain.doFilter(request, response);
		}
	}
}

 

3. interceptor의 afterCompletion 메소드에서 호출 Url, 요청 파라미터, 요청 body를 꺼내서 저장하도록 구현한다.

 

// 관리자 권한 체크 Interceptor 내부

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
	String url = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
    
	Map<String, String> pathVariables = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES)ATTRIBUTE);
	String pathParam = "";
    
	if(pathVariables.size() > 0) {
		JSONObject json = new JSONObject();
		String key = "";
		Object value = null;
		for (Map.Entry<String, String> entry : pathVariables.entrySet()) {
			key = entry.getKey();
			value = entry.getValue();
			json.put(key, value);
		}
		pathParam = json.toString();
	}
    
	String bodyParam = request.getAttribute("requestBody").toString().length() > 0 ? request.getAttribute("requestBody").toString().length() > 0 : null;

	// 위의 url, pathParam, bodyParam 으로 히스토리 저장
}

되긴 된다.

 

 

참고 자료들 (감사합니다👏)

https://stuffdrawers.tistory.com/9

https://singun.github.io/2017/02/04/logging-requestbody/

'개발기록 > Spring Boot' 카테고리의 다른 글

Mybatis에서 반복되는 Update문 한번에 날리기  (0) 2023.01.21

댓글