본문 바로가기
Spring/Spring Boot

[Spring Boot] HTTP Status Code 제어 및 Exception Handling

by yoon_seon 2023. 6. 21.

HTTP Status Code 제어 및 Exception Handling

1. HTTP Status Code의 중요성

2. HTTP Status Code 제어

3. HTTP State Code 제어를 위한  Exception Handling

4. Spring의 AOP를 이용한 Exception Handling


📌 HTTP Status Code의 중요성

간단한 예제를 통해 알아보자. ID가 1~3인 회원이 존재한다.

 

존재하는 회원 조회

 

존재하지 않는 회원 조회(좋지 않은 API 설계)

존재하지 않는 회원을 조회하여 에러가 발생할 것을 예상했지만 상태코드는 200이 반환되었다. 그 이유는 HTTP 입장에서는 해당 URL 호출이 문법적으로 오류가 없고 정상적으로 서버에서 응답을 반환받았기 때문이다.

 

 

존재하지 않는 회원 조회(좋은 API 설계)

존재하지 않는 회원을 조회하여 클라이언트 에러 상태 코드를 반환하도록 수정하였다.(예제 소스는 다음 Step에)

 

 

회원 등록 API(명확한 상태코드를 반환하지 않음)

회원을 생성하는 POST 메서드의 API이다. 회원 생성요청이 정상처리되어 200 응답 상태 코드를 반환하고 있다. 

하지만 더욱 직관적이게 확인할 수 있도록 명확한 상태코드를 반환할 필요가 있다.

 

회원 등록 API(명확한 상태코드 반환)

회원 생성에 성공하고 201 Create 상태코드를 반환하여 더욱 직관적이고 명확하게 상태를 확인할 수 있도록 수정하였다.

 

우리는 요청 결과값의 따라 적절한 상태코드를 반환하여 요청 결과를 직관적이고 명확하게 클라이언트에서 확인할 수 있도록 API를 설계해야 한다.

 


📌 HTTP Status Code 제어

HTTP Status Code란 클라이언트가 보낸 HTTP 요청값에 대한 결과 상태를 서버의 응답 코드로 표현한 것으로 요청 및 응답의 성공 / 실패 / 실패요인등을 알 수 있다

 

Rest API에서 응답 코드 값을 제어하기 위해 ServletUriComponentsBuilde를 이용해서 서버에서 반환하고자 하는 데이터 값을 ResponseEntity에 담아서 전달하는 예제를 보자.

@RestController
public class UserController {
    private UserDaoService service;
    
    @PostMapping("/users")
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User savedUser = service.save(user);
        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(savedUser.getId())
                .toUri();
    
        return ResponseEntity.created(location).build();
    }
}
  • HTTP POST 요청으로 들어온 사용자 정보를 저장한 후, 저장된 사용자의 정보와 생성된 리소스의 위치를 클라이언트에 반환하는 간단한 로직이다.
  • ServletUriComponentsBuilder.fromCurrentRequest()
    현재 요청의 정보를 기반으로 ServletUriComponentsBuilder 객체를 생성하고 현재 요청의 URL, 쿼리 매개변수, 경로 변수 등의 정보를 포함한다.
  • .path("/{id}")
    생성된 URI에 경로 변수인 {id}를 추가한다.(생성된 사용자의 고유 식별자(ID)를 URI에 포함시키기 위함)
  • .buildAndExpand(savedUser.getId())
    경로 변수 {id}의 값을 savedUser 객체의 ID로 대체한다. 이로써 생성된 URI에 실제 사용자 ID가 들어간다.
  • .toUri()
    최종적으로 URI 객체를 생성한다. location 변수에는 생성된 URI가 저장된다.
  • ResponseEntity.created(location).build()
    생성된 URI를 이용하여 HTTP 상태 코드 201(Created)과 함께 응답을 생성한다.
    • ResponseEntity는 Spring Framework에서 제공하는 클래스로, HTTP 응답의 상태 코드, 헤더, 본문 등을 포함
    • created(location)은 상태 코드 201(Created)을 의미하고 생성된 리소스의 위치를 클라이언트에 반환한다.
    • build()는 최종적으로 ResponseEntity 객체를 생성하고 반환한다.

 

API 호출 결과

  • 응답코드 201(Create)이 반환되고 ID가 4인 회원이 저장된 것을 확인할 수 있다.

 

이와 같이, 서버로부터 요청 결괏값의 적절한 상태코드를 반환해 주는 것이 좋은 Rest Api를 설계하는 방법 중의 하나이다.

 


📌 HTTP State Code 제어를 위한  Exception Handling

Exception Handling에 대해 알아보기 앞서, 예제를 통해 HTTP State Code 제어의 중요성에 대해 알아보자

@RestController
public class UserController {
    private UserDaoService service;
    
    @GetMapping("/users/{id}")
    public User retrieveUser(@PathVariable int id) {
        User user = service.findOne(id);
        if (user == null) {
            throw new UserNotFoundException(String.format("ID[%s] not found", id));
        }
    
        return user;
    }
}
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}
  • @ResponseStatus(HttpStatus.NOT_FOUND)
    • @ResponseStatus
      해당 요청이나 예외에 대한 HTTP 응답 상태 코드를 명시적으로 설정
    • HttpStatus.NOT_FOUND
      상태 코드 404(Not Found)를 나타낸다.

요청값으로 회원 ID를 받아 조회하고 존재하는 회원이 없을 경우 에러가 발생하도록 설계하고 Exception Class를 생성하였다.

명확한 에러상태를 반환하여 클라이언트가 직관적이게 상태를 확인할 수 있지만 Exception Class가 많아진다면 서버에서 관리하기 어려워진다는 단점이 있다.

 


📌 Spring의 AOP를 이용한 Exception Handling

Exception을 전역적인 Handling 하여 관리

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExceptionResponse {
    private Date timestamp;
    private String message;
    private String details;
}
@RestControllerAdvice
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
        ExceptionResponse exceptionResponse =
                new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(UserNotFoundException.class)
    public final ResponseEntity<Object> handleUserNotFoundException(Exception ex, WebRequest request) {
        ExceptionResponse exceptionResponse =
                new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND);
    }
}
  • @RestControllerAdvice:
    예외 처리를 위한 전역적인 핸들러 클래스를 정의
  • @ExceptionHandler(Exception.class)
    • Exception 클래스를 상위 클래스로 하는 모든 예외를 처리하는 메서드를 정의한다.
    • 예외가 발생하면 해당 메서드가 실행되고, Exception 객체와 요청에 대한 정보(WebRequest)를 받는다.
    • ExceptionResponse 객체를 생성하여 예외에 대한 응답 본문을 생성한다.
    • 생성된 응답 본문과 상태 코드 500(Internal Server Error)을 함께 ResponseEntity 객체로 반환한다.
  • @ExceptionHandler(UserNotFoundException.class)
    • UserNotFoundException 클래스를 처리하는 메서드를 정의한다.
    • UserNotFoundException이 발생할 경우 실행되며, 예외 객체와 요청에 대한 정보를 받는다.
    • ExceptionResponse 객체를 생성하여 예외에 대한 응답 본문을 생성한다.
    • 생성된 응답 본문과 상태 코드 404(Not Found)를 함께 ResponseEntity 객체로 반환한다.

@RestController의 전역에서 예외 발생 시 표준화된 응답을 생성하여 반환할 수 있고, 클라이언트는 예외에 대한 명확한 정보와 상태 코드를 받을 수 있다.

 

 


해당 글은 인프런의 [Spring Boot를 이용한 RESTful Web Services 개발] 강의를 정리한 내용입니다.

 

 

Spring Boot를 이용한 RESTful Web Services 개발 - 인프런 | 강의

이 강의는 Spring Boot를 이용해서 RESTful Web Services 애플리케이션을 개발하는 과정에 대해 학습하는 강의으로써, REST API 설계에 필요한 기본 지식에 대해 학습할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

댓글