본문 바로가기

백엔드/스프링

PRG (Post Redirect Get)

상황

사용자가 상품을 구매한 상황. 이 때 사용자의 마지막 요청은 POST로 상품 구매 요청이다. 이 상태에서 그대로 사용자가 새로고침 버튼을 누르게 되면 이전의 요청이 그대로 나가서 물건을 한번 더 구매하는 상황이 발생하게 된다. 이를 방지하기 위한 것이 PRG 이다.

 

PRG

 

(1) 구매자가 상품을 구매하는 화면에서 주문 버튼을 클릭한다.
POST메소드를 사용한 요청이 전송된다.
(2) 서버는(1)의 요청에서 받은 상품 구매 처리를 DB에 반영한다.
(3) 서버는 응답값으로 리다이렉트를 하도록 해서 상품 구매 완료 화면을 표시한다.
(4) 리다이렉트에 의해 브라우저가 상품 구매 완료 화면을 표시하도록 서버에 요청을 보낸다.
이때 요청는 GET메소드를 사용한다.
(5) 서버가 상품 구매 완료 화면을 보내준다.
(6) 사용자가 브라우저에서 새로고침을 누른다.
새로고침에 의해서 서버로 마지막 요청이 다시 나가는데 이때 요청은 상품 구매 완료 화면을 표시하기 위한 GET 요청이기 때문에 상품 구매가 중복으로 발생하지 않는다.
(7) 서버는 상품 구매 완료 화면을 전달한다.

 

스프링에서는 PRG를 다음과 같이 구현할 수 있다.

@PostMapping("/add")
  public String Add(@ModelAttribute Item item) {
  repository.save(item);
  return "redirect:/basic/items" + item.getId();
}

 

 

위 방법에는 단점이 있다. 만약 getId에서 한글이나 공백이 들어오면 인코딩되어야 하는데 위의 방식은 인코딩이 되지 않는다. 그래서 RedicrectAttributes 를 사용해야한다.

@PostMapping("/add")
public String addForm(@ModelAttribute item i, Model model, RedirectAttributes ra) {
  item saved = ir.save(i);
  ra.addAttribute("itemId", saved.getId());
  ra.addAttribute("status", true);
  rA.addAttribute("hello", "hihihi");
  
  return "redirect:/basic/items/{itemId}";
}

return 의 redirect 경로에 {변수명} 이 있다면 RedirectAttributes에 해당 변수가 있는지 확인한다. 그래서 실제로 리다이렉트 되는 경로는 saved.getId()로 id값을 가져와서 /basic/items/가져온ID값 이 될 것이다. 나머지는 쿼리 파라미터 형식으로 뒤에 붙는다.

?status=true로 해서 뒤에 붙은 것을 확인할 수 있다.