개발 요구사항 중 페이지 접근시 전처리에서 접근 권한을 확인하고 후처리로 Response 객체에 사용자가 접근할 수 있는 메뉴를 같이 내려달라는 요구를 받았습니다. 해당 요구사항을 Spring 에서 제공하는 Interceptor 로 가능한지 검토해 달라는 요청을 받았습니다.
🤔 Interceptor 에서 데이터 변경이 가능할까?
스프링에서 제공하는 Interceptor 는 아래 코드와 같습니다. 요청을 처리하는 Controller 전에 preHandler 를 거치기 때문에 사용자 접근을 제어해달라는 요구사항은 어렵지 않았습니다.
마찬가지로 요청이 끝난 후에는 postHandler 를 거쳐 나가는데 그때 응답 객체를 수정할 수 있지 않을까? 마침, HttpServletRequest 객체를 매게변수로 받아서 조작이 가능할 것 같은 그런 기분이 들지만, 결론은 안됩니다. (두둥탁 🥁)
응답 객체를 수정할 수 없는 이유를 이해하기 위해서는 먼저, 응답 객체에 값이 어떻게 쓰여지는지를 이해할 필요가 있습니다.
@ResponseBody가 붙은 컨트롤러는 HttpMessageConverter 를 사용하는데, 이 컨버터는 응답 객체의 출력 스트림 을 이용해 데이터를 모두 쓴 후, flush 메서드를 호출합니다. 이때 응답 객체는 commit 상태가 되고 클라이언트로 전송합니다.
응답 객체가 commit 되면 응답 객체에서 제공하는 출력스트림을 close 시켜 해당 스트림을 사용할 수 없게 합니다. 그래서 응답 객체가 commit 된 이후에는 응답 내용을 수정하는 것은 불가능합니다. 결과적으로 commit 된 응답객체를 받는 인터셉터는 데이터 수정이 불가능합니다.
=============== Server =============== Request URI = /empty Response is committed = false
=============== Client =============== "/empty" 응답: "값이 바뀔까요?"# 원래는 응답 값이 없어야 합니다.
❗️ 응답 객체에 값을 가져오는 것도 안된다
관련 내용을 정리하다가 알게 된 것은 응답 객체에 값을 넣은 후 Body 에 넣은 값을 가져와 인터셉터에서 출력로그로 찍으려고 했는데, HttpServletResponse 는 출력 스트림만 제공하기 때문에 값을 꺼내올 방법이 없습니다.
응답 객체 값을 별도로 저장하는 변수를 만들어서 응답 객체에 값을 쓸때 해당 변수에도 값을 같이 써준 후 값을 읽어와야 합니다.
서블릿에서는 기존 응답 객체를 감싸는 HttpServletResponseWrapper 클래스를 제공합니다. 해당 클래스는 HttpServletResponse 를 구현한 클래스라 기존 응답 객체와 동일한 함수를 사용할 수 있고 기존 응답 객체 기능에 원하는 부가 기능을 추가해 사용할 수 있습니다.
HttpServletResponseWrapper - 응답 래핑 클래스
HttpServletResponseWrapper 클래스를 이용해 래핑 클래스를 만들어 줍니다. 해당 클래스에서는 응답 객체에 값을 쓸때 버퍼에도 값을 써주는 기능을 추가하고, 버퍼에 저장된 값을 읽어오는 새로운 함수를 추가해줍니다.