Sử dụng @ResponseStatus để đặt HTTP status code trong Spring

Trong Spring MVC chúng ta có rất nhiều cách để đặt HTTP status code cho một HTTP response. Tuy nhiên trong bài viết này chúng ta sẽ cùng nhau tìm hiểu cách sử dụng @ResponseStatus annotation để đạt được điều tương tự.

@ResponseStatus trên controller method

Khi một endpoint trên controller thực thi thành công, mặc định spring sẽ gán HTTP status 200(OK) cho response. Tuy nhiên chúng ta có thể chỉ định một HTTP status code cụ thể cho response trên một controller method với @ResponseStatus annotation.

@ResponseStatus(HttpStatus.I_AM_A_TEAPOT)
void teaPot() {}

Hoặc khi chúng ta muốn thông báo một error nào đó thì chúng ta có thể cung cấp mã lỗi kèm theo lý do cụ thể như sau:

@ResponseStatus(HttpStatus.BAD_REQUEST, reason = "Some parameters are invalid")
void onIllegalArgumentException(IllegalArgumentException exception) {}

NOTE: Ví dụ ở trên thông thường sẽ không được sử dụng trên các controller method, vì đâu có lý do một endpoint chưa biết thực thi thành công hay không mà đã mark nó trước là trả về mã lỗi rồi. 

Lưu ý nữa là khi chúng ta gán giá trị cho thuộc tính reason, Spring sẽ gọi HttpServletResponse.sendError() để trả về một trang HTML error page cho client việc này sẽ khiến cho một REST API trở nên tồi tệ.’

Cho nên Spring chỉ sử dụng @ResponseStatus khi controller method thực thi thành công (không xảy ra một exception). Nếu có exception xảy ra trong quá trình thực thi thì nó sẽ bị chuyển đến bộ xử lý lỗi của spring, những gì từ @ResponseStatus chúng ta cung cấp sẽ bị bỏ qua.

@ResponseStatus cho error handler

Như lúc nảy mình đã nhắc đến rồi, là khi xảy ra lỗi spring sẽ chuyển đến bộ xử lý error. Tại đây chúng ta có thể sử dụng @ResponseStatus để gán HTTP status cho các exception tương ứng.

Ví dụ mình sử dụng @ControllerAdvice để bắt những exception xảy ra trong quá trình thực thi của REST API. Mỗi method bên trong APIExceptionHandler sẽ được dùng để xử lý một exception cụ thể xảy ra trong quá trình REST API xử lý request.

@ControllerAdvice
public class APIExceptionHandler extends ResponseEntityExceptionHandler {

    private static final String INTERNAL_ERROR_MSG = "A system error has occurred";
    public static final String FORBIDDEN = "User doesn't have permission to access this resources";

    private static final Logger LOG = LoggerFactory.getLogger(APIExceptionHandler.class);

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Object handleConstraintViolationException(ConstraintViolationException e) {
        LOG.error(e.getMessage(), e);
        return new ErrorDTO(MessageType.ERROR, INTERNAL_ERROR_MSG, INTERNAL_ERROR_MSG);
    }

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Object handleUserException(CommonException e) {
        LOG.error(e.getMessage(), e);
        return e.getErrorDTO();
    }

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Object handleResourceNotFoundException(NotFoundException e) {
        LOG.error(e.getMessage(), e);
        return e.getErrorDTO();
    }

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Object handleInvalidParamException(InvalidParamException e) {
        LOG.error(e.getMessage(), e);
        return e.getErrorDTO();
    }

    @ExceptionHandler
    public void handleInvalidFormatException(InvalidFormatException e) {
        LOG.error(e.getMessage(), e);
    }

    @ExceptionHandler
    public void handleMismatchedInputException(MismatchedInputException e) {
        LOG.error(e.getMessage(), e);
    }

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Object handleDuplicatedException(DuplicatedException e) {
        LOG.error(e.getMessage(), e);
        return e.getErrorDTO();
    }

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Object handleFileStoreException(GitlabException e) {
        LOG.error(e.getMessage(), e);
        return e.getErrorDTO();
    }

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Object handleAllExceptions(Exception e) {
        LOG.error(e.getMessage(), e);
        return new ErrorDTO(MessageType.ERROR, INTERNAL_ERROR_MSG, INTERNAL_ERROR_MSG);
    }

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public Object handleAccessDeniedException(AccessDeniedException e) {
        LOG.error(e.getMessage(), e);
        return new ErrorDTO(MessageType.WARNING, FORBIDDEN, FORBIDDEN);
    }

    @ExceptionHandler
    @ResponseBody
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public Object handleAuthenticationException(AuthenticationException e) {
        LOG.error(e.getMessage(), e);
        return new ErrorDTO(MessageType.WARNING, HttpStatus.UNAUTHORIZED.toString(), HttpStatus.UNAUTHORIZED.toString());
    }

    @Override
    public ResponseEntity<Object> handleHttpMessageNotReadable(
            HttpMessageNotReadableException e, HttpHeaders headers, HttpStatus status, WebRequest request) {
        LOG.error(e.getMessage(), e);
        return new ResponseEntity<>(new ErrorDTO(MessageType.WARNING, Errors.PARAM_INVALID.getTitle(), Errors.PARAM_INVALID.getText()), HttpStatus.BAD_REQUEST);
    }

}

Các bạn có thể thấy rất nhiều exception được mình tùy biến, trong đó sử dụng @ResponseStatus để đặt HTTP status code tương ứng cho từng exception.

Tóm lược

Cá nhân mình thấy xử lý @ResponseStatus để đặt HTTP status cho response rất ngắn gọn, chúng ta không cần phải viết thêm một số mã code để đặt HTTP status code response, giảm thiểu duplicate rõ ràng phải không các bạn!

Nguồn tham khảo

https://www.baeldung.com/spring-response-status

 

 

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x