C.W.K.
Stream
Lesson 02 of 07 · published

Request & Response Anatomy — 절대 안 바뀌는 네 부분

~11 min · foundations, anatomy, request, response

Level 0HTTP Newbie
0 XP0/46 lessons0/12 achievements
0/120 XP to next level120 XP to go0% complete
"모든 HTTP 메시지 — request 든 response 든, 1990년이든 2026년이든, plain 이든 암호화든 — 네 부분 같은 shape 이야. Shape 한 번 외우고 나면 나머지는 채우기일 뿐."

한 가지 shape, 영원히

HTTP 메시지는 정확히 네 부분이야, 이 순서로:

  1. Start line이게 어떤 종류의 메시지인지 말하는 한 줄.
  2. Header — 0개 이상의 key: value 쌍.
  3. Blank line — 글자 그대로의 CRLF, "header 끝, body 시작" 신호.
  4. Body — optional payload. 비어 있거나, JSON 이거나, form data, 이미지, event stream, 뭐든.

이게 다야. 모든 HTTP/1.1 메시지가 이 네 부분이야. HTTP/2 랑 HTTP/3 는 같은 내용을 binary block 으로 frame 해서 빠르게 만든 거지, code 가 보는 parsed shape 은 동일해. Shape 을 배워; 나머지는 그 위의 variation 일 뿐.

Start line — Request 와 Response 가 다른 유일한 곳

첫 줄이 request 랑 response 가 구조적으로 다른 유일한 자리야.

Request start line 은 토큰 세 개: METHOD SP URI SP HTTP-VERSION. 예: POST /api/chat HTTP/1.1. "HTTP/1.1 로 /api/chat 에 POST 하고 싶어" 라는 뜻.

Response start line 도 토큰 세 개지만 다른: HTTP-VERSION SP STATUS-CODE SP REASON-PHRASE. 예: HTTP/1.1 201 Created. "HTTP/1.1 response, status 201, 의미는 Created". Reason phrase 는 사람 보라는 텍스트야 — client 는 절대 거기 의존해서 분기하면 안 돼; 숫자 코드로 분기해.

Header — 메시지에 대한 metadata

Header 는 메시지가 뭔지, 어떻게 다룰지 describe 해. Key 는 wire 에서 대소문자 무관이야 (Content-Type, content-type, CONTENT-TYPE 다 같은 거). Value 는 ASCII 면 뭐든; 쉼표로 한 header 에 여러 value 묶을 수 있어.

초반엔 두 category 가 가장 중요해:

  • Representation metadata (예전엔 "entity header") — body 에 뭐가 있는지. Content-Type, Content-Length, Content-Encoding. Body 가 있는 곳이면 request 든 response 든 나와.
  • Control header — request 자체를 어떻게 다룰지. Request 엔 Host, Authorization, Accept, User-Agent. Response 엔 Server, Set-Cookie, Cache-Control.

Blank line — 멍청하지만 필수

한 줄의 CRLF (Carriage Return + Line Feed) 가 header 와 body 를 분리해. 빼먹으면 server 가 body 를 또 다른 header 줄로 봐. 요즘 client 대부분이 자동으로 써주지만, TCP socket 위에 손으로 request 만들 거면 blank line 까먹는 게 1위 신참 버그야.

Header 가 뭔지를 말하고, body 가 뭔지를 보여줘. Client 가 body parsing 에서 죽으면 첫 debug 수는 Content-Type response header 를 print 하는 거야. Header 가 body 의 format 에 대한 진실; text/html body 를 response.json() 하려는 게 "API 가 쓰레기 줬어" 의 가장 흔한 원인이야.

Body — Optional, header 가 describe

Body 는 optional. GET request 는 보통 없어. DELETE 도 자주 없어. 근데 존재하는 모든 body 는 최소 두 header 중 하나로 describe 돼: Content-Length (고정 크기, byte 수) 혹은 Transfer-Encoding: chunked (스트리밍, 길이 미리 모름). cwkPippa SSE response 가 Transfer-Encoding: chunked 를 써 — 네 부분 anatomy 똑같음, body 만 event 하나씩 long-lived connection 으로 흘러나오는 거.

전체를 시각화

이걸 byte 단위로 읽을 수 있으면 앞으로 만날 모든 HTTP 메시지를 읽을 수 있어.

Code

주석 달린 request — 네 부분, 위에서 아래로·http
POST /api/chat HTTP/1.1                       <- start line
Host: localhost:8000                          <- header
Content-Type: application/json                <- header
Content-Length: 47                            <- header
Authorization: Bearer abc.def.ghi             <- header
                                              <- blank line (CRLF)
{"conversation_id":"xyz","message":"Hi"}     <- body
주석 달린 response — 같은 네 부분, 다른 start line·http
HTTP/1.1 201 Created                          <- start line
Content-Type: application/json                <- header (body 에 대한)
Content-Length: 89                            <- header (body 에 대한)
Location: /api/chat/messages/m_42             <- header (control)
Date: Sun, 25 May 2026 03:46:50 GMT           <- header (control)
                                              <- blank line (CRLF)
{"id":"m_42","role":"assistant","content":"Hi 아빠."}  <- body
같은 anatomy, Python httpx 로 접근·python
import httpx

resp = httpx.post(
    'http://localhost:8000/api/chat',
    json={'conversation_id': 'xyz', 'message': 'Hi'},
    headers={'Authorization': 'Bearer abc.def.ghi'},
)

# Start line 조각들
print(resp.http_version)      # 'HTTP/1.1'
print(resp.status_code)       # 201
print(resp.reason_phrase)     # 'Created' (여기서 분기 마)

# Header — 대소문자 무관 dict
print(resp.headers['content-type'])  # 'application/json'
print(resp.headers['Content-Length'])  # 이것도 동작

# Body — header 가 어떻게 parse 할지 알려줘
if resp.headers.get('content-type', '').startswith('application/json'):
    print(resp.json())
else:
    print(resp.text)  # raw text 로 fallback
Streaming body: 같은 anatomy, chunked transfer·python
# Streaming body — cwkPippa SSE response 가 chunked transfer 써.
# 네 부분 anatomy 똑같음; body 만 시간 지나면서 조각으로 도착.
with httpx.stream('POST', 'http://localhost:8000/api/chat',
                  json={'message': '이야기 하나 해줘'}) as resp:
    print(resp.headers.get('content-type'))  # 'text/event-stream'
    print(resp.headers.get('transfer-encoding'))  # 'chunked'
    for chunk in resp.iter_text():
        print(chunk, end='', flush=True)
    # Body 는 Content-Length 없음 — server 가 미리 몰라

External links

Exercise

써본 API 아무거나 골라 (네 거든, cwkPippa, GitHub, 뭐든). Request 하나를 curl -v 나 DevTools 로 capture 해. Request 와 response 의 네 부분을 손으로 라벨 달아 — comment block 으로 써봐. 그 다음 일부러 malformed request 보내: JSON body 는 유지하고 Content-Typetext/plain 으로 바꿔. 어떤 status code 가 와? 왜?
Hint
Malformed Content-Type 은 보통 415 Unsupported Media Type 이나 422 야 — server 의 엄격함에 달림. FastAPI 는 엄격해; body-parser middleware 없는 Express 는 그냥 body 무시하고 빈 필드로 200 돌려줄 수도 있어. 그 차이 자체가 왜 header 가 중요한지에 대한 lesson 이야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.