Redirect와 Forward
2025. 5. 30. 11:55ㆍSpring/MVC
1. 요청과 응답의 방식
| 개념 | 누가 수행? | 의미 |
| POST, GET 등 | 클라이언트(브라우저) | 서버로 어떻게 요청할까, 요청의 종류 |
| redirect, forward | 서버(서블릿, 컨트롤러) | 클라이언트한테 어떻게 응답할까, 응답 후 화면 이동 방식 |
2. Redirect vs Forward
| 리다이렉트 | 포워드 | |
| 요청 흐름 | 요청 2번, 클라이언트한테 새 요청 시킴 | 요청 1번, 서버 안에서 내부 이동 |
| 데이터 유지 | 안됨 (새 요청이니까) | 됨 (request 데이터 유지) |
| URL 변화 | 바뀜 | 안 바뀜 |
| 속도 | 상대적으로 느림 | 빠름 |
| 주로 쓰는 상황 | POST 처리 후 새로고침 문제 방지 | 데이터 넘기면서 화면 전환 |
[Redirect] 요청이 두 번 일어남
(1) 클라이언트 -> 서버 (요청: /login)
(2) 서버 -> 클라이언트 (응답: 상태코드 "302 redirect" + Location "/home")
(3) 클라이언트 -> 서버 (새로운 요청: /home)
(4) 서버 -> 클라이언트 (응답: /home 화면 보여줌)
[Forward] 요청은 한 번, 서버 내부 이동만 있음
(1) 클라이언트 -> 서버 (요청: /login)
(2) 서버 내부에서 /home으로 포워드
(3) 서버 -> 클라이언트 (응답: /home 화면 내용)
3. 사용하는 상황
1) 리다이렉트를 써야하는 상황: POST 등록 후 화면 이동 (PRG 패턴)


- 만약 POST 등록 후 (상품 add) forward를 사용하면
- 서버가 클라이언트한테 바로 상품 상세 화면을 넘겨준다.
- 근데 클라이언트 입장에서는 아직 "요청 URL이 /add" 으로 되어 있기 때문에 사용자가 새로고침(F5) 누르면, 브라우저는 마지막 요청(/add) 을 그대로 다시 보내버리고 /add POST 요청이 또 날아간다.
- 그러면 상품 등록처리가 또 다시 일어나서 중복 처리가 문제 발생한다.
- 반대로 POST 등록 후 redirect를 사용하면
- 서버가 클라이언트한테 /item/{id}으로 다시 요청하고
- 브라우저가 새롭게 GET 요청을 보내게 된다.
- 그러면 브라우저의 주소창도 바뀌어 있으니 새로고침(F5) 해도 /item/{id}을 새로고침 하는 것이고 /add을 재요청하지 않는다.
- 따라서 중복 등록 처리 문제가 해결된다.
- 이렇게 중복 form 제출 문제는 막는 방식을 PRG 패턴이라고 한다. (Post → Redirect → Get)
- 사용자가 form 제출 POST 요청
- 서버가 처리하고 Redirect로 응답
- 브라우저는 해당 화면으로 GET 요청
- 참고로 거의 대부분의 경우, 리다이렉트 후 재요청은 GET 방식이다.
- 일반적인 302, 303 redirect (sendRedirect, redirect:) -> GET 요청으로 바뀐다
- 307, 308 redirect (명시적 사용 시)-> 원래 요청 방식 그대로 유지
2) 포워드를 쓰면 좋은 상황
- 데이터를 그대로 유지하고 싶을 때
- 포워드는 기존 요청과 응답 객체를 그대로 넘기기 때문에 request에 담긴 데이터를 그대로 다음 페이지에서도 꺼내 쓸 수 있다.
- dispatcher.forward(request, response);
- 단순한 화면 이동인 경우
- redirect는 요청이 2번 발생해서 네트워크를 한번 더 타야 해서 조금 느리지만,
- forward는 서버 안에서만 이동하니까 훨씬 빠르고 부담이 적다.
4. 스프링이 지원하는 리다이렉트 방식
| 구분 | 서블릿 | Spring |
| 리다이렉트 방식 | response.sendRedirect("/path") | return "redirect:/path" |
| 누가 처리? | 개발자가 직접 response 객체 조작 | Spring의 뷰리졸버가 알아서 처리 |
| 코드량 | 길고 복잡함 | 간단하고 선언적 |
//흐름 요약!
@Controller 리턴값: "redirect:/items"
→ ViewNameMethodReturnValueHandler에서 ModelAndView("redirect:/items") 생성
→ DispatcherServlet이 등록된 ViewResolver 목록을 순회하며
(InternalResourceViewResolver, ThymeleafViewResolver 등)
viewResolver.resolveViewName("redirect:/items", locale) 호출
첫번째로 null이 아닌 View를 반환하는 경우 해당 뷰를 사용한다.
→ 구현체의 createView(...) 메서드 내부에서 다음 분기 처리:
if (viewName.startsWith("redirect:")) {
return new RedirectView(...);
} else if (viewName.startsWith("forward:")) {
return new InternalResourceView(...);
} else {
return super.createView(viewName, locale);
}
→ "redirect:" 접두어가 있으면 RedirectView 객체 생성됨
→ DispatcherServlet이 view.render(...) 호출
→ RedirectView.render(...) 내부에서 response.sendRedirect(...) 호출