FastAPI ๋ฏธ๋ค์จ์ด๋ฅผ ์ฒ์๋ถํฐ ๋ง์คํฐํ์ธ์. ์ด ์ฌ์ธต ๊ฐ์ด๋๋ ์ฌ์ฉ์ ์ ์ ๋ฏธ๋ค์จ์ด, ์ธ์ฆ, ๋ก๊น , ์ค๋ฅ ์ฒ๋ฆฌ ๋ฐ ๊ฒฌ๊ณ ํ API ๊ตฌ์ถ์ ์ํ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ค๋ฃน๋๋ค.
Python FastAPI ๋ฏธ๋ค์จ์ด: ์์ฒญ ๋ฐ ์๋ต ์ฒ๋ฆฌ์ ์ข ํฉ ๊ฐ์ด๋
์ต์ ์น ๊ฐ๋ฐ ํ๊ฒฝ์์ ์ฑ๋ฅ, ๋ณด์, ์ ์ง๋ณด์์ฑ์ ๋งค์ฐ ์ค์ํฉ๋๋ค. Python์ FastAPI ํ๋ ์์ํฌ๋ ๋๋ผ์ด ์๋์ ๊ฐ๋ฐ์ ์นํ์ ์ธ ๊ธฐ๋ฅ์ผ๋ก ๋น ๋ฅด๊ฒ ์ธ๊ธฐ๋ฅผ ์ป์์ต๋๋ค. ๊ทธ ์ค ๊ฐ์ฅ ๊ฐ๋ ฅํ์ง๋ง ๋๋ก๋ ์คํด๋ฅผ ๋ฐ๋ ๊ธฐ๋ฅ ์ค ํ๋๊ฐ ๋ฐ๋ก ๋ฏธ๋ค์จ์ด์ ๋๋ค. ๋ฏธ๋ค์จ์ด๋ ์์ฒญ ๋ฐ ์๋ต ์ฒ๋ฆฌ ์ฒด์ธ์ ์ค์ํ ์ฐ๊ฒฐ ๊ณ ๋ฆฌ ์ญํ ์ ํ๋ฉฐ, ๊ฐ๋ฐ์๊ฐ ์์ฒญ์ด ๋ชฉ์ ์ง์ ๋๋ฌํ๊ธฐ ์ ์ด๋ ์๋ต์ด ํด๋ผ์ด์ธํธ์ ๋ค์ ์ ์ก๋๊ธฐ ์ ์ ์ฝ๋๋ฅผ ์คํํ๊ณ , ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ณ , ๊ท์น์ ์ ์ฉํ ์ ์๋๋ก ํฉ๋๋ค.
์ด ์ข ํฉ ๊ฐ์ด๋๋ FastAPI๋ฅผ ์ฒ์ ์ ํ๋ ๊ฐ๋ฐ์๋ถํฐ ์ดํด๋ฅผ ์ฌํํ๊ณ ์ ํ๋ ์๋ จ๋ ์ ๋ฌธ๊ฐ์ ์ด๋ฅด๊ธฐ๊น์ง ์ ์ธ๊ณ ๊ฐ๋ฐ์๋ค์ ์ํด ์ ์๋์์ต๋๋ค. ๋ฏธ๋ค์จ์ด์ ํต์ฌ ๊ฐ๋ ์ ํ๊ตฌํ๊ณ , ์ฌ์ฉ์ ์ ์ ์๋ฃจ์ ์ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ์์ฐํ๋ฉฐ, ์ค์ฉ์ ์ธ ์ค์ ์ฌ์ฉ ์ฌ๋ก๋ฅผ ์๋ดํ ๊ฒ์ ๋๋ค. ์ด ๊ฐ์ด๋๋ฅผ ๋ง์น๋ฉด ๋ฏธ๋ค์จ์ด๋ฅผ ํ์ฉํ์ฌ ๋์ฑ ๊ฒฌ๊ณ ํ๊ณ ์์ ํ๋ฉฐ ํจ์จ์ ์ธ API๋ฅผ ๊ตฌ์ถํ ์ ์๊ฒ ๋ ๊ฒ์ ๋๋ค.
์น ํ๋ ์์ํฌ์์ ๋ฏธ๋ค์จ์ด๋ ๋ฌด์์ธ๊ฐ์?
์ฝ๋๋ฅผ ์ดํด๋ณด๊ธฐ ์ ์ ๊ฐ๋ ์ ์ดํดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ฒญ-์๋ต ์ฃผ๊ธฐ๋ฅผ ํ์ดํ๋ผ์ธ ๋๋ ์กฐ๋ฆฝ ๋ผ์ธ์ด๋ผ๊ณ ์์ํด ๋ณด์ธ์. ํด๋ผ์ด์ธํธ๊ฐ API์ ์์ฒญ์ ๋ณด๋ด๋ฉด, ์์ฒญ์ด ์ฆ์ ์๋ํฌ์ธํธ ๋ก์ง์ ๋๋ฌํ๋ ๊ฒ์ด ์๋๋๋ค. ๋์ , ์ผ๋ จ์ ์ฒ๋ฆฌ ๋จ๊ณ๋ฅผ ๊ฑฐ์นฉ๋๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก, ์๋ํฌ์ธํธ๊ฐ ์๋ต์ ์์ฑํ๋ฉด, ํด๋ผ์ด์ธํธ์ ๋๋ฌํ๊ธฐ ์ ์ ์ด ๋จ๊ณ๋ฅผ ๊ฑฐ์ณ ๋ค์ ์ ๋ฌ๋ฉ๋๋ค. ๋ฏธ๋ค์จ์ด ๊ตฌ์ฑ ์์๋ ๋ฐ๋ก ์ด ํ์ดํ๋ผ์ธ์ ๋จ๊ณ๋ค์ ๋๋ค.
์ธ๊ธฐ ์๋ ๋น์ ๋ ์ํ ๋ชจ๋ธ์ ๋๋ค. ์ํ์ ํต์ฌ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋น์ฆ๋์ค ๋ก์ง(์๋ํฌ์ธํธ)์ ๋๋ค. ํต์ฌ์ ๋๋ฌ์ผ ์ํ์ ๊ฐ ์ธต์ ๋ฏธ๋ค์จ์ด ์กฐ๊ฐ์ ๋๋ค. ์์ฒญ์ ํต์ฌ์ ๋๋ฌํ๊ธฐ ์ํด ๊ฐ ์ธ๋ถ ์ธต์ ๋ฒ๊ฒจ๋ด์ผ ํ๋ฉฐ, ์๋ต์ ๋์ผํ ์ธต์ ํตํด ๋ค์ ๋๊ฐ๋๋ค. ๊ฐ ์ธต์ ๋ค์ด์ค๋ ์์ฒญ๊ณผ ๋๊ฐ๋ ์๋ต์ ๊ฒ์ฌํ๊ณ ์์ ํ ์ ์์ต๋๋ค.
๋ณธ์ง์ ์ผ๋ก ๋ฏธ๋ค์จ์ด๋ ์์ฒญ ๊ฐ์ฒด, ์๋ต ๊ฐ์ฒด, ๊ทธ๋ฆฌ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ฒญ-์๋ต ์ฃผ๊ธฐ์์ ๋ค์ ๋ฏธ๋ค์จ์ด์ ์ ๊ทผํ ์ ์๋ ํจ์ ๋๋ ํด๋์ค์ ๋๋ค. ์ฃผ์ ๋ชฉ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ์ฝ๋ ์คํ: ๋ก๊น ๋๋ ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง๊ณผ ๊ฐ์ด ๋ชจ๋ ์์ ์์ฒญ์ ๋ํด ์์ ์ ์ํํฉ๋๋ค.
- ์์ฒญ ๋ฐ ์๋ต ์์ : ํค๋๋ฅผ ์ถ๊ฐํ๊ฑฐ๋, ์๋ต ๋ณธ๋ฌธ์ ์์ถํ๊ฑฐ๋, ๋ฐ์ดํฐ ํ์์ ๋ณํํฉ๋๋ค.
- ์ฃผ๊ธฐ ๋จ์ถ(Short-circuiting): ์์ฒญ-์๋ต ์ฃผ๊ธฐ๋ฅผ ์กฐ๊ธฐ์ ์ข ๋ฃํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ธ์ฆ ๋ฏธ๋ค์จ์ด๋ ์๋ํ ์๋ํฌ์ธํธ์ ๋๋ฌํ๊ธฐ ์ ์ ์ธ์ฆ๋์ง ์์ ์์ฒญ์ ์ฐจ๋จํ ์ ์์ต๋๋ค.
- ์ ์ญ์ ๊ด์ฌ์ฌ ๊ด๋ฆฌ: ์ค๋ฅ ์ฒ๋ฆฌ, CORS(Cross-Origin Resource Sharing), ์ธ์ ๊ด๋ฆฌ์ ๊ฐ์ ํก๋จ ๊ด์ฌ์ฌ๋ฅผ ์ค์ ์ง์ค์์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค.
FastAPI๋ ASGI(Asynchronous Server Gateway Interface) ํ์ค์ ๊ฐ๋ ฅํ ๊ตฌํ์ ์ ๊ณตํ๋ Starlette ํดํท ์์ ๊ตฌ์ถ๋์์ต๋๋ค. ๋ฏธ๋ค์จ์ด๋ ASGI์ ๊ทผ๋ณธ์ ์ธ ๊ฐ๋ ์ด๋ฉฐ, FastAPI ์ํ๊ณ์์ ์ผ๋ฑ ์๋ฏผ์ ๋๋ค.
๊ฐ์ฅ ๊ฐ๋จํ ํํ: ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ด์ฉํ FastAPI ๋ฏธ๋ค์จ์ด
FastAPI๋ @app.middleware("http") ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฏธ๋ค์จ์ด๋ฅผ ์ถ๊ฐํ๋ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค. ์ด๋ ๋ชจ๋ HTTP ์์ฒญ์ ๋ํด ์คํ๋์ด์ผ ํ๋ ๋จ์ํ๊ณ ๋
๋ฆฝ์ ์ธ ๋ก์ง์ ์ ํฉํฉ๋๋ค.
๊ฐ ์์ฒญ์ ์ฒ๋ฆฌ ์๊ฐ์ ๊ณ์ฐํ์ฌ ์๋ต ํค๋์ ์ถ๊ฐํ๋ ๋ฏธ๋ค์จ์ด์ ๊ณ ์ ์ ์ธ ์๋ฅผ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. ์ด๋ ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง์ ๋งค์ฐ ์ ์ฉํฉ๋๋ค.
์์: ์ฒ๋ฆฌ ์๊ฐ ๋ฏธ๋ค์จ์ด
๋จผ์ , FastAPI์ Uvicorn๊ณผ ๊ฐ์ ASGI ์๋ฒ๊ฐ ์ค์น๋์ด ์๋์ง ํ์ธํ์ธ์.
pip install fastapi uvicorn
์ด์ main.py๋ผ๋ ํ์ผ์ ์ฝ๋๋ฅผ ์์ฑํด ๋ณด๊ฒ ์ต๋๋ค.
import time
from fastapi import FastAPI, Request
app = FastAPI()
# Define the middleware function
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
# Record the start time when the request comes in
start_time = time.time()
# Proceed to the next middleware or the endpoint
response = await call_next(request)
# Calculate the processing time
process_time = time.time() - start_time
# Add the custom header to the response
response.headers["X-Process-Time"] = str(process_time)
return response
@app.get("/")
async def root():
# Simulate some work
time.sleep(0.5)
return {"message": "Hello, World!"}
์ด ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํ๋ ค๋ฉด ๋ค์ ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํ์ธ์.
uvicorn main:app --reload
์ด์ cURL ๋๋ Postman๊ณผ ๊ฐ์ API ํด๋ผ์ด์ธํธ๋ฅผ ์ฌ์ฉํ์ฌ http://127.0.0.1:8000์ผ๋ก ์์ฒญ์ ๋ณด๋ด๋ฉด, ์๋ต์ X-Process-Time์ด๋ผ๋ ์ ํค๋๊ฐ ์ฝ 0.5์ด์ ๊ฐ์ผ๋ก ํ์๋๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
์ฝ๋ ๋ถ์:
@app.middleware("http"): ์ด ๋ฐ์ฝ๋ ์ดํฐ๋ ์ฐ๋ฆฌ์ ํจ์๋ฅผ HTTP ๋ฏธ๋ค์จ์ด์ ํ ๋ถ๋ถ์ผ๋ก ๋ฑ๋กํฉ๋๋ค.async def add_process_time_header(request: Request, call_next):: ๋ฏธ๋ค์จ์ด ํจ์๋ ๋น๋๊ธฐ์์ด์ด์ผ ํฉ๋๋ค. ์ด ํจ์๋ ๋ค์ด์ค๋Request๊ฐ์ฒด์ ํน๋ณํ ํจ์์ธcall_next๋ฅผ ๋ฐ์ต๋๋ค.response = await call_next(request): ์ด๊ฒ์ด ๊ฐ์ฅ ์ค์ํ ์ค์ ๋๋ค.call_next๋ ์์ฒญ์ ํ์ดํ๋ผ์ธ์ ๋ค์ ๋จ๊ณ(๋ค๋ฅธ ๋ฏธ๋ค์จ์ด ๋๋ ์ค์ ๊ฒฝ๋ก ์์ )๋ก ์ ๋ฌํฉ๋๋ค. ์ด ํธ์ถ์ ๋ฐ๋์ `await`ํด์ผ ํฉ๋๋ค. ๊ฒฐ๊ณผ๋ ์๋ํฌ์ธํธ์์ ์์ฑ๋Response๊ฐ์ฒด์ ๋๋ค.response.headers[...] = ...: ์๋ํฌ์ธํธ๋ก๋ถํฐ ์๋ต์ ๋ฐ์ ํ, ์ด ๊ฒฝ์ฐ ์ฌ์ฉ์ ์ ์ ํค๋๋ฅผ ์ถ๊ฐํ์ฌ ์๋ต์ ์์ ํ ์ ์์ต๋๋ค.return response: ๋ง์ง๋ง์ผ๋ก, ์์ ๋ ์๋ต์ด ํด๋ผ์ด์ธํธ์ ์ ์ก๋๋๋ก ๋ฐํ๋ฉ๋๋ค.
ํด๋์ค๋ก ์ฌ์ฉ์ ์ ์ ๋ฏธ๋ค์จ์ด ๋ง๋ค๊ธฐ
๋ฐ์ฝ๋ ์ดํฐ ์ ๊ทผ ๋ฐฉ์์ ๊ฐ๋จํ์ง๋ง, ํนํ ๋ฏธ๋ค์จ์ด๊ฐ ๊ตฌ์ฑ์ด ํ์ํ๊ฑฐ๋ ์ผ๋ถ ๋ด๋ถ ์ํ๋ฅผ ๊ด๋ฆฌํด์ผ ํ๋ ๋ณต์กํ ์๋๋ฆฌ์ค์์๋ ํ๊ณ๊ฐ ์์ ์ ์์ต๋๋ค. ์ด๋ฌํ ๊ฒฝ์ฐ, FastAPI(Starlette๋ฅผ ํตํด)๋ BaseHTTPMiddleware๋ฅผ ์ฌ์ฉํ๋ ํด๋์ค ๊ธฐ๋ฐ ๋ฏธ๋ค์จ์ด๋ฅผ ์ง์ํฉ๋๋ค.
ํด๋์ค ๊ธฐ๋ฐ ์ ๊ทผ ๋ฐฉ์์ ๋ ๋์ ๊ตฌ์กฐ๋ฅผ ์ ๊ณตํ๊ณ , ์์ฑ์์์ ์์กด์ฑ ์ฃผ์
์ ํ์ฉํ๋ฉฐ, ๋ณต์กํ ๋ก์ง์ ๋ํด ์ผ๋ฐ์ ์ผ๋ก ๋ ์ ์ง๋ณด์ํ๊ธฐ ์ข์ต๋๋ค. ํต์ฌ ๋ก์ง์ ๋น๋๊ธฐ dispatch ๋ฉ์๋์ ์์ต๋๋ค.
์์: ํด๋์ค ๊ธฐ๋ฐ API ํค ์ธ์ฆ ๋ฏธ๋ค์จ์ด
API๋ฅผ ๋ณดํธํ๋ ๋ ์ค์ฉ์ ์ธ ๋ฏธ๋ค์จ์ด๋ฅผ ๊ตฌ์ถํด ๋ณด๊ฒ ์ต๋๋ค. ํน์ ํค๋ X-API-Key๋ฅผ ํ์ธํ๊ณ , ํค๊ฐ ์๊ฑฐ๋ ์ ํจํ์ง ์์ผ๋ฉด ์ฆ์ 403 Forbidden ์ค๋ฅ ์๋ต์ ๋ฐํํ ๊ฒ์
๋๋ค. ์ด๊ฒ์ ์์ฒญ์ "๋จ์ถ(short-circuiting)"ํ๋ ์์์
๋๋ค.
main.py ํ์ผ์:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.responses import Response
# A list of valid API keys. In a real application, this would come from a database or a secure vault.
VALID_API_KEYS = ["my-super-secret-key", "another-valid-key"]
class APIKeyMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
api_key = request.headers.get("X-API-Key")
if api_key not in VALID_API_KEYS:
# Short-circuit the request and return an error response
return JSONResponse(
status_code=403,
content={"detail": "Forbidden: Invalid or missing API Key"}
)
# If the key is valid, proceed with the request
response = await call_next(request)
return response
app = FastAPI()
# Add the middleware to the application
app.add_middleware(APIKeyMiddleware)
@app.get("/")
async def root():
return {"message": "Welcome to the secure zone!"}
์ด์ ์ด ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํ๋ฉด:
X-API-Keyํค๋ ์์ด (๋๋ ์๋ชป๋ ๊ฐ์ผ๋ก) ์์ฒญ์ ๋ณด๋ด๋ฉด 403 ์ํ ์ฝ๋์ JSON ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ฐ๊ฒ ๋ฉ๋๋ค.- ํค๋
X-API-Key: my-super-secret-key์ ํจ๊ป ์์ฒญ์ ๋ณด๋ด๋ฉด ์ฑ๊ณตํ์ฌ 200 OK ์๋ต์ ๋ฐ๊ฒ ๋ฉ๋๋ค.
์ด ํจํด์ ๋งค์ฐ ๊ฐ๋ ฅํฉ๋๋ค. /์ ์๋ํฌ์ธํธ ์ฝ๋๋ API ํค ์ ํจ์ฑ ๊ฒ์ฌ์ ๋ํด ์๋ฌด๊ฒ๋ ์ ํ์๊ฐ ์์ต๋๋ค. ์ด ๊ด์ฌ์ฌ๋ ๋ฏธ๋ค์จ์ด ๊ณ์ธต์ผ๋ก ์์ ํ ๋ถ๋ฆฌ๋ฉ๋๋ค.
๋ฏธ๋ค์จ์ด์ ์ผ๋ฐ์ ์ด๊ณ ๊ฐ๋ ฅํ ์ฌ์ฉ ์ฌ๋ก
๋ฏธ๋ค์จ์ด๋ ํก๋จ ๊ด์ฌ์ฌ(cross-cutting concerns)๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ ์๋ฒฝํ ๋๊ตฌ์ ๋๋ค. ๊ฐ์ฅ ์ผ๋ฐ์ ์ด๊ณ ์ํฅ๋ ฅ ์๋ ์ฌ์ฉ ์ฌ๋ก ์ค ์ผ๋ถ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
1. ์ค์ ์ง์ค์ ๋ก๊น
ํฌ๊ด์ ์ธ ๋ก๊น ์ ํ๋ก๋์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ํ์ ๋ถ๊ฐ๊ฒฐํฉ๋๋ค. ๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ๋ฉด ๋ชจ๋ ์์ฒญ๊ณผ ํด๋น ์๋ต์ ๋ํ ์ค์ํ ์ ๋ณด๋ฅผ ๊ธฐ๋กํ๋ ๋จ์ผ ์ง์ ์ ๋ง๋ค ์ ์์ต๋๋ค.
์์ ๋ก๊น ๋ฏธ๋ค์จ์ด:
import logging
from fastapi import FastAPI, Request
import time
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI()
@app.middleware("http")
async def logging_middleware(request: Request, call_next):
start_time = time.time()
# Log request details
logger.info(f"Incoming request: {request.method} {request.url.path}")
response = await call_next(request)
process_time = time.time() - start_time
# Log response details
logger.info(f"Response status: {response.status_code} | Process time: {process_time:.4f}s")
return response
์ด ๋ฏธ๋ค์จ์ด๋ ๋ค์ด์ค๋ ์์ฒญ ๋ฉ์๋์ ๊ฒฝ๋ก๋ฅผ ๊ธฐ๋กํ๊ณ , ๋๊ฐ๋ ์๋ต ์ํ ์ฝ๋์ ์ด ์ฒ๋ฆฌ ์๊ฐ์ ๊ธฐ๋กํฉ๋๋ค. ์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ํธ๋ํฝ์ ๋ํ ๊ท์คํ ๊ฐ์์ฑ์ ์ ๊ณตํฉ๋๋ค.
2. ์ ์ญ ์ค๋ฅ ์ฒ๋ฆฌ
๊ธฐ๋ณธ์ ์ผ๋ก ์ฝ๋์์ ์ฒ๋ฆฌ๋์ง ์์ ์์ธ๋ 500 ๋ด๋ถ ์๋ฒ ์ค๋ฅ๋ฅผ ๋ฐ์์ํค๋ฉฐ, ์คํ ํธ๋ ์ด์ค์ ๊ตฌํ ์ธ๋ถ ์ ๋ณด๋ฅผ ํด๋ผ์ด์ธํธ์ ๋ ธ์ถํ ์ ์์ต๋๋ค. ์ ์ญ ์ค๋ฅ ์ฒ๋ฆฌ ๋ฏธ๋ค์จ์ด๋ ๋ชจ๋ ์์ธ๋ฅผ ํฌ์ฐฉํ์ฌ ๋ด๋ถ ๊ฒํ ๋ฅผ ์ํด ๊ธฐ๋กํ๊ณ , ํ์คํ๋ ์ฌ์ฉ์ ์นํ์ ์ธ ์ค๋ฅ ์๋ต์ ๋ฐํํ ์ ์์ต๋๋ค.
์์ ์ค๋ฅ ์ฒ๋ฆฌ ๋ฏธ๋ค์จ์ด:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import logging
logger = logging.getLogger(__name__)
app = FastAPI()
@app.middleware("http")
async def error_handling_middleware(request: Request, call_next):
try:
return await call_next(request)
except Exception as e:
logger.error(f"An unhandled error occurred: {e}", exc_info=True)
return JSONResponse(
status_code=500,
content={"detail": "An internal server error occurred. Please try again later."}
)
@app.get("/error")
async def cause_error():
return 1 / 0 # This will raise a ZeroDivisionError
์ด ๋ฏธ๋ค์จ์ด๊ฐ ์ ์ฉ๋๋ฉด /error๋ก์ ์์ฒญ์ ๋ ์ด์ ์๋ฒ๋ฅผ ์ถฉ๋์ํค๊ฑฐ๋ ์คํ ํธ๋ ์ด์ค๋ฅผ ๋
ธ์ถํ์ง ์์ต๋๋ค. ๋์ , ๊นจ๋ํ JSON ๋ณธ๋ฌธ๊ณผ ํจ๊ป 500 ์ํ ์ฝ๋๋ฅผ ์ฐ์ํ๊ฒ ๋ฐํํ๋ฉฐ, ๊ฐ๋ฐ์๋ค์ด ์กฐ์ฌํ ์ ์๋๋ก ์ ์ฒด ์ค๋ฅ๋ ์๋ฒ ์ธก์ ๊ธฐ๋ก๋ฉ๋๋ค.
3. CORS (๊ต์ฐจ ์ถ์ ์์ ๊ณต์ )
ํ๋ฐํธ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด FastAPI ๋ฐฑ์๋์ ๋ค๋ฅธ ๋๋ฉ์ธ, ํ๋กํ ์ฝ ๋๋ ํฌํธ์์ ์ ๊ณต๋๋ ๊ฒฝ์ฐ, ๋ธ๋ผ์ฐ์ ๋ ๋์ผ ์ถ์ ์ ์ฑ (Same-Origin Policy)์ผ๋ก ์ธํด ์์ฒญ์ ์ฐจ๋จํฉ๋๋ค. CORS๋ ์ด ์ ์ฑ ์ ์ํํ๋ ๋ฉ์ปค๋์ฆ์ ๋๋ค. FastAPI๋ ์ด๋ฌํ ๋ชฉ์ ์ ์ํด ์ ์ฉ์ ๊ณ ๋๋ก ๊ตฌ์ฑ ๊ฐ๋ฅํ `CORSMiddleware`๋ฅผ ์ ๊ณตํฉ๋๋ค.
์์ CORS ๊ตฌ์ฑ:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# Define the list of allowed origins. Use "*" for public APIs, but be specific for better security.
origins = [
"http://localhost:3000",
"https://my-production-frontend.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True, # Allow cookies to be included in cross-origin requests
allow_methods=["*"], # Allow all standard HTTP methods
allow_headers=["*"], # Allow all headers
)
์ด๊ฒ์ ๋ถ๋ฆฌ๋ ํ๋ฐํธ์๋๋ฅผ ๊ฐ์ง ๋ชจ๋ ํ๋ก์ ํธ์ ์ถ๊ฐํ๊ฒ ๋ ์ฒซ ๋ฒ์งธ ๋ฏธ๋ค์จ์ด ์ค ํ๋์ด๋ฉฐ, ๋จ์ผ ์ค์ ์์น์์ ๊ต์ฐจ ์ถ์ ์ ์ฑ ์ ์ฝ๊ฒ ๊ด๋ฆฌํ ์ ์๊ฒ ํฉ๋๋ค.
4. GZip ์์ถ
HTTP ์๋ต์ ์์ถํ๋ฉด ํฌ๊ธฐ๋ฅผ ํฌ๊ฒ ์ค์ฌ ํด๋ผ์ด์ธํธ์ ๋ก๋ ์๊ฐ์ ๋จ์ถํ๊ณ ๋์ญํญ ๋น์ฉ์ ์ ๊ฐํ ์ ์์ต๋๋ค. FastAPI๋ ์ด๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํ๋ `GZipMiddleware`๋ฅผ ํฌํจํ๊ณ ์์ต๋๋ค.
์์ GZip ๋ฏธ๋ค์จ์ด:
from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware
app = FastAPI()
# Add the GZip middleware. You can set a minimum size for compression.
app.add_middleware(GZipMiddleware, minimum_size=1000)
@app.get("/")
async def root():
# This response is small and will not be gzipped.
return {"message": "Hello World"}
@app.get("/large-data")
async def large_data():
# This large response will be automatically gzipped by the middleware.
return {"data": "a_very_long_string..." * 1000}
์ด ๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ๋ฉด ํด๋ผ์ด์ธํธ๊ฐ GZip ์ธ์ฝ๋ฉ์ ์๋ฝํ๋ค๊ณ ํ์ํ๋ฉด(๋๋ถ๋ถ์ ์ต์ ๋ธ๋ผ์ฐ์ ์ ํด๋ผ์ด์ธํธ๊ฐ ๊ทธ๋ ๋ฏ์ด) 1000๋ฐ์ดํธ๋ณด๋ค ํฐ ๋ชจ๋ ์๋ต์ด ์์ถ๋ฉ๋๋ค.
๊ณ ๊ธ ๊ฐ๋ ๋ฐ ๋ชจ๋ฒ ์ฌ๋ก
๋ฏธ๋ค์จ์ด์ ๋ฅ์ํด์ง์๋ก ๊น๋ํ๊ณ ํจ์จ์ ์ด๋ฉฐ ์์ธก ๊ฐ๋ฅํ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ์ํ ๋ช ๊ฐ์ง ๋ฏธ๋ฌํ ์ฐจ์ด์ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์ดํดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
1. ๋ฏธ๋ค์จ์ด ์์๊ฐ ์ค์ํฉ๋๋ค!
์ด๊ฒ์ ๊ธฐ์ตํด์ผ ํ ๊ฐ์ฅ ์ค์ํ ๊ท์น์ ๋๋ค. ๋ฏธ๋ค์จ์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ถ๊ฐ๋ ์์๋๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค. ๊ฐ์ฅ ๋จผ์ ์ถ๊ฐ๋ ๋ฏธ๋ค์จ์ด๊ฐ "์ํ"์ ๊ฐ์ฅ ๋ฐ๊นฅ์ชฝ ์ธต์ ๋๋ค.
์ด ์ค์ ์ ๊ณ ๋ คํด ๋ณด์ธ์.
app.add_middleware(ErrorHandlingMiddleware) # ๊ฐ์ฅ ๋ฐ๊นฅ์ชฝ
app.add_middleware(LoggingMiddleware)
app.add_middleware(AuthenticationMiddleware) # ๊ฐ์ฅ ์์ชฝ
์์ฒญ์ ํ๋ฆ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
ErrorHandlingMiddleware๊ฐ ์์ฒญ์ ๋ฐ์ต๋๋ค. `call_next`๋ฅผ `try...except` ๋ธ๋ก์ผ๋ก ๋ํํฉ๋๋ค.- `next`๋ฅผ ํธ์ถํ์ฌ ์์ฒญ์ `LoggingMiddleware`๋ก ์ ๋ฌํฉ๋๋ค.
LoggingMiddleware๊ฐ ์์ฒญ์ ๋ฐ์ ๊ธฐ๋กํ๊ณ `next`๋ฅผ ํธ์ถํฉ๋๋ค.AuthenticationMiddleware๊ฐ ์์ฒญ์ ๋ฐ์ ์๊ฒฉ ์ฆ๋ช ์ ์ ํจ์ฑ ๊ฒ์ฌํ๊ณ `next`๋ฅผ ํธ์ถํฉ๋๋ค.- ์์ฒญ์ ๋ง์นจ๋ด ์๋ํฌ์ธํธ์ ๋๋ฌํฉ๋๋ค.
- ์๋ํฌ์ธํธ๋ ์๋ต์ ๋ฐํํฉ๋๋ค.
AuthenticationMiddleware๊ฐ ์๋ต์ ๋ฐ์ ์์๋ก ์ ๋ฌํฉ๋๋ค.LoggingMiddleware๊ฐ ์๋ต์ ๋ฐ์ ๊ธฐ๋กํ๊ณ ์์๋ก ์ ๋ฌํฉ๋๋ค.ErrorHandlingMiddleware๊ฐ ์ต์ข ์๋ต์ ๋ฐ์ ํด๋ผ์ด์ธํธ์ ๋ฐํํฉ๋๋ค.
์ด ์์๋ ๋ ผ๋ฆฌ์ ์ ๋๋ค. ์ค๋ฅ ํธ๋ค๋ฌ๋ ์ธ๋ถ ๊ณ์ธต์ ์์ด ๋ค๋ฅธ ๋ฏธ๋ค์จ์ด๋ฅผ ํฌํจํ ๋ชจ๋ ํ์ ๊ณ์ธต์์ ๋ฐ์ํ๋ ์ค๋ฅ๋ฅผ ํฌ์ฐฉํ ์ ์์ต๋๋ค. ์ธ์ฆ ๊ณ์ธต์ ๊น์์ด ์์นํ์ฌ ์ด์ฐจํผ ๊ฑฐ๋ถ๋ ์์ฒญ์ ๋ก๊น ํ๊ฑฐ๋ ์ฒ๋ฆฌํ๋ ์๊ณ ๋ฅผ ๋ ์ ์์ต๋๋ค.
2. `request.state`๋ฅผ ์ด์ฉํ ๋ฐ์ดํฐ ์ ๋ฌ
๋๋๋ก ๋ฏธ๋ค์จ์ด๋ ์๋ํฌ์ธํธ์ ์ ๋ณด๋ฅผ ์ ๋ฌํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ธ์ฆ ๋ฏธ๋ค์จ์ด๋ JWT๋ฅผ ๋์ฝ๋ฉํ๊ณ ์ฌ์ฉ์์ ID๋ฅผ ์ถ์ถํ ์ ์์ต๋๋ค. ์ด ์ฌ์ฉ์ ID๋ฅผ ๊ฒฝ๋ก ์์ ํจ์์ ์ด๋ป๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ํ ๊น์?
์๋ชป๋ ๋ฐฉ๋ฒ์ ์์ฒญ ๊ฐ์ฒด๋ฅผ ์ง์ ์์ ํ๋ ๊ฒ์
๋๋ค. ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ์ request.state ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์
๋๋ค. ์ด๊ฒ์ ์ด ์ ํํ ๋ชฉ์ ์ ์ํด ์ ๊ณต๋๋ ๊ฐ๋จํ๊ณ ๋น ๊ฐ์ฒด์
๋๋ค.
์์: ๋ฏธ๋ค์จ์ด์์ ์ฌ์ฉ์ ๋ฐ์ดํฐ ์ ๋ฌ
# In your authentication middleware's dispatch method:
# ... after validating the token and decoding the user ...
user_data = {"id": 123, "username": "global_dev"}
request.state.user = user_data
response = await call_next(request)
# In your endpoint:
@app.get("/profile")
async def get_user_profile(request: Request):
current_user = request.state.user
return {"profile_for": current_user}
์ด๋ ๊ฒ ํ๋ฉด ๋ก์ง์ ๊น๋ํ๊ฒ ์ ์งํ๊ณ `Request` ๊ฐ์ฒด์ ๋ค์์คํ์ด์ค๋ฅผ ์ค์ผ์ํค๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
3. ์ฑ๋ฅ ๊ณ ๋ ค ์ฌํญ
๋ฏธ๋ค์จ์ด๋ ๊ฐ๋ ฅํ์ง๋ง, ๊ฐ ๊ณ์ธต์ ์ฝ๊ฐ์ ์ค๋ฒํค๋๋ฅผ ์ถ๊ฐํฉ๋๋ค. ๊ณ ์ฑ๋ฅ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒฝ์ฐ ๋ค์ ์ฌํญ์ ์ผ๋์ ๋์ธ์.
- ๊ฐ๊ฒฐํ๊ฒ ์ ์ง: ๋ฏธ๋ค์จ์ด ๋ก์ง์ ๊ฐ๋ฅํ ํ ๋น ๋ฅด๊ณ ํจ์จ์ ์ด์ด์ผ ํฉ๋๋ค.
- ๋น๋๊ธฐ์ ์ผ๋ก: ๋ฏธ๋ค์จ์ด๊ฐ I/O ์์ (์: ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ์ธ)์ ์ํํด์ผ ํ๋ ๊ฒฝ์ฐ, ์๋ฒ์ ์ด๋ฒคํธ ๋ฃจํ๋ฅผ ์ฐจ๋จํ์ง ์๋๋ก ์์ ํ `async`๋ก ์๋ํ๋์ง ํ์ธํ์ธ์.
- ๋ชฉ์ ์ ๋ง๊ฒ ์ฌ์ฉ: ํ์ ์๋ ๋ฏธ๋ค์จ์ด๋ฅผ ์ถ๊ฐํ์ง ๋ง์ธ์. ๊ฐ ๋ฏธ๋ค์จ์ด๋ ํธ์ถ ์คํ ๊น์ด์ ์ฒ๋ฆฌ ์๊ฐ์ ์ฆ๊ฐ์ํต๋๋ค.
4. ๋ฏธ๋ค์จ์ด ํ ์คํธ
๋ฏธ๋ค์จ์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ๋ก์ง์ ์ค์ํ ๋ถ๋ถ์ด๋ฉฐ ์ฒ ์ ํ ํ ์คํธํด์ผ ํฉ๋๋ค. FastAPI์ `TestClient`๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ฅผ ๊ฐ๋จํ๊ฒ ์ํํ ์ ์์ต๋๋ค. ํ์ํ ์กฐ๊ฑด(์: ์ ํจํ API ํค ์ ๋ฌด)์ ๊ฐ์ถ๊ฑฐ๋ ๊ฐ์ถ์ง ์์ ์์ฒญ์ ๋ณด๋ด๊ณ ๋ฏธ๋ค์จ์ด๊ฐ ์์๋๋ก ์๋ํ๋์ง ํ์ธํ ์ ์๋ ํ ์คํธ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
์์ APIKeyMiddleware ํ ์คํธ:
from fastapi.testclient import TestClient
from .main import app # Import your FastAPI app
client = TestClient(app)
def test_request_without_api_key_is_forbidden():
response = client.get("/")
assert response.status_code == 403
assert response.json() == {"detail": "Forbidden: Invalid or missing API Key"}
def test_request_with_valid_api_key_is_successful():
headers = {"X-API-Key": "my-super-secret-key"}
response = client.get("/", headers=headers)
assert response.status_code == 200
assert response.json() == {"message": "Welcome to the secure zone!"}
๊ฒฐ๋ก
FastAPI ๋ฏธ๋ค์จ์ด๋ ํ๋์ ์ธ ์น API๋ฅผ ๊ตฌ์ถํ๋ ๋ชจ๋ ๊ฐ๋ฐ์์๊ฒ ํ์์ ์ด๊ณ ๊ฐ๋ ฅํ ๋๊ตฌ์ ๋๋ค. ์ด๋ ํก๋จ ๊ด์ฌ์ฌ๋ฅผ ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ๋ถ๋ฆฌํ์ฌ ์ฒ๋ฆฌํ๋ ์ฐ์ํ๊ณ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค. ๋ชจ๋ ์์ฒญ๊ณผ ์๋ต์ ๊ฐ๋ก์ฑ๊ณ ์ฒ๋ฆฌํจ์ผ๋ก์จ, ๋ฏธ๋ค์จ์ด๋ ๊ฒฌ๊ณ ํ ๋ก๊น , ์ค์ ์ง์ค์ ์ค๋ฅ ์ฒ๋ฆฌ, ์๊ฒฉํ ๋ณด์ ์ ์ฑ , ์์ถ๊ณผ ๊ฐ์ ์ฑ๋ฅ ํฅ์์ ๊ตฌํํ ์ ์๋๋ก ํฉ๋๋ค.
๊ฐ๋จํ @app.middleware("http") ๋ฐ์ฝ๋ ์ดํฐ๋ถํฐ ์ ๊ตํ ํด๋์ค ๊ธฐ๋ฐ ์๋ฃจ์
์ ์ด๋ฅด๊ธฐ๊น์ง, ํ์์ ๋ง๋ ์ฌ๋ฐ๋ฅธ ์ ๊ทผ ๋ฐฉ์์ ์ ์ฐํ๊ฒ ์ ํํ ์ ์์ต๋๋ค. ๋ฏธ๋ค์จ์ด ์์ ์ง์ ๋ฐ ์ํ ๊ด๋ฆฌ์ ๊ฐ์ ํต์ฌ ๊ฐ๋
, ์ผ๋ฐ์ ์ธ ์ฌ์ฉ ์ฌ๋ก ๋ฐ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์ดํดํจ์ผ๋ก์จ ๋ ๊น๋ํ๊ณ ์์ ํ๋ฉฐ ๊ณ ๋๋ก ์ ์ง๋ณด์ ๊ฐ๋ฅํ FastAPI ์ ํ๋ฆฌ์ผ์ด์
์ ๊ตฌ์ถํ ์ ์์ต๋๋ค.
์ด์ ๋น์ ์ ์ฐจ๋ก์ ๋๋ค. ๋ค์ FastAPI ํ๋ก์ ํธ์ ์ฌ์ฉ์ ์ ์ ๋ฏธ๋ค์จ์ด๋ฅผ ํตํฉํ์ฌ API ์ค๊ณ์์ ์๋ก์ด ์ฐจ์์ ์ ์ด๋ ฅ๊ณผ ์ฐ์ํจ์ ๋๊ปด๋ณด์ธ์. ๊ฐ๋ฅ์ฑ์ ๋ฌด๊ถ๋ฌด์งํ๋ฉฐ, ์ด ๊ธฐ๋ฅ์ ๋ง์คํฐํ๋ฉด ์์ฌํ ์ฌ์ง ์์ด ๋ ํจ๊ณผ์ ์ด๊ณ ํจ์จ์ ์ธ ๊ฐ๋ฐ์๊ฐ ๋ ๊ฒ์ ๋๋ค.