상태 코드와 헤더
HTTP 응답을 받았을 때 가장 먼저 확인하는 것이 상태 코드(Status Code)입니다. 200이면 성공, 404이면 없음, 500이면 서버 오류. 하지만 이 세 개만 아는 것과 전체 체계를 이해하는 것에는 큰 차이가 있습니다.
상태 코드 체계
HTTP 상태 코드는 세 자리 숫자로, 첫 자리가 응답의 범주를 나타냅니다.
클라이언트는 모든 상태 코드를 세부적으로 알지 못하더라도 첫 자리의 범주는 이해해야 합니다. 예를 들어 처음 보는 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 | 게이트웨이 타임아웃 | 업스트림 응답 지연 |
리디렉션은 특히 주의해야 합니다. 301과 302는 역사적 호환성 때문에 클라이언트가 POST를 GET으로 바꾸는 경우가 있고, 메서드를 보존해야 한다면 307 Temporary Redirect 또는 308 Permanent Redirect가 더 명확합니다. 429 Too Many Requests나 503 Service Unavailable에는 상황에 따라 Retry-After 헤더를 함께 보낼 수 있습니다.
REST API 상태 코드 설계 패턴
REST API에서 상태 코드는 “서버 내부 구현 결과”가 아니라 클라이언트가 다음 행동을 판단할 수 있게 해주는 계약입니다. 생성 성공은 201 Created와 Location 헤더, 비동기 접수는 202 Accepted, 응답 본문이 필요 없는 성공은 204 No Content처럼 의도를 분명히 나누는 편이 좋습니다.
주요 요청/응답 헤더
HTTP 헤더는 요청과 응답에 대한 메타데이터를 전달합니다.
| 헤더 | 방향 | 용도 | 주의사항 |
|---|---|---|---|
| Content-Type | 양방향 | MIME 타입 지정 | JSON API에서 누락 시 파싱 실패 |
| Authorization | 요청 | 인증 토큰 | Bearer, Basic 등 스킴 구분 |
| Host | 요청 | 대상 서버 | HTTP/1.1 필수, 가상호스트 구분 |
| Cache-Control | 응답 | 캐시 정책 | no-cache ≠ 캐시 금지 |
| Set-Cookie | 응답 | 쿠키 설정 | HTTPS 서비스는 Secure, 민감 쿠키는 HttpOnly 권장 |
| User-Agent | 요청 | 클라이언트 정보 | 위조 가능하므로 보안 판단 근거로 부적합 |
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는 업스트림 응답 지연 |
다음 절에서는 HTTP의 상태 관리 메커니즘인 쿠키와 세션을 살펴보겠습니다.