ResponseEntity란 무엇인가?
ResponseEntity 사용하면, Spring 에서의 HTTP 응답을 더욱 세밀하게 설정할 수 있도록 만들어준다. ResponseEntity가 무엇이길래 응답을 더욱 세밀하게 제어할 수 있도록 만드는 것일까? 이에 대해 알아보기 위해 ResponseEntity가 어떻게 선언돼 있는지 살펴보자.
public class ResponseEntity<T> extends HttpEntity<T> {
...
}
ResponseEntity는 HttpEntity를 상속하는 클래스로, Http 응답과 관련된 설정들을 응답에 추가할 수 있도록 해준다. 즉, 우리가 일반적으로 아는 응답 코드, 헤더, 바디 모두를 설정할 수 있도록 만들어주는 것이다. 이를 통해 API 호출에 대한 응답을 더욱 직관적으로, 그리고 커스텀하게 만들 수 있다.
예를 들어 다음 두개의 함수를 살펴보자.
@Controller
class HomePageController {
@ResponseBody
@GetMapping("/blog/info")
fun getBlogInfo(): BlogInfo {
return BlogInfo("조세영의 Kotlin World", 3)
}
@GetMapping("/blog/info1")
fun getBlogInfo1(): ResponseEntity<BlogInfo> {
return ResponseEntity.ok()
.body(BlogInfo("조세영의 Kotlin World", 3))
}
}
data class BlogInfo(val name: String, val year: Int)
여기서 getBlogInfo 함수는 사용자로부터 '/blog/info' 경로로 요청이 왔을 때 BlogInfo 객체를 반환하며, 스프링 프레임워크는 이를 JSON 형태로 직렬화 해서 바디에 넣어 반환한다. 요청 값은 항상 성공이며 응답 코드는 200번이다. 요청을 실제로 실행해보면, 다음과 같은 결과를 볼 수 있다.
getBlogInfo1 함수는 같은 getBlogInfo 함수와 완전히 같은 동작을 한다. 다른 점은 반환되는 BlogInfo 객체가 ResponseEntity에 감싸져 반환된다는 점이며, ResponseEntity 객체의 ok() 함수를 호출해 응답 코드 200번을 직접 설정하고, body에 들어갈 BlogInfo 객체를 설정한다는 점이다.
하지만, 이 둘은 같은 역할을 하기 때문에 같은 결과가 반환되는 것을 볼 수 있다.
여기서 어차피 똑같은 역할을 하는데 굳이 응답을 ResponseEntity로 감싸야 하는지에 대한 의문이 생길 수 있다. 하지만, ResponseEntity는 응답이 더욱 커스텀 돼야 하는 경우 큰 역할을 한다.
ResponseEntity를 통한 추가 설정
예를 들어, 응답의 헤더에 블로그의 버전 정보(Blog-Version)를 추가해야 한다고 해보자. 이때, 기존 getBlogInfo 함수에서는 BlogInfo 만을 반환하고 있기 때문에 추가적인 헤더 값을 넣을 방법이 없다. 하지만, getBlogInfo1 함수는 다르다. ResponseEntity로 BlogInfo 객체를 감싸 반환하기 때문에 ResponseEntity의 헤더값에 Blog-Version 을 추가하면 된다.
다음과 같이 HttpHeaders 객체를 만든 후, 헤더 값에 Blog-Version을 2로 입력함으로써 블로그의 헤더를 만들 수 있고, ResponseEntity의 headers 함수의 파라미터로 만든 HttpHeaders 객체를 넘김으로써 블로그의 헤더 정보를 추가할 수 있다.
@Controller
class HomePageController {
...
@GetMapping("/blog/info1")
fun getBlogInfo1(): ResponseEntity<BlogInfo> {
val headers = HttpHeaders().apply { // 헤더 생성
add("Blog-Version", "2")
}
return ResponseEntity.ok()
.headers(headers) // 헤더 입력
.body(BlogInfo("조세영의 Kotlin World", 3))
}
}
그러면 /blog/info1로 요청했을 때 다음과 같은 응답이 오는 것을 확인할 수 있다.
이 외에도 응답 코드를 설정하거나, 헤더의 Content-Type을 설정하거나, Body의 값을 커스텀 하게 바꾸거나 해야 하는 경우에도 ResponseEntity를 사용할 수 있다.
ResponseEntity 사용법
Response Code 변경하기
ResponseCode는 status 함수로 변경할 수 있다.
ResponseEntity.status(200) // 응답코드 200번으로 설정
ResponseEntity.status(201) // 응답코드 201번으로 설정
ResponseEntity.status(202) // 응답코드 202번으로 설정
하지만, 자주 쓰이는 응답 코드의 경우 가독성 좋게 만드는 함수가 이미 있으니 이를 활용하는 것이 좋다.
예를 들어 응답코드 200은 ok 함수로 대체될 수 있으며, 이 함수에는 body 값도 함께 사용될 수 있다. 응답코드 201은 created 함수로 대체될 수 있으며, 여기에는 생성된 리소스의 URI 값이 함께 들어가야 한다. 응답코드 202는 accepted로 대체될 수 있다.
ResponseEntity.ok() // status(200)과 동일
ResponseEntity.created([LocationURI]) // status(201)과 동일
ResponseEntity.accepted() // status(202)와 동일
이 외에도 noContent, badResquest, notFound, unprocessableEntity, internalServerError 등 많은 함수들이 미리 정의돼 있다.
public static HeadersBuilder<?> noContent() {
return status(HttpStatus.NO_CONTENT);
}
public static BodyBuilder badRequest() {
return status(HttpStatus.BAD_REQUEST);
}
public static HeadersBuilder<?> notFound() {
return status(HttpStatus.NOT_FOUND);
}
public static BodyBuilder unprocessableEntity() {
return status(HttpStatus.UNPROCESSABLE_ENTITY);
}
public static BodyBuilder internalServerError() {
return status(HttpStatus.INTERNAL_SERVER_ERROR);
}
헤더 변경하기
헤더 값은 앞에서 봤듯이 HttpHeaders 객체를 headers 함수의 파라미터로 넘김으로써 변경할 수 있다.
val headers = HttpHeaders().apply {
add("Blog-Version", "2")
}
ResponseEntity.ok()
.headers(headers)
바디값 변경하기
바디 값은 body 메서드로 넘김으로써 설정될 수 있다.
@GetMapping("/blog/info1")
fun getBlogInfo(): ResponseEntity<BlogInfo> {
return ResponseEntity.ok()
.body(BlogInfo("조세영의 Kotlin World", 3)) // 바디 값에 직렬화 할 수 있는 객체 넘기기
}
만약 정적 미디어를 넘기고 싶다면 다음과 같이 Resource 객체를 가져온 다음, 미디어 타입과 함께 ResponseEntity에 설정해 반환하면 된다. 나머지는 스프링 프레임 웍에서 알아서 처리해준다.
@GetMapping("/image")
fun getImage(): ResponseEntity<Resource> {
// /static/some-image.jpg 위치의 이미지 파일 로드
val imageResource: Resource = ClassPathResource("/static/some-image.jpg")
// 파일의 미디어 타입 설정
val mediaType = MediaType.IMAGE_JPEG
// ResponseEntity를 사용해 미디어 타입과 이미지 리소스 설정해 반환
return ResponseEntity.ok()
.contentType(mediaType)
.body(imageResource)
}