์ง๋ ํ๋ก์ ํธ์์ ์๋ฒ ์ด์ ์ค ์๊ฐ๋ณด๋ค ๋ง์ ๋ถ๋ถ์์ ์์ธ๊ฐ ๋ฐ์ํ๋ค. ์์ธ๊ฐ ๋ฐ์ํ์์๋ ์ด ์ฌ์ค์ ๋ชจ๋ฅธ์ฑ ๋ฐฉ์นํ๊ฒ ๋๋ ์ผ์ด ์๊ธฐ๊ธฐ ์์ํ๊ณ , ์์ธ๊ฐ ๋ฐ์ํ ๋๋ง๋ค EC2 ์ธ์คํด์ค์ ์ ๊ทผํด ์ง์ ๋ก๊ทธ๋ฅผ ํ์ธํด์ผํ๋ค๋ ๋ฒ๊ฑฐ๋ฌ์์ด ์์๋ค. ์ด๋ฒ ํ๋ก์ ํธ์์ ์ด๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด ์๋ฒ์ ์ฌ๋์ ์ฐ๋ํ๊ณ AOP์์ RequestBody์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด ์๋ฌ ๋ด์ฉ์ ํ์ฑํ ํ ์ฌ๋์ผ๋ก ์ ์กํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ค. ์ด ๊ณผ์ ์์ RequestBody๋ฅผ ์ฌ๋ฌ ๋ฒ ์ฝ์ด์ผ๋ง ํ๊ณ ๊ทธ ๋ฐฉ๋ฒ์ ์ด ๊ธ์ ์จ๋ณด๊ณ ์ ํ๋ค.
โ๏ธ HttpServletRequest Body
HttpServletRequest์ Body ๋ฐ์ดํฐ๋ getInputStream() ๋๋ getReader()๋ฅผ ํตํด ์ ๊ทผํ ์ ์๋ค. ํ์ง๋ง ์ด๋ ์คํธ๋ฆผ ํํ๋ก ์ ์ฅ๋์ด ์๊ธฐ ๋๋ฌธ์ ์ผํ์ฑ ์๋ชจ ๋ฐ์ดํฐ์ด๋ค. ๋ง์ฝ ์ธํฐ์ ํฐ๋ ํํฐ์์ ์ถ๊ฐ ์์ ์ ์ํด ๋ฐ์ดํฐ๋ฅผ ๋จผ์ ์ฝ๋๋ค๋ฉด, RequestBody๋ฅผ ์ฒ๋ฆฌํ๋ ์ปจ๋ฒํฐ์ธ Jackson2HttpMessageConverter๋ฅผ ํตํด Json ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ธ๋ฉํ ๋ ์๋์ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค. ๋จผ์ ๋ฐ์ธ๋ฉ์ ํ ํ ์ถ๊ฐ ์์ ์ ์งํํ๋ ๊ฒฝ์ฐ์๋ ๋ง์ฐฌ๊ฐ์ง์ด๋ค.
java.lang.IllegalStateException: getReader() has already been called for this request
org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Stream closed; nested exception is java.io.IOException: Stream closed
โ๏ธ ํด๊ฒฐ๋ฐฉ๋ฒ
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ContentCachingRequestWrapper๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
Spring์์ ์ ๊ณตํ๋ ContentCachingRequestWrapper๋ Java Servlet API์ HttpServletRequestWrapper๋ฅผ ์์๋ฐ์ ๊ตฌํ๋์ด์๋ค. ์ด๋ ํ ๋ฒ๋ง ์ฝ์ ์ ์๋ Stream ๋ฐ์ดํฐ์ธ RequestBody์ ๋ฐ์ดํฐ๋ฅผ ์บ์ฑํ์ฌ ์ฌ๋ฌ ๋ฒ ์ฝ์ ์ ์๋๋ก ํ๊ธฐ ์ํด ๋ง๋ค์ด์ง ํด๋์ค์ด๋ค.
์ด๋ฅผ ์ฌ์ฉํ์ฌ ํํฐ์์ ๋ํ ํ ๋ฐ๋ก ๋ก๊น ์์ ์ ํ๊ณ ์ ํ๋ค๋ฉด ContentCachingRequestWrapper ํด๋์ค๋ฅผ ์ง์ ์ฌ์ฉํ์ฌ getContentAsByteArray๋ฅผ ํตํด ์ฆ์ ์ฝ์ ์ ์๋ค. ํ์ง๋ง ๋์ ๊ฒฝ์ฐ, ๋ฐ์ธ๋ฉ ์์ ์ ๊ฑฐ์น ํ ์คํ๋๋ ExceptionHandler๋ฅผ ํ๊ฒ์ผ๋ก ํ๋ AOP์์ Body ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด์ผ ํ๋ค. ํ๊ฒ ๋ฉ์๋์์ HttpServletRequest๋ฅผ ์ธ์๋ก ๋ฐ๋ ๊ฒฝ์ฐ๋ฅผ ์ ์ธํ๋ฉด AOP์์ Request๋ฅผ ์ง์ ์ ์ผ๋ก ๋ฐ์ ์ ์๋ ๋ฐฉ๋ฒ์ด ์๊ธฐ์ ๊ฐ์ ์ ์ธ ์ ๊ทผ์ด ํ์ํ๋ค.
ํฌ๊ฒ ๋๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๊ณ ๋๋ ๊ทธ ์ค ์ฒซ ๋ฒ์งธ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ค.
1๏ธโฃ ๋ณ๋์ ํด๋์ค - Request Scope ๋น
์์ฒญ์ด ๋ค์ด์ค๊ณ ๋๊ฐ๊ธฐ ์ ๊น์ง ๋ํผ Request ๊ฐ์ฒด๋ฅผ ์ ์ฅํ๊ณ ์์ ๋ณ๋์ ํด๋์ค ์ด์ฉํ๋ ๋ฐฉ๋ฒ์ด๋ค.
๋จผ์ RequestStorage๋ผ๋ ํด๋์ค๋ฅผ ์ ์ธํด์ค๋ค. ์ดํ ๋น์ผ๋ก ๋ฑ๋กํ๋๋ฐ ์ด ๋ ๊ฐ ์์ฒญ๊ณผ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ฐ์ด ํ๊ธฐ ์ํ Request Scope์ ProxyMode๋ฅผ ์ ์ฉํ๋ค. ์ฌ๊ธฐ์ Proxy mode๋ฅผ ์ฌ์ฉํ๋ ์ด์ ๋ Request Scope๋ Request๊ฐ ๋ค์ด์ฌ ๋ ์ฃผ์ ์ด ์ด๋ฃจ์ด์ง๊ธฐ ๋๋ฌธ์ ํ๋ก์ ๋ชจ๋๋ฅผ ์ค์ ํด์ฃผ์ง ์์ผ๋ฉด Application Context๋ฅผ ๋ก๋ฉํ๋ ์์ ์ ์๋ฌ๊ฐ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ด๋ค.
์๋์ ์ฌ์ฉ ์ฝ๋์ ๊ฐ์ด ํํฐ์์ RequestStorage์ ์ ์ฅํ๋ฉด ์์ฒญ์ด ์ฒ๋ฆฌ๋๋ ๋์ ๋ชจ๋ ๊ณณ์์ get ๋ฉ์๋๋ฅผ ํตํด Request ๊ฐ์ฒด๋ฅผ ํธํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
๐ ์ค์ ์ฝ๋
public class RequestStorage {
private ContentCachingRequestWrapper request;
public void set(ContentCachingRequestWrapper request) {
this.request = request;
}
public ContentCachingRequestWrapper get() {
return request;
}
}
@Configuration
public class WebConfig{
@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public RequestStorage requestStorage() {
return new RequestStorage();
}
}
๐ ์ฌ์ฉ ์ฝ๋
@Component
public class ServletWrappingFilter extends OncePerRequestFilter {
private final RequestStorage requestStorage;
public ServletWrappingFilter(RequestStorage requestStorage) {
this.requestStorage = requestStorage;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
requestStorage.set(wrappedRequest);
filterChain.doFilter(wrappedRequest, response);
}
}
@Aspect
@Component
@RequiredArgsConstructor
public class SlackMessageAop {
private final RequestStorage requestStorage;
@Before("@annotation(###.SlackAlarm)")
public void slackAlarmAspect(JoinPoint joinPoint) {
ContentCachingRequestWrapper request = requestStorage.get();
}
}
2๏ธโฃ RequestContextHolder
์คํ๋ง์ด ์ ๊ณตํ๋ RequestContextHolder๋ฅผ ํตํด ํ์ฌ ์ค๋ ๋์ ๋ฐ์ธ๋ฉ๋ HttpServletRequest๋ฅผ ์ป์ ์ ์๋ค.
์ด ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ค๋ฉด ํํฐ์์๋ ContentCachedRequestWrapper ํด๋์ค๋ฅผ ํตํด ๊ธฐ์กด request๋ฅผ ๋ํํ๋ ๊ณผ์ ๋ง ์ถ๊ฐํ๋ฉด ๋๊ณ ์๋ ์ฝ๋์ ๊ฐ์ด ์ฌ์ฉํ ์ ์๋ค.
๐ ์ฌ์ฉ ์ฝ๋
@Aspect
@Component
public class SlackMessageAspect {
@Before("@annotation(###.SlackAlarm)")
public void slackAlarmAspect(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.currentRequestAttributes()).getRequest();
}
}
โ๏ธ ๊ฒฐ๋ก
RequestContextHolder๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ๊ตฌํ์ด ๋จ์ํ๊ณ ์ง๊ด์ ์ด๋ค. ํ์ง๋ง RequestStorage๋ฅผ ์ฌ์ฉํ๋ฉด ์์ฒญ๊ณผ ๊ด๋ จ๋ ๋ค์ํ ์ ๋ณด๋ฅผ ์ ์ฐํ๊ฒ ์ ์ฅํ๊ณ ๊ด๋ฆฌํ ์ ์๋ค. ์๋ฅผ ๋ค์ด, ํน์ ์์ฒญ์์๋ง ํ์ํ ์ ๋ณด๋ฅผ ์ ์ฅํ๊ฑฐ๋, ์์ฒญ ์ฒ๋ฆฌ ๊ณผ์ ์์ ๋์ ์ผ๋ก ์ ๋ณด๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ๋ณ๊ฒฝํ๋ ๊ฒ์ด ๊ฐ๋ฅํด์ง๋ค. ์ด๋ฌํ ์ ๋ค์ ๊ณ ๋ คํ๋ค๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ํฉ์ ๋ง๊ฒ ์ ์ ํ ์ฌ์ฉํ ์ ์์ ๊ฒ์ด๋ค.