[스프링 디버깅 해보기] 스프링의 기본 오류 페이지 Whitelabel Error Page는 어떻게 생성될까?

이호석·2023년 4월 7일
1
post-thumbnail

진행하고 있는 무백스 스터디 이슈에서 스터디원분의 질문으로 인해 디버깅을 스프링의 기본 오류 페이지에 대한 생성과정을 직접 디버깅하여 정리해봤습니다.

아래 글 내용은 https://github.com/Invincible-Backend-Study/toby-spring-boot/issues/6 이슈에서도 볼 수 있습니다!

혹시라도 잘못된 내용이 있거나, 수정해야 하는 부분이 있다면 댓글로 알려주시면 감사하겠습니다!!!


✅ 테스트 환경 세팅

테스트용 Controller
존재하지 않는 viewName을 반환한다.



✅ Springframwork 디버깅 과정

DispatcherServlet은 가장먼저 “/”로 들어온 요청에 대해 처리를 시작합니다. 이때 요청 URI는 “/”가 되고, DispatcherType은 “REQUEST”가 됩니다.



TestController의 test()메소드가 해당 URI와 매핑되어있으므로 해당 컨트롤러를 실행하고 반환값으로 “dd”라는 viewName을 받게 됩니다.
해당 정보를 가지고 ModelAndView를 생성하고 내부 View 정보를 통해 RequestDispatcher가 forwarding 작업을 합니다.



포워딩 됐으므로 dispatcherType은 FORWARD이며, “/dd” 라는 리소스를 DispatcherServlet이 내부적으로 호출하게 됩니다.
다시 DispatcherServlet을 통해 내부적인 doService() → doDispatch 작업을 수행하게 됩니다.



DispatcherServlet.doDispatch()에서 HandlerAdapter를 찾아 handle()하게 되는데 이는 어댑터(HttpRequestHandlerAdapter)를 통해 찾아온 핸들러를 실행시키게 됩니다.



이때 정적 리소스를 처리하는 ResourceHttpRequestHandler가 선택되고 “/dd”에 대한 실제 리소스를 가져오지만 존재하지 않으므로 null값이 반환됩니다.
resource가 null이므로 Not Found 에러를 Response 객체에 세팅합니다. 이때 sendError내부 코드에서 setError를 통해 0 → 1로 에러 플래그를 세팅합니다.



사용자의 “/” 요청이 마무리되고 WAS까지 404 Error가 전파됩니다. 그리고 Tomcat에서 에러가 존재하는지 묻게 됩니다.
이때 아까 setError를 통해 1로 세팅한 에러값을 통해 에러가 있음을 WAS가 인지하게 됩니다.



이후 404 Not Found에 대한 에러 페이지를 찾게 됩니다. 따로 생성한 에러페이지는 존재하지 않으므로 WAS에서 생성하는 디폴트 에러 페이지를 찾게 됩니다.



/error 로케이션을 받아오고 이후 각종 에러에 대한 세팅을 해줍니다(DispatcherType을 ERROR 세팅 등)
이후 /error에 대해 WAS 내부에서 포워딩 작업을 실행합니다.



포워딩 작업이 시작됐으므로 다시 필터를 거치고 DispatcherServlet을 호출하게 됩니다.



“/error”로 requestURI가 선택되어 있는데 해당 uri는 BasicErrorController의 @RequestMapping과 일치합니다.

또한 Accept headertext/html이므로 BasicErrorController#errorHtml 핸들러 메소드가 선택됩니다.
RequestMappingHandlerAdapter는 이런 @RequestMapping이 되어있는 핸들러를 지원하므로(HandlerAdapter#supports를 지원함) 어댑터로 선택됩니다.

(이 이미지는 좌측 하단의 main 쓰레드내 메소드 스택을 통해 흐름을 이해하기 편하도록 첨부했습니다.)



이후 실제 BasicErrorController#errorHtml을 실행합니다.



errorHtml의 내부 호출 메소드인 resolveErrorView에서는DefaultErrorViewResolver#resolveErrorView를 호출해 에러페이지에 대한 view resolving 수행합니다.



DefaultErrorViewResolver#resoveErrorView 에서는 두번째 사진의 staticLocations과 같은 순서로 view를 찾습니다.
하지만 일치하는 View가 없으므로 null을 반환하게 됩니다. BasicErrrorController#errorHtml에서는 반환된 ModelAndView 객체가 null값이라면 “error”라는 viewName을 가진 ModelAndView 객체를 생성해 반환합니다.



반환된 ModelAndView 객체를 처리하가 위해 ModelAndView 응답 value를 처리하는 핸들러로 ModelAndViewMethodReturnValueHandler가 선택됩니다.
이곳에서 뷰 이름 세팅, 응답 코드 세팅 , 리다이렉트에 대한 처리 등을 수행합니다.



이후 BasicErrorController#errorHtml을 통해 생성된 ModelAndView 객체가 DispatcherServlet으로 반환됩니다.
받아온 ModelAndView 객체 내부의 View를 reolving하는데 이때 ContentNegotiatingViewReolver가 선택됩니다.



ContentNegotiatingViewResolver#resolveViewName 에서는 getCandiateViews()를 통해 후보가 될 수 있는 View를 선택하고 이후 getBestView()를 통해 최적의 View를 선택합니다.

후보군 View로는 ErrorMvcAutoConfiguration$StaticView와 정적 리소스를 처리하는 InternalResourceView 2개가 선택된다.



best view로는 ErrorMvcAutoConfigurationStaticView가더높은우선순위를가지고있고,text/html에대해ErrorMvcAutoConfigurationStaticView가 더 높은 우선순위를 가지고 있고, text/html에 대해 ErrorMvcAutoConfigurationStaticView가 처리할 수 있으므로 즉시 해당 뷰를 반환합니다.



이후 DispatcherServlet은 받아온 ErrorMvcAutoConfiguration$StaticView를 통해 뷰 렌더링을 진행합니다.
실제 StaticView의 render 메소드를 살펴보면 우리가 익히 볼 수 있는 Whitelabel Error Page를 볼 수 있습니다. 여기서 이전에 정해진 error 내용과 status code를 결합해 응답 HTML을 완성합니다.



응답 처리를 마치고 브라우저에는 404 Whitelabel Error Page가 응답됩니다.



후기

스프링 프레임워크의 코드를 디버깅해보면서 느낀것지만 상당히 양이 방대하며, 사소한 것들 하나하나 정말 많은 코드들이 존재합니다.
모든 코드에 대한 설명을 해석하고 적기에는 굉장히 양이 많고, 시간적 압박이 컸으므로.. (사실 이것만 해도 하루 종일 디버깅을 했습니다.. ㅎㅎ) 최대한 핵심이 되는 내용을 담으려고 노력했습니다.

이전 자바 웹 프로그래밍 Next Step 책 스터디를 통해 Spring MVC의 구조에 대해 꽤 알게 됐다고 생각하고 자신있게 디버깅을 시도했습니다만,
예상보다 방대한 양의 코드와 아직 부족한 디버깅 실력으로 코드를 이해하는데 꽤 많은 시간이 걸렸습니다.

그래도 이렇게 디버깅을 통해 실제 프레임워크를 분석해보면서 느낀건 어떤 코드를 보면 내부 구조를 반드시 전부 해석하고 이해해야했던 과거와 달리 직관적으로 해석할 수 있는 능력(메소드 이름, 클래스 명, 변수명 등을 통해)이 많이 향상된 것 같습니다!

profile
꾸준함이 주는 변화를 믿습니다.

0개의 댓글