SPRING

[Spring] RequestBody ์—ฌ๋Ÿฌ ๋ฒˆ ์ฝ๊ธฐ

YeopJu 2024. 3. 6. 00:38
๋ฐ˜์‘ํ˜•

์ง€๋‚œ ํ”„๋กœ์ ํŠธ์—์„œ ์„œ๋ฒ„ ์šด์˜ ์ค‘ ์ƒ๊ฐ๋ณด๋‹ค ๋งŽ์€ ๋ถ€๋ถ„์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Œ์—๋„ ์ด ์‚ฌ์‹ค์„ ๋ชจ๋ฅธ์ฑ„ ๋ฐฉ์น˜ํ•˜๊ฒŒ ๋˜๋Š” ์ผ์ด ์ƒ๊ธฐ๊ธฐ ์‹œ์ž‘ํ–ˆ๊ณ , ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ๋งˆ๋‹ค 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๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์š”์ฒญ๊ณผ ๊ด€๋ จ๋œ ๋‹ค์–‘ํ•œ ์ •๋ณด๋ฅผ ์œ ์—ฐํ•˜๊ฒŒ ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํŠน์ • ์š”์ฒญ์—์„œ๋งŒ ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ฑฐ๋‚˜, ์š”์ฒญ ์ฒ˜๋ฆฌ ๊ณผ์ •์—์„œ ๋™์ ์œผ๋กœ ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค. ์ด๋Ÿฌํ•œ ์ ๋“ค์„ ๊ณ ๋ คํ•œ๋‹ค๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํ™ฉ์— ๋งž๊ฒŒ ์ ์ ˆํžˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

 

 

๋ฐ˜์‘ํ˜•