Forward & Redirect

웹에서 제공하는 현재 작업중인 페이지에서 다른페이지로 이동하는 2가지 페이지 전환기능

  • 가장 큰 차이 : 사용자에게(웹브라우저에서) URL 변경되어 보이는지


Forward

제목 없음

  • WAS의 Servlet or JSP가 요청받아 처리하다가,
    추가적인 처리를 같은 웹 어플리케이션 안에 있는 다른 Servlet or JSP에 위임하여 처리
    • 주로 Servlet으로 request 요청을 보내고 응답처리를 JSP로 데이터를 가공하여 만듦
  • Web Container 차원에서 페이지의 이동만 존재, 웹 브라우저는 다른 페이지로 이동했음을 알 수 없음
    => 웹 브라우저에는 최초 호출한 URL만 표시(이동한 페이지의 URL 정보는 확인할 수 없음)
  • 현재 페이지와 forward에 의해 호출될 페이지는 Request 객체와 Response 객체를 공유
    = 현재 페이지에서 이동할 URL로 사용자가 최초로 요청한 요청정보를 그대로 전달함

    • ex) 게시글 작성 - forward 사용하여 응답 페이지 부르면 문제 발생
      사용자가 보낸 요청 정보로 글쓰기 기능 수행
      -> 만약 글쓰기 응답 페이지에서 새로고침
      -> 요청 정보가 그대로 살아있어 요청이 여러 번 전달되어 동일한 게시물이 여러 번 등록될 수 있음


    => forward로 응답하는 것은 시스템에 변화가 생기지 않는 단순 조회 요청(READ)의 경우 바람직
    ex) 글 목록 보기, 검색 등


forward 동작과정

  1. Client(웹 브라우저) -> Server(Servlet1) : 요청을 보냄
  2. Servlet1 : 요청처리 후, 결과를 HttpServletRequest에 저장
  3. Servlet1 -> (같은 웹 어플리케이션 안에 있는) Servlet2
    결과가 저장된 HttpServletRequest와 응답을 위한 HttpServletResponse 전송(forward)
    = request, response 객체가 한번만 생성
  4. Servlet2 : 받은 HttpServletRequest와 HttpServletResponse를 이용하여 요청처리 후 웹 브라우저에게 결과 전송



Redirect

http프로토콜로 정해진 규칙

제목 없음

  • Server가 Client로부터 요청 받은 후, Client에게 특정 URL로 이동하라고 요청
    • 웹 브라우저(Client)는 URL을 지시된 주소로 바꾸고 해당 주소로 이동
  • 새로운 페이지에서 Request와 Response객체가 새롭게 생성
    (처음 보냈던 최초의 Request와 Response 객체는 유효하지 않음)

    • ex) 게시글 작성 - redirect를 사용하여 응답 페이지 부르기
      사용자가 보낸 요청 정보로 글쓰기 기능 수행
      -> 글쓰기 응답 페이지에서 새로고침
      -> 처음의 요청 정보는 존재하지 않으므로 게시물이 여러 번 등록되지 않음


    => redirection 사용은 시스템에 변화가 생기는 요청(CREATE, UPDATE, DELETE)의 경우 바람직
    ex) 회원가입, 글쓰기 등


redirect 동작과정

  1. Client -> Server(Servlet1) : 요청 받음
  2. Servlet1 -> Client
    헤더(Location)에 상태코드 302 + redirect 할 URL 포함하여 반환
    같은 서버, 다른 서버에 있는 페이지 모두 가능
  3. Client
    1) 서버로부터 받은 상태 값이 302이면(= 리다이렉션 응답을 받게 되면) Location에 포함된 URL로 재요청
    2) 이때 브라우저의 주소창은 새 URL로 변경 + Request와 Response객체 새롭게 생성
    = request, response 객체 여러번 생성
    3) 서블릿이나 JSP는 redirect하기 위해 HttpServletResponse 클래스의 sendRedirect() 사용



Forward 실습

  1. FrontServlet, NextServlet이란 두 개의 서블릿을 작성
  2. localhost:8080/firstweb/front를 요청하면 FrontServlet이 실행
  3. FrontServlet에서는 랜덤한 주사위 값을 구하고, 그 값을 NextServlet에게 forward
  4. NextServlet에서는 FrontServlet으로부터 전달 받은 주사위 값만큼 “hello” 출력


@WebServlet("/front")
public class FrontServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    public FrontServlet() {
    	super();
    }

    protected void service(HttpServletRequest request,
    	HttpServletResponse response) throws ServletException, IOException {
            
    	int diceValue = (int)(Math.random() * 6) + 1; 
    	request.setAttribute("dice", diceValue);

    	RequestDispatcher requestDispatehcer = request.getRequestDispatcher("/next");
    	requestDispatehcer.forward(request, response);
    }
}
  • NextServlet에게 request, response 객체 전달 : RequestDispatcher라는 객체를 이용해 전달
    • HttpServletRequestgetRequestDispatcher() : 포워딩을 수행할 서블릿 지정
      -> 인자 값으로 포워딩을 수행할 서블릿의 url 입력
    • RequestDispatcher의 forward() : 전달할 request와 response를 인자로 받음


@WebServlet("/next")
public class NextServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    public NextServlet() {
    	super();
    }
    
    protected void service(HttpServletRequest request,
    	HttpServletResponse response) throws ServletException, IOException {
      
    	response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head><title>form</title></head>");
        out.println("<body>");
        
        //전달받은 request에서 dice 정보 get
        int dice = (Integer)request.getAttribute("dice");
        out.println("dice : " + dice);
        
        for(int i = 0; i < dice; i++) {
            out.print("<br>hello");
        }
        
        out.println("</body>");
        out.println("</html>");
    }
}

결과

제목 없음

Spring에서 Forward 사용

  • 구매상세 페이지 : 시스템에 변화가 생기지 않는 단순 조회 요청
    -> forward
  • return "viewname";
    • 앞에 / 가 붙든 안붙든 상관없이 view/ (SpringBoot에서는 yml의 view 설정에 따라) 다음으로 감
    • view 밑으로 폴더 명부터 적어주기
@Controller
@RequestMapping("/api/v1/purchase/*")
@RequiredArgsConstructor
public class PurchaseController {

    private final PurchaseService purchaseService;
    private final UserService userService;
  
    //구매상세
    @GetMapping("purchase/{purchaseNo}")
    public String getPurchase(@PathVariable int purchaseNo, Purchase purchase, 
    	Model model) throws Exception {

    	purchase = purchaseService.getPurchase(purchaseNo);
    	model.addAttribute(purchase);
    	return "purchase/getPurchase";
    }
}



Spring에서 Redirect 사용

  • 결제 후 구매상세 페이지로 이동 : 요청 정보가 그대로 살아있으면 요청이 여러 번 전달되어 여러 번 구매될 수 있음
    -> redirect 이용해서 구매상세 메서드로 재요청
  • return "redirect:/";
    • 컨트롤러로 재 요청하는 주소
@Controller
@RequestMapping("/api/v1/purchase/*")
@RequiredArgsConstructor
public class PurchaseController {

    private final PurchaseService purchaseService;

    //결제
    @PostMapping("payment")
    public String insertPurchase(Purchase purchase, HttpSession session, 
    	Model model) throws Exception {
    	
	purchase = purchaseService.insertPurchase(purchase);
    	model.addAttribute(purchase);

    	return "redirect:/api/v1/purchase/purchase/" + purchase.getPurchaseNo();
    }
}



Reference
https://mangkyu.tistory.com/51?category=762469
https://goodncuteman.tistory.com/58
https://bellog.tistory.com/80
https://bellog.tistory.com/79?category=911042
https://velog.io/@100bona/Spring-Redirect-vs-Forward
http://jonggs.blogspot.com/2018/11/spring-redirect-forward.html

업데이트: