본문으로 건너뛰기
yutils

HTTP 요청 안에는 뭐가 들었을까?

HTTP 요청을 wire 수준에서 풀어본다 — request line, header, body, GET·POST 뒤의 대화 패턴. content negotiation, conditional request, cookie, preflight, 다들 잊는 헤더들.

약 9분 읽기

브라우저 주소창에 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 부분:

  1. Request line — method + path + HTTP version
  2. Headers — name: value 줄 묶음, 빈 줄로 종료
  3. Body — 선택. Content-Length 또는 chunked

Method — GET vs POST vs ...

Method의미bodyidempotentcacheable
GET리소스 조회XOO
POST새 리소스 생성·임의 처리OXX
PUT리소스 전체 교체OOX
PATCH리소스 부분 수정O구현 따라X
DELETE리소스 삭제선택OX
HEADGET 의 헤더만XOO
OPTIONS지원 method·CORS preflightXOX

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-Languageko-KR, ko;q=0.9, en;q=0.8. 서버가 다국어 응답 분기.
  • Accept-Encodinggzip, deflate, br, zstd. 서버가 압축 결정.
  • AuthorizationBearer ... / Basic ... / Digest ....
  • Cookiename=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" — 변경 없으면 304
  • If-Modified-Since: Date — 그 후 변경 없으면 304
  • Cache-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=true

HTML 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-insensitive

4. 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 조합.

참고 자료

요약

  • 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 상태 코드.
가이드 목록으로