Servlet은 개발자가 Http 요청 메시지를 편리하게 사용할 수 있도록 개발자 대신에 HTTP요청 메시지를 파싱한다.
그 파싱한 결과를 HttpServletRequest에 객체에 담아서 제공한다.
ServletRequest 인터페이스를 HttpServletRequest 가 상속 받는 구조
따라서 사용자의 요청은 http 프로토콜로 서버로 전송되며
전송된 요청 정보는 HttpServletRequest, HttpServletResponse 객체를 가지고 서블릿 컨테이너로 들어가서 HttpServlet클래스의 메소드들을 통하여 처리가 된다
Servlet의 정의
클라이언트의 요청을 처리하고, 그 결과를 반환하는
Servlet클래스의 구현 규칙을 지킨 자바 웹 프로그래밍 기술
요청된 Http reqeuset 는 그림과 같은 흐름을 가지고 처리가 된다.
Dispatcher Servlet 이란
디스패처 서블릿도 HttpServlet을 상속하는 Servlet이다. 가장 먼저 요청을 받고 적절하게 처리할 함수(컨트롤러)를 찾아서 정해주는 역할을 한다.
필터는 기본적으로 Dispatcher Servlet 이전에 수행이되며
Chain.doFilter() 이전 코드가 먼저 수행되고 서블릿이 실행된 이후에 그 이후 코드가 실행이 되는 로직입니다.
하지만 이렇게 서블릿이 수행되는 동안 다른 서블릿 요청이 있을 수 있으며
해당 서블릿에도 동일하게 필터가 수행됩니다.
즉 동일한 요청에서 filter가 반복적으로 호출되는 문제가 발생할 수 도 있고, 불필요한 메모리가 낭비될 수 도 있다. 이러한 문제를 해결하기 위한게 OncePerRequestFilter 이다.
해당 필터는 한 요청에 대해서 딱 한번만 호출된다.
// filter example
@Component
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException
{
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
System.out.println("Request URI is: " + req.getRequestURI());
chain.doFilter(request, response);
System.out.println("Response Status Code is: " + res.getStatus());
}
}
// OncePerRequestFilter
@Component
public class MyFilter extends OncePerRequestFilter {
@Override
public void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException
{
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
System.out.println("Request URI is: " + req.getRequestURI());
filterChain.doFilter(request, response);
System.out.println("Response Status Code is: " + res.getStatus());
}
}
Filter는 Spring framework와 무관하게 동작하며
진입하는 가장 최초 시점이고, 응답하는 가장 마지막 시점이기 때문에 logging 을 남기기에 적합하다.
주의해야할 점은 HttpServletrequest의 body 부분은
최초 1회를 읽고 난 뒤에는 읽을 수 없기 때문에 logging을 남길때 바로 읽으면 안되며 ContentCachingRequestWrappe를 사용해줘야한다.(getInputStream을 통해서 값을 읽으면 readStatred가 true 로 변경)
response 부분도 내부 서블릿 과정중에 변경될 수 도 있기 때문에
ContentCachingResponseWrapper로 감싸서 chain에 값을 넣어서 처리한다.
ps. spring boot 는 기본적으로 UTF-8 이며 contentCachingRequest는 최초에 셋팅 값이 없으면 ISO-8859-1이 설정된다, Sentry를 연동하는경우에 값을 안주면 UTF-8이 설정 안되면서 charset에 문제가 발생하는 케이스도 있었다.
getContentAsByteArray() 에 들어있는 값은 최초에 content wrapper로 감쌀때가 아니라 dofilter에 값이 전달되고 뒤쪽에서 읽어서 처리될때 값이 들어가는것이다.