무한 리다이렉트 루프

·2025년 12월 8일

troubleshooting

목록 보기
1/11

1.문제 요약

  • 발생 일시:2025-12-04
  • 관련 모듈 / 서비스: 전체
  • 에러 메시지:
    • java.lang.OutOfMemoryError: Java heap space
    • jakarta.servlet.ServletException: Handler processing failed
  • 증상 요약: API 호출 시 무한 리다이렉트 루프 발생으로 인한 OutOfMemoryError 및 애플리케이션 크래시

2. 문제 상황

  • 어떤 작업을 하던 중 발생했는가 : API 호출 시 발생
    • POST /api/producsts
    • PG payments
  • 환경 정보 :
    • Spring Boot 3.x
      • Spring Web MVC 6.2.14
      • JDK 17
      • 포트: 8087, 8086
  • 관련 코드 또는 설정
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

      @ExceptionHandler(NoResourceFoundException.class)
      @ResponseStatus(HttpStatus.NOT_FOUND)
      public ResponseDto<String> handleNoResourceFoundException(NoResourceFoundException e) {
          log.error(ERROR_LOG_FORMAT, HttpStatus.NOT_FOUND, e.getMessage(), e);
          return new ResponseDto<>(HttpStatus.NOT_FOUND, null, e.getMessage());
      }

      @ExceptionHandler(Exception.class)
      @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
      public ResponseDto<String> handleException(Exception e) {
          log.error(ERROR_LOG_FORMAT, HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e);
          return new ResponseDto<>(HttpStatus.INTERNAL_SERVER_ERROR, null, e.getMessage());
      }
}

3. 문제 원인 분석

  • 최초 예상 원인: 포트 충돌 또는 메모리 부족 문제
  • 실제 원인: @ControllerAdvice + ResponseDto 반환 조합으로 인한 무한 뷰 리졸빙 루프
  • 원인 판단 근거:
    a. 에러 로그에서 URL이 /api/api/api/...로 무한 반복됨을 확인
    b. InternalResourceView를 렌더링하려는 시도가 반복적으로 발생
    c. @ControllerAdvice는 예외 핸들러의 반환값을 뷰 이름으로 해석
    d. ResponseDto 객체를 뷰 이름으로 해석하면서 뷰를 찾지 못함
    e. 뷰를 찾지 못한 것도 예외이므로 다시 예외 핸들러 호출 → 무한 루프
  • 메커니즘:
    요청 → 예외 발생 → @ControllerAdvice 핸들러 호출
    → ResponseDto 반환 → Spring이 이를 뷰 이름으로 해석
    → 뷰를 찾지 못함 → NoResourceFoundException 발생
    → 다시 핸들러 호출 → 무한 반복 → OutOfMemoryError

4. 시도한 해결 방법

  1. 첫 번째 시도
    • 수정: gateway application.yml 파일 수정
    • 결과: 각 모듈의 포트로 접속하기 때문에 에러 원인 아님
  2. 두 번째 시도
    • 수정: @RequestMapping 삭제
    • 결과: 해결되는 API가 있고 안 되는 API가 있음

5. 최종 해결 방법

  • 어떻게 해결했는지
    • @ControllerAdvice를 @RestControllerAdvice로 변경
  • 변경된 코드/설정
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
      private static final String ERROR_LOG_FORMAT = "[{}] {}";

      @ExceptionHandler(NoResourceFoundException.class)
      @ResponseStatus(HttpStatus.NOT_FOUND)
      public ResponseDto<String> handleNoResourceFoundException(NoResourceFoundException e) {
          log.error(ERROR_LOG_FORMAT, HttpStatus.NOT_FOUND, e.getMessage(), e);
          return new ResponseDto<>(HttpStatus.NOT_FOUND, null, e.getMessage());
      }

      @ExceptionHandler(Exception.class)
      @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
      public ResponseDto<String> handleException(Exception e) {
          log.error(ERROR_LOG_FORMAT, HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e);
          return new ResponseDto<>(HttpStatus.INTERNAL_SERVER_ERROR, null, e.getMessage());
      }
}
  • 적용 후 정상 동작 확인 내용
    • 빌드 성공
    • 로그 정상
    • API 정상 응답

6. 교훈 / 재발 방지 대책

  • 이번 문제에서 배운 점

    1. @ControllerAdvice vs @RestControllerAdvice 차이 명확히 이해
    • @ControllerAdvice: 반환값을 뷰 이름으로 해석 (MVC 용)
    • @RestControllerAdvice: 반환값을 HTTP 응답 본문으로 직렬화 (REST API 용)
    • @RestControllerAdvice = @ControllerAdvice + @ResponseBody
    1. ResponseEntity의 특수성
    • ResponseEntity는 @ControllerAdvice에서도 자동으로 HTTP 응답으로 처리됨
    • 일반 POJO 객체(ResponseDto 등)는 반드시 @ResponseBody 또는 @RestControllerAdvice 필요
    1. 무한 루프 에러의 특징
    • URL이 반복되는 패턴 (/api/api/api/...)
    • OutOfMemoryError와 함께 발생
    • 뷰 리졸버 관련 에러 메시지
  • 앞으로 비슷한 문제를 막기 위한 체크리스트

    • REST API 프로젝트에서는 항상 @RestControllerAdvice 사용
    • @ControllerAdvice 사용 시 반드시 ResponseEntity 반환 또는 각 메서드에 @ResponseBody 추가
    • 새로운 마이크로서비스 생성 시 예외 처리 어노테이션 검증
    • 코드 리뷰 시 GlobalExceptionHandler 어노테이션 확인
    • 무한 루프 의심 시 로그에서 URL 반복 패턴 우선 확인
    • Spring Boot 프로젝트 템플릿에 @RestControllerAdvice 기본 설정

0개의 댓글