본문 바로가기

백엔드 개발/SpringMVC

Redirect와 Forward에 대하여

1. 원리

(1) redirect

a. 클라이언트가 특정 주소로 요청을 보냄. 

b. 근데 서버가 받아보니 해당 일은 해당 주소에서 처리할 수 없는 일임. 

c. 그래서 서버는 응답 메세지 Header에다가 상태코드 300(redirect 요청)과 함께 어디로 가야 이 일을 처리할 수 있는지 주소(location)를 같이 써서 다시 브라우저에게 보낸다. 

d. 브라우저가 자동으로 해당 주소로 재요청을 한다.

    (브라우저가 재요청 시 상단의 URL이  재요청된 주소로 바뀌어서 클라이언트도 재요청 하는 줄 알 수 있음.)

e. 올바른 주소가 응답하여 클라이언트의 브라우저에 띄워진다.

(이 때 이 자동요청은 오로지 GET 요청밖에 안된다.

따라서 클라이언트가 로그인 정보 쳐서 보냈다하더라도 redirect 되면 올바른 로그인 페이지가 브라우저에 보여질 뿐이다.

클라이언트는 해당 내용을 다시 써야 한다.)

 

redirect를 실생활 예로 설명하면 다음과 같다. 

내가 카드사에 1년치 결제 내역을 요청할려고 전화했다. 그리고 문의 내용을 말했다.

근데 내선전화 타고 타고 간 곳이 알고보니 한도초과를 알려주는 부서였다! 

그래서 해당 부서 직원이 "~~여기로 전화해보세요." 전화번호 주고 끊었다. (우리 학교 행정특)

그래서 내가 그 번호로 직접 다시 전화했다. (이 역할은 웹에선 브라우저가 대신 해준다.)

나는 같은 내용을 다시 한번 문의 해야한다.

(2) Forward

a. 클라이언트가 특정 주소로 요청을 보냈다. 

b. 근데 서버가 보니 해당 내용은 그 주소에서 다룰 수 있는 내용이 아니었다. 그래서 이번엔 서버가 직접 해당 내용을 다룰 수 있는 주소로 해당 요청을 보내줬다. (이 떄 서버는 추가사항을 요청과 함께 실어 보낼 수 있다.)

c. 해당 요청을 처리할 수 있는 주소에서 내용 처리하고 그 결과를 브라우저에 띄운다. 

    ( 원 요청이 GET이면 GET, POST면 POST 그대로 재요청이 되어 응답한다는 점에 Redirect와 다르다.)

** 요청 보내서 응답이 오기까지 클라이언트는 해당 내용이 다른 주소로 넘어가서 처리된 줄 모른다.

   해당 과정들은 전부 서버 내에서 이루어졌기 때문이다. 따라서 브라우저에 띄워진 URL은 요청한 그대로 적혀있다.

 

실생활 예를 들어 설명하면, 

이번엔 카드사에 카드 발급 신청을 위해 전화 했다. 근데 이번에도 한도초과 부서에 전화를 걸어버렸다. 

똑똑한 직원이 자기가 직접 한도초과 부서에 전화해서 고객 정보 넘겨주고 일처리 했다. 

그리고 그 결과를 고객에게 말해줬다. 

고객은 그 일이 딴 부서에서 처리한 줄 모른다. (회사에서 알아서 했으니)

 

여기서도 MVC의 원리가 작동한다. 실생활 예에서는 한도초과 직원이 컨트롤러의 역할이다. 내가 처리 못하는 요청인 걸 알고 가능한 부서로 전화하는 처리를 했기 때문이다. 그리고 부서 간에 넘어간 요청이 바로 모델이다. 둘 사이에 매개체 역할을 한다. 그리고 진짜 일을 처리한 부서가 view 이다. 

 

똑같이 요청을 forward한 jsp가 controller, 이때 넘어가는 request 저장소가 model, 요청을 실제 처리해 브라우저에 띄운 jsp가 view의 역할이다.

2. Spring에서 redirect, forward 돌아가는 과정 

위에는 두 명령어가 돌아가는 원리를 설명했다. 이제 Spring에서 해당 원리들을 어떻게 구현했는지 설명하겠다.

(1) redirect view 

a. 클라이언트가 자신의 회원가입 정보를 저장하기 위해 /register/save라는 주소로 POST 요청을 보냈다. 

b. DispatcherServlet에서 해당 요청을 받아서 그걸 처리할 수 있는 컨트롤러의 매핑된 매소드로 요청을 보냈다.

c. 컨트롤러가 받아보니, 회원가입 기입을 잘못된 방식으로 했다. 그래서 DispatcherServlet에게 redirect를 반환했다.

d. DispatcherServlet이 redirect 라는 반환 값을 받으면, Controller가 반환과 함께 보낸 정보들이 저장된 Model을     

    RedirectView에게 보낸다. (반환과 함께 보낸 정보: 위에선 뭐가 잘못 되었는지 알려주는 String 변수 msg)

e. RedirectView는 모델을 토대로 Redirect 해라는 HTTP 응답 메세지를 만들어 브라우저에게 보낸다. 

    (상태코드: 300, location: 진짜 가야할 주소)

f. 브라우저가 응답메세지에 적힌 주소로 다시 재요청을 보낸다.  

(2) forward

a. forward 들어가기전, Spring의 MVC 과정을 더 구체적으로 알아야 한다. 

우리는 대략적으로 MVC 모델이 어떻게 돌아가는지를 안다. 하지만 DispatcherServlet과 Controller, View 사이를 연결해주는 매개체들이 여럿 있다. 일단 그걸 살펴봐야 forward를 잘 이해할 수 있다. 

밑은 정상적인 요청으로 정상적인 응답까지의 과정을 나타냈다.

   ㄱ. /ch/register/add 주소로 요청이 왔고, DispatcherServlet이 해당 요청을 올바른 컨트롤러에게 전달함. 

   ㄴ. 컨트롤러가 일처리 끝냄. 그리고 "registerForm" 을 반환. (이 이름의 view로 가라.)

         하지만 view 이름만 안다고 해서 과연 DispatcherServlet이 해당 jsp를 찾을 수 있을까? NO!

         경로를 모르기 떄문에 안된다!  찾으려면 경로가 붙은 진짜 이름이 필요 하다! 

   ㄷ. DispatcherServlet이 InternalResourceViewResolver라는 녀석에게

         Controller로 부터 받은 반환 값 "registerForm" 을 보냄. 

   ㄹ. InternalResourceViewResolver가 해당 이름을 경로가 붙은 진짜 이름으로 변환해 DispatcherServlet에게 반환해줌.

      (registerForm > /WEB-INF/views/registerForm.jsp)

   ㅁ. DistpatcherServlet은 해당 진짜 이름을 JSTLview에게 전달. 

   ㅅ. JSTL view는 탐색기 같은 개념. 이 녀석이 registerForm.jsp를 찾아서 연다. 

   ㅇ. registerForm.jsp가 받은 Model을 토대로 응답 만들어서 브라우저에게 보냄. 

b.Spring에서 forward가 일어나는 과정 (InternalResourceView를 이용)

ㄱ. 클라이언트가 특정 주소로 요청을 보냄.

ㄴ. DispatcherServlet이 해당 주소에 맞는 컨트롤러에게 요청을 보냄. 

ㄷ. 컨트롤러가 받아보니 지가 할 수 있는 일이 아니였음! 그래서 B라는 주소로 fowarding 하세요 라고 반환

      (명령어: foward:/register/add) 

ㄹ. 반환 받은 DispatcherServlet은 지가 반환 받은 내용을 InternalResourceView에게 보냄. 

ㅁ. InternalResourceView는 반환 받은 내용을 보고 "아 이 주소는 B란 컨트롤러의 매소드에 매핑되어있습니다."

     라고 DispatcherServlet에게 알려줌. 

ㅅ. DispatcherServlet은 B 컨트롤러에게 다시 요청을 보내고 그 이후 과정은 정상적으로 흘러감.

 

** Forward 쓰는 방법

Forward는 그냥 잘못된 요청에 의한 재처리 용도로만 쓰이지 않는다. 

if 문과 연결해 경우의 수마다 다른 컨트롤러로 갈 수 있게끔 하는 용도로도 쓰인다. 

위의 예제는 은행에 1년치 입출금 내역을 요청했을 때의 예제이다. 

이때 클라이언트가 원하는 양식을 누를 수 있다고 가정하자 (ex- pdf로 받기, csv로 받기 excel로 받기)

해당 요청을 받은 컨트롤러가 회원의 입출금 내역을 모두 다운한 후 클라이언트의 요청에 따라 pdf로 요청했으면, pdf로 출력하는 컨트롤러로, csv면 csv로 출력하는 컨트롤러로 forwarding 했다. 

3. 스스로 해보기 

(1) redirect

	@PostMapping("/register/save") // 4.3부터
	public String save(User user, Model m) throws Exception {
		// 1. 유효성 검사, 잘못 되었으면 redirect 
		if(!isValid(user)) {
			String msg = URLEncoder.encode("id를 잘못 입력하셨습니다.", "utf-8");
			
			m.addAttribute("msg",msg);
			return "redirect:/register/add";
			
			
//			return "redirect:/register/add?msg="+msg; //URL 재작성(reWriting)
		}
		// 2. DB에 신규회원 정보를 저장
		return "registerinfo";
	}

	// 일단 무조건 id 잘못 친걸로 뱉게 만들었음.
		private boolean isValid(User user) {
			return false;
		}
	}

요청 보내기 전

/register/save로 보냈고, 요청이 잘못 되어서 /register/add로 redirecting이 일어났다. 위에서 보면, URL이 바뀐 것을 볼 수 있다. 이는

브라우저가 이와 같은 메세지를 받고 해당 로케이션으로 자동 재요청을 했기 때문이다. 

(2) forward

@PostMapping("/register/save") // 4.3부터
	public String save(User user, Model m) throws Exception {
		// 1. 유효성 검사, 잘못 되었으면 redirect 
		if(!isValid(user)) {
			String msg = URLEncoder.encode("id를 잘못 입력하셨습니다.", "utf-8");
			
			m.addAttribute("msg",msg);
			return "forward:/register/add";
			
			
//			return "redirect:/register/add?msg="+msg; //URL 재작성(reWriting)
		}
		// 2. DB에 신규회원 정보를 저장
		return "registerinfo";
	}

	// 일단 무조건 id 잘못 친걸로 뱉게 만들었음.
		private boolean isValid(User user) {
			return false;
		}
	}

이번엔 명령을 forward로 바꾸고 실행

에러 뜸

** forward시 에러 나는 점, 이유 

우리가 저번에 web-app의 설정을 관리하는 servlet-context에서 @GetMapping 대신에

	<view-controller path="/register/add" view-name="registerForm"/>

을 넣었다. 이유는 /register/add와 매핑된 매소드가 하는 일이 registerForm이라는 view를 띄우는 거 밖에 없었기 때문이다. 그래서 설정에 해당 경로로 요청 들어오면 자동으로 registerForm으로 가는 view-controller라는 설정을 해서 컨트롤러에 매소드 타이핑하는 수고를 덜었다. 

하지만 위에서 설명했다시피 ,forward는 get 요청은 get으로 재요청, post는 post 그대로 재요청 한다. 

해당 회원가입은 클라이언트가 기입한 정보를 데이터 베이스에 쓰는 행위이니 Post에 해당한다. 근데 위와 같이 

view-controller 설정을 해놓으면, 해당 코드는 form을 띄우는 것밖에 못한다. get 요청 밖에 수행을 못하는 것이다.

Post 요청을 수행해야 하는데 해당 컨트롤러가 get 요청밖에 못하는 상황이 발생해서 에러가 난 것이다. 

servlet-context에서 해당 코드를 주석처리하고 원래 컨트롤러의 /register/add와 매핑된 매소드를 post도 가능하게 하여 살리자.  

	
    // GET POST 둘 다 가능하게 매핑
    @RequestMapping(value="/register/add", method={RequestMethod.GET, RequestMethod.POST}) 	
	public String register(){
		return "registerForm";
		
	}

다시 요청을 보내보면

다시 register/add 화면으로 돌아온 것을 볼 수 있다. URL은 바뀌지 않았다. 왜냐하면 서버내에서 재요청 다 해결했기 때문이다. 그래서 상태코드도 200으로 정상이다.

'백엔드 개발 > SpringMVC' 카테고리의 다른 글

Session- 이론  (0) 2023.03.09
쿠키란?  (0) 2023.03.05
@RequestMapping More , 인코딩의 원리  (0) 2023.03.04
@ 어노테이션 전문화, view Controller, Redirection  (0) 2023.03.04
회원 가입 화면 작성하기  (0) 2023.03.03