티스토리 뷰
토이 프로젝트로 배우는 자바 스프링 [7]. @Controller에서 객체 리턴했을 때 발생하는 호출 (FORWARD)
kkoon9 2024. 11. 18. 21:50자바와 스프링에 대한 기본 지식을 기르기 위해 토이 프로젝트를 시작했습니다.
프론트 코드 : https://github.com/laboratory-kkoon9/connector_front
백엔드 코드 : https://github.com/laboratory-kkoon9/connector_back
배경
@RestController와 @Controller의 차이점을 공부하는 도중 생긴 에러입니다.
@Controller
public class MyRestController {
@GetMapping("/api/hello")
public User hello() {
return User.builder().id(1L).name("eric").build();
}
}
이전 포스팅에서 Filter를 거치지 않게 하기 (shouldNotFilter)를 배웠습니다.
현재 상황을 보면 @Controller로 선언된 API를 호출했는데, api 요청이 끝날 때 /api를 prefix로 한 요청이 한 번 더 filter에 걸리고 있습니다. 이때, dispatcherType이 FORWARD로 되어 있다는 것을 확인했습니다.
그래서 /api/api/hello라는 request는 jwtFilter에 걸쳐서 jwt token 예외를 발생시켰습니다.
원인
이 문제는 서블릿 포워드(forward) 동작이 발생하고 있기 때문에 나타나는 현상이라고 합니다.
@Controller로 선언된 API에서는 dispatcherType이 FORWARD로 설정되고 추가 요청이 발생한다고 합니다.
먼저 @Controller와@RestController의 차이점을 알아봅시다.
@Controller vs @RestController
Spring MVC에서는 기본적으로 @Controller는 뷰(view)를 반환하는 것으로 간주합니다.
예를 들어, String을 반환하면 이는 뷰 이름(view name)으로 간주되어, Spring의 뷰 리졸버(View Resolver)에서 해당 이름의 뷰를 찾으려 합니다. 뷰 이름이 매핑되지 않으면, 기본적으로 포워드(forward)를 통해 해당 경로로 다시 요청이 전달됩니다.
@RestController는 @Controller + @ResponseBody의 조합입니다.
즉, JSON 형식으로 직접 응답을 반환하므로 뷰 리졸버가 동작하지 않습니다. 반환 값이 뷰 이름이 아니라 HTTP 응답 바디로 전송되기 때문에 포워드 동작이 발생하지 않습니다.
prefix가 붙는 원인
@Controller로 선언했기 때문에 뷰 리졸버가 동작했다는 걸 알았고, 뷰 이름이 매핑되지 않았기 때문에 forward를 통해 해당 경로로 다시 요청이 전달되었다는 것까지 알게 되었습니다.
그렇다면 왜 "/api/api/hello"로 forward 되었을까요?
/api/hello 뿐만 아니라 /api/hello/temp 등등 여러 path로 호출해봤을 때 다음과 같은 패턴이 있었습니다.
- /api/hello/temp로 호출했을 때 : /api/hello/api/hello/temp 로 forward
- /api/hello/temp/tmp로 호출했을 때 : /api/hello/temp/api/hello/temp/tmp 로 forward
이걸로 미루어봤을 때 api path에서 마지막 depth를 삭제 한 path + api path로 forward하는게 아닐까 싶습니다.
정확한 이유는 좀 더 공부해본 뒤에 다른 포스팅에서 다뤄보도록 하겠습니다.
해결방법
forward를 하게 하지 않으려면 여러 방법이 존재합니다.
제 상황에서 가장 현명한 해결 방법은 @RestController를 사용하거나 리턴 타입을 ResponseEntity 타입으로 내려주는 것입니다.
@Controller
public class MyRestController {
@GetMapping("/api/hello")
public ResponseEntity<User> helzzlo() {
return ResponseEntity.ok(User.builder().id(1L).name("eric").build());
}
}
@RestController
public class MyRestController {
@GetMapping("/api/hello")
public User helzzlo() {
return User.builder().id(1L).name("eric").build();
}
}
'개발 노트 > 토이 프로젝트로 배우는 스프링+자바' 카테고리의 다른 글
토이 프로젝트로 배우는 자바 스프링 [6]. Filter를 거치지 않게 하기 (shouldNotFilter) (0) | 2024.07.12 |
---|---|
토이 프로젝트로 배우는 자바 스프링 [5]. 엔티티의 생성시각을 자동으로 저장하기 (JPA Auditing) (0) | 2024.04.23 |
토이 프로젝트로 배우는 자바 스프링 [4]. WebSecurityConfigurerAdapter deprecated (1) | 2024.01.28 |
토이 프로젝트로 배우는 자바 스프링 [3]. API path와 HTTP Method로 권한 분리하기 (0) | 2024.01.23 |
토이 프로젝트로 배우는 자바 스프링 [2]. 연관관계 추가 및 삭제 (0) | 2024.01.16 |
- Total
- Today
- Yesterday
- node.js
- 클린 코드
- Effective Java
- 프로그래머스
- Olympiad
- 클린 아키텍처
- 정규표현식
- BAEKJOON
- Spring Boot
- Kotlin
- 코테
- kotest
- programmers
- Spring
- BOJ
- kkoon9
- 디자인패턴
- 테라폼
- C++
- 디자인 패턴
- AWS
- MSA
- Algorithm
- JPA
- 알고리즘
- 백준
- 이팩티브 자바
- 이펙티브 자바
- Java
- 객체지향
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |