HttpServletRequestWrapper, 필터에 @Component

213kky·2024년 10월 22일

문제 1


프로젝트 진행중 RecommendInterceptor를 사용할 일이 있었다.
인터셉터를 추가하여 사용 도중 아래와 같은 오류가 발생하게 되었다.

org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing

문제의 원인은 HttpServletRequest의 ServletInputStream을 통해 RequestBody를 String 형태로 읽어와야 하는데 한번이라도 HttpServletRequest에서 Stream형태로 RequestBody에 접근해 데이터를 가져갈경우 외부에서는 HttpServletRequest 저장되어 있었던 RequestBody에 접근할 수 없게되어 위 오류가 발생한다.

나는 RecommendInterceptor에서 HttpServletRequest를 이용해 body값을 읽어 사용하는 부분이 있는데 이 때문에 필터를 거처 컨트롤러단으로 넘어가게되면 body값을 읽지 못해 아래와 같은 오류가 발생하게 되었던 것이다.


해결 1


해결 방법으로 나는 HTTP Request Body 여러 번 사용하기 글을 참고하여 HttpServletRequestWrapper 클래스를 상속 받아 캐싱하는 방식으로 해결하였다.

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) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean isReady() {
        return true;
    }

    @Override
    public int read() throws IOException {
        return cachedBodyInputStream.read();
    }

    @Override
    public void setReadListener(ReadListener listener) {
    }
}

public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {

    private byte[] cachedBody;

    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        InputStream is = request.getInputStream();
        this.cachedBody = StreamUtils.copyToByteArray(is);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new CachedBodyServletInputStream(this.cachedBody);
    }

    @Override
    public BufferedReader getReader() throws IOException {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
        return new BufferedReader(new InputStreamReader(byteArrayInputStream));
    }
}

@Slf4j
@Component
public class ContentCachingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {

        log.info("ContentCachingFilter doFilterInternal Method");
        CachedBodyHttpServletRequest cachedBodyHttpServletRequest = new CachedBodyHttpServletRequest(request);
        filterChain.doFilter(cachedBodyHttpServletRequest, response);
    }
}

문제 2


ContentCachingFilter 클래스를 만들고 필터에 @Component 붙이면 전역적으로 모든 url에 필터가 적용되고 특정 url에 대해 따로 설정이 안된다.

해결 2


ContentCachingFilter의 @Component를 제거하고 설정 파일을 만들어 관리하도록 했다.

@Bean
    public FilterRegistrationBean<ContentCachingFilter> contentCachingFilter() {
        FilterRegistrationBean<ContentCachingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new ContentCachingFilter());
        registrationBean.addUrlPatterns("/api/recommends/thanked", "/api/recommends/likes");
        registrationBean.setOrder(Ordered.LOWEST_PRECEDENCE);
        return registrationBean;
    }

추가로 setOrder 부분은 따로 설정안하면 알아서 Integer.MAX_VALUE 상태 즉 우선순위에서 가장 뒤로 밀린다.


참고
HTTP Request Body 여러 번 사용하기

profile
since 2022

0개의 댓글