Spring interceptor에서 Response 수정하기

Jeongkuk Seo
sjk5766
Published in
9 min readFeb 27, 2023

how to modify response in spring interceptor? 와 같은 키워드를 구글에 검색할 누군가를 위해 최근에 알게 된 내용들을 정리하겠다.

결론부터 말하면 RestController를 사용하거나 Controller + ResponseBody 어노테이션을 사용할 때 dto, 엔티티, String, long 등을 반환하게 되면 Interceptor에서 response를 꺼내 수정할 수 없다.

가령 아래와 같이 RestController 어노테이션이 선언된 User 컨트롤러가 제공하는 API는 모두 Interceptor에서 값을 수정할 수 없다. 여기서 중요한 건 controller의 return type이다.

@RestController
@RequestMapping("/users")
public class UserController {
// UserService 생성자 주입받는 코드

@GetMapping("/string")
public String returnString() {
return "AA";
}

@GetMapping("/boolean")
public boolean returnBoolean() {
return true;
}

@GetMapping("/user")
public User returnUser() {
return new User("email", "password", "01012331233", true);
}

@GetMapping()
public List<UserResponse> findAll() {
return userService.findAll();
}
}

반대로 Interceptor에서 response 수정이 가능한 return type을 가진 형태는 아래와 같다. 보면 void나 Null, ResponseEntity<Void>와 같은 리턴 타입을 가지고 있다.

@RestController
@RequestMapping("/users")
public class UserController {
// UserService 생성자 주입받는 코드

@GetMapping("/null")
public Null returnNull() {
return null;
}

@GetMapping("/response-entity")
public ResponseEntity<Void> returnResponseEntityVoid() {
return ResponseEntity.ok().build();
}

@PostMapping()
public void signUp(@RequestBody UserRequest request) {
userService.signUp(request);
}
}

Interceptor를 통해 response를 조작하고 싶다면 Controller가 리턴한 뒤에 실행되는 postHandle을 생각하게 되는데 이 때 인자로 받는 HttpServletResponse 에는 isCommitted라는 메소드가 있다.

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("isCommitted: " + response.isCommitted());
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

HttpServletResponse 의 isCommited 메소드가 true 라면 이미 response data가 쓰여진 상태로 출력 stream을 통해 전송되었기 때문에 이 상태에서는 response의 데이터를 꺼내 수정해서 다시 넣을 수 없다.

ServletResponse.isCommited() checks if the response has been already committed to the client or not (Means the servlet output stream has been opened to writing the response content).

The committed response holds the HTTP Status and Headers and you can’t modify it.

Interceptor를 적용하고 위에서 값을 수정할 수 없는 API는 모두 isCommited가 true를 반환하고 값을 수정할 수 있는 API는 false를 반환한다. 이를 확인하기 위해 Interceptor를 등록해보자.

아래와 같이 WebMvcConfigurer를 구현하는 class에 Custom 인터셉터를 등록했다.

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomInterceptor());
}
}

response를 조작한다는 관점에서 관심사는 postHandle 메소드이기 때문에 이 메소드만 오버라이딩 해서 application을 실행해보자.

@Component
public class CustomInterceptor implements HandlerInterceptor {
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("isCommitted: " + response.isCommitted());
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
}

컨트롤러에 적당한 로깅을 남기고 테스트 해보면 맨 위에서 결론을 낸 것 처럼 반환 값이 없을 때 Interceptor에서 수정이 가능하다.

======return User Entity======
postHandle isCommitted: true

======return User Response======
postHandle isCommitted: true

======return Null======
postHandle isCommitted: false

======return ResponseEntity<Void>======
postHandle isCommitted: false

======signUp======
postHandle isCommitted: false

내 경우는 반환 값이 있을 때 이 데이터를 꺼내 조작하고 싶었기 때문에 이 시점에서 흥미를 잃었지만 만약 반환 값이 없는 경우(isCommitted이 false)에 데이터를 수정하고 싶다면 여러 방법 중의 하나로 아래와 같은 방식으로 수정할 수 있다.

@Component
public class CustomInterceptor implements HandlerInterceptor {
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
User user = new User("email", "password", "01012331233", true);

String newContent = new ObjectMapper().writeValueAsString(user);
response.setContentType("application/json");
response.setContentLength(newContent.length());
response.getOutputStream().write(newContent.getBytes());
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
}

그렇다면 이어지는 자연스러운 흐름은 리턴되는 데이터가 있을 때는 어떻게 response를 조작할 수 있을까? 우선 Filter를 통해 조작을 직접 해봤고 이 글은 바로 이어서 작성하도록 하겠다. 또한 ResponseBodyAdvice를 사용하면 응답 데이터를 수정할 수 있다고 하는데 아직 확인은 안 해봤다.

레퍼런스

--

--