URL, 헤더, 쿠키, 세션, CORS
URL 해부학
섹션 제목: “URL 해부학”URL(Uniform Resource Locator)은 네트워크 상의 리소스 위치를 표현하는 문자열입니다. 복잡해 보이지만 각 부분은 명확한 역할을 가집니다.
https://api.mymodel.com:8443/v1/predict?model=bert&lang=ko#result │ │ │ │ │ │scheme host port path query string fragment| 구성 요소 | 예시 | 설명 |
|---|---|---|
scheme | https | 통신 프로토콜 (http, https, ws, wss) |
host | api.mymodel.com | 서버 도메인 또는 IP |
port | 8443 | 포트 번호 (생략 시 https=443, http=80) |
path | /v1/predict | 서버 내 리소스 경로 |
query string | model=bert&lang=ko | ?로 시작, &로 구분하는 파라미터 |
fragment | result | 페이지 내 특정 위치 (서버로 전송 안 됨) |
쿼리 파라미터 vs 경로 파라미터
섹션 제목: “쿼리 파라미터 vs 경로 파라미터”FastAPI에서는 두 가지 방식으로 파라미터를 받습니다.
# 경로 파라미터: 리소스 식별에 사용@app.get("/models/{model_id}/predict")async def predict(model_id: str): ...
# 쿼리 파라미터: 필터링, 정렬, 옵션에 사용@app.post("/predict")async def predict(lang: str = "ko", top_k: int = 5): ... # GET /predict?lang=ko&top_k=5경로 파라미터는 “어떤 리소스인지”를, 쿼리 파라미터는 “어떻게 처리할지”를 표현합니다.
핵심 HTTP 헤더
섹션 제목: “핵심 HTTP 헤더”헤더는 요청과 응답에 대한 메타데이터를 전달합니다. AI 서빙에서 자주 쓰이는 헤더들입니다.
Content-Type
섹션 제목: “Content-Type”바디의 데이터 형식을 알립니다.
Content-Type: application/json # JSONContent-Type: multipart/form-data # 파일 업로드Content-Type: image/jpeg # 이미지Content-Type: text/event-stream # SSE 스트리밍LLM 스트리밍 응답에는 text/event-stream을 사용합니다. Content-Type이 맞지 않으면 클라이언트가 바디를 올바르게 파싱하지 못합니다.
Authorization
섹션 제목: “Authorization”API 인증에 사용합니다. Bearer 토큰 방식이 가장 일반적입니다.
Authorization: Bearer hf_aBcDeFgHiJkL...# FastAPI에서 헤더 읽기from fastapi import Header, HTTPException
async def predict(authorization: str = Header(None)): if not authorization or not authorization.startswith("Bearer "): raise HTTPException(status_code=401, detail="인증 필요") token = authorization.split(" ")[1] ...기타 중요 헤더
섹션 제목: “기타 중요 헤더”| 헤더 | 방향 | 역할 |
|---|---|---|
Accept | 요청 | 원하는 응답 형식 (application/json) |
User-Agent | 요청 | 클라이언트 식별 |
X-Request-Id | 양방향 | 요청 추적용 UUID |
Cache-Control | 양방향 | 캐싱 동작 제어 |
X-RateLimit-Remaining | 응답 | 남은 API 호출 횟수 |
X-로 시작하는 헤더는 비표준 커스텀 헤더입니다. OpenAI와 HuggingFace는 X-RateLimit-* 헤더로 요청 한도 정보를 제공합니다.
상태 저장: 쿠키, localStorage, sessionStorage
섹션 제목: “상태 저장: 쿠키, localStorage, sessionStorage”HTTP는 기본적으로 무상태(stateless) 프로토콜입니다. 각 요청은 독립적이라 서버는 이전 요청을 기억하지 않습니다. 로그인 상태 유지 같은 기능을 위해 클라이언트 측에 상태를 저장합니다.
| 저장소 | 만료 | 서버 전송 | 용량 | 주 용도 |
|---|---|---|---|---|
| 쿠키 | 설정 가능 | 자동 (같은 도메인) | ~4KB | 세션 ID, 인증 토큰 |
| localStorage | 영구 | 수동 | ~5MB | 사용자 설정, 캐시 |
| sessionStorage | 탭 닫을 때 | 수동 | ~5MB | 임시 폼 데이터 |
쿠키 작동 방식
섹션 제목: “쿠키 작동 방식”# 서버 응답: 쿠키 설정Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Strict
# 이후 브라우저 요청: 자동으로 쿠키 포함Cookie: session_id=abc123HttpOnly: JavaScript에서 쿠키 접근 불가 (XSS 방어). Secure: HTTPS 연결에서만 전송. SameSite=Strict: 다른 사이트에서 온 요청에는 쿠키 미포함 (CSRF 방어).
AI 서비스에서의 선택
섹션 제목: “AI 서비스에서의 선택”API 키 저장: localStorage (편리하지만 XSS에 취약)세션 토큰: HttpOnly 쿠키 (보안상 권장)추론 결과 캐시: localStorage (재사용 가능)현재 업로드 중인 파일 상태: sessionStorageCORS: AI 개발자의 흔한 장벽
섹션 제목: “CORS: AI 개발자의 흔한 장벽”React 개발 서버(http://localhost:5173)에서 FastAPI(http://localhost:8000)로 요청을 보내면 이런 오류를 만납니다.
Access to fetch at 'http://localhost:8000/predict' from origin'http://localhost:5173' has been blocked by CORS policy:No 'Access-Control-Allow-Origin' header is present on the requested resource.CORS가 존재하는 이유
섹션 제목: “CORS가 존재하는 이유”브라우저는 동일 출처 정책(Same-Origin Policy) 을 따릅니다. 출처(Origin)는 스킴 + 호스트 + 포트의 조합입니다.
http://localhost:5173 ≠ http://localhost:8000 (포트 다름 → 다른 출처)https://myapp.com ≠ https://api.myapp.com (호스트 다름 → 다른 출처)다른 출처로 JavaScript fetch 요청을 보내면 브라우저가 차단합니다. 악성 사이트가 사용자의 쿠키를 이용해 다른 서버에 요청을 보내는 것을 막기 위한 보안 정책입니다.
Preflight 요청
섹션 제목: “Preflight 요청”Content-Type: application/json 같은 커스텀 헤더가 있는 요청은 브라우저가 먼저 OPTIONS 메서드 로 예비 요청(preflight)을 보냅니다.
OPTIONS /predict HTTP/1.1Origin: http://localhost:5173Access-Control-Request-Method: POSTAccess-Control-Request-Headers: Content-Type, Authorization서버가 이 preflight에 올바르게 응답해야 실제 POST 요청이 전송됩니다.
FastAPI에서 CORS 해결
섹션 제목: “FastAPI에서 CORS 해결”from fastapi import FastAPIfrom fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware( CORSMiddleware, allow_origins=[ "http://localhost:5173", # 개발 환경 "https://myapp.com", # 프로덕션 ], allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE"], allow_headers=["Content-Type", "Authorization"],)allow_origins=["*"]는 모든 출처를 허용합니다. 개발 중에는 편하지만 프로덕션에서는 명시적으로 허용할 도메인을 지정해야 합니다.
CORS는 브라우저 정책이다
섹션 제목: “CORS는 브라우저 정책이다”중요한 점: CORS는 브라우저에서만 적용 됩니다. curl이나 Python requests로 보내는 요청은 CORS 제한을 받지 않습니다. 그래서 curl 테스트는 성공하는데 브라우저에서만 실패하는 상황이 발생합니다.
# 이건 성공: CORS 없음curl -X POST http://localhost:8000/predict -d '{"text": "test"}'
# 브라우저 JS에서는 CORS 오류 발생 가능fetch('http://localhost:8000/predict', { ... })핵심 정리
섹션 제목: “핵심 정리”- URL은
스킴://호스트:포트/경로?쿼리#프래그먼트구조다. 프래그먼트는 서버로 전달되지 않는다. Content-Type은 바디 형식을,Authorization은 인증을 담당하는 핵심 헤더다.- 쿠키는 서버가 설정하고 자동으로 전송된다. localStorage는 JavaScript가 직접 관리하고 서버로 자동 전송되지 않는다.
- CORS는 브라우저의 동일 출처 정책에서 비롯된다. React(5173)에서 FastAPI(8000)를 호출하면 다른 포트라 CORS 오류가 발생한다.
- FastAPI에서는
CORSMiddleware로 허용할 출처를 명시적으로 등록해 해결한다. - curl 테스트가 성공해도 브라우저에서 실패한다면 CORS 설정을 먼저 확인하라.