스프링 부트 API 예외 처리 [1] 서블릿 처리와 스프링 부트 처리

최준호·2022년 5월 16일
0

Spring

목록 보기
25/47
post-thumbnail

스프링 부트에서 에러 페이지에 대한 처리는 매우 간단하게 처리되었고 사용자들에게 제한된 정보만 제공하기 때문에 크게 신경쓸 것이 없었다.

하지만 API의 경우 에러에 대한 JSON 데이터와 약속한 에러 응답 스펙으로 정의해서 반환해주어야한다.

스프링부트에서 기본으로 제공하는 방법도 있지만 우선 서블릿으로 처리하는 방법부터 시작해보자!

📗프로젝트 설정

에러가 날 경우 서블릿에서 사용자가 등록된 url로 처리할 수 있도록 위와 같이 다시 스캔이 될수 있도록 설정해준다.

@RestController
@Slf4j
public class ApiExceptionController {
    @GetMapping("/api/members/{id}")
    public MemberDto getMember(@PathVariable("id") String id){
        if(id.equals("ex")) throw new RuntimeException("잘못된 사용자");

        return new MemberDto(id, "hello " + id);
    }
    
    @Data
    @AllArgsConstructor
    static class MemberDto{
        private String memberId;
        private String name;
    }
}

api 스펙으로 반환하기 위해 다음과 같이 컨트롤러를 만들고 MemberDto를 반환하도록 했다. 하지만 id값이 ex일 경우 RuntimeException으로 처리된다.

정상적인 요청일 경우 spring이란 memberId와 name 값이 노출된다.

이제 ex로 에러로 던져보자

결과는 다음과 같이 우리가 설정했던 에러 페이지 html이 노출된다. 하지만 api는 html이 아닌 json 형태의 에러 정보를 반환해주어야한다.

왜 500 페이지가 반환이 되었을까??

@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
    @Override
    public void customize(ConfigurableWebServerFactory factory) {

        //페이지 생성
        ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");
        ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500");
        ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500");   //Exception으로 기준을 잡을 경우 해당 exception을 상속한 자식 class들도 모두 포함된다.

        //페이지 등록
        factory.addErrorPages(errorPage404);
        factory.addErrorPages(errorPage500);
        factory.addErrorPages(errorPageEx);
    }
}

여기서 짚고 넘어갈 부분은 우리는 RuntimeException이 발생했을 때 500 에러 페이지로 연결되도록 설정해두었기 때문에 다음과 같이 500 화면이 반환된것이다.

📗Api 방식으로 반환하도록 설정

@Controller
@Slf4j
public class ErrorPageController {

	...
    
    @RequestMapping(value = "/error-page/500", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Map<String, Object>> errorPage500Api(HttpServletRequest req, HttpServletResponse res){
        log.info("error 500 api");
        HashMap<String, Object> result = new HashMap<>();
        Exception ex = (Exception) req.getAttribute(ERROR_EXCEPTION);
        result.put("status", req.getAttribute(ERROR_STATUS_CODE));
        result.put("message", ex.getMessage());
        Integer statusCode = (Integer) req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);

        return new ResponseEntity<>(result, HttpStatus.valueOf(statusCode));
    }
}

produces의 설정을 통해 json으로 반환해야할 경우 다음 url로 요청이 되게 설정해둔 뒤 위 코드로 반환하도록 한다면

이제 json 형태로 반환할 수 있게 되었다. 여기서 실수할 수 있는 것은 포스트맨 요청시 Accept 기본값이 */*로 json 형태가 아니여도 되는데 해당 값을 위와 같이 수정해주면 요청 후 반환 값으로 json 형태를 대기하고 있기 때문에 우리가 위에서 설정한 produces를 통해 json 형태의 반환 컨트롤러를 찾아가게 해야한다.

📗스프링 부트 BasicErrorController 활용

이전과 마찬가지로 서블릿이 아닌 스프링 부트의 처리 방법을 그대로 사용할수도 있다.

이번에도 사용자가 직접 등록하는 컴포넌트를 주석처리하고 스프링 부트에서 자동으로 처리해주도록 해보자

우리가 처리해놓지 않아도 스프링부트에서는 어떤 에러가 발생했는지 json으로 처리해서 보내주고 있다.

무조건 json으로만 반환???
그렇지 않다. 우리가 Accept를 json으로 설정해두었기 때문에 json으로 반환된것이지 만약 html을 설정해둔다면

기존의 에러 페이지 html을 반환하게 된다! 스프링 부트에서 자동으로 처리해주고 있는것이다.

BasicErrorController 내부를 살펴보면

다음과 같이 HTML일 경우와 아닌 경우 2개의 경우를 처리해놌는데 HTML일 경우에는 ModelAndView를 반환하여 페이지를 동적으로 처리할 수 있게 해주고 그 외에 경우에는 ResponseEntity를 반환하여 json 형태의 데이터를 반환할 수 있게 해두었다.

또한 application.properties에

server.error.include-exception=true
server.error.include-message=always
server.error.include-stacktrace=always
server.error.include-binding-errors=always

내용을 입력해주면 이전과 동일하게 데이터를 추가적으로 더 확인해볼 수 있다.

하지만 에러 데이터는 외부로 노출시키지 않는 것이 좋으므로 알아만 두자!

지금까지 학습한 api 예외 처리 방법은 사실 api 예외 처리에 적합한 방법은 아니다. 스프링 부트에서 다음과 같이 처리됨을 이해하고 이후에 학습할 내용을 이해하면 더 좋은 처리방법으로 처리할 수 있을 것이다! 오늘은 이해만 하고 넘어가도록 하자.

profile
코딩을 깔끔하게 하고 싶어하는 초보 개발자 (편하게 글을 쓰기위해 반말체를 사용하고 있습니다! 양해 부탁드려요!) 현재 KakaoVX 근무중입니다!

0개의 댓글