상태 코드와 헤더
HTTP 상태 코드 범주와 주요 요청·응답 헤더를 해석하고 REST API의 성공·오류 응답을 일관되게 설계합니다.
HTTP 응답을 받았을 때 가장 먼저 확인하는 것이 상태 코드(Status Code)입니다.
200이면 성공, 404이면 없음, 500이면 서버 오류.
하지만 이 세 개만 아는 것과 전체 체계를 이해하는 것에는 큰 차이가 있습니다.
상태 코드 체계
HTTP 상태 코드는 세 자리 숫자로, 첫 자리가 응답의 범주를 나타냅니다.
상태 코드는 서버 내부 오류명이 아니라 클라이언트가 다음 행동을 정하도록 보내는 프로토콜 신호다.
- 1xx
Informational 요청 처리 전 중간 상태
- 2xx
Successful 요청 의미가 성공적으로 처리됨
- 3xx
Redirection 다른 URI나 캐시 재검증 필요
- 4xx
Client Error 요청 형식, 인증, 권한, 리소스 문제
- 5xx
Server Error 서버가 유효한 요청을 처리하지 못함
| 질문 | 대표 코드 | 클라이언트 행동 |
|---|---|---|
| 인증 정보가 없거나 만료됐나? | 401 | 인증을 다시 시도한다. |
| 요청은 이해됐지만 허용되지 않나? | 403 | 권한 문제로 사용자에게 알려준다. |
| 리소스 위치가 바뀌었나? | 301 / 302 / 307 / 308 | 메서드 보존 여부를 확인해 재요청한다. |
| 서버가 나중에 다시 받기를 원하나? | 429 / 503 | Retry-After와 지수 백오프를 본다. |
클라이언트는 모든 상태 코드를 세부적으로 알지 못하더라도 첫 자리의 범주는 이해해야 합니다.
예를 들어 처음 보는 499라면 구체적 의미는 몰라도 4xx 클라이언트 오류 계열로 다뤄야 합니다.
상태 코드는 확장 가능하며, 공식 등록값은 IANA HTTP Status Code Registry에서 관리됩니다.
주요 상태 코드 상세
실무에서 자주 만나는 상태 코드를 하나씩 살펴보겠습니다.
| 코드 | 이름 | 의미 | 사용 사례 |
|---|---|---|---|
| 200 | OK | 성공 | GET/PUT/PATCH 성공 응답 |
| 201 | Created | 리소스 생성 | POST 성공, Location 헤더 포함 |
| 204 | No Content | 성공, 본문 없음 | DELETE 성공, 반환 데이터 없을 때 |
| 301 | Moved Permanently | 영구 이동 | URL이 영구적으로 바뀜, 캐시와 검색엔진 신호에 영향 |
| 302 | Found | 임시 이동 | 오래된 관례상 POST가 GET으로 바뀔 수 있음 |
| 304 | Not Modified | 변경 없음 | 캐시 유효, 본문 생략 |
| 400 | Bad Request | 잘못된 요청 | 파라미터 누락, 형식 오류 |
| 401 | Unauthorized | 인증 필요 | 유효한 인증 정보 없음, WWW-Authenticate 필요 |
| 403 | Forbidden | 요청 처리 거부 | 서버가 요청을 이해했지만 허용하지 않음 |
| 404 | Not Found | 리소스 없음 | 잘못된 URL, 삭제된 리소스 |
| 405 | Method Not Allowed | 메서드 불가 | GET만 허용인데 POST 요청 |
| 409 | Conflict | 충돌 | 이미 존재하는 리소스 생성 시도 |
| 429 | Too Many Requests | 요청 과다 | Rate Limiting 초과 |
| 500 | Internal Server Error | 서버 오류 | 코드 버그, 예외 미처리 |
| 502 | Bad Gateway | 게이트웨이 오류 | 업스트림에서 유효하지 않은 응답을 받음 |
| 503 | Service Unavailable | 서비스 불가 | 과부하, 유지보수 |
| 504 | Gateway Timeout | 게이트웨이 타임아웃 | 업스트림 응답 지연 |
숫자 이름을 외우는 것보다 “인증을 다시 할지, 권한 문제로 멈출지, 나중에 재시도할지”를 분리하는 기준이 중요하다.
- 인증 정보가 없거나 유효하지 않음
401 인증 정보가 없거나 유효하지 않음 WWW-Authenticate를 보고 로그인, 토큰 갱신, 인증 스킴 확인을 시도한다.
- 인증은 됐지만 허용되지 않음
403 인증은 됐지만 허용되지 않음 같은 인증 정보로는 접근할 수 없다는 의미에 가깝다.
- 리디렉션과 메서드 보존
302 / 307 / 308 리디렉션과 메서드 보존 POST 의미를 보존해야 하면 307 또는 308이 더 명확하다.
- 재시도 가능한 실패
429 / 503 재시도 가능한 실패 Retry-After가 있으면 클라이언트가 다시 시도할 시점을 판단할 수 있다.
| 질문 | 선택 기준 | 대표 코드 |
|---|---|---|
| 다시 로그인해야 하나? | 인증 정보가 없거나 만료됨 | 401 |
| 권한 자체가 부족한가? | 서버가 요청을 이해했지만 허용하지 않음 | 403 |
| 같은 메서드를 유지해야 하나? | POST/PUT 의미를 보존하는 리디렉션 필요 | 307, 308 |
| 나중에 다시 보내야 하나? | rate limit, 과부하, 유지보수 | 429, 503 |
리디렉션은 특히 주의해야 합니다.
301과 302는 역사적 호환성 때문에 클라이언트가 POST를 GET으로 바꾸는 경우가 있고, 메서드를 보존해야 한다면 307 Temporary Redirect 또는 308 Permanent Redirect가 더 명확합니다.
429 Too Many Requests나 503 Service Unavailable에는 상황에 따라 Retry-After 헤더를 함께 보낼 수 있습니다.
REST API 상태 코드 설계 패턴
CRUD에 상태 코드를 기계적으로 붙이지 말고, 리소스가 새로 생겼는지, 본문이 필요한지, 처리가 끝났는지까지 같이 본다.
- Read
GET /items/1 200 OK 표현 본문을 함께 돌려준다. 없으면 404 또는 상황에 따라 410.
- Create
POST /items 201 Created 새 URI가 있으면 Location 헤더로 알려준다.
- Async
POST /jobs 202 Accepted 접수됐지만 처리는 아직 끝나지 않았다.
- Conflict
PUT /items/1 409 / 412 현재 리소스 상태나 조건부 요청이 맞지 않는다.
| 상황 | 권장 표현 | 잘못 고르면 생기는 문제 |
|---|---|---|
| 생성이 완료됨 | 201 Created + Location | 클라이언트가 새 리소스 위치를 모른다. |
| 본문이 없음 | 204 No Content | 빈 본문을 성공 데이터로 오해할 수 있다. |
| 비동기 처리 | 202 Accepted + 상태 조회 URI | 처리가 끝난 것으로 착각한다. |
| 검증 실패 | 400 / 422 | 클라이언트가 재시도할지 수정할지 헷갈린다. |
REST API에서 상태 코드는 “서버 내부 구현 결과”가 아니라 클라이언트가 다음 행동을 판단할 수 있게 해주는 계약입니다.
생성 성공은 201 Created와 Location 헤더, 비동기 접수는 202 Accepted, 응답 본문이 필요 없는 성공은 204 No Content처럼 의도를 분명히 나누는 편이 좋습니다.
주요 요청/응답 헤더
HTTP 헤더는 요청과 응답에 대한 메타데이터를 전달합니다.
상태 코드가 결과 범주를 말한다면, 헤더는 대상 서버, 인증, 본문 형식, 캐시, 쿠키처럼 메시지 처리 조건을 전달한다.
- Request
Host 대상 origin과 가상 호스트를 구분한다. Authorization Bearer, Basic 같은 인증 스킴과 자격 증명을 보낸다. Accept 클라이언트가 받을 수 있는 표현 형식을 협상한다. If-Match 조건부 변경으로 충돌을 줄인다.
- Response
Content-Type 본문이 JSON, HTML, form data인지 알려준다. Cache-Control 저장 가능 여부, freshness, 재검증 방식을 정한다. Set-Cookie Secure, HttpOnly, SameSite 같은 브라우저 상태 규칙을 보낸다. Location 201 Created나 3xx에서 다음 리소스 위치를 알려준다.
| 헤더 | 방향 | 용도 | 주의사항 |
|---|---|---|---|
| Content-Type | 양방향 | MIME 타입 지정 | JSON API에서 누락 시 파싱 실패 |
| Authorization | 요청 | 인증 토큰 | Bearer, Basic 등 스킴 구분 |
| Host | 요청 | 대상 서버 | HTTP/1.1 필수, 가상호스트 구분 |
| Cache-Control | 응답 | 캐시 정책 | no-cache ≠ 캐시 금지 |
| Set-Cookie | 응답 | 쿠키 설정 | HTTPS 서비스는 Secure, 민감 쿠키는 HttpOnly 권장 |
| User-Agent | 요청 | 클라이언트 정보 | 위조 가능하므로 보안 판단 근거로 부적합 |
캐시, 쿠키, 보안 판단은 헤더 이름 하나가 아니라 지시어와 전송 방향을 함께 봐야 한다.
- Cache-Controlno-cache
Cache-Control no-cache 저장 금지가 아니라, 재사용 전에 origin 재검증을 요구한다.
- Cache-Controlno-store
Cache-Control no-store 요청 또는 응답을 저장하지 말라는 더 강한 지시어다.
- Set-CookieHttpOnly / Secure
Set-Cookie HttpOnly / Secure 스크립트 접근을 막고 HTTPS 전송으로 제한하는 편이 안전하다.
- Accept받고 싶은 표현
Accept 받고 싶은 표현 JSON, HTML, 이미지 등 클라이언트가 선호하는 응답 형식을 말한다.
Content-Type은 본문이 어떤 표현 형식인지 알려주고, Accept는 클라이언트가 선호하는 응답 형식을 알려줍니다.
Cache-Control: no-cache는 “저장 금지”가 아니라 재사용 전에 재검증하라는 의미이고, 저장 자체를 막고 싶다면 no-store를 사용합니다.
from http.client import HTTPSConnection
import json
def inspect_headers(host, path="/"):
"""HTTP 응답 헤더 분석"""
conn = HTTPSConnection(host)
conn.request("GET", path, headers={
"Accept": "text/html",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "ko-KR,ko;q=0.9",
})
response = conn.getresponse()
print(f"=== {host}{path} ===")
print(f"Status: {response.status} {response.reason}")
print("\nResponse Headers:")
security_headers = [
"strict-transport-security",
"content-security-policy",
"x-frame-options",
"x-content-type-options",
]
for name, value in response.getheaders():
marker = " [보안]" if name.lower() in security_headers else ""
print(f" {name}: {value[:80]}{marker}")
response.read()
conn.close()
# inspect_headers("www.google.com")
# inspect_headers("github.com")면접 포인트
| 질문 | 핵심 답변 |
|---|---|
| 401과 403의 차이? | 401은 유효한 인증 정보 필요, 403은 인가(권한) 부족 |
| 302와 307의 차이? | 302는 관례상 메서드가 바뀔 수 있고, 307은 메서드 보존 |
| Content-Type 역할? | 본문 MIME 타입 지정, 파서가 올바르게 처리하기 위해 필수 |
| 502와 504 차이? | 502는 업스트림 비정상 응답, 504는 업스트림 응답 지연 |
숫자 암기보다 클라이언트가 인증, 재시도, 리다이렉션, 파싱을 어떻게 해야 하는지로 설명한다.
- 401 vs 403 인증 / 인
401은 인증 정보 필요, 403은 인증돼도 권한 부족.
- 302 vs 307 리다이렉션 307
메서드와 본문 보존을 명확히 요구한다.
- 502 vs 504 게이트웨
502는 비정상 응답, 504는 업스트림 시간 초과.
- Content-Type 본문 해석 파서
어떤 형식으로 읽을지 알려주는 계약.
상태 코드와 헤더는 응답 의미, 캐시 정책, 리다이렉션, 오류 원인 기준으로 점검합니다.
클라이언트는 status와 header를 함께 보고 재시도, 로그인, 캐시 재검증, 오류 처리를 결정한다.
- 상태 코드
성공, 리다이렉트, 클라이언트 오류, 서버 오류를 먼저 나눈다.
- 헤더
Content-Type, Location, ETag, Retry-After가 후속 처리를 구체화한다.
- 클라이언트 행동
본문 파싱, 재검증, 재로그인, 재시도 여부를 결정한다.
| 상황 | 코드와 헤더 | 클라이언트 판단 |
|---|---|---|
| 인증 필요 | 401 + WWW-Authenticate | 로그인 또는 토큰 갱신 |
| 권한 부족 | 403 | 같은 인증 정보로 재시도하지 않음 |
| 캐시 재검증 | 304 + ETag | 저장된 본문 재사용 |
| 과부하 | 429/503 + Retry-After | 지정 시간 이후 재시도 |
다음 절에서는 HTTP의 상태 관리 메커니즘인 쿠키와 세션을 살펴보겠습니다.