[BE] fix: 핸들러 메서드가 매핑되지 않고, 인터셉터가 적용되는 경로에 접근 시 예외 핸들링이 되지 않아 500 예외가 발생하는 버그 수정 (#829) #833
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
📌 관련 이슈
✨ PR 세부 내용
제목이 조금 길긴 한데, 제목 그대로 핸들러 메서드가 매핑되지 않고, 인터셉터가 적용되는 경로에 접근 시 예외 핸들링이 되지 않아 500 예외가 발생하는 버그를 수정했습니다.
해결 방법은 매우 간단한데, 그저 프로퍼티에 다음과 같은 설정을 추가하면 됩니다.
그저 이 설정만 추가하면 문제가 해결되니 왜 이 문제가 발생했는지 분석 결과, DispatcherServlet의 심연을 탐구하고 말았습니다..
우선 결론부터 말하자면 경로에 매핑된 핸들러가
null
또는HandlerMethod
일때만 ExceptionHandler로 예외 처리가 됩니다.그렇다는 말은 예외가 발생한 시점의 매핑된 핸들러가
null
이 아닌 경우인데, 당연하게HandlerMethod
는 아닙니다.해당 핸들러의 정체는 바로
ResourceHttpRequestHandler
인데, 이는 등록된 HandlerMapping 중SimpleUrlHandlerMapping
때문입니다.해당
SimpleUrlHandlerMapping
를 타고 들어가면 상속하고 있는AbstractUrlHandlerMapping
를 볼 수 있는데, 해당 클래스에 선언된 필드인pathPatternHandlerMap
으로 경로에 대해 패턴을 매칭시킨 뒤, 패턴에 맞으면ResourceHttpRequestHandler
를 반환합니다.바로 이 때문에 경로가
HandlerMethod
로 매핑되지 않아도,ResourceHttpRequestHandler
로 매핑되는 이유입니다.더 정확하게
pathPatternHandlerMap
의 값을 보면 다음과 같이/**
로 시작하는 패턴이 있기 때문에 무조건ResourceHttpRequestHandler
핸들러가 매핑될 수 밖에 없습니다. 😂이는
spring.web.resources.add-mappings: false
설정을 추가하여 기본으로 등록되는 PathPattern을 없앨 수 있습니다.이렇게 되면 정적 경로에 대한 자원이 매핑되지 않으므로,
static
폴더에 있는 파일을 제공할 수 없지만, 저희는 현재 정적 파일을 제공하고 있지 않기 때문에 문제가 되지 않습니다.그러면
HandlerMethod
에 등록된 경로가 아니면 핸들러가null
이 반환 되기에 404 응답이 내려옵니다.근데 문제는 이 방법만 사용해서는 404 응답을 커스텀 하기가 불가능 합니다. (Whitelabel Error Page가 나옵니다)
따라서
spring.mvc.throw-exception-if-no-handler-found: true
설정을 추가해, 예외가 발생되게 하여 ControllerAdvice에서 예외를 잡게하여 저희가 원하는 응답으로 커스텀 할 수 있습니다.(장문 주의)
여기까지만 보면, 그렇구나 할 수 있겠지만 매핑된 핸들러가
null
인데, 왜ControllerAdvice
에서 예외가 잡히는지 궁금하여 심연을 더 파보았습니다.예외 처리는 DispatcherServlet의
processHandlerException()
메서드에서 처리되는데,List<HandlerExceptionResolver>
필드를 반복문을 돌려 예외를 처리합니다.여기서 핸들러의 타입마다 반환되는
exMv
변수를 확인해보면 다음과 같습니다.HandlerMethod
ResourceHttpRequestHandler
null
ResourceHttpRequestHandler
의 경우null
이 할당되어 예외 처리할 수 없어, 예외를 던지게 되고, 더 이상 예외는 처리되지 못하여 톰캣까지 타고 흘러가게 됩니다.이게 해당 이슈가 발생한 이유입니다...!
그리고 핸들러가
null
일 때 예외가 처리되는 이유는HandlerExceptionResolver
가HandlerExceptionResolverComposite
타입일 때 처리되는데, 해당 클래스에서 위와 똑같이 예외를 핸들링하고 있습니다. (이름 그대로 Composite 디자인 패턴을 구현했습니다)또 여기서
resolvers
를 반복문 돌리며 예외를 처리하는데,resolvers
는 다음과 같이 3개의HandlerExceptionResolver
를 가지고 있습니다.여기서
ExceptionHandlerExceptionResolver
이 녀석이 바로 ControllerAdvice를 통해 등록한 ExceptionHandler를 가지고 예외를 핸들링 하는 녀석입니다.(호출하는
ExceptionHandlerMethodResolver
의mappedMethods
필드에 있습니다)그 뒤 설명하기엔 너무 길어질 것 같아.. 이 정도만 하겠습니다. 😂
자세한 건
ExceptionHandlerExceptionResolver
클래스의doResolveHandlerMethodException()
메서드를 직접 살펴보는 것이 빠를 것 같네요.(핸들러에 따라 예외가 처리되는 분기는
AbstractHandlerMethodExceptionResolver
의shouldApplyTo()
메서드를 통해 이뤄집니다)그래서 정리하면 다음과 같습니다.
매핑된 handler가
null
또는HandlerMethod
일 때, 발생한 예외가 ControllerAdvice의 Exception Handler에 등록되었으면 처리된다.하지만 매핑된 handler가
ResourceHttpRequestHandler
일 때, 발생된 예외가 Exception Handler에 등록 되었어도, 예외를 처리할 수 없어 500 에러가 발생한다.추가로 다음과 같은 로그 프로퍼티도 추가했습니다.
이유는
noHandlerFound()
메서드가 호출될 때 WARN 로그가 발생하기 때문입니다. 😂(대체 왜 INFO 또는 DEBUG로 하지 않았는지 모르겠네요)