9장 : HTTP
HTTP 기본 구조
DNS가 도메인 이름을 IP 주소로 변환해 주었으니, 이제 실제로 서버와 데이터를 주고받을 차례입니다. 웹에서 이 통신을 담당하는 프로토콜이 HTTP(Hypertext Transfer Protocol)입니다.
웹 개발자가 매일 사용하는 프로토콜이지만, GET과 POST의 차이가 뭔가요?를 넘어서 HTTP의 구조를 정확히 이해하면, API 설계, 성능 최적화, 보안 설정에서 훨씬 나은 판단을 내릴 수 있습니다.
요청과 응답 포맷
HTTP는 요청-응답(Request-Response) 패턴으로 동작합니다. 클라이언트가 요청을 보내면 서버가 응답을 돌려보냅니다.
┌─────────────────────────────────────────┐
│ 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/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 /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) 패턴으로 해결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.0 | 1996 | 요청마다 새 연결 | 비효율적 |
| HTTP/1.1 | 1997 | Keep-Alive (연결 재사용) | 파이프라이닝 지원 (실패) |
| HTTP/2 | 2015 | 멀티플렉싱 (1연결, 병렬 전송) | 바이너리, 헤더 압축, 서버 푸시 |
| HTTP/3 | 2022 | QUIC (UDP 기반) | HOL Blocking 제거, 0-RTT |
HTTP/1.1 (Keep-Alive)
┌─TCP 연결────┐
│ 요청1→응답1 │ ← 순차 처리
│ 요청2→응답2 │ ← 앞 응답이 끝나야 다음 요청
│ 요청3→응답3 │
└─────────────┘
해결: 브라우저가 6개 TCP 연결 병렬 사용
HTTP/2 (Multiplexing)
┌──────TCP 연결─────────┐
│ [요청1][요청2][요청3] │ ← 하나의 연결에서 동시 전송
│ [응답2][응답1][응답3] │ ← 응답 순서 무관
└───────────────────────┘
하나의 연결로 수십 개 요청 동시 처리다음 절에서는 서버가 응답할 때 사용하는 상태 코드와 요청/응답에 포함되는 헤더를 살펴보겠습니다.