icon

안동민 개발노트

9장 : HTTP

상태 코드와 헤더


HTTP 응답을 받았을 때 가장 먼저 확인하는 것이 상태 코드(Status Code)입니다. 200이면 성공, 404이면 없음, 500이면 서버 오류. 하지만 이 세 개만 아는 것과 전체 체계를 이해하는 것에는 큰 차이가 있습니다.


상태 코드 체계

HTTP 상태 코드는 세 자리 숫자로, 첫 자리가 응답의 범주를 나타냅니다.

상태 코드 5개 범주
1xx (정보)     ─→ 요청 수신, 처리 진행 중
2xx (성공)     ─→ 요청 성공적으로 처리됨
3xx (리디렉션) ─→ 추가 동작 필요 (다른 URL로)
4xx (클라이언트 에러) → 요청이 잘못됨
5xx (서버 에러) ─→ 서버에서 처리 실패

주요 상태 코드 상세

실무에서 자주 만나는 상태 코드를 하나씩 살펴보겠습니다.

코드이름의미사용 사례
200OK성공GET/PUT/PATCH 성공 응답
201Created리소스 생성POST 성공, Location 헤더 포함
204No Content성공, 본문 없음DELETE 성공, 반환 데이터 없을 때
301Moved Permanently영구 이동URL 변경, SEO 반영됨
302Found임시 이동로그인 후 리디렉션
304Not Modified변경 없음캐시 유효, 본문 생략
400Bad Request잘못된 요청파라미터 누락, 형식 오류
401Unauthorized인증 필요로그인 안 함 (실제로는 Unauthenticated)
403Forbidden인가 거부권한 없음 (인증은 됐지만)
404Not Found리소스 없음잘못된 URL, 삭제된 리소스
405Method Not Allowed메서드 불가GET만 허용인데 POST 요청
409Conflict충돌이미 존재하는 리소스 생성 시도
429Too Many Requests요청 과다Rate Limiting 초과
500Internal Server Error서버 오류코드 버그, 예외 미처리
502Bad Gateway게이트웨이 오류업스트림 서버 다운
503Service Unavailable서비스 불가과부하, 유지보수
504Gateway 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 상태 코드 설계 패턴

CRUD 별 적절한 상태 코드
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 브라우저 구분에 사용
Content-Type 주요 MIME 타입
텍스트
  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+xml
http_headers.py
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은 인가(권한) 부족
301과 302의 차이?301은 영구 이동(SEO 반영), 302는 임시 이동
Content-Type 역할?본문 MIME 타입 지정, 파서가 올바르게 처리하기 위해 필수
502와 504 차이?502는 업스트림 비정상 응답, 504는 업스트림 응답 지연

다음 절에서는 HTTP의 상태 관리 메커니즘인 쿠키와 세션을 살펴보겠습니다.

목차