<< 학습 목표 >>
1. 상태 코드를 담아 응답할 수 있다.
2. 데이터 또는 정보를 담아 응답할 수 있다.
3. 상태 코드와 데이터 또는 정보를 담아 응답할 수 있다.
4. @RestController 애너테이션을 사용해 API를 개발할 수 있다.
지금까지 Chapter02 에서는 다음과 같은 것들을 했음
1. 클라이언트의 요청을 받기 위해 컨트롤러 추가하는 방법 : https://codingaja.tistory.com/103
2. 클라이언트가 보낸 다양한 형태의 값 꺼내는 방법 : https://codingaja.tistory.com/104, https://codingaja.tistory.com/105, https://codingaja.tistory.com/106
3. Lombok
4. 클라이언트가 보낸 값을 활용하기 전 유효성 검증하는 방법 : https://codingaja.tistory.com/109, https://codingaja.tistory.com/110
5. RestfulAPI
- 이론 : https://codingaja.tistory.com/125
- 다양한 요청 방식 받는 방법 : https://codingaja.tistory.com/126
- 상황에 맞는 URI 정하는 방법 : https://codingaja.tistory.com/127
이번에는 RestfulAPI의 마지막으로 클라이언트에게 다양한 형태로 RestfulAPI 답게 응답하는 방법을 배워보자
RestfulAPI 답게 응답하려면 상태 코드(Status Code)를 적극적으로 활용해야함
상태 코드에 대해서는 이미 Servlet에서 배웠을 것이므로 상태 코드 자체에 대한 설명은 생략함
"클라이언트가 GET, POST, PUT, DELETE 중 한 방식으로 요청을 했는데 서버가 그 요청을 제대로 처리했다" 라면 200 상태 코드로 응답하면 됨
더 정확하게 POST 방식으로 요청했다면 201 상태 코드로 응답해야함
또한 PUT 방식을 통해 새로운 리소스가 생성됐다면 201 상태 코드로 응답해야함
그러나 PUT 방식을 통해 새로운 리소스가 생성된게 아닌 기존의 리소스가 수정됐다면 200 상태 코드로 응답해야함
예를 클라이언트가 상품 상세 정보를 요청했다면 클라이언트는 GET 방식으로 요청했을것이고 서버가 상품 상세 정보를 찾았다면 서버는 상품 상세 정보와 함께 200 상태 코드로 응답해야함
만약 클라이언트가 잘못된 정보를 보내 서버가 상품 상세 정보를 찾지 못했다면 서버는 400번대 상태 코드만 응답하면 됨
클라이언트가 상품 정보 추가를 요청했다면 클라이언트는 POST 방식으로 요청했을 것임
서버가 상품 정보를 추가했다면 200 또는 201 상태 코드로 응답해야함
이때 간혹 상품 정보 추가에 성공했다는 의미로 "success", "ok" 등의 문자열을 응답하는 개발자가 있는데 이는 잘못된 것
200 또는 201 상태 코드가 "이미 요청을 성공적으로 처리했다" 라는 의미를 갖고 있는 것
"sucess", "ok" 등의 문자열과 200 또는 201 상태 코드로 응답 하는 건 "역 전 앞" 처럼 잘못된 표현임
만약 상품 이름을 빠뜨리고 상품 정보 추가 요청을 했다면 클라이언트가 잘못된 요청을 한 것이므로 서버는 400번대 상태 코드만 응답하면 됨
이때 역시 추가를 하지 못했다는 의미로 "fail" 등을 담아 응답하면 안됨
클라이언트가 상품 이미지 수정을 요청했다면 클라이언트는 PUT 방식으로 요청했을 것임
서버가 상품 이미지를 수정했다면 200 상태 코드로 응답해야함
클라이언트가 상품 상세 정보 삭제 또는 상품 이미지 삭제 요청을 했다면 클라이언트는 DELETE 방식으로 요청했을 것임
서버가 클라이언트의 요청을 처리했다면 200 상태 코드로 응답해야함
이제 SpringFramework의 컨트롤러가 RestfulAPI 답게 응답하는 방법을 배워보자
방법1. Servlet에서 배웠던것처럼 Servlet과 똑같이 HttpServletResponse 를 사용해 상태 코드 응답하기
방법2. ResponseEntity<Void> 를 사용해 상태 코드 응답하기
<< 방법1. Servlet에서 배웠던것처럼 Servlet과 똑같이 HttpServletResponse 를 사용해 상태 코드 응답하기 >>
프로젝트 -> com.study.chapter02 -> RestController 클래스를 추가하고 아래 코드를 추가하자
package com.study.chapter02;
import java.time.LocalDateTime;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import jakarta.servlet.http.HttpServletResponse;
@Controller
public class RestController {
@GetMapping("/chapter02/rest/1")
public void controller1(HttpServletResponse response) {
System.out.println("controller1 컨트롤러 호출 => " + LocalDateTime.now());
response.setStatus(200);
}
}
<< 코드 설명 >>
(1). 컨트롤러에 HttpServletResponse 타입 매개변수를 선언해 클라이언트에게 상태 코드를 설정한 응답이 가도록 할 수 있음
(2). setStatus 메서드를 사용해 클라이언트에게 응답할 상태 코드를 설정할 수 있음
이제 서버를 시작한 후 해당 경로로 접근해보자
방법1은 이게 끝임
200이 아닌 201로 응답하고 싶다면 [ response.setStatus(201); ] 로 설정하면 됨
400이나 500번대 상태 코드를 응답하고 싶다면 [ response.setStatus(응답할 상태 코드 번호); ] 로 설정하면 됨
여기서 잠깐! [ 방법2 ] 로 넘어가기 전에 이 글 전의 컨트롤러와 이 글의 컨트롤러를 비교해보자
RestController 클래스 내 코드를 다음과 같이 바꾸자
package com.study.chapter02;
import java.time.LocalDateTime;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import jakarta.servlet.http.HttpServletResponse;
@Controller
public class RestController {
@GetMapping("/chapter02/rest/1")
public void controller1(HttpServletResponse response) {
System.out.println("controller1 컨트롤러 호출 => " + LocalDateTime.now());
response.setStatus(200);
}
@GetMapping("/chapter02/rest/2")
public void controller2() {
// ...
System.out.println("controller2 컨트롤러 호출 => " + LocalDateTime.now());
// ...
}
}
<< 코드 설명 >>
(1). 이 글에서 배운 상태 코드로 응답하는 컨트롤러
(2). 이 전 글까지 계속 사용했던 아무것도 응답하지 않는 컨트롤러
서버를 재시작하고 (2) 컨트롤러로 접근해보자
지금까지 계속 사용하면서 느꼈지만 접근했을 때 항상 결과가 다음과 같았음
그 이유는 컨트롤러는 재대로 호출했지만 컨트롤러가 아무것도 응답하지 않아서 발생했던 것
이제 다시 (1) 컨트롤러로 접근해보자
지금까지와는 다르게 상태 코드를 설정해 응답했으므로 상태 코드만 보임
이렇게 API를 RestfulAPI 답게 만들면 응답이 내가 의도한대로 됨
여기서 또 다시 잠깐! [ 방법1 ] 에서 컨트롤러에 매개변수를 HttpServletResponse 만 넣었는데 이렇게 활용할 수 있다는 것이지 꼭 이렇게만 해야된다는게 아님
컨트롤러에서 클라이언트가 보낸 값을 사용하려면 전에 배운 것처럼 클라이언트가 보낸값을 꺼내기 위해 매개변수를 적절히 추가할 수 있음
예를 들어 URI가 /chapter02/rest/1 인 컨트롤러로 클라이언트가 데이터를 보낸다고 하자
클라이언트가 이름이 tel 인 파라미터에 값을 담아 보냈고 컨트롤러가 tel 파라미터를 꺼내려면 다음과 같이 할 수 있음
다시 본론으로 돌아가서
<< 방법2. ResponseEntity<Void> 를 사용해 상태 코드 응답하기 >>
프로젝트 -> com.study.chapter02 -> RestController 에 아래 코드를 추가하자
package com.study.chapter02;
import java.time.LocalDateTime;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import jakarta.servlet.http.HttpServletResponse;
@Controller
public class RestController {
// ...
@GetMapping("/chapter02/rest/3")
public ResponseEntity<Void> controller3() {
System.out.println("controller3 컨트롤러 호출 => " + LocalDateTime.now());
return ResponseEntity.status(200).build();
}
// ...
}
<< 코드 설명 >>
(1). 컨트롤러의 반환 타입이 ResponseEntity<Void> 임
컨트롤러의 반환 타입이 ResponseEntity<?> 라면 "이 컨트롤러는 RestfulAPI 다" 라고 명시한 것
[ 방법1 ] 은 "이 컨트롤러는 RestfulAPI 다" 라고 명시하진 않았지만 응답을 RestfulAPI답게 응답한 것이고 [ 방법2 ] 는 "이 컨트롤러는 RestfulAPI 다" 라고 명시한 것
(2). status 메서드를 사용해서 클라이언트에게 응답할 상태 코드를 설정할 수 있음
서버를 재시작하고 이 컨트롤러로 접근하면 [ 방법1 ] 과 마찬가지로 의도한대로 상태 코드만 보이는 응답 결과가 보임
클라이언트에게 응답할 때 상태 코드만 응답하진 않음
상품 정보 조회 같은 경우에는 조회한 상품의 정보와 상태코드를 함께 응답해야함
이렇게 컨트롤러가 클라이언트에게 데이터(또는 정보)를 담아 응답할 때는 ResponseEntity<?> 를 사용해야함
? 자리에는 응답할 데이터 타입을 명시함
또한 ? 자리는 제네릭스 타입이 들어가는 자리이므로 Wrapper 클래스 또는 DTO 클래스명을 쓰면 됨
직접 실습을 통해 알아보자
우선 서버가 간단한 데이터를 응답하는 경우를 보자
프로젝트 -> com.study.chapter02 -> RestController 클래스 내 아래 컨트롤러를 추가하자
package com.study.chapter02;
import java.time.LocalDateTime;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import jakarta.servlet.http.HttpServletResponse;
@Controller
public class RestController {
// ...
@GetMapping("/chapter02/rest/4")
public ResponseEntity<Integer> controller4() {
System.out.println("controller4 컨트롤러 호출 => " + LocalDateTime.now());
return ResponseEntity.status(200).body(117);
}
// ...
}
<< 코드 설명 >>
(1). 컨트롤러가 클라이언트에게 정수를 응답하기 위해 ? 자리에 정수(int)의 Wrapper 클래스인 Integer 를 넣었음
(2). 응답할 때는 우선 status 메서드로 상태 코드를 설정하고 body 메서드를 사용해 응답할 값을 담음
컨트롤러가 서버로 실수를 응답할 때는 ? 자리에 Double 을 넣으면 됨
컨트롤러가 서버로 문자를 응답할 때는 ? 자리에 Character 를 넣으면 됨
컨트롤러가 서버로 문자열을 응답할 때는 ? 자리에 String 을 넣으면 됨
그렇다면 컨트롤러가 클라이언트로 정보를 전달하려면 어떻게 해야할까?
이도 다르지 않음
먼저 실습용 DTO를 만들자
프로젝트 -> com.study.chapter02 -> DataDto 클래스를 추가 하고 아래 코드를 추가하자
package com.study.chapter02;
import java.time.LocalDateTime;
import lombok.Data;
@Data
public class DataDto {
private int idx; // 회원 번호
private String id; // 아이디
private String pw; // 비밀번호
private LocalDateTime joinDateTime; // 회원 가입 날짜
}
이제 컨트롤러를 추가하자
프로젝트 -> com.study.chapter02 -> RestController 에 아래 코드를 추가하자
package com.study.chapter02;
import java.time.LocalDateTime;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import jakarta.servlet.http.HttpServletResponse;
@Controller
public class RestController {
// ...
@GetMapping("/chapter02/rest/5")
public ResponseEntity<DataDto> controller5() {
System.out.println("controller5 컨트롤러 호출 => " + LocalDateTime.now());
DataDto data = new DataDto();
data.setIdx(11);
data.setId("id123");
data.setPw("mypassword");
data.setJoinDateTime(LocalDateTime.now());
return ResponseEntity.status(200).body(data);
}
// ...
}
<< 코드 설명 >>
(1). 앞서 실습했던 방법처럼 ? 자리에 응답할 정보의 데이터 타입인 DTO 클래스명을 넣었음
(2). 마찬가지로 앞서 실습했던 방법처럼 body에 응답할 정보를 넣었음
서버를 재시작 한 후 이 컨트롤러로 접근해보자
애너테이션 중에 @RestController 애너테이션이 있음
이 애너테이션은 "여기에 속한 모든 컨트롤러가 RestfulAPI다" 라고 명시한 것
프로젝트 -> com.study.chapter02 -> RestfulAPIController 클래스를 추가하고 아래 코드를 추가하자
package com.study.chapter02;
import java.time.LocalDateTime;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RestfulAPIController {
@GetMapping("/chapter02/restful/1")
public DataDto controller1() {
System.out.println("controller1 컨트롤러 호출 => " + LocalDateTime.now());
DataDto data = new DataDto();
data.setIdx(11);
data.setId("id123");
data.setPw("mypassword");
data.setJoinDateTime(LocalDateTime.now());
return data;
}
}
<< 코드 설명 >>
@RestController 애너테이션을 정확하게 이해하기 위해서는 비교해서 봐야함
@Controller 애너테이션이 붙은 컨트롤러에서 DTO를 응답하려면 반환 타입이 반드시 ResponseEntity<?> 로 해야함
@RestController 애너테이션이 붙은 컨트롤러에서 DTO를 응답하려면 반환 타입은 ? 로 하면 됨
그러나 이때 꼭 ? 로 해야하는건 아니고 ResponseEntity<?> 로 해도 됨
@RestController 애너테이션이 붙은 컨트롤러의 또 다른 특징은 상태코드가 항상 200으로 고정된다는 것
상태코드가 200으로 고정된건 @RestController 애너테이션 때문이 아닌 서버의 설정 때문임
컨트롤러가 상태 코드를 설정하지 않고 응답을 한다면 서버는 응답의 상태 코드를 200으로 설정함
즉, 상태 코드 200이 기본값임
그래서 @RestController 애너테이션이 붙은 컨트롤러는 기본적으로 200 상태 코드로 고정되어있음
만약 @RestController 애너테이션이 붙은 컨트롤러의 상태 코드를 201, 400, 500 등 다른 값으로 바꾸고 싶다면 컨트롤러의 반환 타입을 ResponseEntity<?> 로 해주면 됨
프로젝트 -> com.study.chapter02 -> RestfulAPIController 에 아래 코드를 추가하자
package com.study.chapter02;
import java.time.LocalDateTime;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RestfulAPIController {
// ...
@GetMapping("/chapter02/restful/2")
public ResponseEntity<DataDto> controller2() {
System.out.println("controller2 컨트롤러 호출 => " + LocalDateTime.now());
DataDto data = new DataDto();
data.setIdx(11);
data.setId("id123");
data.setPw("mypassword");
data.setJoinDateTime(LocalDateTime.now());
return ResponseEntity.status(400).body(data);
}
// ...
}
<< 코드 설명 >>
(1). 앞서 얘기했듯 @RestController 애너테이션이 붙은 컨트롤러라도 반환 타입을 ResponseEntity<?> 로 할 수 있음
(2). 이럴 경우 내가 원하는 상태 코드와 함께 데이터를 담아 보낼 수 있음
'Spring + Boot > Boot-Chapter02' 카테고리의 다른 글
Chapter02. Spring Boot - Restful실습, @PathVariable (0) | 2023.06.01 |
---|---|
Chapter02. Spring Boot - RestfulAPI 실습, PUT, DELETE 방식 요청 받기 (0) | 2023.05.31 |
Chapter02. Spring Boot - Restful API 이론 (0) | 2023.05.31 |
Chapter02. Spring Boot - Validator 유연하게 생성하기 (0) | 2023.04.12 |
Chapter02. Spring Boot - 커맨드 객체 검증 하기 (0) | 2023.04.11 |