API서버 RequestBody 여러번 읽는방법
2023. 2. 7. 11:08ㆍ개인노트
반응형
기본적으로 Request내에 있는 inputStream의 경우 1회만 읽을 수 있다. 로그성 데이터를 남기기 위해서 한번 읽어버리면 이후 로직에서 데이터를 읽지못하는 경우가 발생한다.
예를들어 API서버 만들었는데 요청과 응답에 대한 로그성 데이터를 남기고 싶었다.
그런경우 Interceptor에서 해당 바디에 있는 데이터를 읽어와서 String으로 변환하여 로그 데이터를 남겼고 그 이후 컨트롤러로 진입하였을때 바디에 있는 정보를 이미 앞에서 읽었기 때문에 데이터가 없는 경우가 발생했다.
아래와 같이 인터셉터에서 요청 바디와, 요청 파람, 응답 바디 등을 받아서 로그성 데이터를 남기고 싶다면 추가적으로 필터를 추가하고 요청에 대한 Cache처리가 필요했다.
public class TestInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String payload = getReqBody(request);
String requestParam = requestToParamString(request);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
/** 응답(String) */
String responseBody = getResponseBody(response);
}
/**
* Response 응답정보를 저장하기위해 String으로 가공처리
* @param response
* @return String
* @throws IOException
*/
private String getResponseBody(final HttpServletResponse response) throws IOException {
String payload = null;
ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (wrapper != null) {
wrapper.setCharacterEncoding("UTF-8");
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
}
}
return null == payload ? "" : payload;
}
public static String requestToParamString(HttpServletRequest request, String prefix) {
String returnString = "";
Enumeration<String> en = request.getParameterNames();
String param = null;
String value = null;
while(en.hasMoreElements()) {
param = en.nextElement();
value = request.getParameter(param);
if (!StringUtils.contains(param, "Pwd")) {
if (StringUtils.isNotBlank(value)) {
if (StringUtils.isBlank(prefix)) {
returnString += param + "=" + value + "||";
} else {
if (StringUtils.startsWith(param, prefix)) {
returnString += param + "=" + value + "||";
}
}
}
}
}
return returnString;
}
}
캐싱에 사용되는 클래스 생성
CachedBodyHttpServletRequest
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private byte[] cachedBody;
private Map<String, String[]> parameterMap;
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
parameterMap = request.getParameterMap();
InputStream requestInputStream = request.getInputStream();
this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
}
@Override
public BufferedReader getReader() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new CachedBodyServletInputStream(this.cachedBody);
}
@Override
public Map<String, String[]> getParameterMap() {
return this.parameterMap;
}
}
CachedBodyServletInputStream
public class CachedBodyServletInputStream extends ServletInputStream {
private InputStream cachedBodyInputStream;
public CachedBodyServletInputStream(byte[] cachedBody) {
this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
}
@Override
public boolean isFinished() {
try {
return cachedBodyInputStream.available() == 0;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() throws IOException {
return cachedBodyInputStream.read();
}
}
요청에 대해 한번만 적용되는 필터 생성
/**
* 응답시 ResData를 로그로 남기기위해 사용하는 Wrapping Filter
*/
@Slf4j
public class ServletWrapperFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {
// reponse를 ContentCachingResponseWrapper 객체로 래핑
ContentCachingResponseWrapper httpServletResponse = new ContentCachingResponseWrapper(response);
log.debug("ServletWrapperFilter 필터통과확인");
CachedBodyHttpServletRequest cachedBodyHttpServletRequest = new CachedBodyHttpServletRequest(request);
filterChain.doFilter(cachedBodyHttpServletRequest, httpServletResponse);
/** interceptor에서 throws 해서 응답하는 경우 body를 복사처리가 필요함 */
ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(httpServletResponse, ContentCachingResponseWrapper.class);
wrapper.copyBodyToResponse();
}
}
마지막으로 스프링에서 사용하는 필터에 등록을 해주면된다.
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean filterBean(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean(new ServletWrapperFilter());
registrationBean.setOrder(1); //필터 여러개 적용 시 순번
registrationBean.addUrlPatterns("/*"); //전체 URL 포함
return registrationBean;
}
}
반응형
'개인노트' 카테고리의 다른 글
개발자가 되기 까지...!(2021 ~ 2023년)(개발자 회고록 2) (0) | 2023.03.02 |
---|---|
개발자가 되기 까지...!(2021 ~ 2023년)(개발자 회고록 1) (1) | 2023.02.28 |
NPM 사용 할 때 고려되어야 하는 사항 (0) | 2023.01.12 |
개발자 채용 과제 수정해보기 (0) | 2021.10.28 |
(인터셉터+어노테이션)로그인 체크하기(session) (0) | 2021.10.19 |