Status Code Families — Lookup table 가 아니라 polymorphic protocol
~12 min · foundations, status-codes, polymorphic, dispatch
Level 0HTTP Newbie
0 XP0/46 lessons0/12 achievements
0/120 XP to next level120 XP to go0% complete
"표준 status code 가 60개 넘어. 실전에선 ~20개 만나. 근데 외울 필요 없어 — 첫 숫자가 어느 family 인지 알려주고, 그것만으로 보통 행동 결정 가능해."
모든 response 를 커버하는 다섯 family
모든 HTTP status code 는 첫 숫자로 식별되는 다섯 family 중 하나에 들어가. 각 family 는 client 와의 계약 — polymorphic dispatch. 잘 쓴 client 는 처음 보는 status code 에도 안 죽어, family 숫자만으로 뭘 할지 알아:
1xx — Informational. "Request 받음, 계속 진행." 가장 흔한 거: 100 Continue (큰 업로드용), 101 Switching Protocols (WebSocket upgrade), 103 Early Hints (실제 response 전 preload 힌트).
2xx — Success. "성공." 흔한: 200 OK (body 있는 일반 성공), 201 Created (새 resource — Location header 가 가리킴), 202 Accepted (async — 작업 큐에 들어감, 완료 안 됨), 204 No Content (성공, body 없음), 206 Partial Content (range request).
3xx — Redirection. "다른 데 봐." 흔한: 301 Moved Permanently (북마크 업데이트), 302 Found (임시), 304 Not Modified (cache 유효, body 없음), 307 Temporary Redirect (method 보존), 308 Permanent Redirect (method 보존, 영구).
4xx — Client Error. "네가 뭘 깼어." 흔한: 400 Bad Request (malformed), 401 Unauthorized (먼저 인증해), 403 Forbidden (인증됐지만 허용 안 됨), 404 Not Found, 405 Method Not Allowed, 409 Conflict, 410 Gone, 415 Unsupported Media Type, 422 Unprocessable Entity (parse 됐지만 validation 실패), 429 Too Many Requests.
5xx — Server Error. "내가 뭘 깼어." 흔한: 500 Internal Server Error (catch-all), 502 Bad Gateway (upstream 거짓말), 503 Service Unavailable (과부하 / 점검 중), 504 Gateway Timeout (upstream 시간 안에 응답 안 함).
Family 로 dispatch, code 로 refine
Production HTTP client 는 family 숫자를 일차 분기로 써:
2xx → response parse, 성공 반환.
3xx → redirect 따라가 (혹은 surface).
4xx → retry 마. Client 가 뭘 잘못한 거고, 같은 입력으로 retry 하면 같은 에러 나.
5xx → exponential backoff 로 retry. Server 가 잠깐 안 좋은 거고, 같은 request 가 나중에 성공할 수도.
1xx → 보통 invisible (HTTP 라이브러리가 처리).
특정 code 로 refine 은 알려진 case 에 — 401 이 auth refresh trigger, 429 가 delay 위해 Retry-After 읽음, 409 가 merge trigger 할 수도 — 근데 load-bearing 결정은 family 숫자야.
Status code 는 lookup table 이 아니라 polymorphic protocol 이야.if status == 200: ... elif status == 201: ... elif status == 204: ... 쓰면 switch 재발명한 거야, if 200 <= status < 300: 로 될 일을. 후자는 향후 모든 2xx (몇 개는 아직 표준화 중) 를 코드 변경 없이 처리해.
가장 헷갈리는 세 쌍
401 vs 403 — 401 은 "자격 증명 없거나 무효; 로그인해." 403 은 "자격 증명은 유효한데 이거 하면 안 돼." 인증 안 된 사용자한테 403 돌려주면 정보 누설 ("이 resource 존재해, 단지 허용 안 됨"); 인증된 사용자한테 401 돌려주는 건 그냥 거짓말.
400 vs 422 — 400 은 request 가 malformed (JSON parse 안 됨, 필수 header 누락). 422 는 parse 는 되는데 비즈니스 validation 실패 (email 필드에 "not-an-email"). FastAPI 는 Pydantic validation 에러에 기본 422 돌려줌, 맞아. Express 기본은 다양.
301 vs 308 (그리고 302 vs 307) — 넷 다 redirect 인데 301/302 는 역사적으로 client 가 method 바꿔도 됐어 (POST → GET on follow). 307/308 은 명시적으로 method 변경 금지. 현대 API 는 307/308 써서 "POST 가 GET 됐어" 놀라움 피해.
cwkPippa 의 status code 단어
cwkPippa backend 가 정기적으로 쓰는 code ~10 개: 200 (대부분 GET), 201 (POST 가 create), 202 (council finalize 같은 long-running), 204 (DELETE / PUT-no-body), 400 (malformed JSON), 401 (auth 없음), 403 (admin-only endpoint), 404 (없는 conversation), 422 (Pydantic validation), 500 (잡히지 않은 backend 예외). Frontend 의 fetch wrapper (frontend/src/lib/api.ts) 가 status_code // 100 으로 먼저 분기하고 그 다음 refine.
Code
Family-first dispatch — 존재한 모든 code 에 통함·python
# Polymorphic dispatcher 패턴 — 처음 보는 code 에도 안 죽음
import httpx
def handle(resp: httpx.Response):
family = resp.status_code // 100
if family == 2:
return resp.json() if resp.content else None # 성공 경로
if family == 3:
return follow_redirect(resp.headers['location']) # 3xx 경로
if family == 4:
# Client error — 알려진 case refine, retry 마
if resp.status_code == 401: refresh_auth()
elif resp.status_code == 429:
delay = int(resp.headers.get('retry-after', 1))
return retry_after(delay)
raise ClientError(resp.status_code, resp.text)
if family == 5:
# Server error — backoff 로 retry
return retry_with_backoff(resp.request)
if family == 1:
# 1xx — HTTP 라이브러리가 처리; 보통 invisible
return None
raise Exception(f'알 수 없는 status family: {resp.status_code}')
httpx — family 당 predicate + raise_for_status helper·python
# httpx 는 같은 아이디어의 편의 predicate 줘
import httpx
resp = httpx.get('https://example.com/')
print(resp.is_informational) # 1xx
print(resp.is_success) # 2xx
print(resp.is_redirect) # 3xx (httpx 가 기본 자동 follow)
print(resp.is_client_error) # 4xx
print(resp.is_server_error) # 5xx
print(resp.is_error) # 4xx 나 5xx
# 흔한 idiom — non-2xx 에 raise
try:
resp.raise_for_status() # 4xx/5xx 에 HTTPStatusError raise
except httpx.HTTPStatusError as e:
print(f'{e.response.status_code} on {e.request.url}')
FastAPI — 가독성 위해 status.* 상수로 HTTPException raise·python
# Server 쪽 — FastAPI 는 적절한 code 로 HTTPException raise
from fastapi import FastAPI, HTTPException, status
app = FastAPI()
@app.get('/users/{uid}')
async def read_user(uid: str):
user = await db_find(uid)
if user is None:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail='없는 user')
return user
@app.post('/users')
async def create_user(payload: dict):
if 'email' not in payload:
# 422 — request 는 well-formed, validation 만 실패
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY,
detail='email 필수')
return {'id': 'new', **payload} # FastAPI 기본 200; status_code=201 설정 가능
@app.post('/admin/wipe')
async def wipe(user=Depends(current_user)):
if user.role != 'admin':
raise HTTPException(status.HTTP_403_FORBIDDEN) # 인증됐지만 허용 안 됨
...
작은 Python 함수 classify(code: int) -> str 써서 어느 입력에도 'informational', 'success', 'redirect', 'client_error', 'server_error' 중 하나 반환해. 그 다음 공개 API (cwkPippa 의 /api/health, GitHub 의 /users/octocat, 네 server 의 일부러 깬 URL) 쳐서 response 분류해. 보너스: classify 에 'should_retry' boolean 추가 — 5xx 와 408/429 만 true.
Hint
60 case switch 쓰지 마. code // 100 이 family 숫자 줘; 1→'informational', 2→'success', 3→'redirect', 4→'client_error', 5→'server_error' 매핑 dict 나 if/elif. Retry 는: family == 5, 혹은 (family == 4 and code in {408, 429}). 향후 ship 될 모든 status code 에 future-proof.
Progress
Progress is local-only — sign in to sync across devices.