영호
ResponseEntity의 created(URI)는 뭘까? (feat.httpCode 201) 본문
개요
레벨2 자동차 경주 미션을 진행하면서 201응답을 반환하는 상황이 있었다. 이 과정에서 ResponseEntity의 created는 왜 URI를 인자로 받는지 페어와 이야기 했었다. 당시에 추측만 하고 명확하게 답을 내지 못해서 따로 공부한 내용을 정리해보려고 한다.
ResponseEntity.created(URI)구현코드
public static BodyBuilder created(URI location) {
return status(HttpStatus.CREATED).location(location);
}
ResponseEntity클래스의 created메서드는 위와 같이 구현되어 있다. parameter에 URI가 있다.
도대체 저 URI가 뭐지? 당시 디투와 얘기 했을 때는 redirect를 위한 것 같다고 결론 지었다. 그러나 확실한 건 아니어서 mozila문서의 201코드에 대해 확인해봤다.
201 status code
http응답으로 상태코드 201이 왔다는 것은 새로운 자원이 정상적으로 생성되었다는 것을 의미한다. 새로운 자원이 생성됐다는 것은 이제 클라이언트는 해당 자원에 접근해 조회, 수정, 삭제를 할 수 있다는 것을 의미한다.
그렇다면 해당 자원에 접근하기 위해선 무엇이 필요할까? 식별자다. 즉, 생성된 자원에 접근할 수 있도록 식별자가 포함된 URI를 반환하기 위해 created(URI)를 받는것이다.
이 식별자를 클라이언트에게 반환하기 위해서는 아래와 같은 방법이 있다.
- 생성된 자원에 접근할 수 있는 URI반환
- 생성된 자원 반환
ResponseEntity클래스 내부 뜯어보기
HeaderBuilder
build()는 ResponseEntity내부에 정의되어 있다. ResponseEntity내부에 HeaderBuilder인터페이스가 정의되어 있고 이 인터페이스 안에 build()가 정의되어 있다.
전체코드를 가져오기엔 너무 많아서 build만 가져왔다.
/**
* Defines a builder that adds headers to the response entity.
* @since 4.1
* @param <B> the builder subclass
*/
public interface HeadersBuilder<B extends HeadersBuilder<B>> {
/**
* Build the response entity with no body.
* @return the response entity
* @see BodyBuilder#body(Object)
*/
<T> ResponseEntity<T> build();
}
build()의 주석을 보면 response body없이 ResponseEntity를 생성한다고 한다.
BodyBuilder
위에 있는 HeaderBuilder를 상속한 인터페이스다. 네이밍 그대로 response의 body부분을 설정하는 기능을 담당한다.
코드의 일부분만 가져왔다.
/**
* Defines a builder that adds a body to the response entity.
* @since 4.1
*/
public interface BodyBuilder extends HeadersBuilder<BodyBuilder> {
/**
* Set the body of the response entity and returns it.
* @param <T> the type of the body
* @param body the body of the response entity
* @return the built response entity
*/
<T> ResponseEntity<T> body(@Nullable T body);
}
DefaultBuilder
BodyBuilder를 구현한 static class로 ResponseEntity안에 위치한다.
build()와 body(@Nullable T body)를 구현하고 있다.
private static class DefaultBuilder implements BodyBuilder {
private final Object statusCode;
private final HttpHeaders headers = new HttpHeaders();
public DefaultBuilder(Object statusCode) {
this.statusCode = statusCode;
}
@Override
public <T> ResponseEntity<T> build() {
return body(null);
}
@Override
public <T> ResponseEntity<T> body(@Nullable T body) {
return new ResponseEntity<>(body, this.headers, this.statusCode);
}
}
이제 식별자를 반환하는 방법에 대해 알아보자.
1. 생성된 자원의 URI 반환하기
이 방법은 build()를 통해 식별자가 포함된 URI를 header의 location에 담아 반환한다.
만약 User를 새로 생성했다고 가정하고 생성된 User에 접근할 수 있는 URI를 반환한다고 해보자. 아래와 같은 코드를 작성할 수 있다.
return ResponseEntity.created(URI.create("/users/" + id)).build();
위 코드의 실행 결과를 포스트맨으로 확인해보면 reponse header의 Location에 생성한 URI가 삽입된 것을 확인할 수 있다.
ResponseEntity를 반환하는 코드를 다시 보면, URI.create()를 통해 URI를 생성 후 build()메서드를 호출했다. 해당 메서드의 기능을 간략하게 설명하면 아래와 같다.
build메서드 기능 : 응답의 body가 없을 때 ResponseEntity를 생성해 반환해준다(빌더패턴)
build()의 ResponseEntity생성 과정
build()를 사용하는 아래 코드는 어떤 식으로 ResponseEntity를 만드는 과정을 따라가보겠습니다.
return ResponseEntity.created(URI.create("/users/" + id)).build();
1. DefaultBuilder의 build()호출
우선, DefaultBuilder의 build메서드를 호출한다.
현재 build()를 호출한 것이기 때문에 status, URI는 만들어진 상태로 header의 Location도 설정되어 있다.
2. body값 설정 후 ResponseEntity생성 후 반환
이후 build()에 구현되어 있는 body(null)을 호출한다.
body값을 설정하고 ResponseEntity를 생성해 반환한다.
ResponseEntity는 HttpEntity를 상속했기 때문에 뒤에 추가적인 작업이 있지만, 너무 많아지기 때문에 생략하겠습니다.
이제 반환된 Location값을 통해 새롭게 생성된 자원에 접근할 수 있다.
내가 생각한 활용방안은 물건을 주문 후 주문 내역 페이지로 이동하는데 활용할 수 있지 않을까 싶다.
2. 생성된 자원 반환
이 방법은 식별자를 response body에 포함하는 방식으로 식별자를 반환한다.
코드부터 보자면 아래와 같이 사용할 수 있다.
User createUser = userService.saveUser(user);
return ResponseEntity.status(HttpStatus.CREATED).body(createUser);
reposnse body가 존재하기 때문에 build()를 호출하지 않는다. 대신 body(@Nullable T body)를 통해 response body에 새롭게 저장된 자원을 반환한다.
그리고, URI를 반환하지 않는다. 그래서, status code를 created(URI)가 아닌 status(HttpStatus)를 통해 설정한다.
body()의 ResponseEntity생성 과정
body값을 설정한다.
build()와 마찬가지로 DefaultBuilder의 메서드를 호출한다.
차이점은 build()가 아닌 body()를 통해 body에 null이 아닌 실제 데이터를 집어 넣는다.
이후 과정은 build()와 동일하게 진행된다.
response확인
header
body
사실 이 방법은 나중에 어떻게 활용할지 생각이 안난다.
'Spring' 카테고리의 다른 글
하나의 객체에서 여러 Builder 사용해보기 (feat. Builder 컴파일 과정, lombok) (2) | 2023.09.07 |
---|---|
[Spring] test context caching (0) | 2023.05.21 |
공식문서로 알아보는 IoC container (0) | 2023.04.20 |
Jdbc와 Jdbc template비교 (0) | 2023.04.20 |
[Spring JDBC] SimpleJdbcInsert로 쉽게 INSERT하기 (0) | 2023.04.12 |