icon

안동민 개발노트

9장 : HTTP

HTTP 기본 구조


DNS가 도메인 이름을 IP 주소로 변환해 주었으니, 이제 실제로 서버와 데이터를 주고받을 차례입니다. 웹에서 이 통신을 담당하는 프로토콜이 HTTP(Hypertext Transfer Protocol)입니다.

웹 개발자가 매일 사용하는 프로토콜이지만, GET과 POST의 차이가 뭔가요?를 넘어서 HTTP의 구조를 정확히 이해하면, API 설계, 성능 최적화, 보안 설정에서 훨씬 나은 판단을 내릴 수 있습니다.


요청과 응답 포맷

HTTP는 요청-응답(Request-Response) 패턴으로 동작합니다. 클라이언트가 요청을 보내면 서버가 응답을 돌려보냅니다.

HTTP 요청 메시지 구조
┌─────────────────────────────────────────┐
│ GET /api/users?page=1 HTTP/1.1          │ ← 요청 라인
├─────────────────────────────────────────┤
│ Host: api.example.com                   │
│ Accept: application/json                │ ← 헤더
│ Authorization: Bearer eyJhbGci...       │
│ User-Agent: Mozilla/5.0                 │
│ Connection: keep-alive                  │
├─────────────────────────────────────────┤
│                                         │ ← 빈 줄 (헤더와 본문 구분)
├─────────────────────────────────────────┤
│ (GET은 보통 본문 없음)                  │ ← 본문 (Body)
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ POST /api/users HTTP/1.1                │ ← 요청 라인
├─────────────────────────────────────────┤
│ Host: api.example.com                   │
│ Content-Type: application/json          │ ← 헤더
│ Content-Length: 45                      │
├─────────────────────────────────────────┤
│                                         │ ← 빈 줄
├─────────────────────────────────────────┤
│{"name":"홍길동","email":"hong@test.com"}│ ← 본문
└─────────────────────────────────────────┘
HTTP 응답 메시지 구조
┌─────────────────────────────────────────┐
│ HTTP/1.1 200 OK                         │ ← 상태 라인
├─────────────────────────────────────────┤
│ Content-Type: application/json          │
│ Content-Length: 92                      │ ← 헤더
│ Set-Cookie: session=abc; HttpOnly       │
│ Cache-Control: max-age=3600             │
├─────────────────────────────────────────┤
│                                         │ ← 빈 줄
├─────────────────────────────────────────┤
│ {"id":1,"name":"홍길동",                │ ← 본문
│  "email":"hong@test.com"}               │
└─────────────────────────────────────────┘

HTTP는 HTTP/1.1까지 텍스트 기반입니다. Wireshark로 패킷을 캡처하면 HTTP 메시지를 직접 읽을 수 있습니다. HTTP/2부터는 바이너리 프레이밍으로 바뀌었지만, 논리적 구조는 동일합니다.


HTTP 메서드

HTTP 메서드는 클라이언트가 서버에 어떤 동작을 원하는지 표현합니다.

메서드용도본문멱등안전사용 예
GET조회없음페이지 로드, API 데이터 조회
POST생성있음회원가입, 게시글 작성, 파일 업로드
PUT전체 대체있음프로필 전체 수정
PATCH부분 수정있음이메일만 변경
DELETE삭제없음계정 삭제, 게시글 삭제
OPTIONS메서드 확인없음CORS 프리플라이트
HEAD헤더만 조회없음리소스 존재 확인, 크기 확인
GET vs POST 상세 비교
GET /search?q=network&page=1 HTTP/1.1
  * 데이터를 URL 쿼리스트링에 포함
  * 브라우저 히스토리에 남음
  * 북마크 가능
  * URL 길이 제한 (~2048자)
  * 캐시 가능
  * 브라우저 뒤로 가기 시 재요청 없음

POST /api/users HTTP/1.1
Content-Type: application/json
{"name": "홍길동"}
  * 데이터를 본문(Body)에 포함
  * 히스토리에 안 남음
  * 북마크 불가
  * 크기 제한 없음 (서버 설정에 따라)
  * 기본적으로 캐시 안 됨
  * 뒤로 가기 시 "다시 제출?" 확인

멱등성과 안전성

HTTP 메서드를 분류하는 두 가지 중요한 속성이 있습니다.

안전성(Safety): 서버의 상태를 변경하지 않는 메서드입니다. GET, HEAD, OPTIONS가 안전합니다.

멱등성(Idempotency): 같은 요청을 한 번 보내든 여러 번 보내든 결과가 동일한 메서드입니다.

멱등성 이해
PUT /users/123 {"name": "홍길동"}
  1번 실행: name = "홍길동"
  3번 실행: name = "홍길동" (동일!)
  → 멱등 ✓

DELETE /users/123
  1번 실행: 사용자 삭제됨
  3번 실행: 이미 없음 (결과 상태 동일)
  → 멱등 ✓

POST /users {"name": "홍길동"}
  1번 실행: 사용자 1명 생성
  3번 실행: 사용자 3명 생성!
  → 멱등 ✗

멱등성이 중요한 이유
  네트워크 타임아웃으로 응답을 못 받았을 때
  ┌──────┐     ┌──────┐
  │Client│──→  │Server│  요청 도달? 처리됨?
  │      │  ✗  │      │  응답이 안 옴!
  └──────┘     └──────┘

  멱등 메서드(PUT): 안전하게 재시도 가능
  비멱등 메서드(POST): 재시도하면 중복 생성 위험!
    → 멱등키(Idempotency Key) 패턴으로 해결
http_methods.py
import json
from http.client import HTTPSConnection

def http_request(method, host, path, body=None):
    """다양한 HTTP 메서드로 요청 전송"""
    conn = HTTPSConnection(host)
    
    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json",
    }
    
    json_body = json.dumps(body) if body else None
    conn.request(method, path, body=json_body, headers=headers)
    
    response = conn.getresponse()
    data = response.read().decode()
    
    print(f"{method} {path}")
    print(f"  Status: {response.status} {response.reason}")
    print(f"  Content-Type: {response.getheader('Content-Type')}")
    if data and len(data) < 200:
        print(f"  Body: {data}")
    print()
    
    conn.close()
    return response.status, data

# 사용 예시 (httpbin.org는 HTTP 테스트 서비스)
# http_request("GET", "httpbin.org", "/get")
# http_request("POST", "httpbin.org", "/post", {"name": "test"})
# http_request("PUT", "httpbin.org", "/put", {"name": "updated"})
# http_request("DELETE", "httpbin.org", "/delete")

HTTP 버전별 차이

버전연도연결 방식특징
HTTP/1.01996요청마다 새 연결비효율적
HTTP/1.11997Keep-Alive (연결 재사용)파이프라이닝 지원 (실패)
HTTP/22015멀티플렉싱 (1연결, 병렬 전송)바이너리, 헤더 압축, 서버 푸시
HTTP/32022QUIC (UDP 기반)HOL Blocking 제거, 0-RTT
HTTP/1.1 vs HTTP/2 요청 처리
HTTP/1.1 (Keep-Alive)
  ┌─TCP 연결────┐
  │ 요청1→응답1 │  ← 순차 처리
  │ 요청2→응답2 │  ← 앞 응답이 끝나야 다음 요청
  │ 요청3→응답3 │
  └─────────────┘
  해결: 브라우저가 6개 TCP 연결 병렬 사용

HTTP/2 (Multiplexing)
  ┌──────TCP 연결─────────┐
  │ [요청1][요청2][요청3] │  ← 하나의 연결에서 동시 전송
  │ [응답2][응답1][응답3] │  ← 응답 순서 무관
  └───────────────────────┘
  하나의 연결로 수십 개 요청 동시 처리

다음 절에서는 서버가 응답할 때 사용하는 상태 코드와 요청/응답에 포함되는 헤더를 살펴보겠습니다.

목차