깃허브 링크 : 클릭
servlet 패키지의 Filter interface를 구현하여 필터를 구현할 수 있다.
@Slf4j
public class FirstFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("FirstFilter start");
chain.doFilter(request, response);
log.info("FirstFilter end");
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
설정 파일을 만들어 빈으로 등록한다.
@Configuration
public class CustomFilterRegister {
@Bean
public FilterRegistrationBean firstFilter() {
FilterRegistrationBean firstFilter = new FilterRegistrationBean(new FirstFilter());
ArrayList<String> urls = new ArrayList<>(); urls.add("/");
firstFilter.setUrlPatterns(urls);
firstFilter.setOrder(0);
return firstFilter;
}
}
컨트롤러를 등록하고 테스트 코드를 작성해서 확인해보자
@RestController
@Slf4j
public class HelloController {
@GetMapping("/")
public String hello() {
log.info("/ 진입");
return "Hello";
}
}
@SpringBootTest
@AutoConfigureMockMvc
class HelloControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void enter_root() throws Exception {
//when, then
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
컨트롤러, 필터를 두 개씩 더 추가해서 테스트해보자
@Slf4j
public class SecondFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("SecondFilter start");
chain.doFilter(request, response);
log.info("SecondFilter end");
}
}
@Slf4j
public class ThirdFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("ThirdFilter start");
chain.doFilter(req, response);
log.info("ThirdFilter end");
}
}
@Bean
public FilterRegistrationBean secondFilter() {
FilterRegistrationBean secondFilter = new FilterRegistrationBean(new SecondFilter());
ArrayList<String> urls = new ArrayList<>(); urls.add("/"); urls.add("/second-filter");
secondFilter.setUrlPatterns(urls);
secondFilter.setOrder(1);
return secondFilter;
}
@Bean
public FilterRegistrationBean thirdFilter() {
FilterRegistrationBean thirdFilter = new FilterRegistrationBean(new ThirdFilter());
ArrayList<String> urls = new ArrayList<>(); urls.add("/"); urls.add("/third-filter");
thirdFilter.setUrlPatterns(urls);
thirdFilter.setOrder(2);
return thirdFilter;
}
@GetMapping("/second-filter")
public String hello2() {
log.info("/second-filter 진입");
return "Hello";
}
@GetMapping("/third-filter")
public String hello3() {
log.info("/third-filter 진입");
return "Hello";
}
@SpringBootTest
@AutoConfigureMockMvc
class HelloControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void enter_root() throws Exception {
//when, then
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isOk());
mockMvc.perform(MockMvcRequestBuilders.get("/second-filter"))
.andExpect(MockMvcResultMatchers.status().isOk());
mockMvc.perform(MockMvcRequestBuilders.get("/third-filter"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
- | first | second | third |
---|---|---|---|
우선순위 | 0 | 1 | 2 |
url patterns | / | /, /second-filter | /, /third-filter |
만약 사용자의 요청 body를 읽으려고 할 때 아래와 같이 소스 코드를 짜게 될 것이다.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
BufferedReader reader = req.getReader();
String line = "";
int lineCnt = 1;
while ((line = reader.readLine()) != null) {
log.info(lineCnt + " " + line);
lineCnt += 1;
}
chain.doFilter(req, response);
}
다음과 같은 오류를 만나게 되는데 getReader()
또는 getInputStream()
을 이미 사용했다는 에러가 뜨게 된다. 이유는 HttpServletRequset의 위 두 함수는 한 번만 읽을 수 있게 구현이 되어있기 때문에 Spring Context에서 Json 데이터로 Convert할 때 에러가 발생하게 된다.
이 문제를 해결하기 위해서 Wrapper 클래스를 이용해 InputStream을 읽어서 내용을 저장해놓고 계속해서 쓰는 방식을 이용해야 한다.
ContentCachingRequestWrapper의 래퍼 클래스를 이용하여 값을 캐시에 저장하여 사용하여 문제를 해결할 수 있다.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(req);
System.out.println(new String(requestWrapper.getContentAsByteArray()));
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(res);
chain.doFilter(requestWrapper, responseWrapper);
responseWrapper.copyBodyToResponse();
}