Body 부재가 전체 절약. 항상 response.json() 호출하는 client 가 304 에 죽음 — body 길이 영. 진짜 HTTP client (브라우저, httpx, fetch) 가 304 오면 이전 cached body 서빙으로 투명 처리.
Conditional GET 이 공짜 엔지니어링 — 협조하면. Protocol 이 이미 있음. 브라우저, CDN, 라이브러리, reverse proxy 다 구현. Response 에 ETag 와 Cache-Control 발신만 하면 됨. 절약이 자동 나타남; client-쪽 conditional-GET 코드 안 씀.
흔한 gotcha
1. Non-deterministic serialization. Server JSON 출력이 byte-stable 아니면 (key 순서 변화, timestamp 변화) hash 기반 ETag 가 모든 request 마다 변하고 304 경로 절대 안 켜짐. Key 정렬; timestamp 반올림; 안정 serialization 써.
2. Vary 까먹기. Response 가 Accept-Encoding (gzip/br) 이나 Authorization 따라 변하면 cache 가 알아야 — Vary 없으면 user A 의 compressed 인증된 response 가 plain 요청한 user B 한테 서빙.
3. Public 으로 auth-필요 response 캐시. Response 가 진짜로 모든 user 한테 같을 때만 public 표시; user 당 response 엔 client-쪽 cache + conditional-GET 승리 위한 CDN 노출 없이 private + ETag 써.
cwkPippa 의 conditional GET 현실
cwkPippa 가 현재 API endpoint 에 ETag 안 발신 — healing 층이 매 GET 에 JSONL 에서 response 재구성, byte-stable serialization 이 추가 작업 필요. Static asset (Vite-built React) 가 framework 의 hashed-filename + immutable-cache 전략에서 자동 conditional GET 받아, 승리 거기 나타남. 미래 Pippa instance 가 "느린 Tailscale 링크 너머 반복된 100KB download" debug 하면, 대화 list 에 ETag 발신이 싼 fix. 그게 물기 전까진 비용이 작업보다 큼.
Code
curl 이 304 에 안 보내는 byte 드러냄·bash
# curl 로 conditional GET 춤 봐
# 1. 첫 request — ETag 저장
ETAG=$(curl -s -i https://api.example.com/users/42 | grep -i '^etag:' | awk '{print $2}' | tr -d '\r')
echo "받은 ETag: $ETAG"
# 2. Conditional GET — server 가 304, body 없이 돌려줌
curl -i https://api.example.com/users/42 -H "If-None-Match: $ETAG"
# HTTP/1.1 304 Not Modified
# ETag: "v17-abc123"
# Cache-Control: public, max-age=60
# (body 없음)
# 3. 절약된 body byte 검증
echo "--- 둘 다 시간 재기 ---"
time curl -s https://api.example.com/users/42 > /dev/null # full
time curl -s https://api.example.com/users/42 -H "If-None-Match: $ETAG" > /dev/null # conditional
# Client 쪽 — 대부분 HTTP client 가 conditional GET 투명 처리
import httpx
# httpx (와 fetch 와 -H If-None-Match 가진 curl) 가 너 위해 춤 처리
# 근데 명확성 위해 수동도 가능
cache = {} # url -> (etag, body)
def get_with_cache(client: httpx.Client, url: str):
cached = cache.get(url)
headers = {'If-None-Match': cached[0]} if cached else {}
resp = client.get(url, headers=headers)
if resp.status_code == 304:
return cached[1] # cached body 씀
if resp.status_code == 200 and 'ETag' in resp.headers:
body = resp.json()
cache[url] = (resp.headers['ETag'], body)
return body
resp.raise_for_status()
return resp.json()
# 두 번 호출 — 두 번째 호출이 304 + cached body
with httpx.Client() as c:
user1 = get_with_cache(c, 'https://api.example.com/users/42')
user2 = get_with_cache(c, 'https://api.example.com/users/42') # 304 경로
assert user1 == user2
어느 JSON resource 든 서빙하는 단일 FastAPI endpoint 에 ETag + conditional-GET 처리 추가. 그 다음 후속 If-None-Match 에 첫 response 의 ETag 써서 curl 로 연속 100 request. 전송된 총 byte 측정 (--write-out '%{size_download}\n' 으로). 비교: full response 100 vs 1 full + 99 conditional 304. 보너스: ETag 계산에 일부러 sort_keys=True 빼고 dict-순서 non-determinism 이 일부 request 의 304 경로 깨는 거 봐.
Hint
ETag = sha256(json.dumps(resource, sort_keys=True)) 계산. Bash: total=0; etag=''; for i in {1..100}; do read body etag < <(curl -s -i -H "If-None-Match: $etag" url | ...); total=$((total + ${#body})); done; echo $total. 기대 결과: 전송 byte ~99% 감소. sort_keys=False 보너스가 gotcha — Python dict 순회가 현재 3.7+ 에선 순서 보존, 근데 set 입력 위 dict comprehension 쓰는 production 코드에서 hash 변동이 얼마나 자주인지 놀라.
Progress
Progress is local-only — sign in to sync across devices.