1. ๋ฏธ๋ค์จ์ด๋?
FastAPI์ ๋ฏธ๋ค์จ์ด๋ ํด๋ผ์ด์ธํธ์ ์์ฒญ(Request)์ ์๋ต(Response) ์ฌ์ด์ ์คํ๋๋ ๊ฐ๋ก์ฑ๊ธฐ ํจ์
- ์ฃผ๋ก ์ธ์ฆ, ๋ก๊น , ์ฑ๋ฅ ์ธก์ , ์๋ฌ ์ฒ๋ฆฌ, ๋ณด์ ๋ฑ ๊ณตํต ๊ธฐ๋ฅ์ ๊ตฌํํ๋๋ฐ ์ฌ์ฉ ๊ฐ๋ฅ
- ๋น์ฆ๋์ค & ๋๋ฉ์ธ ๋ก์ง์ ๋ถ๋ฆฌํ ์ ์์ด, ๊ฐ๋ ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ ํฅ์
2. FastAPI ์์
import time
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.perf_counter() # ์ค์ ๋ผ์ฐํฐ ํจ์ ํธ์ถ ์
response = await call_next(request) # ์ค์ ๋ผ์ฐํฐ ํจ์ ํธ์ถ
process_time = time.perf_counter() - start_time # ์ค์ ๋ผ์ฐํฐ ํจ์ ํธ์ถ ํ ~
response.headers["X-Process-Time"] = str(process_time)
return response
# https://fastapi.tiangolo.com/tutorial/middleware/?h=midd
http ์์ฒญ์ด ๋ค์ด์์ ๋, ํค๋์ "X-Process-Time" ์ด๋ฆ์ผ๋ก ์คํ์๊ฐ์ ๋ถ์ฌ์ฃผ๋ ํค๋๋ฅผ ๋ถ์ฌ ์ฃผ๋ ๋ฏธ๋ค์จ์ด
- request
- ํด๋ผ์ธํธ๊ฐ ๋ณด๋ธ HTTP ์์ฒญ ๊ฐ์ฒด
- ๋ฉ์๋, ๊ฒฝ๋ก, ํค๋, ๋ฐ๋ ๋ฑ ์์ฒญ์ ๋ํ ์ ๋ณด๋ฅผ ๊ฐ๊ณ ์์
- call_next
- ๋ค์ ๋ฏธ๋ค์จ์ด ๋๋ ๋ผ์ฐํฐ ํจ์๋ก ์์ฒญ์ ์ ๋ฌํ๋ ์ฝ๋ฐฑํจ์
- await call_next(request)๋ฅผ ํธ์ถํ๋ฉด, ์ดํ์ ์์ฒญ ์ฒ๋ฆฌ ๊ณผ์ (์ค์ ๋ผ์ฐํฐ ํจ์)์ด ์คํ๋๊ณ Response ๊ฐ์ฒด๋ฅผ ๋ฐํ
- call_next()๋ฅผ ํธ์ถํ์ง ์์ผ๋ฉด ์ค์ ๋ผ์ฐํธ๊น์ง ์์ฒญ ์ ๋ฌ X
3. CORS
๋ฏธ๋ค์จ์ด๋ฅผ ํตํด ์์ฃผ ํด๊ฒฐํ๋ ๋ฌธ์ ๊ฐ ์๋๋ฐ, CORS(Cross-Origin Resource Sharing) ๊ต์ฐจ-์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ ๋ฌธ์
1. ์ถ์ฒ
์ถ์ฒ(origin) = ํ๋กํ ์ฝ (http/https) + ๋๋ฉ์ธ + ํฌํธ์ ์กฐํฉ
์๋๋ ๋ชจ๋ ์๋ก ๋ค๋ฅธ ์ถ์ฒ
- http://localhost
- https://localhost
- http://localhost:8080
2. CORS ๋์ ๋จ๊ณ
- ๋ธ๋ผ์ฐ์ ์ JS์์ ๋ค๋ฅธ ์ถ์ฒ์ ์๋ฒ์ ์์ฒญ์ ์๋
- HTTP OPTIONS (Preflight) ์์ฒญ์ ์๋ฒ๋ก ๋ณด๋
- ์ค์ ์์ฒญ์ ๋ณด๋ด๊ธฐ์ ์, ํ์ฉ ์ฌ๋ถ๋ฅผ ํ์ธํ๊ธฐ ์ํ ์ฌ์ ์์ฒญ
- Origin, Access-Control-Request-Method, Access-Control-Request-Headers ํค๋๊ฐ ํฌํจ๋จ
- ์๋ฒ๊ฐ CORS ํ์ฉ ํค๋๋ฅผ ์๋ต์ผ๋ก ๋ณด๋
- Access-Control-Allow-Origin: http://localhost:8080
- Access-Control-Allow-Headers: Content-Type, Authorization
- Access-Control-Allow-Methods: GET, POST, OPTIONS
- ๋ธ๋ผ์ฐ์ ๊ฐ ์๋ต์ ํ๋ณ
- ์กฐ๊ฑด์ด ๋ชจ๋ ๋ง์ผ๋ฉด → ์ค์ ์์ฒญ์ ๋ณด๋
- ํ๋๋ผ๋ ์๋ง์ผ๋ฉด → JS ๋ ๋ฒจ์์ ์ฐจ๋จ
ํค๋ ํญ๋ชฉ | ํ๋ณ ์๊ตฌ ์ฌํญ | ์ค๋ช |
Access-Control-Allow-Origin | ์์ฒญํ Origin๊ณผ ์ ํํ ์ผ์นํ๊ฑฐ๋ * | *์์ผ๋์นด๋๋ ์ฟ ํค/์ธ์ฆ์ ๋ณด์ ํจ๊ป ์ฌ์ฉ ๋ถ๊ฐ |
Access-Control-Allow-Methods | ์์ฒญํ ๋ฉ์๋(GET, POST ๋ฑ)๊ฐ ํฌํจ๋์ด์ผ ํจ | ์: ์ค์ ์์ฒญ์ด POST๋ฉด, ์๋ต์ POST๊ฐ ํฌํจ๋์ด์ผ |
Access-Control-Allow-Headers | ์์ฒญํ ์ปค์คํ ํค๋๋ค์ด ๋ชจ๋ ํฌํจ๋์ด์ผ ํจ | ์: Authorization, Content-Type ๋ฑ |
Access-Control-Allow-Credentials | (ํ์ ์) true ์ค์ | ์ฟ ํค/์ธ์ ๋ฑ์ ์ฃผ๊ณ ๋ฐ์ ๊ฒฝ์ฐ ํ์ |
Status Code | ์๋ต ์ํ๊ฐ 2xx์ฌ์ผ ํจ | ์๋ฌ์ฝ๋(4xx/5xx)๋ฉด ๋ฌด์กฐ๊ฑด ์ฐจ๋จ๋จ |
3. FastAPI CORS ์ค์
CORS์ ๋ํ ์ ์ ํ ์๋ต์ ์ฃผ๊ธฐ ์ํ ์ค์
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# ํ์ฉํ ์ถ์ฒ(origin) ๋ชฉ๋ก ์ ์
origins = [
"http://example-fronted.com", # ์ด์ ์ค์ธ ํ๋ก ํธ์๋ ๋๋ฉ์ธ
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins, # ํ์ฉํ Origin ๋ฆฌ์คํธ (ํ์)
allow_credentials=True, # ์ฟ ํค/์ธ์
๋ฑ ์ธ์ฆ์ ๋ณด ํ์ฉ
allow_methods=["*"], # ๋ชจ๋ HTTP ๋ฉ์๋ ํ์ฉ (GET, POST ๋ฑ)
allow_headers=["*"], # ๋ชจ๋ ์์ฒญ ํค๋ ํ์ฉ (Authorization ๋ฑ)
)
# https://fastapi.tiangolo.com/ko/tutorial/cors/ - ์ผ๋ถ ์์
OPTIONS - ์ฌ์ ์์ฒญ curl
curl -X OPTIONS http://localhost:8000/secure/data \
-H "Origin: http://example-fronted.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Authorization, Content-Type" \
-i
4. Logging
import logging
import time
from fastapi import FastAPI, Request, Response
from fastapi_cli import logging as fastapi_logging
app = FastAPI()
fastapi_logging.setup_logging(level=logging.INFO)
logger = logging.getLogger("fastapi_cli") # fastapi_cli set up
@app.middleware("http")
async def log_request_response(request: Request, call_next) -> Response:
start = time.time()
logger.error(f"[REQ] {request.method} {request.url.path}") # ERROR - log
response = await call_next(request)
duration = time.time() - start
logger.info(f"[RES] {request.method} {request.url.path} → {response.status_code} | {duration:.2f}s") # INFO - log
return response
5. Auth
๊ฐ๋จํ ์ธ์ฆ ์ ์ฐจ๋ ๊ตฌํํ ์ ์์ง๋ง → Depends (์์กด์ฑ ์ฃผ์ )์ ํตํ ์ธ์ฆ ์ค๊ณ๋ฅผ ํ์
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
@app.middleware("http")
async def extract_user_from_header(request: Request, call_next):
token = request.headers.get("Authorization")
if token == "Bearer secrettoken":
request.state.user = {"id": "user123", "role": "admin"}
else:
request.state.user = None
return await call_next(request)
@app.get("/secure/data") # ๋ณดํธ๋ ๋ผ์ฐํฐ
async def secure_data(request: Request):
if request.state.user is None:
raise HTTPException(status_code=401, detail="Unauthorized")
return {"message": f"Welcome {request.state.user['id']}"}
@app.get("/") # ๊ณต๊ฐ ๋ผ์ฐํฐ
async def public():
return {"message": "Anyone can access this route"}
์์ฒญ | ์๋ต | ||
๊ฒฝ๋ก | ์ธ์ฆ ํค๋ | ๋ช ๋ น์ด | |
๋ณดํธ๋ ๊ฒฝ๋ก | O | curl -H "Authorization: Bearer secrettoken" http://localhost:8000/secure/data | {"message":"Welcome user123"} |
๋ณดํธ๋ ๊ฒฝ๋ก | X | curl http://localhost:8000/secure/data | {"detail":"Unauthorized"} |
๊ณต๊ฐ ๊ฒฝ๋ก | X | curl http://localhost:8000/ | {"message":"Anyone can access this route"} |
6. ๊ด๋ จ ์๋ฃ
https://fastapi.tiangolo.com/ko/tutorial/middleware/?h=%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4
https://fastapi.tiangolo.com/ko/advanced/middlewares/?h=%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4
'๐ Backend' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[FastAPI] N+1 Problem (0) | 2025.05.07 |
---|---|
[Swift + Supabase] OAuth - Sign in with Apple (0) | 2025.04.28 |
[FastAPI] SSE (Server-Sent Events) (0) | 2025.04.16 |
[FastAPI] SQLModel (0) | 2025.03.14 |
[FastAPI] Best Practices - Project Structure (0) | 2025.02.14 |