C.W.K.
Stream
Lesson 01 of 05 · published

HTTP Cache Header — Server 가 intermediary 한테 말하는 계약

~11 min · caching-perf, cache-control, etag, last-modified

Level 0HTTP Newbie
0 XP0/46 lessons0/12 achievements
0/120 XP to next level120 XP to go0% complete
"Caching 이 위에 추가하는 거 아냐 — server 가 유창하게 말하거나 실수로 깨는 protocol. Header 제대로 하면 CDN, 브라우저, reverse proxy 다 협조. 잘못하면 한 user 한테 stale 데이터 ship 하고 다른 user 의 auth response 잘못 캐시."

Caching 굴리는 header 다섯

  • Cache-Control — 현대, 포괄적 지시. 나머지 다 fallback 이나 refinement.
  • ETag — conditional revalidation 위한 opaque version tag (Track 2 Lesson 4).
  • Last-Modified — date 기반 validator, If-Modified-Since 와 함께 사용.
  • Expires — 절대 expiration date. HTTP/1.1 전; 둘 다 있으면 Cache-Control 이 override.
  • Vary — cache 한테 어느 request header 를 cache key 에 추가할지 알려줌.

실제 쓰는 Cache-Control 지시

Cache-Control 값이 comma-separated 지시. 조합이 중요:

  • max-age=N — N초 동안 fresh. Cache 가 재체크 없이 서빙 가능.
  • s-maxage=N — max-age 같은데 SHARED cache (CDN, proxy) 만. 그것들 한텐 max-age override. 짧은 브라우저 TTL + 긴 CDN TTL 설정 가능.
  • public — 어느 cache 든 이 response 저장 가능. 인증된 response 의 CDN caching 에 필수.
  • private — 요청 브라우저 (private cache) 만 저장 가능. CDN 과 proxy 가 캐시 안 함.
  • no-cache — 저장은 하는데, 서빙 전 항상 revalidate (conditional GET 보냄). 오해 소지 이름; "캐시 안 함" 의미 아님.
  • no-store — 실제 캐시 안 함. 끝. 민감 데이터, payment form, 영속 안 돼야 하는 거에.
  • must-revalidate — 만료되면 revalidate 필수; 네트워크 다운돼도 stale 안 서빙.
  • stale-while-revalidate=N — N초 동안 background revalidate 하면서 stale 서빙. 현대, 브라우저-지원, 큰 UX 승리.
  • immutable — 이 response 가 수명 동안 절대 안 변함. Revalidation 완전 건너뜀. Fingerprinted asset URL (app.abc123.css) 에 써.

가장 자주 잡는 패턴 셋

1. Versioned static asset (filename 에 hash 가진 CSS, JS, 이미지):

Cache-Control: public, max-age=31536000, immutable

1년, 절대 revalidate 안 함. 안전 — content 변하면 filename 변함, client 가 새 URL fetch.

2. 자주 변하는 API response (list, user dashboard):

Cache-Control: private, max-age=60, must-revalidate
ETag: "v17-abc"
Vary: Accept-Encoding, Authorization

짧은 브라우저 캐시, 60s 후 conditional GET, CDN 절대 캐시 안 함. ETag 가 304 response 가능; Vary 가 user 당 데이터 분리.

3. 개인 / 민감 데이터 (auth response, payment form):

Cache-Control: no-store

어디서도 절대 캐시 안 함. Token, 세션 데이터, 디스크에 영속 안 돼야 하는 거의 유일 안전 선택.

기본이 조용히 틀림. Cache-Control header 없는 API endpoint 가 intermediary 가 heuristic 기반 캐시 — 가끔 분, 가끔 시간. 항상 Cache-Control 명시 — 그냥 no-storeprivate, max-age=0 이라도. 묵시 caching 이 production 데이터 누설 사는 곳.

cwkPippa 의 cache header

cwkPippa API endpoint 기본 Cache-Control: no-store — 데이터가 user 당이고 WebUI 가 유일 client. Vite 서빙 static asset 이 hashed bundle (Vite 기본 출력) 에 Cache-Control: public, max-age=31536000, immutable 받고 index.htmlno-cache 받아 배포가 즉시 효과. cwk-site 블로그 글이 public, s-maxage=300, stale-while-revalidate=86400 — 편집 통제 위한 짧은 CDN 신선도, 견고함 위한 긴 stale-허용. 패턴이 데이터의 신선도 필요와 매칭.

Code

FastAPI: 데이터의 신선도 모델과 매칭된 response 당 Cache-Control·python
# FastAPI — response 당 Cache-Control 설정, globally 아님
from fastapi import FastAPI, Response

app = FastAPI()

@app.get('/api/health')
async def health(response: Response):
    response.headers['Cache-Control'] = 'public, max-age=10'
    return {'ok': True}

@app.get('/api/me')
async def me(response: Response):
    # User 당 데이터 — share-cache 절대 안 함
    response.headers['Cache-Control'] = 'private, no-store'
    return {'user_id': 'u_42'}

@app.get('/api/posts')
async def list_posts(response: Response):
    # 자주 변하는 list — 짧은 캐시, revalidation 용 ETag, auth 용 Vary
    response.headers['Cache-Control'] = 'private, max-age=60, must-revalidate'
    response.headers['ETag']          = '"v17-abc"'
    response.headers['Vary']          = 'Accept-Encoding, Authorization'
    return {'items': [...]}

@app.get('/assets/app.abc123.css')
async def static_asset(response: Response):
    # Hashed filename — content 변할 수 없음; 영원 캐시
    response.headers['Cache-Control'] = 'public, max-age=31536000, immutable'
    return Response('body{...}', media_type='text/css')
Wire 위 canonical Cache-Control 패턴 셋·http
# Canonical Cache-Control 패턴 셋

# 1. Versioned static asset — 영원 캐시
GET /assets/app.abc123.css HTTP/1.1

HTTP/1.1 200 OK
Content-Type: text/css
Cache-Control: public, max-age=31536000, immutable

# 2. API list — 짧은 캐시 + conditional revalidation + Vary
GET /api/posts HTTP/1.1
Authorization: Bearer abc

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, max-age=60, must-revalidate
ETag: "v17-abc"
Vary: Accept-Encoding, Authorization

# 3. 민감 개인 데이터 — 절대 캐시 안 함
GET /api/me HTTP/1.1
Authorization: Bearer abc

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, no-store
브라우저가 server Cache-Control 존중; client 가 fetch 당 override 가능·javascript
// Client 쪽 — Cache API + stale-while-revalidate 동작 가진 fetch
// (현대 브라우저가 server header 의 stale-while-revalidate 자동 처리)

// 그냥 정상 fetch; 브라우저가 Cache-Control 존중
const resp = await fetch('/api/posts');
const data = await resp.json();

// Client 에서 specific 캐시 정책 opt-in 도 가능
const freshOnly = await fetch('/api/posts', { cache: 'no-cache' });    // 항상 revalidate
const neverNetwork = await fetch('/api/posts', { cache: 'force-cache' }); // 캐시 있으면 서빙
const noCache = await fetch('/api/posts', { cache: 'no-store' });      // 캐시 완전 건너뜀

// 세밀 control 위한 Service Worker
// (여기 자세히 안 함 — fetch event + caches.match + cache.put 패턴)

External links

Exercise

FastAPI 에 다른 Cache-Control 패턴 셋 가진 endpoint 셋 만들어: (1) public, max-age=31536000, immutable 가진 /static/asset.css, (2) private, max-age=60, must-revalidate + ETag + Vary 가진 /api/posts, (3) no-store 가진 /api/me. 각각을 curl -i 로 치고 header 읽어. 그 다음 브라우저 DevTools 세션에서 연속 호출하고 Network 탭이 304 (#2 revalidation 에) 와 'from cache' (#1 에) 보여주는 거 봐. 보너스: 네 번째 endpoint 에 일부러 Cache-Control 빼고 Chrome/Firefox 가 어떻게 추측하는지 봐.
Hint
FastAPI 의 Response 객체가 header inline 설정 가능. ETag 값이 quote 안 어느 string 이든 — 데모엔 "v17-abc" 하드코딩. 브라우저 DevTools 'Network' 패널이 request 당 캐시 상태 보여줘 (200 from network, 304 not modified, from disk cache, from memory cache). 네 번째 endpoint 실험이 명시 caching 이 항상 묵시 이기는 이유 보여줘: 각 브라우저 heuristic 다르고, 선언 안 한 동작 추론 못 함.

Progress

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

댓글 0

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

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