Spring MVC/01.Spring @MVC

08. Redirection과 flash scope

  • -
반응형

post /mod의 문제점

페이지 흐름과 관련해서 기존의 post /mod의 문제점에 대해 살펴보자.

post로 요청된 /mod가 성공적으로 동작되면 index.html로 연결된다.

@PostMapping("/mod")
public String doModEmp(Model model, @ModelAttribute("emp") @Valid Employee emp, BindingResult result) {
    if (result.hasErrors()) {
        List<ObjectError> globalErrors = result.getGlobalErrors();
        for (ObjectError ge : globalErrors) {
            if (ge.getCode().equals(MyPasswordEquals.class.getSimpleName())) {
                result.rejectValue("cPassword", "Equal.pass", ge.getDefaultMessage());
            }
        }
        // 직원 정보 수정 페이지로 다시 이동한다.
        return "modEmp";
    }
    model.addAttribute("message", emp);
    service.setEmp(emp);
    return "modSuccess";
}

 

만약 페이지 렌더링에 시간이 좀 걸려서 클라이언트 쪽에 화면이 안보이면 클라이언트는 어떻게 행동할까? 아마도 재촉의 의미로 새로고침을 시도할 것이다. 이 새로고침의 의도는 빨리 결과를 보여줘일 확률이 높다. 그런데 정작 새로고침이 동작하면 다음과 같은 흉물스런 경고 페이지가 출력된다.

 

페이지의 새로고침이란 화면을 다시 그린다는 뜻인데 화면을 새로 그리려면 서버에 요청해서 출력할 HTML을 다시 받아와야 한다. 즉 새로 고침의 기술적인 의미는 서버로 새롭게 요청을 날리는 것으로 위 경고 메시지의 의미는 사용자가 마지막으로 제출했던 양식 즉 form 데이터를 다시 제출할것이냐는 의미이다. 겨우 새로고침 한번 했을 뿐인데..

아래 이미지를 참조하면 처음 사용자가 post로 요청 후 성공해서 200 상태값을 수신한 상태에서 사용자가 새로고침 하면 마지막 처리했던 post 요청을 다시 호출하는 것이다.

원본 이미지 경로: https://en.wikipedia.org/wiki/Post/Redirect/Get

 

만약에 구매 행위였다면 계속해서 중복 구매가 이뤄질 수 있는 것이다.

 

PRG

위 상황을 피하기 위해서 사용하는 페이지 흐름 패턴으로 PRG 사용한다. PRGPost로 들어온 요청이 성공했을 때 redirect를 이용해서 Get으로 유도하라는 이야기이다.

위 그림을 살펴보면 POST 요청이 성공했을 때 redirect 명령으로 사용자에게 새로운 GET 요청이 전달되었고 200으로 종료한다. 이제 마지막으로 사용자가 가지고 있는 요청은 GET 이기 때문에 처음의  POST 요청이 다시 시도되지 않는다. 물론 기존의 forward와 달리 새로운 요청이기 때문에 결과를 사용자에게 보여주기 위해서는 다시 조회 해야 한다.

 

redirect 처리

요청을 redirect 처리할 때 HttpServletResponse의 sendRedirect를 사용할 수 있다.

스프링에서 redirect를 처리할 때는 return 하는 문자열의 앞에 redirect: 라는 접두어를 붙여주면 끝이다. 필요에 따라서 절대 경로 및 상대경로를 사용할 수 있는데 '/' 의 의미는 context-root를 나타내게 된다.

return "redirect:modSuccess";               // 상대 경로
return "redirect:/modSuccess";              // 절대 경로
return "redirect:http://www.google.com";    // 다른 서버 자산 호출

 

이제 기존의 요청을 PRG로 변경해보자.

@PostMapping("/mod")
public String doModEmp(Model model, @ModelAttribute("emp") @Valid Employee emp, BindingResult result) {
    if (result.hasErrors()) {
        List<ObjectError> globalErrors = result.getGlobalErrors();
        for (ObjectError ge : globalErrors) {
            if (ge.getCode().equals(MyPasswordEquals.class.getSimpleName())) {
                result.rejectValue("cPassword", "Equal.pass", ge.getDefaultMessage());
            }
        }
        return "modEmp";
    }
    return "redirect:/modSuccess";
}

@GetMapping("/modSuccess")
public String modSuccess(Model model) {
    Employee emp = service.getEmp();
    model.addAttribute("emp", emp);
    return "modSuccess";
}

이제 새로고침을 하더라도 POST 요청이 발생하지 않기 때문에 경고창이 나오지 않는 것을 확인할 수 있다.

 

redirect 시의 데이터 전달

위의 흐름에서 modSuccess 페이지가 열릴 때 처음 열리는 경우와 두번, 세번째 열리는 것은 상황이 다르다. 처음 열렸을 때에는 요청한 수정 동작이 잘 동작 되었음을 알려주면 좋겠고 이후는 그럴 필요가 없다.

그렇게 하기 위해서는 각각의 상황을 구별하는 특별한 flag가 있어야 하는데 redirect는 새로운 request를 유발하기 때문에 request parameter나 attribute를 이용할 수 없고 session attribute를 이용해야 한다. session은 서버의 메모리를 사용하기 때문에 불필요한 경우는 삭제해줘야 하는 번거러움은 덤이다.

스프링에서는 이런 상황에서 써먹을 수 있는 RedirectAttributes와  Flash Scope를 제공해준다.

RedirectAttributes는 Model을 상속 받아서 사용법은 Model과 동일한데 redirect 시 query-string 을 이용해서 다음 페이지로 파라미터를 전달한다.

또 하나의 재미있는 기능으로 Flash Scope라는 것을 지원하는데 Flash Scope는 redirect 가 완료될 때까지만 저장되는 스프링이 만든 scope로 1회성 세션이라고 생각하면 편하다. 

@PostMapping("/mod")
public String doModEmp(Model model, @ModelAttribute("emp") @Valid Employee emp, 
                                    BindingResult result, RedirectAttributes redir) {
    if (result.hasErrors()) {
        List<ObjectError> globalErrors = result.getGlobalErrors();
        for (ObjectError ge : globalErrors) {
            if (ge.getCode().equals(MyPasswordEquals.class.getSimpleName())) {
                result.rejectValue("cPassword", "Equal.pass", ge.getDefaultMessage());
            }
        }
        // 직원 정보 수정 페이지로 다시 이동한다.
        return "modEmp";
    }
    service.setEmp(emp);
    // URL에 query string 형태로 파라미터를 전달할 때
    redir.addAttribute("id", emp.getId());
    // flash scope에 속성 저장
    redir.addFlashAttribute("modify", true);
    return "redirect:/modSuccess";
}

 

flash scope에 넣어둔 modify 라는 플레그는 modSuccess가 처음 불렸는지 아닌지를 판별할 수 있는 값이다. 

modSuccess.html 하단에 modify 플래그를 사용하는 javascript 코드를 추가하자.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>수정 결과</h1>
	<ul th:object="${emp}">
		<li th:text="|아이디: *{id}|">아이디
		<li th:text="|이름: *{name}|">이름
		<li th:text="|비밀번호: *{password}|">비밀번호
		<li th:text="|이메일: *{email}|">이메일
		<li th:text="|생일: *{birth}|">생일
		<li th:text="|은행잔고: *{balance}|">은행잔고
		<li th:text="|사번: *{empNo}|">사번
	</ul>
</body>
<script th:inline="javascript">
	if([[${modify}]]){
		alert("수정 되었습니다.");
	}
</script>
</html>

 

동작 결과 페이지의 url에는 id라는 파라미터가 설정 되었고(여기서 의미는 없다. 된다는거..) 처음 한번은 "수정 되었습니다. "라는 메시지가 출력되는 것을 볼 수 있다. 하지만 페이지를 새로고침 하면 더 이상 출력되지 않는다.

반응형

'Spring MVC > 01.Spring @MVC' 카테고리의 다른 글

[spring]filter vs interceptor vs AOP  (0) 2021.10.21
[SpringBoot]file upload /download 처리  (9) 2021.05.06
07. 파라미터와 validation  (0) 2020.07.08
06. 파라미터의 formatting  (0) 2020.07.07
05. Handler Interceptor  (1) 2020.07.03
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.