Spring Boot를 이용한 RESTful Web Services 개발
HTTP Status Code 제어
GET Method와 POST Method OK 응답 코드 구분하기
- UserController.java 수정
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
User savedUser = service.save(user);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(savedUser.getId()) // {id}에 값 지정
.toUri(); // uri 형태로 변환
return ResponseEntity.created(location).build();
}
- 포스트맨에서 확인
포스트맨에서 새 사용자를 추가하면 응답 코드로 200이 아닌 201 Created가 보인다. 이렇게 서버로부터 요청 결과 값에 적절한 상태 코드를 반환시켜 주는 것이 좋은 REST API를 설계하는 방법이다.
그 다음 header 정보를 봐보자.
header 정보에 Location 값이 아까 Controller에서 추가한 코드의 값이 저장되어 있는 걸 확인할 수 있다. 관련 된 코드는 아래와 같다.
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(savedUser.getId()) // {id}에 값 지정
.toUri(); // uri 형태로 변환
return ResponseEntity.created(location).build();
정해진 응답 코드 외 의미 없는 값을 넘기고 싶으면 아래 코드와 같이 작성하면 된다.
return ResponseEntity.status(222).location(location).build();
- HTTP Status Code
- 2XX → OK
- 4XX → Client로부터 발생된 예외
- 5XX → Server로부터 발생된 예외
HTTP Status Code 제어를 위한 Exception Handling
- 포스트맨 확인
사용자 id는 총 3번까지만 있는데 존재하지 않는 10번의 사용자를 검색해도 200 OK로 떨어진다. 컨트롤러와 서비스의 코드는 아래와 같다.
// 컨트롤러
@GetMapping("/users/{id}")
public User retrieveUser(@PathVariable int id) {
return service.findOne(id);
}
// 서비스
public User findOne(int id) {
for( User user : users ) {
if(user.getId() == id) return user;
}
// 없으면 null 반환
return null;
}
서비스 코드를 보면 알겠지만 user list에 매개변수로 전달받은 id를 가진 user가 있으면 해당하는 User 객체를 반환하고, 없으면 null을 반환한다. null을 반환하는 행위는 서버 입장에서 보면 잘못된 로직이 아니라, 정상적으로 잘 수행했기 때문에 200 OK가 떨어진 것이다.
그래서 user list에서 검색 결과가 null일 경우 예외를 일으키도록 코드를 변경해야한다.
- UserController.java
@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;
}
- UserNotFoundException.java
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
생성자는 message를 부모쪽으로 넘겨버리면 끝이다.
- 서버 재실행 후 포스트맨에서 확인
Status code가 500으로, server에서 발생된 에러 코드라 적혀있다. 지금 발생시킨 에러는 서버에서 잘못 처리된 것이 아니라 적절하게 처리된 것으로 결과 값을 찾을 수 없다는 예외 코드(404)로 변경해주자.
- UserNotFoundException.java 수정
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
- 서버 재실행 후 포스트맨에서 확인
Spring의 AOP를 이용한 Exception Handling
위에서 발생한 에러를 자세히 살펴보자. 에러 메세지 중 trace를 잘 보면 어느 곳에서 에러가 발생했는지, 몇번째 줄인지가 보인다. 이런 메세지는 외부로 노출되면 보안상의 이슈가 되기 때문에 적절하게 없애주는 것이 좋다.
Spring의 AOP를 이용한 Exception Handling은 exception 객체를 생성해주어야한다. exception 객체는 user에서만 사용하는 것이 아니라 전체적으로 사용할 것이기 때문에 따로 package를 생성한다.
- ExceptionResponse.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExceptionResponse {
private LocalDateTime timestamp;
private String message;
private String details;
}
- CustomizedResponseEntityExceptionHandler.java
@RestController
@ControllerAdvice // 모든 컨트롤러가 실행될 때 이 어노테이션이 붙은 Bean이 사전에 실행되도록 한다.
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class) // 이 메서드가 ExceptionHandler 라는 걸 알려주는 어노테이션
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
ExceptionResponse exceptionResponse
= new ExceptionResponse(LocalDateTime.now(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
- 서버 재기동 후 포스트맨 확인
trace 정보가 나타나지 않음을 확인한다.
이제 위에서 열심히 만들었던 UserNotFoundException에 대한 처리도 해주자.
- CustomizedResponseEntityExceptionHandler.java
// user 클래스 전용
@ExceptionHandler(UserNotFoundException.class)
public final ResponseEntity<Object> handleUserNotFoundException(Exception ex, WebRequest request) {
ExceptionResponse exceptionResponse
= new ExceptionResponse(LocalDateTime.now(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND);
}
- 서버 재실행 후 포스트맨에서 확인
HTTP Status Code가 404로 바뀐 걸 확인하면 된다.
사용자 삭제를 위한 API 구현 - DELETE HTTP Mehtod
- UserDaoService.java 수정
public User deleteById(int id) {
Iterator<User> iterator = users.iterator();
while(iterator.hasNext()) {
User user = iterator.next();
if(user.getId() == id) {
iterator.remove();
return user;
}
}
return null;
}
- UserController.java 수정
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable int id) {
User user = service.deleteById(id);
if(user == null) {
throw new UserNotFoundException(String.format("ID[%S] not found", id));
}
}
- 서버 재기동 후 포스트맨 확인
user 데이터가 없는 경우
user 데이터가 있는 경우
1번 사용자 삭제 후 전체 사용자 조회를 해보자.
1번 사용자가 삭제되었다.
- REST API의 단점
- HTTP Method 중 GET, POST, PUT, DELETE만 지원한다.
- 출처 : 인프런 Spring Boot를 이용한 RESTful Web Services 개발 강의