개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

공부 › 네트워크 › HTTP

리다이렉션

"다른 주소로 가라"는 응답을 받으면 다음 요청은 어떻게 나가나

서버는 요청을 직접 처리하는 대신 3xx 상태 코드와 Location 헤더로 "이 주소로 다시 요청하라"고 안내할 수 있습니다. 이것을 리다이렉션이라고 합니다. 301, 302, 303, 307, 308은 모두 같은 안내처럼 보이지만, 영구인지 임시인지와 다음 요청의 메서드가 유지되는지가 다릅니다.

실행으로 대조 · 시험 서버(127.0.0.1)에 Node 24의 fetch와 curl 8.21(-L)로 서른 가지 조합을 실행해 대조. 영구·임시 구분과 표준의 문구는 RFC 9110 15.4절을 따름

이 실습에 나오는 개념

리다이렉션과 Location 헤더
서버가 요청을 직접 처리하지 않고 "다른 주소로 다시 요청하라"고 안내하는 응답이다. 상태 코드는 3xx이고, 다음에 요청할 주소는 응답의 Location 헤더에 적는다. 그 주소로 다시 요청을 보내는 것은 서버가 아니라 클라이언트이며, 따라갈지 말지도 클라이언트가 정한다. 301과 308은 영구 이동, 302와 307은 임시 이동을 뜻하고, 307과 308은 다음 요청의 메서드와 본문을 바꾸지 못하게 한다. 두 주소가 서로를 가리키면 끝나지 않으므로 클라이언트는 정해 둔 횟수를 넘으면 오류를 내고 멈춘다.
PRG 패턴(Post/Redirect/Get)
폼을 POST로 받은 서버가 결과 화면을 바로 돌려주지 않고, 결과를 볼 주소로 리다이렉트하는 방식이다. 브라우저는 그 주소를 GET으로 다시 요청하므로 화면에 남는 마지막 요청은 GET이 된다. 그래서 사용자가 새로 고침을 눌러도 POST가 다시 전송되지 않아 주문이나 결제가 두 번 처리되는 일을 막는다. 이때의 리다이렉트에는 "결과는 다른 주소에서 GET으로 조회하라"는 뜻의 303 See Other가 맞고, 관례로 302도 많이 쓴다.
HTTP 상태 코드
서버가 요청을 처리한 결과를 나타내는 세 자리 숫자다. 2xx는 성공, 3xx는 다른 위치로의 이동(리다이렉션), 4xx는 클라이언트의 요청에 문제가 있음, 5xx는 서버 쪽 오류를 뜻한다. 200(성공), 201(생성됨), 400(잘못된 요청), 401(인증 필요), 403(권한 없음), 404(없음), 500(서버 오류)이 자주 쓰인다.
HTTP 메서드
요청이 자원에 대해 수행하려는 동작을 나타낸다. GET은 조회, POST는 생성이나 처리 요청, PUT은 전체 교체, PATCH는 일부 수정, DELETE는 삭제를 뜻한다. 메서드의 의미에 맞게 사용해야 캐시, 재시도 같은 HTTP의 동작이 기대대로 이뤄진다.
안전한 메서드와 멱등한 메서드
안전한 메서드는 서버의 상태를 바꾸지 않는 읽기 전용 메서드로 GET, HEAD, OPTIONS, TRACE가 해당한다. 멱등한 메서드는 같은 요청을 여러 번 보내도 서버에 미치는 효과가 한 번 보낸 것과 같은 메서드로, 안전한 메서드 전부와 PUT, DELETE가 해당한다. POST와 PATCH는 멱등이 보장되지 않아 두 번 보내면 두 번 처리될 수 있다. 응답을 받지 못했을 때 다시 보내도 되는지를 판단하는 기준이 된다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. 클라이언트가 POST /order + 본문 요청을 보냅니다. 본문에는 주문 내용(item=keyboard)이 들어 있습니다.
  2. 서버가 302 Found로 응답합니다. 응답의 Location 헤더에 다음에 요청할 주소 /orders가 적혀 있습니다. 302 응답은 임시 이동입니다. 지금만 /orders로 가라는 뜻이라, 다음에도 원래 주소인 /order로 먼저 요청해야 합니다.
  3. 클라이언트가 Location을 읽고 다음 요청을 만듭니다. 표준(RFC 9110)은 301과 302에서 POST를 GET으로 바꾸는 것을 허용했습니다. 의무는 아니지만 fetch와 curl은 실제로 GET으로 바꾸고 본문을 버립니다.
  4. 클라이언트가 GET /orders (본문 없음) 요청을 보냅니다. 사용자가 시킨 것이 아니라 클라이언트가 Location을 보고 스스로 보내는 두 번째 요청입니다. 처음 요청의 본문은 이 요청에 실리지 않습니다.
  5. /orders의 서버가 200 OK로 응답합니다. 받은 것이 GET이므로 주문 목록을 돌려줍니다. 처음에 보낸 본문은 이 서버에 오지 않았습니다. 요청을 보낸 코드에는 이 마지막 응답만 전달됩니다.

실행 결과

코드

// 서버 (Node.js)
http.createServer((req, res) => {
    if (req.url === '/order') {
        res.writeHead(302, { Location: '/orders' });    // 302 Found
        return res.end();                              // 본문 없이 안내만 한다
    }
    res.writeHead(200);
    res.end('주문 목록');
});

// 클라이언트 1: fetch는 따로 지정하지 않으면 Location을 따라간다
await fetch('http://shop.example/order', {
    method: 'POST',
    body: 'item=keyboard'
});

// 클라이언트 2: curl은 -L을 붙여야 따라간다
// curl -L -d 'item=keyboard' http://shop.example/order

이 모형의 전제

서른 가지 조합(상태 코드 5 × 메서드 3 × 가리키는 곳 2)을 127.0.0.1에 띄운 시험 서버와 두 가지 클라이언트(Node.js 24의 fetch, curl 8.21의 -L 옵션)로 실행해, 두 번째 요청의 메서드와 본문, 서버에 도착한 요청 수, 끝난 모습이 모형과 같은 것을 확인했습니다. 두 클라이언트의 두 번째 요청은 모든 조합에서 같았고, 멈추는 횟수만 달랐습니다(fetch 20번, curl 50번). 이 횟수는 클라이언트마다 다르며 curl은 --max-redirs로 바꿀 수 있습니다. 브라우저의 한도와 오류 화면은 실행으로 확인하지 않았습니다. "종류"와 "표준이 정한 메서드 처리" 열은 실행 결과가 아니라 HTTP 표준(RFC 9110 15.4절)의 문구를 옮긴 것입니다. 301과 302에서 POST만 GET으로 바꾸고 303에서는 GET과 HEAD가 아닌 메서드를 모두 GET으로 바꾸는 규칙은 Fetch 표준(HTTP-redirect fetch)에 적혀 있습니다. 영구 이동을 브라우저가 기억한다는 설명은 표준이 301과 308 응답을 따로 지시가 없어도 저장할 수 있는 응답으로 정한 데 따른 것으로, 실행으로 확인하지 않았습니다. 리다이렉트를 따라갈지는 클라이언트가 정합니다. Node의 http 모듈, -L이 없는 curl, redirect: 'manual'로 부른 fetch는 따라가지 않고 3xx 응답을 그대로 돌려주는 것을 같은 시험 서버에서 확인했습니다. curl의 PUT은 -T(파일 올리기)로 보냈습니다. curl에서 -X로 메서드를 직접 지정하면 리다이렉트 뒤에도 그 메서드가 그대로 쓰이므로 결과가 달라집니다.

앞 실습: 재시도와 멱등성 · 다음 실습: 내용 협상과 압축

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요