HTTP Status Code 300, Redirection 과 PRG 패턴

Posted by , November 16, 2022
HTTP

지난 포스팅에 이어서 HTTP 의 300번대 상태코드에 대해 알아보겠습니다. 그리고 이에 꼭 아셔야할 이론인 Redirection 과 PRG 패턴이 무엇인지 살펴봅시다.


3xx (Redirection)

  • 클라이언튿가 보낸 요청을 완료하려면 서버에서 아직 추가적인 작업이 필요해서, 다시 클라이언트에게 보내는 것

  • 즉, 요청을 완료하기 위해 유저 에이전트에서 아직 추가 조치가 필요함을 의미합니다.

유저 에이전트란?

유저 에이전트란 클라이언트 프로그램, 즉 주로 웹브라우저를 통칭합니다.


리다이렉션이란? (redirection) & 301 Moved Permanently

저희희는 3xx 번대 상태코드에 포함된 개념인 redirection 에 대해 제대로 이해하고 넘어갈 필요가 있습니다.

  • 웹 브라우저는 3xx 번대 응답(Response)의 결과에 Location 헤더가 있다면, Location 위치로 자동 이동(redirect)합니다.
  • 즉, 리다이렉션 == location 이라는 URL 로 자동 이동하는 행위

예를들어 기존 이벤트 페이지로 "/event" 라는 url 을 사용하고, 새로운 이벤트 페이지 url 로 "/new-event" 라는 것을 사용한다고 해봅시다.

만일 new-event 라고 url 이 바뀐것을 모르고 기존 url 로 이벤트 페이지에 접속하려는 사용자들은 "/event" 를 검색해서 들어온다고 해봅시다.

그러면 서버는 사용자(클라이언트)에게 더 이상 "/event" 라는 기존의 경로는 더 이상 사용하지 않고, 새로운 경로인 "/new-event" 를 사용한다고 알려줄 수 있습니다. 이떄 사용하는 상태코드가 바로 301 상태코드 (301 Moved Permanently) 입니다.

아무튼, 301 상태코드와 함께 Location 헤더에 "/new-event" 라는 새로운 경로를 응답값으로 함께 실어서 클라이언트에게 보내줍니디.

그러면 클라이언트(웹 브라우저)는 301번 상태코드임을 확인하고, Location 헤더 필드 부분을 확인합니다. 그러고 Location 헤더 필드가 비어있지 않고 어떤 url 경로 값이 들어있다면, 부여된 Location 경로 url 로 자동으로 리다이렉션합니다. 즉, 자동으로 Location 경로로 GET 요청 서버에게 보내서 그에 대한 새로운 응답과 HTML 파일을 제공 받습니다. 이로써 새로운 웹페이지가 열리게 되는 것입니다.

즉, 사용자 입장에서는 옛날의 URL 로 GET 요청을 보냈지만 자동으로 새로운 URL 경로로 바뀌면서(Redirection 하면서) 해당 웹페이지로 접속할 수 있게 됩니다.

  • 사용자 입장에서는 자동으로 URL 이 입력되고 새로운 URL 로 리다이렉션 되는것이 너무 처리속도가 빨라서 인식을 못할 정도임!

리다이렉션의 종류

이러한 리다이렉션은 크게 3가지의 종류로 구분할 수 있습니다.

  • 영구 리다이렉션 : 특정 리소스의 URI 가 영구적으로 이동
  • 일시 리다이렉션 : 일시적인 잠깐 URL 를 변경
  • 특수 리다이렉션 : 결과 대신 캐시를 사용

  • cf) 영구 리다이렉션은 실무에서 잘 사용하지 않으니, 일시 리다이렉션을 중점으로 학습하시는 것을 권장드립니다.

영구 리다이렉션

특정 리소스의 URI 가 영구적으로 변경되었음을 의미합니다. 직전에 예시로 말씀드린 리다이렉션이 이에 해당합니다. 예를들어 URL 이 "/members-event" 에서 "/users-event" 로 새롭게 변경된 경우입니다.

일시 리다이렉션

일시적으로 URI 를 잠깐동안 변경할 떄 사용합니다. 예를들어서 주문완료 후에 주문 내역 화면으로 일시적으로 잠깐 이동할떄 사용됩니다.

  • 이와 관련해 RPG(Post/Redirect/Get) 이라는 패턴이 자주 사용됩니다.

특수 리다이렉션

  • 결과 대신 캐시를 사용하는 것을 의미

클라이언트에 캐시가 있는데 이 캐시가 만료된 것 같을떄, 클라이언트가 서버에게 이 캐시가 만료가 된것인지 요청을 보내고, 서버가 캐시 만료기간과 관련한 정보를 클라이언트에게 넘겨주는 방식입니다.

이떄 클라이언트에게 캐시를 더 사용해도 되는지 안되는지를 알려주면서, 캐시가 아직 만료가 안되서 다운로드 받지 않아도 되는경우에 캐시에서 다시 조회하라고 클라이언트에게 서버가 응답을 보내는 방식입니다. 즉 결과 대신에 캐시를 사용하라고 알려주는 것


영구 리다이렉션 : 301, 308

리소스의 URI가 영구적으로 이동했다고 알려주는 것입니다.

  • cf) 영구 리다이렉션은 실무에서 잘 사용하지 않으니, 일시 리다이렉션을 중점으로 학습하시는 것을 권장드립니다.

301 Moved Permanently

  • 리디이렉스시에 요청 메소드가 POST, DELETE 등 어떤 요청 메소드로 보내도 GET 요청으로 자동 변환되고, 요청에 실어서보낸 Body 본문 자체 내용이 완전히 제거될 수도 있습니다.

308 Permanent Redirect

  • 301과 기능은 같으나, 리다이렉트시에 요청 메소드와 본문(Body) 내용이 301과 달리 그대로 유지되면서 리다이렉트 할떄도 그대로 함께 실어서 보내는 것입니다.

301 Moved Permanently 의 문제점

아래처럼 POST 요청과 함꼐 "name=hello&age=20" 라는 메시지를 Body에 실어서 보냈습니다. 그런데 리다이렉션이 된다면 기존의 POST 요청이 자동으로 GET 요청을 변환되고, Body 에 존재하던 내용이 전부 사라지고 "/new-event" 페이지를 새롭게 브라우저가 띄워주는 방식입니다.

여기서 문제점은, 이벤트 등록을 하기위해 POST 요청을 보냈다면 결과적으로 GET 요청으로 변하게 됩니다. 결국엔 사용자는 새로운 이벤트 페이지에서 이벤트 등록을 위한 정보들을 새롭게 또 다시 입력해야 하는 번거로운 상황이 발생하게 됩니다.

308 : 301 의 문제점을 해결

반대로 308 은 리다이렉션을 해도 POST 요청을 계속 유지하고, Body 의 내용도 삭제시키지 않고 그대로 새로운 리디이렉션하는 페이지에 전달해준다고 했었죠? 이로써 301의 번거로움을 308 로 해결이 가능해집니다.


일시적인 리다이렉션 : 302, 307, 303

일시적인 리다이렉션은 리소스의 URI가 일시적으로 변경되는 것입니다.

  • 302 Found : "제거"
  • 리다이레트시에 **요청 메소드가 GET 으로 변하고, Body(본문)의 내용이 "제거"**될 수도 있습니다.

  • 307 Temporary Redirect : "유지"
    • 302과 기능은 같습니다.
    • 리다이렉트시에 **요청 메소드와 Body 의 내용을 그대로 "유지"**시킵니다.

  • 303 See Other : "GET 으로 변경"
    • 302과 기능은 같습니다.
    • 리다이렉트시에 **요청 메소드가 "GET 으로 변경"**됩니다.

PRG : Post/Redirect/Get

일시적인 리다이렉션이 꼭 반드시 쓰이는 상황이있습니다. 그 상황은 바로 PRG 입니다.

예를들어, 주문페이지에서 원하는 상품을 POST 요청으로 주문하면 POST 요청으로 인해 원하는 뎅터가 넘어갈 겁니다. 그러고 웹 브라우저를 새로고침하면, 서버에 중복해서 주문에 또 들어갈 수도 있습니다.

즉, 내가 요청했던 POST 요청이 새로고침을 하면 또 POST 요청이 들어갈 수 있는겁니다. 이를 해결하려면 PRG 패턴을 사용해야합니다.

상황가정 : 주문을 잘 못하는 경우(POST 요청을 새로고침에서 또 보내는 경우)

예를들어 아래처럼 "/order" 라는 페이지에 POST 요청으로 주문을 요청하는 상황을 생각해봅시다.

사용자가 마우스를 1개 구매하는 상황을 가정하면, Body에 "itemid=mouse&count=1" 과 같은 내용을 실어서 주문 내용을 보낼겁니다. 그러면 주문 데이터베이스에 상품을 1개 저장하고, 동시에 200 ok 라는 응답을 사용자에게 보내게 됩니다.

만일 실수로 새로고침을 한다면, 마지막 요청을 기준으로 요청을 처리하므로 사용자가 의도치않게 한번 더 주문이 들어가게 될 수 있습니다. 이럴 때 PRG 패턴을 사용하면 됩니다.

PRG 패턴 적용하기

위와 같이 POST로 주문후에 새로 고침으로 인한 중복 주문을 방지하기 위해, 클라이언트에서 POST 로 주문후에 주문 결과 화면을 GET 메소드로 리다이렉트 시켜서 해결할 수 있습니다.

즉, 주문 완료후에 주문 완료 결과 페이지로 넘어가도록(리다이렉트) 되도록 하면 됩니다. => 새로고침을 해도 주문 결과화면을 GET 으로 조회하도록 해서 중복으로 주문하는 POST 대신에, 주문 결과 화면만 GET 으로 다시 요청 하도록 구현하면 됩니다.

보듯이 주문을 완료한 후에 클라이언트에 대한 응답으로 200 OK 가 아닌, 302 Found 를 응답으로 주면 됩니다. (또는 303 See Other 를 줘도 됩니다!) 동시에 리다이렉션할 Location 정보도 응답을 주면 됩니다.

그러면 클라이언트는 302 를 확인하고 새로운 URL 로 GET 요청을 통해 리다이렉션을 하면서, 주문 완료 페이지를 띄어주면 됩니다. 이때 주문 완료 페이지에 주문 데이터를 띄어주기위해, 직전에 주문한 내역을 DB 로 부터 조회해서 이 정보를 기반으로 HTML 화면을 구성하면 됩니다. 즉 200 OK 와 함께 HTML 내용을 클라이언트에 보내주면 됩니다.

이 상황에서, 즉 주문 결과화면에서 새로고침을 하면 POST 가 아닌 GET 요청이 들어오면서 주문 내역을 보여주는 화면으로 계쏙 GET 요청이 들어오게 되는 방식이 된것입니다.

PRG 과정 이후에 리다이렉트를 해도, URL 이 이미 POST 에서 GET 으로 리다이렉트 되어서 새로고림을 하더라도 GET 으로 주문 결과 화면만 조회됩니다.


Redirection 302, 307, 303 중 어떤것을 써야할까?

역사를 잠시 살펴보자면, 모호한 302 을 대신하는 명확한 307, 303이 등장했습니다. 302는 GET 요청으로 반드시 꼭 바꿔주는 것이 아니라, 간혹 GET 으로 바꿔주지 않는 상황이 있기 떄문입니다.

따라서 307, 303 을 권장하지만, 그러나 현실적으로는 이미 많은 애플리케이션 라이브러리들이 302를 기본값으로 사용하고 있습니다. 따라서 자동 리다이렉션시에 GET 으로 변해도 되면 그냥 302를 사용해도 큰 문제가 없습니다.