๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿš€ Backend

[FastAPI] Middleware ๋ฏธ๋“ค์›จ์–ด (CORS, Logging)

by dev.py 2025. 4. 20.

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

curl ์š”์ฒญ : x-process-time ํ—ค๋”๊ฐ€ ์ถ”๊ฐ€๋œ ์‘๋‹ต

 

 

3. CORS

๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ†ตํ•ด ์ž์ฃผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š”๋ฐ,  CORS(Cross-Origin Resource Sharing) ๊ต์ฐจ-์ถœ์ฒ˜ ๋ฆฌ์†Œ์Šค ๊ณต์œ  ๋ฌธ์ œ

1. ์ถœ์ฒ˜

์ถœ์ฒ˜(origin) = ํ”„๋กœํ† ์ฝœ (http/https) + ๋„๋ฉ”์ธ + ํฌํŠธ์˜ ์กฐํ•ฉ

 

์•„๋ž˜๋Š” ๋ชจ๋‘ ์„œ๋กœ ๋‹ค๋ฅธ ์ถœ์ฒ˜

  • http://localhost
  • https://localhost
  • http://localhost:8080

 

2. CORS ๋™์ž‘ ๋‹จ๊ณ„

  1. ๋ธŒ๋ผ์šฐ์ €์˜ JS์—์„œ ๋‹ค๋ฅธ ์ถœ์ฒ˜์˜ ์„œ๋ฒ„์— ์š”์ฒญ์„ ์‹œ๋„
  2. HTTP OPTIONS (Preflight) ์š”์ฒญ์„ ์„œ๋ฒ„๋กœ ๋ณด๋ƒ„ 
    • ์‹ค์ œ ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ์ „์—, ํ—ˆ์šฉ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•œ ์‚ฌ์ „ ์š”์ฒญ
    • Origin, Access-Control-Request-Method, Access-Control-Request-Headers ํ—ค๋”๊ฐ€ ํฌํ•จ๋จ
  3. ์„œ๋ฒ„๊ฐ€ CORS ํ—ˆ์šฉ ํ—ค๋”๋ฅผ ์‘๋‹ต์œผ๋กœ ๋ณด๋ƒ„ 
    • Access-Control-Allow-Origin: http://localhost:8080
    • Access-Control-Allow-Headers: Content-Type, Authorization
    • Access-Control-Allow-Methods: GET, POST, OPTIONS
  4. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์‘๋‹ต์„ ํŒ๋ณ„ 
    • ์กฐ๊ฑด์ด ๋ชจ๋‘ ๋งž์œผ๋ฉด → ์‹ค์ œ ์š”์ฒญ์„ ๋ณด๋ƒ„
    • ํ•˜๋‚˜๋ผ๋„ ์•ˆ๋งž์œผ๋ฉด → 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

 

CORS์šฉ preflight - ์ •์ƒ์ ์œผ๋กœ ์‘๋‹ต

 

 

 

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

 

 

ERROR -> INFO ์ถœ๋ ฅ ํ›„, ์‘๋‹ต

 

 

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

https://fastapi.tiangolo.com/ko/tutorial/cors/?h=

'๐Ÿš€ 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