본문 바로가기
C.W.K.
Stream
Lesson 02 of 06 · published

curl — 실제 HTTP 교환을 확인하는 도구

~10 min · production, curl, debugging

Level 0HTTP 입문자
0 XP0/46 lessons0/12 achievements
0/120 XP to next level120 XP to go0% complete
"curl은 macOS와 Linux 운영 환경에서 널리 쓸 수 있고, 프레임워크가 가린 HTTP 교환을 드러내 줘. 최소 컨테이너에는 빠져 있을 수 있지만, API 호출이 이상할 때 가장 먼저 꺼낼 만한 진단 도구야."

자주 쓰는 옵션

다음 옵션만 익혀도 대부분의 HTTP 진단을 시작할 수 있어:

  • -v — 상세 모드야. 요청 헤더는 (>), 응답 헤더는 (<), DNS·연결·TLS 상태 같은 curl 진단 메시지는 (*)로 구분해 stderr에 보여 줘. 본문 전체를 같은 표식으로 보여 주는 옵션은 아니야.
  • -i — 응답 본문을 출력하면서 그 앞에 응답 헤더도 포함해. 헤더만 받는 옵션이 아니라는 점에 주의해.
  • -I (대문자) — HEAD 요청을 보내. 서버가 HEAD를 올바르게 지원한다면 본문을 내려받지 않고 메타데이터를 확인할 수 있어.
  • -X METHOD — 사용할 메서드 문자열을 직접 지정해. -X POST, -X DELETE처럼 쓸 수 있지만, 전송 동작까지 메서드에 맞춰 주지는 않아. 다른 옵션이 이미 올바른 메서드를 선택한다면 불필요하게 붙이지 않는 편이 안전해.
  • -H 'Name: value' — 요청 헤더를 추가해. 헤더가 여러 개라면 옵션을 반복하면 돼.
  • -d 'body' — 요청 본문을 보내며, 별도 메서드를 지정하지 않으면 POST를 선택해. -d @file.json는 파일에서 데이터를 읽고 기본 Content-Type은 form 데이터 형식이므로, JSON이라면 해당 헤더를 명시해야 해.
  • --data-binary @file — 파일 내용을 줄바꿈 변환 없이 그대로 보내. 원본 바이트 보존이 필요한 업로드에 알맞아.
  • -u user:pass — HTTP Basic 인증을 설정하는 -H 'Authorization: Basic ...'의 편의 기능이야. 명령줄 기록과 프로세스 목록에 자격 증명이 노출되지 않도록 주의해야 해.
  • -N — curl 출력 버퍼링을 끄는 옵션이야. SSE처럼 도착하는 즉시 내용을 확인해야 하는 스트림에서 유용해.
  • --compressed — curl 빌드가 지원하는 압축 인코딩을 요청하고 응답을 자동으로 풀어 줘. 지원 형식은 curl 빌드마다 다를 수 있어.
  • -L — 리디렉션을 따라가. 사용하지 않으면 보통 첫 3xx 응답에서 멈춰.
  • -o file / -O — 응답 본문을 지정한 파일명 또는 URL에서 가져온 원격 파일명으로 저장해.
  • -w '%{http_code}\n' — 전송이 끝난 뒤 상태 코드, 크기, 단계별 소요 시간 같은 변수를 원하는 형식으로 출력해.

HTTP 교환을 들여다보는 법

curl -v는 요청이 예상과 다르게 동작할 때 가장 먼저 비교하기 좋은 도구야:

# 보낸 모든 byte 와 받은 모든 byte 봐
curl -v -X POST https://api.example.com/users \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer abc.def.ghi' \
  -d '{"name":"Pippa"}'

# 출력 shape:
# *   Trying 1.2.3.4:443...               (TCP connect)
# * TLSv1.3 (OUT), TLS handshake, ...    (TLS handshake)
# > POST /users HTTP/1.1                  (request 줄)
# > Host: api.example.com                 (request header)
# > Authorization: Bearer abc.def.ghi     (request header)
# > Content-Type: application/json        (request header)
# > Content-Length: 17                    (request header)
# >
# > {"name":"Pippa"}                       (request body)
# < HTTP/1.1 201 Created                  (status 줄)
# < Location: /users/u_abc                 (response header)
# < Content-Type: application/json         (response header)
# < {"id":"u_abc","name":"Pippa"}          (response body)

위 동결 블록의 출력 모양은 HTTP 교환을 개념적으로 한데 그린 그림이야. 실제 -v 출력에서 >와 <는 주로 프로토콜 헤더에 붙고, 응답 본문은 일반 출력으로 분리되며 요청 본문도 저 모양 그대로 표시되지 않을 수 있어. 원시 데이터를 더 깊게 조사해야 한다면 --trace나 --trace-ascii를 검토해. httpx 호출은 401인데 curl은 200이라면 메서드, URL, 헤더, 쿠키, 본문 바이트가 정말 같은지 비교해야 해. 상세 출력에는 토큰과 쿠키가 노출될 수 있으므로 저장하거나 공유하기 전에 반드시 가려.

라이브러리 호출이 이상하면 같은 요청을 curl로 최소 재현해. curl만 성공한다고 곧바로 라이브러리 버그라고 단정할 수는 없어. 두 요청의 인증 정보, 프록시, 인증서 저장소, HTTP 버전, 리디렉션, DNS 경로가 다를 수 있기 때문이야. 반대로 둘 다 실패해도 서버 결함만이 아니라 요청이나 환경 설정이 원인일 수 있어. 핵심은 조건을 하나씩 맞춰 원인 범위를 좁히는 데 있어.

유용한 조합

단계별 시간 측정 — DNS, TCP, TLS, 첫 바이트, 전체 전송에 걸린 시간을 나눠 봐:

curl -w 'dns=%{time_namelookup}s connect=%{time_connect}s tls=%{time_appconnect}s ttfb=%{time_starttransfer}s total=%{time_total}s\n' -o /dev/null -s https://api.example.com/

출력 버퍼링을 끈 SSE 스트림:

curl -N -H 'Accept: text/event-stream' https://api.example.com/stream

요청률 제한 관찰 — 테스트가 허용된 엔드포인트에 요청을 100번 보내 상태 코드 분포를 세어 봐:

for i in {1..100}; do
  curl -s -o /dev/null -w '%{http_code}\n' https://api.example.com/
done | sort | uniq -c

브라우저 요청 재현 — Chrome DevTools의 Network 패널에서 "Copy as cURL"을 선택하면 브라우저가 보낸 요청을 터미널에서 재현할 수 있어. 복사된 명령에는 쿠키와 토큰이 들어갈 수 있으니 비밀 정보처럼 다뤄야 해.

cwkPippa에서 curl을 쓰는 방식

cwkPippa WebUI가 멈추거나 500 응답을 돌려줄 때는 백엔드 엔드포인트에 curl -v http://localhost:8000/api/conversations/{id}를 보내 연결 거부, 401 인증 실패, 500 서버 오류, 200이지만 잘못된 본문 같은 경우를 먼저 구분할 수 있어. curl은 브라우저처럼 CORS 정책을 집행하지 않으므로 curl 성공만으로 CORS 문제가 없다고 결론 내리면 안 돼. CORS는 응답 헤더와 브라우저 콘솔을 함께 확인해야 해. 브라우저에서만 나타나는 문제는 Copy as cURL로 동일한 요청을 옮긴 뒤, 쿠키와 헤더를 하나씩 줄여 가며 원인을 찾을 수 있어.

Code

자주 쓰는 curl 명령 모음·bash
# 매일 curl 레시피 — copy/paste

# 단일 request X-ray (canonical debug 수)
curl -v https://api.example.com/users/42

# Auth 가진 JSON POST
curl -X POST https://api.example.com/users \
  -H 'Authorization: Bearer abc' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Pippa"}'

# 파일을 body 로 POST (binary-safe)
curl -X POST https://api.example.com/upload \
  -H 'Content-Type: application/octet-stream' \
  --data-binary @./image.png

# HEAD — header 빨리, body 없음
curl -I https://api.example.com/users/42

# Header 만 (curl chatter 없음)
curl -i https://api.example.com/users/42

# Redirect follow + 일어나는 거 봐
curl -L -v https://example.com/short-url

# 출력 buffering 없는 SSE stream
curl -N -H 'Accept: text/event-stream' https://api.example.com/stream
단계별 시간 측정과 상태 코드 분포·bash
# Timing 분해 — 시간 어디로 가는지 봐
curl -w '\ndns=%{time_namelookup}\nconnect=%{time_connect}\ntls=%{time_appconnect}\nttfb=%{time_starttransfer}\ntotal=%{time_total}\n' \
  -o /dev/null -s https://api.example.com/
# 출력:
# dns=0.005    (DNS lookup)
# connect=0.045  (TCP handshake — RTT 의존)
# tls=0.120    (TLS handshake)
# ttfb=0.180   (time-to-first-byte — server 처리)
# total=0.190  (full body 받음)

# Hammer 테스트 — 100 request, status code 분포
for i in {1..100}; do
  curl -s -o /dev/null -w '%{http_code}\n' https://api.example.com/
done | sort | uniq -c
# 출력 (예): 92 200, 8 429 (rate limit 발동)
Copy as cURL — 브라우저 요청을 터미널에서 재현·bash
# Chrome DevTools → 터미널
# 1. Chrome DevTools 열기 (Mac 에서 Cmd+Option+I)
# 2. Network 탭
# 3. Request 우클릭 → Copy → Copy as cURL
# 4. 터미널에 붙여넣기
#
# 결과: 브라우저가 보낸 정확한 request, 모든 header, cookie, body,
# timing 포함. Curl, grep, jq, 전체 toolkit 있는 command line 에서
# 브라우저 전용 버그 재현.

# 붙여넣어진 결과 예:
curl 'https://api.example.com/api/me' \
  -H 'authority: api.example.com' \
  -H 'accept: application/json' \
  -H 'cookie: session=abc123' \
  -H 'user-agent: Mozilla/5.0 ...' \
  --compressed

External links

Exercise

최근에 라이브러리로 호출하다 문제가 생긴 HTTP 요청 하나를 골라 curl -v로 최소 재현해 봐. curl과 라이브러리 요청의 URL, 메서드, 헤더, 쿠키, 본문 형식, 리디렉션 처리, 프록시 설정을 비교하고 차이를 하나씩 없애. 원인을 찾았다면 라이브러리 호출을 올바르게 수정해. 보너스로 느린 API에 curl -w를 사용해 DNS, TCP, TLS, 첫 바이트, 전체 전송 시간을 측정하고 가장 오래 걸린 단계를 찾아봐.
Hint
차이는 인증 헤더나 Content-Type, Accept, 사용자 정의 헤더처럼 눈에 보이는 곳에만 있지 않아. httpx 같은 클라이언트가 Accept-Encoding이나 User-Agent를 자동으로 붙이거나, 프록시·인증서·리디렉션 정책을 다르게 적용할 수도 있어. curl -v의 헤더와 연결 정보를 비교하되, 본문 바이트가 의심되면 trace 옵션이나 캡처 도구를 추가로 사용해. 민감한 값은 기록과 공유 전에 가려야 해.

Progress

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

댓글 0

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

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