1 분 소요


시작

ApiExceptionController

@Slf4j
@RestController
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;
    }
}

URL에 전달된 id의 값이 ex이면 예외가 발생하도록 했다.
WebServerCustomizer.class(new ErrorPage)가 동작하여 HTML 화면을 렌더링해준다.

정상 호출

{
    "memberId": "spring",
    "name": "hello spring"
}

API를 요청해서 정상의 경우 JSON 형식으로 데이터가 정상 반환된다.
하지만, 오류가 발생하면 JSON반환이 아닌 미리 만들어둔 오류 HTML 페이지가 반환된다.
문제를 해결하려면 오류 페이지 컨트롤러도 JSON 응답을 할 수 있도록 수정해야 한다.

ErrorPageController

// @RequestMapping("/error-page/500) 생략...
// ...

@RequestMapping(value = "/error-page/500", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> errorPage500Api(
            HttpServletRequest request, HttpServletResponse response) {

    log.info("API errorPage 500");

    Map<String, Object> result = new HashMap<>();

    Exception ex = (Exception) request.getAttribute(ERROR_EXCEPTION);
    result.put("status", request.getAttribute(ERROR_STATUS_CODE));
    result.put("message", ex.getMessage());
    Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);

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

produces = MediaType.APPLICATION_JSON_VALUE는 클라이언트가 요청하는 HTTP Header의 Accept값이 application/json일 때 해당 메서드가 호출되게 한다.
결국, 클라이언트가 받고 싶은 미디어타입이 json이면 이 컨트롤러의 메서드가 호출된다.

ResponseEntity를 사용해서 응답하기 때문에 메시지 컨버터가 동작하면서 클라이언트에 JSON이 반환된다.

예외 호출

{
    "message": "잘못된 사용자",
    "status": 500
}


스프링 부트 기본 오류 처리

API 예외 처리도 스프링 부트가 제공하는 BasicErrorController를 통해 처리할 수 있다.

BasicErrorController

@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {}

@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {}
  • errorHtml(): produces = MediaType.TEXT_HTML_VALUE: 클라이언트 요청의 Accept헤더 값이 text/html인 경우에 errorHtml()를 호출해서 View를 제공한다.
  • error(): 그 외의 경우에 호출되고, ResponseEntity로 HTTP Body에 JSON 데이터를 반환한다.

호출 결과

{
    "timestamp": "2021-04-28T00:00:00.000+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "exception": "java.lang.RuntimeException",
    "trace": "java.lang.RuntimeException: 잘못된 사용자\n\tat 
    hello.exception.web.api.ApiExceptionController.getMember(ApiExceptionController.java:19...,
    "message": "잘못된 사용자",
    "path": "/api/members/ex"
}

스프링 부트는 BasicErrorController가 제공하는 기본 정보들을 활용해서 오류 API를 생성해준다.

스프링 부트의 예외 처리

스프링 부트의 기본 설정은 오류 발생시 /error를 오류 페이지로 요청한다.
BasicErrorController는 이 경로를 기본으로 받는다.(server.error.path로 수정 가능.)

HTML vs API

BasicErrorController를 확장하면 JSON 메시지도 변경할 수 있다. 하지만 이보다 더 좋은 기능인 @ExceptionHandler가 제공되니 이는 참고만 해두면 된다.

BasicErrorController는 HTML 페이지를 제공하는 경우에는 매우 편리하기 때문에 이 방법은 HTML 화면을 처리할 때 사용되고, API 오류 처리는 @ExceptionHandler를 사용하면 된다.


<출처 : 인프런 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술(김영한)>

댓글남기기