도입
물건을 주문하는 서비스를 개발하거나 회원을 등록하는 서비스를 개발할 때 HTTP의 Post 메소드를 주로 사용한다.
이 때, Post로 상품을 주문하도록 하고, 웹페이지를 바로 랜더링했다고 생각하면 매우 끔찍한 일이 벌어진다. 왜냐면 최종적인 HTTP가 Post이기 때문에 만약 사용자가 결제 후 랜더링된 페이지에서 새로고침을 누르면 중복해서 결제가 진행되기 때문이다.
Post - Redirect - Get
PRG를 사용해야하는 이유는 도입부분에서 매우 자세하게 설명이 되어있다. 클라이언트가 form tag에 데이터를 입력하고 주문을 하면 Post로 데이터가 넘어와 서버에서 해당 부분을 처리하게 된다. 이렇게 처리가 된 경우 최종적인 HTTP입력이 Post이므로 새로고침시 계속해서 Post가 실행되는 문제가 발생한다.
이러한 문제를 해결하기 위해서 Redirect를 사용하게 된다. Redirect를 사용하여 Get 메소드를 이용하는 Controller가 웹페이지를 랜더링하도록 해야한다.
Redirect 하는 방법?
Redirect는 어떻게 지정할 수 있을까?
@PostMapping("/add")
public String addItemV6(Item item, RedirectAttributes redirectAttributes) {
Item saveItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId",saveItem.getId());
redirectAttributes.addAttribute("status",true);
return "redirect:/basic/items/{itemId}";
}
위에서 보듯이 "redirect:URL"로 새로운 url로 이동할 수 있다. 위 코드는 새로운 상품을 등록하는 핸들러이다. 상품이 등록되고 난 뒤에는 등록된 상품에 대한 정보를 보여주고 싶은 상황이다. 이러한 경우 위처럼 redirection을 수행해야한다. 그렇게 되면 redirection의 명시된 URL을 처리하는 핸들러가 확인 후 처리한다.
@GetMapping("/{itemId}")
public String item(@PathVariable long itemId, Model model){
Item item = itemRepository.findById(itemId);
model.addAttribute("item",item);
return "basic/item";
}
위에서 redirection으로 데이터를 넘겨주면 이곳에서 새롭게 처리하여 웹페이지를 랜더링하게 된다.
이렇게 되면 새로고침시에 Post로 계속 HTTP 요청이 오는 것이 아니라, Redirect된 Get방식의 핸들러로 계속 처리하게 되어 결과적으로 계속해서 추가되는 일을 해결할 수 있게 된다.
Redirect Attributes ?
Redirect를 할 때 약간 고민되는 문제가 있다. 만약 상품을 등록을 했다면 등록된 상품을 보여주는 것이 스펙이라고 하자. 그런데 해당 상품을 직접 조회할 때와 다르게 상품을 등록하고 보여주는 경우 "정상적으로 등록이 되었습니다"라는 문구를 추가해주고 싶어진 것이다.
그렇게 되면 HTML과 핸들러를 추가해 다르게 처리를 해줘야할까? 아니다. 더 좋은 방법이 있다. 바로 RedirectAttributes와 Thymeleaf를 사용하는 것이다. 이것은 Redirection에 필요한 데이터를 넘겨주기 위하여 사용된다.
@PostMapping("/add")
public String addItemV6(Item item, RedirectAttributes redirectAttributes) {
Item saveItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId",saveItem.getId());
redirectAttributes.addAttribute("status",true);
return "redirect:/basic/items/{itemId}";
}
위에서 보여준 추가하는 예제를 다시 보면, RedirectAttribute 라는 파라미터를 추가한 것을 확인할 수 있다. 그리고 위 변수에 addAttribute로 값들을 넣어주고 있다. 그리고 해당 값은 Redirection에서 URL로 사용 : {itemId}되고 있다.
이렇게 redirection을 할 때 주소에 필요한 변수를 넣어줄 때 위 변수를 생성하여 사용하게 된다. 그냥 + 연산을 하여 saveItem.getId()를 사용할 수도 있다. 하지만 이 방법은 절대 사용하면 안된다. 왜냐하면 이렇게 사용을 하게되면 URL에 한글이 들어가는 불상사가 발생할 수 도 있어 URL에 대한 신뢰성이 떨어지게 된다. RedirectAttribute는 URL 인코딩을 해주는 기능까지 제공하기 때문에 생성하는 URL의 신뢰성을 높일 수 있다.
그렇다면 어떻게 위의 궁금증을 해결할 수 있을까? RedirectAttributes에 추가된 값들 중에서 URL에 사용되지 않은 값은 리디렉션 후에 쿼리 파라미터로 들어가게 된다. 따라서 만약 등록일 경우 쿼리파라미터를 사용해 구분해주고 HTML에서 타임리프의 th:if를 활용하여 해당 쿼리파라미터를 조회해 새롭게 웹페이지를 구상해주면 된다.
*타임리프는 쿼리파라미터를 조회하기 위해 param 이라는 예약어를 지원한다. ( ${param.status} 로 사용이 가능하다.)
배운내용
1. PRG를 사용하는 이유는 새로고침으로 인한 서버에서의 치명적인 오류를 막기 위함이다.
2. RedirectAttribute를 활용하여 리디렉션을 위한 값을 URL인코딩을하여 제공해주고 사용하지 않은 값은 쿼리파라미터로 넘겨준다.
이 글은 김영한님의 'Spring MVC 1편' 강의를 공부한 후 작성한 글입니다.
'Back-end > Spring' 카테고리의 다른 글
Server Side Rendering (Thymeleaf) (0) | 2021.11.16 |
---|---|
HTTP Message Converter (2) | 2021.11.09 |
SSR Response, Response Body (0) | 2021.11.08 |
Request Body 받아오기 (0) | 2021.11.04 |
Request Parameter 가져오기 (0) | 2021.11.04 |