thenullpage.com

숫자 세 자리로 결과를 알린다

서버 응답의 맨 첫 줄에는 HTTP/1.1 200 OK 같은 문장이 놓인다. 가운데 세 자리 숫자가 상태코드이고 뒤의 영어 문구는 이유구절이라 부른다. 이유구절은 참고용이라 HTTP/2부터는 전송하지도 않는다. 결과를 판정하는 쪽은 숫자다.


숫자를 보는 이유는 응답을 읽는 쪽이 사람만이 아니기 때문이다. 브라우저는 301을 받으면 사용자가 누르지 않아도 다른 주소로 이동하고, 캐시 서버는 200만 저장하며, 검색 크롤러는 404가 뜬 주소를 색인에서 뺀다. 이 장치들은 화면의 오류 문구를 읽지 못한다.


첫 자리가 범주를 가른다. 1은 중간 통보, 2는 성공, 3은 요청한 쪽이 한 번 더 움직여야 한다는 뜻, 4는 요청을 보낸 쪽의 잘못, 5는 서버 쪽의 잘못이다.


성공에도 종류가 있다

200은 처리에 성공했고 결과가 본문에 담겼다는 뜻이다. 새 자원을 만드는 요청이 성공하면 201을 쓰고 그것이 생긴 위치를 Location 헤더에 적어준다. 202는 접수만 했다는 뜻으로, 동영상 인코딩처럼 응답을 붙들고 기다릴 수 없는 작업에 쓴다.


204는 성공했지만 돌려줄 본문이 없다는 뜻이라 삭제 요청에 흔히 쓴다. 여기에 본문을 실으면 뒤따르는 응답과 경계가 어긋난다. 206은 일부만 보냈다는 뜻이다. 동영상 재생 막대를 중간으로 끌면 브라우저는 필요한 바이트 범위를 Range 헤더에 적어 보내고, 서버는 그 구간만 206으로 돌려준다.


GET /videos/lecture01.mp4 HTTP/1.1
Range: bytes=1048576-2097151

HTTP/1.1 206 Partial Content
Content-Range: bytes 1048576-2097151/483920112


끝의 숫자가 파일 전체 크기라서, 다운로드가 끊겨도 이어받을 수 있다.


이동 지시와 메서드가 바뀌는 함정

3으로 시작하는 코드는 원하는 것이 다른 곳에 있다는 지시이고, 새 주소는 Location 헤더에 담긴다.


301 Moved Permanently 영구 이동, 메서드가 GET으로 바뀔 수 있음
302 Found 임시 이동, 메서드가 GET으로 바뀔 수 있음
303 See Other 결과는 다른 곳에, 항상 GET으로 전환
307 Temporary Redirect 임시 이동, 메서드와 본문 유지
308 Permanent Redirect 영구 이동, 메서드와 본문 유지


302를 받은 브라우저가 POST를 GET으로 바꿔 보내는 관행이 초기에 굳어졌고, 되돌릴 수 없어 메서드 보존을 명시한 307과 308이 뒤늦게 추가됐다. 본문이 있는 요청을 302로 넘기면 본문이 사라진 GET이 도착한다.


303은 그 전환을 의도적으로 쓴다. 폼을 POST로 제출한 뒤 303으로 결과 페이지를 돌려주면 새로고침해도 글이 다시 등록되지 않는다.


301은 되돌리기 어렵다. 브라우저가 결과를 오래 붙들어두고 서버에 다시 묻지도 않아서, 주소를 잘못 걸고 며칠 뒤 고쳐도 이미 방문한 사용자는 옛 지시를 따른다.


304는 이동 지시가 아니라 가진 사본이 최신이라는 통보다. 저장해둔 파일의 식별값을 If-None-Match 헤더에 실어 보내면 서버는 값이 유효할 때 본문 없이 304만 돌려준다. 200이면 파일 전체가 내려오고 304면 헤더 몇 줄로 끝난다.


요청한 쪽의 잘못을 나누는 코드들

4로 시작하는 코드는 서버는 멀쩡하고 요청 쪽에 문제가 있다는 신호다. 400은 형식이 깨져 서버가 해석조차 못 한 경우, 422는 해석은 됐는데 값이 규칙에 어긋난 경우다. 구분해 두면 400일 때는 요청 만드는 코드를, 422일 때는 어느 입력칸이 틀렸는지를 짚어줄 수 있다.


POST /users HTTP/1.1

{"email": "kim@", "age": -3}

HTTP/1.1 422 Unprocessable Content

{"errors": [{"field": "email", "message": "형식 오류"}, {"field": "age", "message": "0 이상"}]}


401과 403은 가장 자주 뒤바뀌는 짝이다. 401은 요청을 보낸 주체가 누구인지 확인되지 않았다는 뜻이고, 403은 신원은 확인됐지만 그 자원에 손댈 권한이 없다는 뜻이다.


이 구분은 클라이언트의 분기에서 쓰인다. 401이면 토큰을 갱신하거나 로그인 화면으로 보내면 되지만, 403은 다시 로그인해도 결과가 같아서 로그인 화면으로 보내면 사용자가 맴돈다.


404는 그 주소에 아무것도 없다는 뜻일 뿐이고, 410은 있었지만 영구히 삭제됐다는 선언이라 검색엔진이 더 빠르게 색인에서 뺀다. 405는 주소는 있지만 그 메서드는 받지 않는다는 뜻이라 Allow 헤더에 허용 메서드를 담아야 한다. 409는 이미 쓰이는 아이디로 가입할 때처럼 상태가 충돌한 경우, 429는 요청이 잦다는 뜻이며 Retry-After에 대기 시간을 담는다.


서버 쪽 숫자를 갈라 보는 이유

500은 서버 코드가 처리하지 못한 예외가 터졌다는 뜻이라 요청을 고쳐 보내봐야 소용이 없다. 502와 504는 리버스 프록시가 요청을 받아 뒤쪽 서버로 넘기는 구조에서 나온다. 502는 뒤쪽에서 말이 안 되는 응답을 받았다는 뜻, 504는 시간 안에 아무 응답도 못 받았다는 뜻이다.


덕분에 장애가 났을 때 볼 곳이 갈린다. 502면 뒤쪽 프로세스가 죽었거나 포트가 어긋났을 가능성이 크니 프로세스 상태부터 보고, 504면 살아는 있는데 느린 것이므로 데이터베이스 질의 시간을 본다.


503은 지금 요청을 받을 여력이 없다는 뜻이다. 잠깐 지나가는 상황이라는 의미가 담겨 있어서, 배포 중 내려가는 페이지를 500이 아니라 503으로 돌려주면 검색엔진이 색인에서 빼지 않는다. 5로 시작하는 코드는 재시도해도 되지만 간격을 1초, 2초, 4초처럼 배로 늘려 보낸다.


200으로 감싼 실패

응답 코드는 200으로 두고 본문 안에 실패 여부를 적는 방식이 자주 보인다.


HTTP/1.1 200 OK

{"success": false, "code": "NO_PERMISSION", "message": "권한이 없습니다"}


화면에는 오류 문구가 잘 뜨니 문제없어 보인다. 어긋나는 쪽은 본문을 읽지 않는 장치들이다. 모니터링 도구는 오류율을 0퍼센트로 유지하고, 앞단 캐시는 실패 응답을 정상으로 알고 저장해 다른 사용자에게도 같은 화면을 준다. fetch 함수도 ok 값을 상태코드로만 판단해 오류 처리 분기를 건너뛴다.


없는 페이지를 200으로 돌려주는 것도 같은 실수다. 화면에 글을 찾을 수 없다고 적어놓고 코드가 200이면 검색엔진은 정상 문서가 있다고 믿는다.


직접 확인하는 방법

상태코드는 화면만 봐서는 드러나지 않는다. curl에 -i 옵션을 붙이면 헤더와 본문이 함께 나오고, 숫자만 필요하면 다음처럼 뽑는다.


curl -o /dev/null -s -w "%{http_code} %{redirect_url}\n" https://example.com/old-page
301 https://example.com/new-page


리다이렉트가 몇 번 이어지는지는 -L과 -v를 함께 붙여 각 단계를 출력해 본다. 브라우저에서는 개발자 도구 Network 탭의 Status 열을 보면 되는데, 회색으로 흐린 304는 본문이 오지 않았다는 표시이고 Size 열의 disk cache는 요청 자체가 나가지 않았다는 뜻이다.


서버를 운영한다면 접근 로그를 코드별로 세어 보는 습관이 도움이 된다. 4로 시작하는 코드가 갑자기 늘면 깨진 링크나 잘못 배포된 클라이언트를 의심하고, 5로 시작하는 코드가 늘면 방금 올린 배포부터 되짚는다.