9장 : HTTP
상태 코드와 헤더
HTTP 응답을 받았을 때 가장 먼저 확인하는 것이 상태 코드(Status Code)입니다. 200이면 성공, 404이면 없음, 500이면 서버 오류. 하지만 이 세 개만 아는 것과 전체 체계를 이해하는 것에는 큰 차이가 있습니다.
상태 코드 체계
HTTP 상태 코드는 세 자리 숫자로, 첫 자리가 응답의 범주를 나타냅니다.
1xx (정보) ─→ 요청 수신, 처리 진행 중
2xx (성공) ─→ 요청 성공적으로 처리됨
3xx (리디렉션) ─→ 추가 동작 필요 (다른 URL로)
4xx (클라이언트 에러) → 요청이 잘못됨
5xx (서버 에러) ─→ 서버에서 처리 실패주요 상태 코드 상세
실무에서 자주 만나는 상태 코드를 하나씩 살펴보겠습니다.
| 코드 | 이름 | 의미 | 사용 사례 |
|---|---|---|---|
| 200 | OK | 성공 | GET/PUT/PATCH 성공 응답 |
| 201 | Created | 리소스 생성 | POST 성공, Location 헤더 포함 |
| 204 | No Content | 성공, 본문 없음 | DELETE 성공, 반환 데이터 없을 때 |
| 301 | Moved Permanently | 영구 이동 | URL 변경, SEO 반영됨 |
| 302 | Found | 임시 이동 | 로그인 후 리디렉션 |
| 304 | Not Modified | 변경 없음 | 캐시 유효, 본문 생략 |
| 400 | Bad Request | 잘못된 요청 | 파라미터 누락, 형식 오류 |
| 401 | Unauthorized | 인증 필요 | 로그인 안 함 (실제로는 Unauthenticated) |
| 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 vs 403
401: "너 누구야?" → 인증(Authentication) 필요
로그인하면 접근 가능할 수 있음
403: "너 권한 없어" → 인가(Authorization) 거부
로그인해도 관리자 아니면 불가
301 vs 302
301: "영구히 이동" → 검색엔진이 새 URL로 인덱스 갱신
http://old.com → https://new.com
302: "잠깐 다른 곳으로" → 검색엔진이 원래 URL 유지
로그인 성공 후 /dashboard로 리디렉션
500 vs 502 vs 503 vs 504
┌─────────┐ ┌─────────┐ ┌──────────┐
│ Client │────→│ Nginx │────→│ App │
│ │ │ (Proxy) │ │ Server │
└─────────┘ └─────────┘ └──────────┘
500: App Server 내부 에러 (코드 버그)
502: App Server 응답이 비정상 (프로세스 크래시)
503: App Server 과부하 / 유지보수 중
504: App Server 응답 시간 초과 (DB 쿼리 지연 등)REST API 상태 코드 설계 패턴
CREATE (POST)
성공 → 201 Created + Location: /api/users/123
중복 → 409 Conflict
검증 실패 → 400 Bad Request
READ (GET)
성공 → 200 OK + 데이터
없음 → 404 Not Found
캐시 유효 → 304 Not Modified
UPDATE (PUT/PATCH)
성공 → 200 OK + 수정된 데이터
없음 → 404 Not Found
검증 실패 → 400 Bad Request
DELETE
성공 → 204 No Content (또는 200 OK)
없음 → 404 Not Found (또는 204 멱등 처리)주요 요청/응답 헤더
HTTP 헤더는 요청과 응답에 대한 메타데이터를 전달합니다.
요청 헤더 (Client → Server)
┌──────────────────────────────────────┐
│ Host: api.example.com │ 대상 호스트
│ Accept: application/json │ 원하는 응답 형식
│ Authorization: Bearer eyJ... │ 인증 정보
│ User-Agent: Mozilla/5.0... │ 클라이언트 정보
│ Accept-Language: ko-KR │ 원하는 언어
│ Accept-Encoding: gzip, deflate │ 지원 압축 형식
│ If-None-Match: "abc123" │ 조건부 요청 (ETag)
│ Cookie: session=xyz │ 저장된 쿠키
└──────────────────────────────────────┘
응답 헤더 (Server → Client)
┌──────────────────────────────────────┐
│ Content-Type: application/json │ 본문 타입
│ Content-Length: 256 │ 본문 크기
│ Content-Encoding: gzip │ 압축 방식
│ Set-Cookie: session=abc; HttpOnly │ 쿠키 설정
│ Cache-Control: max-age=3600 │ 캐시 정책
│ ETag: "abc123" │ 리소스 식별값
│ Location: /api/users/123 │ 리디렉션/생성 위치
│ Access-Control-Allow-Origin: * │ CORS 허용
└──────────────────────────────────────┘| 헤더 | 방향 | 용도 | 주의사항 |
|---|---|---|---|
| Content-Type | 양방향 | MIME 타입 지정 | JSON API에서 누락 시 파싱 실패 |
| Authorization | 요청 | 인증 토큰 | Bearer, Basic 등 스킴 구분 |
| Host | 요청 | 대상 서버 | HTTP/1.1 필수, 가상호스트 구분 |
| Cache-Control | 응답 | 캐시 정책 | no-cache ≠ 캐시 금지 |
| Set-Cookie | 응답 | 쿠키 설정 | HttpOnly, Secure 필수 |
| User-Agent | 요청 | 클라이언트 정보 | 봇 vs 브라우저 구분에 사용 |
텍스트
text/html → HTML 문서
text/css → 스타일시트
text/plain → 일반 텍스트
애플리케이션
application/json → JSON 데이터 (API 기본)
application/xml → XML 데이터
application/pdf → PDF 파일
application/octet-stream → 알 수 없는 바이너리
폼 데이터
application/x-www-form-urlencoded → 기본 폼 제출
multipart/form-data → 파일 업로드 포함 폼
이미지
image/jpeg, image/png, image/webp, image/svg+xmlfrom 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은 인가(권한) 부족 |
| 301과 302의 차이? | 301은 영구 이동(SEO 반영), 302는 임시 이동 |
| Content-Type 역할? | 본문 MIME 타입 지정, 파서가 올바르게 처리하기 위해 필수 |
| 502와 504 차이? | 502는 업스트림 비정상 응답, 504는 업스트림 응답 지연 |
다음 절에서는 HTTP의 상태 관리 메커니즘인 쿠키와 세션을 살펴보겠습니다.