브라우저 주소창에 URL 을 치면 wire 에는 텍스트 한 묶음이 흘러간다. Request line 1 줄 + headers + blank line + (선택) body. 단순 같은데 실제로는 content negotiation, conditional request, cookie 의 복잡한 대화. 이 가이드는 HTTP 요청의 실제 구조, 자주 보는 헤더, preflight 의 정체, 그리고 디버깅 시 자주 빠뜨리는 헤더를 정리한다.
요청 한 줄로 — wire 위의 모양
GET /api/users?page=2 HTTP/1.1
Host: example.com
Accept: application/json
User-Agent: Mozilla/5.0 (...)
Accept-Encoding: gzip, deflate, br
Cookie: session=abc123; theme=dark
Authorization: Bearer eyJhbGciOiJIUzI1Ni...
(빈 줄 — body 없음)POST 요청 예시 — body 가 있음:
POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 42
Authorization: Bearer eyJhbGciOiJIUzI1Ni...
{"name":"Alice","email":"a@b.c"}구조 3 부분:
- Request line — method + path + HTTP version
- Headers — name: value 줄 묶음, 빈 줄로 종료
- Body — 선택. Content-Length 또는 chunked
Method — GET vs POST vs ...
| Method | 의미 | body | idempotent | cacheable |
|---|---|---|---|---|
| GET | 리소스 조회 | X | O | O |
| POST | 새 리소스 생성·임의 처리 | O | X | X |
| PUT | 리소스 전체 교체 | O | O | X |
| PATCH | 리소스 부분 수정 | O | 구현 따라 | X |
| DELETE | 리소스 삭제 | 선택 | O | X |
| HEAD | GET 의 헤더만 | X | O | O |
| OPTIONS | 지원 method·CORS preflight | X | O | X |
idempotent — N 번 호출과 1 번 호출의 결과 같음. 네트워크 재시도 안전한지 판단 기준.
GET 으로 server state 변경 X (HTTP/1.1 spec) — search bot 이 모든 GET URL crawl. ?delete=1 같은 패턴이 사고 원인.
Headers — 종류 너무 많음
일반·표준 headers
Host— 가상 호스팅용. 한 IP 의 여러 도메인 구분. HTTP/1.1 필수.User-Agent— 클라이언트 식별 (User-Agent 파서 가이드 참조).Accept— 받을 수 있는 MIME type.application/json, text/html;q=0.9(q = 선호도).Accept-Language—ko-KR, ko;q=0.9, en;q=0.8. 서버가 다국어 응답 분기.Accept-Encoding—gzip, deflate, br, zstd. 서버가 압축 결정.Authorization—Bearer .../Basic .../Digest ....Cookie—name=value; name2=value2(쿠키 파서).Referer— 이전 페이지 URL (typo 그대로). 사용자 추적· analytics·CSRF 검증에 사용.Origin— 요청 발생 origin. CORS 의 핵심.
Body 관련
Content-Type— body 의 MIME.application/json,multipart/form-data,application/x-www-form-urlencoded.Content-Length— body byte 수.Transfer-Encoding: chunked— body 크기 모를 때. streaming 응답.Content-Encoding— gzip / br / zstd 등 body 자체 압축 (Accept-Encoding 의 응답).
Conditional / 캐시 관련
If-None-Match: "etag"— 변경 없으면 304If-Modified-Since: Date— 그 후 변경 없으면 304Cache-Control: max-age=3600— 캐시 정책
현대 보안 / 진단
Sec-CH-UA시리즈 — User-Agent 의 미래 (Client Hints)Sec-Fetch-*— 요청의 컨텍스트 (Mode/Dest/Site/User)X-Request-ID— 분산 추적 (Datadog / Honeycomb)
Content Negotiation — 왜 Accept-* 이 필요한가
Client → Server:
GET /article/42
Accept: application/json, text/html;q=0.9
Accept-Language: ko-KR, ko;q=0.9
Accept-Encoding: gzip
Server 응답 분기:
- /article/42.json (한국어) 가 있고 클라이언트가 JSON 우선
- 그것 gzip 압축 후 전송
- 응답 헤더에 Vary: Accept, Accept-Language 박음 (캐시용)q value (quality) = 선호도 0.0-1.0. 명시 X 면 1.0. server 는 가장 높은 q 의 type 선택.
Conditional GET — 캐시의 핵심
첫 요청:
GET /image.png
→ 200 OK
ETag: "abc123"
Cache-Control: max-age=86400
브라우저 캐시 만료 후:
GET /image.png
If-None-Match: "abc123"
→ 304 Not Modified (body 0 byte!)304 = "당신이 가진 게 여전히 최신". 네트워크 절약 + 빠른 페이지 로드. CDN 의 핵심.
POST body 의 4 가지 인코딩
application/json
POST /api/users HTTP/1.1
Content-Type: application/json
{"name":"Alice","age":30}application/x-www-form-urlencoded
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded
name=Alice&password=secret&remember=trueHTML form 의 default. URL encoding 적용 (한글 = %xx).
multipart/form-data
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="user"
Alice
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="avatar"; filename="me.jpg"
Content-Type: image/jpeg
(binary image data)
------WebKitFormBoundary7MA4YWxkTrZu0gW--파일 업로드. boundary 로 part 구분.
raw
text/plain / application/xml / 임의 binary. 보통 API server 가 처리. SOAP 가 XML.
CORS Preflight — OPTIONS 의 정체
브라우저에서 cross-origin 요청 시 보안 검사. "복잡한" 요청 ( non-simple method 또는 custom header) 은 실제 요청 전에 OPTIONS preflight:
1. Preflight:
OPTIONS /api/data HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: x-custom-header
2. 서버 응답:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: x-custom-header
Access-Control-Max-Age: 86400
3. 실제 요청 (preflight 통과 후):
PUT /api/data HTTP/1.1
Origin: https://app.example.com
x-custom-header: value
...cors-explained 가이드 참조. preflight 가이드 핵심:
- Simple request (GET/HEAD + 표준 header) 는 preflight 없음
- Custom header 또는 PUT/DELETE/PATCH = preflight
- Max-Age 로 preflight 결과 캐시 (재요청 0). 보통 1 일.
HTTP/1.1 vs HTTP/2 vs HTTP/3
- HTTP/1.1 (1997) — text-based, 1 connection 당 1 request. Keep-Alive 로 multiple. 본 가이드의 wire format 이 1.1
- HTTP/2 (2015) — binary frame, multiplexing (한 connection 에 다수 요청 동시). 헤더 압축 (HPACK).
- HTTP/3 (2022) — UDP 기반 QUIC 위. TLS 통합. conn 시작 RTT ↓. 모바일 packet loss 강함.
Semantic (method / header / body 의미) 은 모두 같음. wire 표현만 다름. 본 가이드는 1.1 위주 — 가장 보편적.
curl 로 wire 직접 보기
curl -v https://example.com/api/data
# -v 가 wire trace 표시
# > 가 요청 라인
# < 가 응답 라인
curl -X POST https://example.com/api/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer abc123" \
-d '{"name":"Alice"}'cURL 빌더 가 URL · method · header · body input → curl 명령 빌드. 복잡한 요청 디버깅에 편리.
흔한 함정
1. Content-Type 누락
POST body 박았지만 Content-Type 없음 → 서버가 거부 (415 Unsupported Media Type) 또는 잘못 파싱.
2. Cookie 의 SameSite / Secure 누락
Set-Cookie: session=...; SameSite=None; Secure 없으면 cross-site 요청에서 cookie 안 따라감. CORS 와 별개.
3. Authorization 의 case 함정
Authorization: Bearer abc123 ← 표준
Authorization: bearer abc123 ← 일부 서버가 거부
authorization: Bearer abc123 ← header name 은 case-insensitive4. GET 의 query string 길이 제한
2,000 ~ 8,000 byte 정도가 안전. 일부 서버 / proxy 가 자름. 큰 파라미터는 POST body.
5. Accept 무시하는 서버
Accept: application/json 박았지만 server 가 HTML 응답 → 클라이언트 parse 에러. server side spec 확인.
6. Origin 만 검증하는 CORS
attacker 가 같은 Origin 의 다른 path 에서 요청 → 통과. Origin + path + custom header 조합.
참고 자료
- RFC 9110 (HTTP semantics) — datatracker
- MDN HTTP headers — MDN
- CORS spec — WHATWG
- curl manual — curl.se
요약
- HTTP 요청 = request line + headers + (blank line) + (선택) body. 텍스트 기반 (HTTP/1.1).
- method 의 idempotent / cacheable 속성이 다름. GET 으로 state 변경 X.
- Content Negotiation — Accept / Accept-Language / Accept-Encoding + q value 우선도.
- Conditional GET — ETag / If-None-Match → 304 Not Modified. CDN 의 핵심.
- POST body 4 가지 — json / form-urlencoded / multipart / raw.
- CORS preflight = OPTIONS. simple request 외 모두.
- HTTP/2/3 은 wire 만 다르고 semantic 동일.
- 실험 — cURL 빌더 / 쿠키 파서 / User-Agent 파서 / HTTP 상태 코드.