SpringBoot

HTTP Status Code 제어 및 Exception Handling

알 수 없는 사용자 2022. 4. 4. 00:47

HTTP Status Code 제어

이번 포스팅에서는 Spring Boot에서는 Http Status Code를 어떻게 제어하는지와 Exception Handling을 어떻게 했는지에 대해 공부한 내용을 정리해보려 한다.

User Controller

유저를 만드는 기능을 하는 POST 방식의 메소드가 하나 있다.

@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
    User createdUser = service.save(user);

    URI location = ServletUriComponentsBuilder.fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(createdUser.getId())
            .toUri();

    return ResponseEntity.created(location).build();
}

UriComponentsBuilder 클래스를 상속하는 ServletUriComponentsBuilder 클래스는 UriComponentBuilder 클래스의 정적 팩토리 메소드와 더불어 추가적인 메소드를 제공한다.

ServletUriComponentsBuilderfromCurrentRequest메소드에서 문서에 따르면 HttpServeltRequestscheme, host, port, path, and quertstring을 복사해서 빌더를 전달한다.

ServletUriComponentsBuilder.fromRequest docs

다음으로 path 메소드를 통해서 경로를 지정해주고 buildAndExpand메소드에서 UriComponents인스턴스를 빌드하고 전달된 인자 값으로 URI의 템플릿 변수로 대체한다.

마지막으로 toUri메소드까지 실행되면 location에 결과로 사용될 URI가 들어간다.

마지막으로 상태코드는 ResponseEntity를 이용해 created(201)location을 포함해서 반환한다.

결과 이미지를 보면 201코드와 location이 잘 전달되서 반환됐다.

HTTP Status Code 제어를 위한 Exception Handling

필자가 여태까지 사용해오던 Node.js환경의 Express프레임워크에서도 특정 Http Status Code의 경우를 핸들링하기 위한 방법들이 존재했다. 그리고 분명 실무에서 이 특정 상태 코드를 캐치해서 핸들링해야할 상황이 발생한다. 그래서 Spring Boot에서 특정 상태 코드를 핸들링하는 방법을 알아봤다.

프로젝트 루트레벨에 먼저 exception이란 이름으로 패키지를 생성했다.

ExceptionResponse.class

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExceptionResponse {
    private Date timestamp; // Exception 발생 시간
    private String message; // 메세지
    private String details; // 상세 메세지
}

Exception이 발생했을 때 반환할 필드를 가진 ExceptionResponse class를 생성한다.

다음으로 일반화된 에러를 처리하기 위한 핸들러 클래스를 생성한다.

CustomizeResponseEntityExceptionHandler.java

@RestController
@ControllerAdvice
public class CustomizeResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(Exception.class)
    // 모든 예외를 처리하는 일반화된 Exception
    public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
        ExceptionResponse res = new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));

        return new ResponseEntity(res, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(UserNotFoundException.class)
    // 404 Not Found Exception
    public final ResponseEntity<Object> handleUserNotFoundException(Exception ex, WebRequest request) {
        ExceptionResponse res = new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));

        return new ResponseEntity(res, HttpStatus.NOT_FOUND);
    }
    ...
}

모든 에러를 처리하는 handleAllException에서는 현재 시간과 전달받은 기본 메세지와 내부 서버 에러를 뜻하는 500에러를 전달한다.

그리고 특정 404코드의 경우를 핸들링 하기 위해 UserNotFoundException class를 생성한다.

class 상단에 ResponseCode를 Not Found를 의미하는 400으로 등록해둔다.

위 코드에서 handleUserNotFoundException메소드는 ExceptionHandlerUserNotFoundException Class로 등록해둬서 404일 경우 실행된다.

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}

적용 후 테스트 결과

500 Error
404 Error