Redirect와 Forward

2025. 5. 30. 11:55Spring/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(...) 호출​