API 문서를 보다가 처음 보는 상태 코드를 만나면 보통 검색을 하죠. 그런데 검색해서 나오는 코드 표가 사이트마다 조금씩 달라요. 어떤 표에는 있고 어떤 표에는 없고, 어느 문서에서 온 코드인지는 대개 안 적혀 있어요.
그래서 이번에는 정리된 표를 읽는 대신 RFC 원문을 직접 받아서 셌어요. 코드를 세는 데 쓴 문서는 rfc-editor.org 가 올려 둔 plain text 판 15편이고, 여기에 마스터 색인 1편과 대조용 옛 문서 1편을 더 받았어요. 전부 HTTP 200 으로 받았고, 각 문서의 절 제목 줄에서 세 자리 숫자를 긁어 셌어요.
결과부터 적을게요. HTTP 상태 코드가 들어갈 수 있는 자리는 100 부터 599 까지 500칸인데, 이 15편이 이름을 붙인 칸은 62칸이었어요. 나머지 438칸은 이 문서들 어디에도 이름이 없어요.
그리고 하나 더 있어요. API 를 쓰는 사람이 가장 자주 만나는 429 가 RFC 9110 전문에 한 번도 안 나와요. 502,941바이트를 전부 훑어서 429 라는 숫자가 0회예요.

먼저 무엇을 열었는지 밝힐게요
숫자를 믿으려면 범위를 먼저 알아야 하니까 연 문서부터 적을게요. 조회 시각은 2026년 9월 17일 오전이고, 전부 https://www.rfc-editor.org/rfc/rfcXXXX.txt 형식의 공개 URL 이에요. 인증이 없어서 누가 받아도 같은 바이트예요.
| 문서 | 바이트 | 등급 |
|---|
| RFC 9110 HTTP Semantics | 502,941 | INTERNET STANDARD (STD97) |
| RFC 9111 HTTP Caching | 84,477 | INTERNET STANDARD (STD98) |
| RFC 9112 HTTP/1.1 | 109,913 | INTERNET STANDARD (STD99) |
| RFC 6585 Additional HTTP Status Codes | 17,164 | PROPOSED STANDARD |
| RFC 4918 WebDAV | 276,352 | PROPOSED STANDARD |
| RFC 5842 Binding Extensions to WebDAV | 91,544 | EXPERIMENTAL |
| RFC 8297 Indicating Hints | 13,303 | PROPOSED STANDARD |
| RFC 8470 Using Early Data in HTTP | 25,880 | PROPOSED STANDARD |
| RFC 7725 Report Legal Obstacles | 9,309 | PROPOSED STANDARD |
| RFC 3229 Delta encoding in HTTP | 111,953 | PROPOSED STANDARD |
| RFC 7538 Status Code 308 | 11,189 | PROPOSED STANDARD |
| RFC 2295 Transparent Content Negotiation | 125,130 | EXPERIMENTAL |
| RFC 2774 An HTTP Extension Framework | 39,719 | HISTORIC |
| RFC 2324 HTCPCP/1.0 | 19,610 | INFORMATIONAL |
| RFC 9309 Robots Exclusion Protocol | 25,265 | PROPOSED STANDARD |
등급은 제가 붙인 말이 아니라 마스터 색인 rfc-index.txt 에 적힌 표기를 그대로 옮긴 것이에요. 이 색인 파일은 2,025,376바이트였고 엔트리 8,996건이 들어 있었어요.
맨 아래 RFC 9309 는 대조용으로 같이 받아 둔 문서예요. 상태 코드를 정의하지 않아서 이 글의 집계에는 영향을 주지 않아요. 그러니까 62칸을 만든 건 실제로는 이 표의 나머지 14편이에요.
여기에 더해 이미 대체된 옛 문서 RFC 7231 을 한 편 더 열었어요. 지금도 인용이 많이 남아 있는 번호라 비교해 보려고 받은 건데, 새로 나오는 칸이 하나도 없어서 집계에는 안 넣었어요. 이 대조 결과는 글 뒤쪽에 따로 적어 뒀어요.
RFC 9110 이 정의한 건 46칸이에요
HTTP 의 본체에 해당하는 문서는 RFC 9110 HTTP Semantics 예요. 2022년 6월에 나왔고, 등급이 INTERNET STANDARD 이고 STD97 이라는 표준 번호까지 받았어요. 이전 세대 문서 9편을 한꺼번에 대체한 문서이기도 해요.
이 문서의 15절이 상태 코드를 정의하는 자리예요. 절 번호를 세어 보면 이렇게 나뉘어요.
15.2.1 부터 15.2.2 까지 1xx 2칸
15.3.1 부터 15.3.7 까지 2xx 7칸
15.4.1 부터 15.4.9 까지 3xx 9칸
15.5.1 부터 15.5.22 까지 4xx 22칸
15.6.1 부터 15.6.6 까지 5xx 6칸
합쳐서 46칸이에요. 우리가 매일 보는 200, 301, 400, 401, 403, 404, 500, 502, 503 이 전부 여기 들어 있어요.
그런데 46칸 중 두 칸은 좀 이상해요. 306 과 418 은 절 제목 자체가 Unused 예요. 쓰라고 정의한 게 아니라 쓰지 말라고 자리를 잡아 둔 거예요. 그래서 이 문서가 실제로 쓸 수 있게 정의한 코드는 44칸인 셈이에요.
418 쪽 설명이 재미있어요. 원문은 이렇게 적어요.
[RFC2324] was an April 1 RFC that lampooned the various ways HTTP was
abused; one such abuse was the definition of an application-specific
418 status code, which has been deployed as a joke often enough for
the code to be unusable for any future use.
Therefore, the 418 status code is reserved in the IANA HTTP Status
Code Registry.
1998년 4월 1일자 만우절 문서에서 나온 농담 코드가 너무 많이 배포돼서, 앞으로 다른 용도로 못 쓰게 됐다는 이야기예요. 그래서 예약 상태로 묶어 뒀고, 4NN 대가 고갈되면 그때 재배정할 수 있다는 여지만 남겨 뒀어요.
429 는 RFC 9110 에 0번 나와요
여기가 이번 조사에서 제일 놀란 자리예요.
레이트 리밋에 걸렸을 때 받는 429 Too Many Requests 는 AI API 를 쓰는 사람이라면 거의 매일 보는 코드예요. 그런데 HTTP 의 본체 문서인 RFC 9110 을 전부 훑어도 429 라는 숫자가 0회예요. Too Many Requests 라는 문구도 아예 없어요.
429 는 RFC 6585 Additional HTTP Status Codes 라는 별개 문서의 4절에 있어요. 2012년 4월 문서이고 크기가 17,164바이트밖에 안 돼요. 이 작은 문서 하나가 코드 네 칸을 담당해요.
- 3절 428 Precondition Required
- 4절 429 Too Many Requests
- 5절 431 Request Header Fields Too Large
- 6절 511 Network Authentication Required
429 의 정의 문장은 이래요.
The 429 status code indicates that the user has sent too many
requests in a given amount of time ("rate limiting").
The response representations SHOULD include details explaining the
condition, and MAY include a Retry-After header indicating how long
to wait before making a new request.
Note that this specification does not define how the origin server
identifies the user, nor how it counts requests.
마지막 문단을 눈여겨봐 주세요. 이 명세는 서버가 사용자를 어떻게 식별하는지도, 요청을 어떻게 세는지도 정하지 않아요. 그러니까 429 를 받았을 때 무엇을 기준으로 몇 개가 넘었다는 뜻인지는 표준이 답을 안 주고 각 서비스가 정해요. 레이트 리밋 정책이 서비스마다 제각각인 건 구현이 게을러서가 아니라 규격이 원래 거기까지만 정하고 멈춘 거였어요.
한 가지 더 있어요. 색인에서 RFC 6585 의 줄을 보면 Updates RFC2616 이라고 적혀 있어요. 그런데 그 RFC 2616 은 이미 다른 문서들에 의해 폐기된 상태예요. 429 를 정의한 문서가 지금은 사라진 문서를 갱신한다고 적힌 채로 여전히 살아 있는 거예요.
500칸 중 이름이 붙은 건 62칸이에요
이제 전체를 세어 볼게요. 상태 코드는 세 자리 정수이고, RFC 9110 의 15절이 유효한 범위는 100 부터 599 까지라고 못 박아요. 그러면 자리는 정확히 500칸이에요.
제가 연 15편이 절 제목으로 이름을 붙인 칸을 모아 보니 62칸이었어요. 나머지 438칸은 비어 있어요.
| 클래스 | 이름 붙은 칸 | 코드 |
|---|
| 1xx | 3 | 100, 101, 103 |
| 2xx | 10 | 200, 201, 202, 203, 204, 205, 206, 207, 208, 226 |
| 3xx | 9 | 300 부터 308 까지 |
| 4xx | 29 | 400 부터 418 까지 19칸, 421, 422, 423, 424, 425, 426, 428, 429, 431, 451 |
| 5xx | 11 | 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511 |
1xx 는 3칸뿐이고 제약이 많아요
가장 한산한 건 1xx 예요. 100, 101, 103 세 칸이 전부고 그나마 103 은 2017년에 나온 별개 문서 RFC 8297 소관이에요.
칸이 적은 이유가 RFC 9110 의 15.2 절 서두에 나와요.
Since HTTP/1.0 did not define any 1xx status codes, a server MUST NOT
send a 1xx response to an HTTP/1.0 client.
A 1xx response is terminated by the end of the header section; it
cannot contain content or trailers.
A client MUST be able to parse one or more 1xx responses received
prior to a final response, even if the client does not expect one. A
user agent MAY ignore unexpected 1xx responses.
1xx 응답은 본문을 가질 수 없어요. 헤더 구간이 끝나면 같이 끝나거든요. 담을 수 있는 게 헤더뿐이라 용도가 애초에 좁아요. 게다가 HTTP/1.0 클라이언트에게는 보내면 안 되고, 클라이언트는 기대하지 않았더라도 1xx 를 파싱할 수 있어야 하는 대신 무시해도 돼요.
103 Early Hints 가 이 좁은 틈을 쓰는 코드예요. 최종 응답에 들어갈 헤더를 미리 흘려 보내서 브라우저가 리소스를 먼저 받기 시작하게 하는 용도인데, 원문은 이 힌트가 최종 응답의 처리 방식에 영향을 주면 안 된다고 MUST NOT 으로 못 박아요. 어디까지나 성능 최적화용이라는 뜻이에요.
4xx 만 유독 붐벼요
한눈에 봐도 4xx 가 29칸으로 제일 많아요. 클라이언트가 잘못할 수 있는 방식이 서버가 실패하는 방식보다 훨씬 다양하다는 뜻으로 읽혀요. 인증이 없는지, 권한이 없는지, 본문이 너무 큰지, 형식이 안 맞는지, 조건부 요청이 어긋났는지가 다 다른 칸을 차지하고 있으니까요.
그래도 4xx 안에도 빈칸이 꽤 있어요. 419, 420, 427, 430 이 비어 있고 432 부터 450 까지도 전부 비어 있어요. 그러다 451 하나가 뚝 떨어져 있어요. 검열이나 법적 차단을 알리는 451 Unavailable For Legal Reasons 인데, RFC 7725 라는 9,309바이트짜리 단독 문서가 이 한 칸만 담당해요.
5xx 는 11칸뿐이고 529 자리는 비어 있어요
서버 쪽은 훨씬 한산해요. 11칸이 전부고, 그중 500 부터 505 까지 6칸만 RFC 9110 에 있어요.
- 506 Variant Also Negotiates 는 RFC 2295 소관인데 등급이 EXPERIMENTAL 이에요
- 507 Insufficient Storage 는 WebDAV 문서인 RFC 4918 소관이에요
- 508 Loop Detected 는 RFC 5842 소관이고 역시 EXPERIMENTAL 이에요
- 509 는 비어 있어요
- 510 Not Extended 는 RFC 2774 소관인데 이 문서는 등급이 HISTORIC 이에요
- 511 Network Authentication Required 는 아까 그 RFC 6585 소관이에요
그리고 512 부터 599 까지 88칸은 전부 비어 있어요. 그러니까 5xx 대에서 511 을 넘는 숫자를 보면, 그건 이 15편 중 어느 문서에도 이름이 없는 자리예요. 과부하 신호로 쓰이는 걸 종종 보게 되는 529 도 이 빈 구간에 들어가요.
여기서 한 가지 분명히 해 둘게요. 어느 서비스가 529 를 실제로 내려주는지는 이번 조사가 확인하지 않았어요. 벤더 문서는 이번에 열지 않았거든요. 확인한 건 529 가 이 15편 어디에도 정의돼 있지 않다는 것까지예요. 실제 장애가 어떤 등급으로 기록되는지는 챗GPT 상태 페이지의 사고 25건을 직접 훑어본 글에서 따로 다뤘어요.
같은 상태 코드인데 문서 등급이 달라요
62칸을 정의 문서의 등급으로 묶어 봤어요.
| 등급 | 코드 수 |
|---|
| INTERNET STANDARD | 46 |
| PROPOSED STANDARD | 12 |
| EXPERIMENTAL | 3 |
| HISTORIC | 1 |
우리가 아는 코드의 대부분은 사실 RFC 9110 한 문서에서 나와요. 46칸이 거기 있고, 나머지 16칸이 문서 아홉 편에 흩어져 있어요.
그리고 506 과 208 과 508 은 EXPERIMENTAL 문서에서, 510 은 HISTORIC 문서에서 나와요. 이 네 칸을 그냥 표준이라고 부르면 정확하지 않아요. 등급이 낮다고 쓰면 안 된다는 뜻은 아니지만, 다른 팀과 규격을 맞출 때 근거로 내밀면 얘기가 길어질 수 있는 자리예요.
정의 문서별로 보면 이렇게 나뉘어요.
| 문서 | 담당 칸 |
|---|
| RFC 9110 | 46칸 |
| RFC 4918 | 207, 423, 424, 507 |
| RFC 6585 | 428, 429, 431, 511 |
| RFC 5842 | 208, 508 |
| RFC 8297 | 103 |
| RFC 3229 | 226 |
| RFC 8470 | 425 |
| RFC 7725 | 451 |
| RFC 2295 | 506 |
| RFC 2774 | 510 |
표를 옮겨 쓸 때 주의할 칸이 두 개 있어요. 422 는 원래 WebDAV 문서에 있었는데 RFC 9110 이 가져갔어요. 이름도 Unprocessable Entity 에서 Unprocessable Content 로 바뀌었고요. 308 도 원래 RFC 7538 단독 문서였는데 RFC 9110 이 흡수했어요. 색인이 7538 을 Obsoleted by RFC9110 으로 표기하고 있어요. 이 두 칸을 양쪽에 다 넣으면 합계가 어긋나요.
하나 더. RFC 4918 본문에는 102 Processing 이 이 명세에서 제거됐다는 문장이 들어 있어요. 이전 세대 WebDAV 문서에 있던 코드인데 지금은 빠져 있어서, 이번 집계의 62칸에도 안 들어가요.

4xx 와 5xx 는 서두 한 문장부터 달라요
코드 하나하나보다 클래스 서두를 읽는 게 실은 더 쓸모 있어요. RFC 9110 은 두 클래스를 이렇게 열어요.
15.5. Client Error 4xx
The 4xx (Client Error) class of status code indicates that the client
seems to have erred.
15.6. Server Error 5xx
The 5xx (Server Error) class of status code indicates that the server
is aware that it has erred or is incapable of performing the
requested method.
4xx 는 클라이언트가 잘못한 것 같다는 뜻이고, 5xx 는 서버가 자기가 잘못했음을 안다는 뜻이에요. 요청을 그대로 두고 다시 보냈을 때 결과가 달라질 여지가 어느 쪽에 있는지가 여기서 갈려요.
그리고 두 서두에 똑같이 들어 있는 요구가 하나 있어요. 서버는 에러 상황을 설명하는 표현을 보내되 그것이 일시적인지 영구적인지까지 담으라고 적혀 있어요. 재시도 여부를 코드 숫자만으로 정하지 말라는 태도가 규격에 이미 들어 있는 셈이에요.
다만 조심할 게 있어요. 원문 어디에도 4xx 를 재시도하지 말라고 적혀 있지는 않아요. 실제로 4xx 안에 들어 있는 408 과 429 는 요청을 안 고치고 다시 보내는 게 정상 동작이에요. 서두 문장은 판단의 출발점이지 규칙이 아니에요.
모르는 코드를 받았을 때 표준이 정해 둔 답이 있어요
여기가 이 글에서 실무에 제일 바로 쓰이는 부분이에요.
빈칸이 438개나 되니까 자연히 이런 생각이 들어요. 목록에 없는 코드를 받으면 어떻게 하지? RFC 9110 의 15절이 이 질문에 직접 답을 적어 뒀어요.
HTTP status codes are extensible. A client is not required to
understand the meaning of all registered status codes, though such
understanding is obviously desirable. However, a client MUST
understand the class of any status code, as indicated by the first
digit, and treat an unrecognized status code as being equivalent to
the x00 status code of that class.
For example, if a client receives an unrecognized status code of 471,
it can see from the first digit that there was something wrong with
its request and treat the response as if it had received a 400 (Bad
Request) status code.
정리하면 이래요. 모든 코드의 뜻을 알 필요는 없지만 첫 자리는 반드시 알아야 하고, 모르는 코드는 그 클래스의 x00 과 같게 다뤄라. 471 을 받으면 400 처럼, 529 를 받으면 500 처럼 처리하면 된다는 뜻이에요.
같은 절에 범위를 벗어난 값에 대한 규정도 있어요.
Values outside the range 100..599 are invalid. Implementations often
use three-digit integer values outside of that range (i.e., 600..999)
for internal communication of non-HTTP status (e.g., library errors).
A client that receives a response with an invalid status code SHOULD
process the response as if it had a 5xx (Server Error) status code.
600 부터 999 까지는 라이브러리가 내부 에러를 표시하려고 쓰는 관행이 있다는 서술까지 붙어 있어요. 그런 값이 응답으로 들어오면 5xx 처럼 처리하라는 게 규격의 답이에요.
이게 실무에서 뜻하는 바는 단순해요. 코드 목록을 통째로 하드코딩해서 분기하지 말고 첫 자리로 분기하면 돼요. 그러면 이 글의 62칸이 내년에 63칸이 되어도 코드를 안 고쳐도 되거든요.
Retry-After 는 형식이 두 가지예요
재시도 로직에서 가장 많이 어긋나는 자리가 이 헤더의 파싱이에요. RFC 9110 의 10.2.3 절이 형식을 이렇게 정해요.
The Retry-After field value can be either an HTTP-date or a number of
seconds to delay after receiving the response.
Retry-After = HTTP-date / delay-seconds
Two examples of its use are
Retry-After: Fri, 31 Dec 1999 23:59:59 GMT
Retry-After: 120
초 단위 정수와 날짜 문자열, 둘 다 올 수 있어요. 정수만 가정하고 parseInt 같은 걸로 받으면 날짜가 왔을 때 조용히 틀린 값이 나와요.
그리고 이 절에는 중요한 공백이 하나 있어요. 10.2.3 절 본문은 503 과 3xx 만 언급하고 429 는 한 번도 언급하지 않아요. 429 에 Retry-After 를 붙일 수 있다는 근거는 이 절이 아니라 RFC 6585 의 4절에 따로 있어요. 헤더 하나를 두 문서가 나눠 들고 있는 셈이에요.
503 쪽도 조건을 정확히 읽어야 해요. RFC 9110 의 15.6.4 절은 이렇게 적어요.
The server MAY send a Retry-After header field
(Section 10.2.3) to suggest an appropriate amount of time for the
client to wait before retrying the request.
| *Note:* The existence of the 503 status code does not imply
| that a server has to use it when becoming overloaded. Some
| servers might simply refuse the connection.
MAY 예요. MUST 가 아니에요. 503 인데 Retry-After 가 안 오는 응답은 규격 위반이 아니에요. 게다가 주석은 서버가 과부하일 때 503 을 꼭 써야 하는 것도 아니고 그냥 연결을 거절할 수도 있다고 적어 뒀어요. 그러니까 헤더가 없을 때 쓸 기본 대기값을 따로 들고 있어야 해요. 워크플로 도구에서 이 대기와 재시도를 어떻게 배선하는지는 n8n 에러 처리 패턴을 정리한 글에 실제 설정 단위로 적어 뒀어요.
진짜 함정은 코드가 아니라 메서드예요
여기가 이번 조사에서 제일 중요한 발견이에요.
재시도 이야기는 보통 상태 코드로만 해요. 429 면 기다렸다 다시, 5xx 면 백오프 걸고 다시. 그런데 RFC 9110 은 재시도의 조건을 코드가 아니라 메서드에 걸어 뒀어요.
9.3 절이 정의하는 메서드는 8개예요. GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE.
그중 9.2.1 절이 안전한 메서드로 지목한 건 GET, HEAD, OPTIONS, TRACE 4개예요. 그리고 9.2.2 절이 멱등의 범위를 이렇게 적어요.
A request method is considered "idempotent" if the intended effect on
the server of multiple identical requests with that method is the
same as the effect for a single such request. Of the request methods
defined by this specification, PUT, DELETE, and safe request methods
are idempotent.
안전한 4개에 PUT 과 DELETE 를 더해서 멱등인 메서드는 6개예요. 남는 둘은 POST 와 CONNECT 고요.
그러니까 POST 는 멱등이 아니에요. 그리고 생성형 API 호출은 거의 전부 POST 예요. 이어지는 문장이 그래서 중요해요.
A client SHOULD NOT automatically retry a request with a non-
idempotent method unless it has some means to know that the request
semantics are actually idempotent, regardless of the method, or some
means to detect that the original request was never applied.
A proxy MUST NOT automatically retry non-idempotent requests. A
client SHOULD NOT automatically retry a failed automatic retry.
세 문장을 하나씩 볼게요.
첫째, 비멱등 메서드의 자동 재시도는 SHOULD NOT 이에요. 다만 원문이 예외 조건을 직접 적어 뒀어요. 그 요청이 메서드와 무관하게 실제로는 멱등이라는 것을 알 수단이 있거나, 원래 요청이 적용되지 않았음을 확인할 수단이 있을 때예요. 요청마다 고유 키를 붙여서 서버가 중복을 걸러 주는 방식이 바로 이 조건을 만드는 장치예요. 같은 작업이 두 번 실행되는 걸 막는 패턴은 자동화 중복 실행을 막는 방법을 정리한 글에서 더 자세히 다뤘어요.
둘째, 프록시는 비멱등 요청을 자동 재시도하면 안 돼요. 여기는 MUST NOT 이라 강도가 다르고요.
셋째가 의외로 자주 어긋나는 자리예요. 실패한 자동 재시도를 다시 자동 재시도하지 말라는 문장이에요. 지수 백오프를 걸어 두고 최대 횟수를 안 정해 두면 규격이 말리는 모양이 돼요.
자동 재시도를 하라고 적은 코드는 딱 하나였어요
재미있는 대비가 하나 있어요. 이번에 연 15편을 통틀어 클라이언트에게 자동으로 재시도하라고 적은 코드는 425 하나였어요. RFC 8470 의 5.2 절이에요.
A 425 (Too Early) status code indicates that the server is unwilling
to risk processing a request that might be replayed.
User agents that send a request in early data are expected to retry
the request when receiving a 425 (Too Early) response status code. A
user agent SHOULD retry automatically, but any retries MUST NOT be
sent in early data.
SHOULD retry automatically 라고 적혀 있죠. 429 는 MAY include a Retry-After 까지고 503 도 MAY 인데, 여기만 재시도 자체를 권해요.
다만 전제가 아주 좁아요. TLS 핸드셰이크가 끝나기 전에 보내는 early data, 그러니까 0-RTT 로 나간 요청일 때 이야기예요. 서버가 재전송 위험을 감수하지 않겠다고 거절한 것이니 다시 보내면 되고, 대신 그 재시도는 early data 로 보내면 안 된다는 조건까지 붙어요. 조건을 빼고 옮기면 전혀 다른 말이 돼요.
한 가지 덧붙이면, SHOULD NOT 을 금지로 읽으면 안 돼요. 이건 RFC 의 규범 어휘라서, 하지 않는 것이 원칙이고 사정이 있으면 그 뜻과 결과를 이해한 상태에서 해야 한다는 뜻이에요. 실제로 원문도 어떤 클라이언트는 위험을 감수하고 POST 를 자동 재시도하기도 한다는 서술을 그 아래에 달아 뒀어요.
429 응답은 캐시에 저장하면 안 돼요
RFC 6585 의 4절 마지막 문장이 이거예요.
Responses with the 429 status code MUST NOT be stored by a cache.
MUST NOT 이에요. 레이트 리밋 응답이 캐시에 남으면 제한이 풀린 뒤에도 계속 429 를 돌려주게 되니까 당연한 규정인데, 응답을 통째로 캐싱하는 프록시를 앞에 두고 있다면 한 번 확인해 볼 만한 자리예요.
캐싱 쪽에서 같이 볼 게 하나 더 있어요. RFC 9110 의 15.1 절이 휴리스틱 캐시가 가능한 코드를 예로 들어 두는데, 세어 보면 12개예요.
Responses with status codes that are defined as heuristically
cacheable (e.g., 200, 203, 204, 206, 300, 301, 308, 404, 405, 410,
414, and 501 in this specification) can be reused by a cache with
heuristic expiration unless otherwise indicated by the method
definition or explicit cache controls [CACHING]; all other status
codes are not heuristically cacheable.
눈여겨볼 건 404 와 405 와 410 이 이 목록에 있다는 점이에요. 없는 페이지에 대한 응답도 캐시가 알아서 재사용할 수 있다는 뜻이거든요. 반대로 503 은 이 목록에 없어요.
다만 원문이 e.g. 라고 적었으니 이 12개가 전부라고 읽으면 안 돼요. 이 명세가 예로 든 12개까지가 확인된 범위예요.
코드를 검색하면 낡은 번호가 먼저 나와요
조사하다가 알게 된 게 하나 더 있어요. 색인에서 RFC 9110 줄을 읽으면 이렇게 적혀 있어요.
9110 HTTP Semantics. R. Fielding, Ed., M. Nottingham, Ed., J. Reschke,
Ed.. June 2022. (Format: HTML, TXT, PDF, XML) (Obsoletes RFC2818,
RFC7230, RFC7231, RFC7232, RFC7233, RFC7235, RFC7538, RFC7615,
RFC7694) (Updates RFC3864) (Also STD97) (Status: INTERNET STANDARD)
Obsoletes 뒤에 번호가 9개 붙어 있어요. 2818, 7230, 7231, 7232, 7233, 7235, 7538, 7615, 7694 예요.
이 중 7231 이 상태 코드 인용처로 아직도 가장 많이 돌아다니는 번호예요. 그래서 이 문서만 따로 한 편 더 열어 봤어요. 235,053바이트였고 절 제목이 이렇게 돼 있었어요.
6. Response Status Codes
6.5.4. 404 Not Found
6.6.4. 503 Service Unavailable
장 번호가 통째로 달라요. 예전에는 6장이 상태 코드 자리였는데 RFC 9110 에서는 15장이에요. 그래서 section-6.5.4 같은 옛 앵커를 9110 주소에 그대로 붙이면 엉뚱한 자리로 가요. 404 를 보려면 9110 에서는 15.5.5 절이에요.
담고 있는 칸 수도 달라요. RFC 7231 의 6장이 정의한 코드는 36칸인데 RFC 9110 의 15장은 46칸이에요. 실제로 7231 의 6장에는 206 과 304 와 401 과 407 과 412 와 416 이 없어요.
그럴 만한 이유가 대체된 문서들의 제목에 그대로 드러나요. 색인에 적힌 제목을 보면 RFC 7232 는 Conditional Requests, RFC 7233 은 Range Requests, RFC 7235 는 Authentication 이에요. 조건부 요청과 범위 요청과 인증이 각각 딴 문서로 쪼개져 있었고, 그 문서들이 들고 있던 칸이 6장에 없었던 거예요. 9110 이 그 넷을 한 문서로 다시 합친 결과가 46칸이에요.
그래서 7231 의 36칸은 전부 9110 의 46칸 안에 들어가요. 이 문서를 따로 열어도 이 글의 62라는 수는 안 바뀌어요. 인용할 문서를 고른다면 지금은 9110 쪽이 맞고, 7231 링크를 쓰고 있었다면 앵커까지 같이 고쳐야 해요.
그리고 목록을 제목 검색으로 만들면 빠지는 게 생겨요. 마스터 색인에서 제목에 Status Code 가 든 RFC 를 뽑으면 9편이 나오는데, 그중 4편은 HTTP 가 아니라 메일과 SMTP 쪽 문서예요. 반대로 4918 이나 5842 나 2295 나 3229 나 2774 나 8470 처럼 제목에 그 말을 안 쓰고 코드를 정의한 문서 6편은 아예 안 잡혀요. 제목만 긁으면 절반쯤 틀린 목록이 나오는 셈이에요.
정리
이번 조사에서 확인한 걸 모아 볼게요.
- 상태 코드가 들어갈 자리는 500칸이고, 이 조사가 연 RFC 15편이 이름을 붙인 칸은 62칸이에요. 그중 306 과 418 두 칸은 예약이라 실제로 쓰는 건 60칸이에요.
- 46칸이 RFC 9110 한 문서에 있어요. 나머지 16칸이 아홉 편에 흩어져 있고, 그중 3칸은 EXPERIMENTAL, 1칸은 HISTORIC 문서 소관이에요.
- 429 는 RFC 9110 에 0번 나와요. 17,164바이트짜리 RFC 6585 가 담당해요.
- 모르는 코드는 첫 자리만 보고 x00 처럼 다루라고 규격이 직접 정해 뒀어요. 그래서 529 는 500 처럼 처리하면 돼요.
Retry-After 는 초와 날짜 두 형식이 다 올 수 있고, 503 에 붙는 건 MAY 라서 안 올 수도 있어요.
- POST 는 멱등이 아니에요. 비멱등 요청의 자동 재시도는 SHOULD NOT 이고, 예외 조건은 원래 요청이 적용되지 않았음을 확인할 수단이 있을 때예요.
실무로 옮기면 할 일은 세 가지쯤이에요. 코드 목록을 하드코딩하는 대신 첫 자리로 분기하기, Retry-After 파서가 날짜도 받게 하기, 그리고 POST 재시도에는 고유 키를 붙여서 중복을 서버가 거르게 하기예요.
이 조사가 확인하지 않은 것
숫자를 옮겨 쓰기 전에 범위를 다시 적어 둘게요.
- 62칸은 이 조사가 연 RFC 15편 기준이에요. IANA 가 운영하는 상태 코드 등록부는 이번에 열지 않았어요. 등록부에는 이 15편 밖의 코드가 더 있을 수 있어요.
- 실제 API 를 한 번도 호출하지 않았어요. 어떤 서비스가 어떤 코드를 언제 내려주는지에 대한 측정은 이 글에 없어요. 전부 명세 텍스트를 읽고 센 거예요.
- 벤더가 자체적으로 쓰는 코드는 세는 대상이 아니었어요. 529 를 다룬 것도 정의가 없다는 사실까지고, 누가 쓰는지는 확인하지 않았어요.
- 102 Processing 은 이전 세대 WebDAV 문서 소관이었어요. 그 원문을 직접 열지는 않았고, RFC 4918 본문의 제거 서술 한 줄로만 확인했어요.
- 마스터 색인에서 제목에
Status Code 가 든 RFC 는 9편이었지만, 제목에 그 말을 안 쓰고 코드를 정의한 문서가 여섯 편 더 있었어요. 제목 검색만으로 목록을 만들면 빠지는 게 생겨요.
- RFC 7232 와 7233 과 7235 의 본문은 열지 않았어요. 그 문서들이 어떤 칸을 들고 있었는지는 색인에 적힌 제목으로 추정한 것이고, 절 번호까지 확인한 건 아니에요.
검색해서 나온 코드 표가 서로 다르게 보였던 이유가 조사 끝에 좀 분명해졌어요. 코드가 한 문서에 모여 있지 않고, 문서마다 등급도 다르고, 어떤 칸은 다른 문서로 옮겨 갔거든요. 표를 외우는 것보다 첫 자리로 분기하라는 규격의 문장 하나를 붙잡는 편이 오래가요.