FastAPI - Middleware

Kjjeddยท2026๋…„ 1์›” 14์ผ

FastAPI

๋ชฉ๋ก ๋ณด๊ธฐ
14/16
post-thumbnail

๐Ÿงฑ Middleware โ€“ ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์„ค๊ณ„ํ•˜๋Š” ํ•ต์‹ฌ ์ง€์ 

FastAPI์—์„œ Middleware๋Š” ์š”์ฒญ(Request)๊ณผ ์‘๋‹ต(Response)์˜ ํ๋ฆ„์„ ํ†ต์ œํ•˜๋Š” ์„ค๊ณ„ ์ง€์ ์ด๋‹ค.

์ธ์ฆ, ๋กœ๊น…, ์„ฑ๋Šฅ ์ธก์ •, ๋ณด์•ˆ, ์š”์ฒญ ์ฐจ๋‹จ์ฒ˜๋Ÿผ
๋ชจ๋“  ์š”์ฒญ์— ๊ณตํ†ต์œผ๋กœ ์ ์šฉ๋˜๋Š” ๊ทœ์น™์€ ์ „๋ถ€ ๋ฏธ๋“ค์›จ์–ด ๊ณ„์ธต์—์„œ ์ฒ˜๋ฆฌ๋œ๋‹ค.


๐Ÿค” Middleware ๋ž€?

Middleware๋Š” ์š”์ฒญ(Request)๊ณผ ์‘๋‹ต(Response) ์‚ฌ์ด์— ์œ„์น˜ํ•œ ์ฝ”๋“œ๋‹ค.

ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด

  • ์š”์ฒญ์ด ๋จผ์ € Middleware๋ฅผ ๊ฑฐ์น˜๊ณ 
  • ๊ทธ ๋‹ค์Œ Path Operation(๋ผ์šฐํŠธ ํ•จ์ˆ˜)๋กœ ๋“ค์–ด๊ฐ€๋ฉฐ
  • ์‘๋‹ต์ด ๋‚˜๊ฐˆ ๋•Œ ๋‹ค์‹œ Middleware๋ฅผ ๊ฑฐ์นœ๋‹ค

๐Ÿ‘‰ ์š”์ฒญ ์‹œ ํ•œ ๋ฒˆ, ์‘๋‹ต ์‹œ ํ•œ ๋ฒˆ
๐Ÿ‘‰ ํ•ญ์ƒ ๋‘ ๋ฒˆ ์‹คํ–‰๋œ๋‹ค


๐Ÿšช ๋น„์œ ๋กœ ์ดํ•ดํ•˜๊ธฐ

Middleware๋Š” ๊ฑด๋ฌผ ์ž…๊ตฌ์˜ ๋ณด์•ˆ ๊ฒ€์ƒ‰๋Œ€์™€ ๊ฐ™๋‹ค.

  • ๋“ค์–ด์˜ฌ ๋•Œ ๊ฒ€์‚ฌํ•œ๋‹ค
  • ๋‚˜๊ฐˆ ๋•Œ ๊ธฐ๋กํ•œ๋‹ค

๋ˆ„๊ฐ€ ๋“ค์–ด์™”๋Š”์ง€,๋“ค์–ด์™€๋„ ๋˜๋Š”์ง€, ์–ผ๋งˆ๋‚˜ ์˜ค๋ž˜ ๋จธ๋ฌผ๋ €๋Š”์ง€๋ฅผ
๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๋“ค์–ด๊ฐ€๊ธฐ ์ „์— ํŒ๋‹จํ•œ๋‹ค.


๐Ÿ” Middleware ๋™์ž‘ ํ๋ฆ„

์š”์ฒญ / ์‘๋‹ต ํ๋ฆ„ ์š”์•ฝ

Client
  โ†“
Middleware (์š”์ฒญ ์ „ ์ฒ˜๋ฆฌ)
  โ†“
call_next(request)
  โ†“
Path Operation (๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง)
  โ†“
Middleware (์‘๋‹ต ํ›„ ์ฒ˜๋ฆฌ)
  โ†“
Client

Middleware๋Š” ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„๊ณ , ๋‹ค์‹œ ๋˜๋Œ๋ ค ๋ฐ›๋Š” ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„๋‹ค.


๐Ÿง  Middleware ํ•จ์ˆ˜ ๊ตฌ์กฐ

๋ชจ๋“  Middleware๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„๋‹ค.

@app.middleware("http")
async def middleware(request: Request, call_next):
    # ์š”์ฒญ ์ „ ์ฒ˜๋ฆฌ (Request ๋‹จ๊ณ„)
    response = await call_next(request)
    # ์‘๋‹ต ํ›„ ์ฒ˜๋ฆฌ (Response ๋‹จ๊ณ„)
    return response
call_next(request)์˜ ์˜๋ฏธ

call_next๋Š” ์š”์ฒญ์„ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋„˜๊ธฐ๋Š” ๊ด€๋ฌธ์ด๋‹ค.
์ด ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์œผ๋ฉด ์š”์ฒญ์€ ๊ทธ ์ž๋ฆฌ์—์„œ ์ข…๋ฃŒ๋œ๋‹ค.

๐Ÿšซ Middleware์—์„œ ๊ฐ€๋Šฅํ•œ ๊ฒƒ

  • ์ธ์ฆ ์‹คํŒจ ์‹œ ์š”์ฒญ ์ฐจ๋‹จ
  • ํŠน์ • ํ—ค๋” ์—†์œผ๋ฉด ์ฆ‰์‹œ 403 ๋ฐ˜ํ™˜
  • ๋กœ๊น…๋งŒ ์ˆ˜ํ–‰ํ•˜๊ณ  ์š”์ฒญ ํ†ต๊ณผ

โ— Middleware๋Š” ๋‹จ์ˆœํ•œ ํ—ฌํผ๊ฐ€ ์•„๋‹ˆ๋‹ค
โ€œ์ด ์š”์ฒญ์„ ์„œ๋ฒ„ ์•ˆ์œผ๋กœ ๋“ค์ผ์ง€ ๋ง์ง€โ€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๊ณ„์ธต์ด๋‹ค


๐Ÿงฑ ์—ฌ๋Ÿฌ Middleware๊ฐ€ ์žˆ์„ ๋•Œ

Middleware๋Š” ์Šคํƒ(Stack) ๊ตฌ์กฐ๋กœ ์Œ“์ธ๋‹ค.
๋‚˜์ค‘์— ๋“ฑ๋กํ•œ Middleware๊ฐ€ ๊ฐ€์žฅ ๋ฐ”๊นฅ์ชฝ์— ์œ„์น˜ํ•œ๋‹ค.

์š”์ฒญ ํ๋ฆ„
Client
  โ†“
Middleware C (์ธ์ฆ)
  โ†“
Middleware B (์‹œ๊ฐ„ ์ธก์ •)
  โ†“
Middleware A (๋กœ๊น…)
  โ†“
Route
์‘๋‹ต ํ๋ฆ„
Route
  โ†“
Middleware A
  โ†“
Middleware B
  โ†“
Middleware C
  โ†“
Client

LIFO ๊ตฌ์กฐ๋ฅผ ๋”ฐ๋ฅธ๋‹ค.


๐Ÿ—๏ธ ์„ค๊ณ„ ๊ด€์ ์—์„œ์˜ Middleware

  • ๋ชจ๋“  ์š”์ฒญ์— ๊ณตํ†ต์œผ๋กœ ์ ์šฉ๋˜๋Š”๊ฐ€?
  • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ๋ถ„๋ฆฌ ๊ฐ€๋Šฅํ•œ๊ฐ€?
  • ์š”์ฒญ์„ ์ฐจ๋‹จํ•  ์ฑ…์ž„์ด ์žˆ๋Š”๊ฐ€?

๐Ÿ‘‰ ์œ„ ์งˆ๋ฌธ์— โ€œ์˜ˆโ€๋ผ๋ฉด
Middleware๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๋งž๋‹ค


๐Ÿงช ์‹ค์ „ ์˜ˆ์ œ

์ด์ œ Middleware์˜ ๊ฐœ๋…๊ณผ ํ๋ฆ„์„ ์ดํ•ดํ–ˆ์œผ๋‹ˆ,
์‹ค์ œ๋กœ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์–ด๋–ป๊ฒŒ ์Œ“์ด๊ณ  ์‹คํ–‰๋˜๋Š”์ง€๋ฅผ ํ™•์ธํ•ด๋ณด์ž.

๐Ÿงฑ ๋ฏธ๋“ค์›จ์–ด 3๊ฐœ ๋“ฑ๋กํ•˜๊ธฐ

์•„๋ž˜ ์˜ˆ์ œ์—์„œ๋Š” ์—ญํ• ์ด ์„œ๋กœ ๋‹ค๋ฅธ ๋ฏธ๋“ค์›จ์–ด 3๊ฐœ๋ฅผ ๋“ฑ๋กํ•œ๋‹ค.

  • Middleware A โ†’ ์š”์ฒญ/์‘๋‹ต ๋กœ๊น…
  • Middleware B โ†’ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ์ธก์ •
  • Middleware C โ†’ ์ธ์ฆ ๊ฒ€์‚ฌ

๋‹ค์‹œ ํ•œ ๋ฒˆ ๊ฐ•์กฐ
๋‚˜์ค‘์— ๋“ฑ๋กํ•œ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ๊ฐ€์žฅ ๋ฐ”๊นฅ์ชฝ์— ์œ„์น˜ํ•œ๋‹ค.

import time
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

# 1๏ธโƒฃ ๊ฐ€์žฅ ์•ˆ์ชฝ (๊ฐ€์žฅ ๋งˆ์ง€๋ง‰์— ์‹คํ–‰)
@app.middleware("http")
async def middleware_a(request: Request, call_next):
    print("[A] ์š”์ฒญ ๋“ค์–ด์˜ด - ๋กœ๊น… ์‹œ์ž‘")
    print(f"[A] ์š”์ฒญ ์ •๋ณด: {request.method} {request.url.path}")

    response = await call_next(request)

    print("[A] ์‘๋‹ต ๋‚˜๊ฐ - ๋กœ๊น… ์™„๋ฃŒ")
    return response


# 2๏ธโƒฃ ์ค‘๊ฐ„
@app.middleware("http")
async def middleware_b(request: Request, call_next):
    print("[B] ์š”์ฒญ ๋“ค์–ด์˜ด - ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ์ธก์ • ์‹œ์ž‘")
    start_time = time.perf_counter()

    response = await call_next(request)

    process_time = time.perf_counter() - start_time
    response.headers["X-Process-Time"] = f"{process_time:.4f}"
    print(f"[B] ์‘๋‹ต ๋‚˜๊ฐ - ์ฒ˜๋ฆฌ ์‹œ๊ฐ„: {process_time:.4f}์ดˆ")
    return response


# 3๏ธโƒฃ ๊ฐ€์žฅ ๋ฐ”๊นฅ์ชฝ (๊ฐ€์žฅ ๋จผ์ € ์‹คํ–‰)
@app.middleware("http")
async def middleware_c(request: Request, call_next):
    print("[C] ์š”์ฒญ ๋“ค์–ด์˜ด - ์ธ์ฆ ๊ฒ€์‚ฌ ์‹œ์ž‘")

    # ๋ฌธ์„œ ๊ฒฝ๋กœ๋Š” ์ธ์ฆ ์ œ์™ธ
    if request.url.path in ["/docs", "/openapi.json", "/redoc"]:
        print("[C] ๋ฌธ์„œ ๊ฒฝ๋กœ๋Š” ์ธ์ฆ ๊ฑด๋„ˆ๋œ€")
        response = await call_next(request)
        print("[C] ์‘๋‹ต ๋‚˜๊ฐ")
        return response

    auth_token = request.headers.get("X-Auth-Token")
    if request.url.path == "/secret" and not auth_token:
        print("[C] ์ธ์ฆ ์‹คํŒจ! ์š”์ฒญ ๊ฑฐ๋ถ€")
        return JSONResponse(
            status_code=403,
            content={"detail": "X-Auth-Token ํ—ค๋”๊ฐ€ ํ•„์š”ํ•ด์š”"}
        )

    print("[C] ์ธ์ฆ ํ†ต๊ณผ")
    response = await call_next(request)
    print("[C] ์‘๋‹ต ๋‚˜๊ฐ")
    return response


@app.get("/")
def root():
    print("--- [Route] / ์‹คํ–‰ ---")
    return {"message": "Hello, jeff!"}


@app.get("/secret")
async def secret():
    print("--- [Route] /secret ์‹คํ–‰ ---")
    return {"message": "๋น„๋ฐ€ ๋ฐ์ดํ„ฐ์˜ˆ์š”"}

โ–ถ ์‹คํ–‰ ๊ฒฐ๊ณผ ๋กœ๊ทธ

์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜๊ณ  http://localhost:8000์— ์ ‘์†ํ•˜๋ฉด
ํ„ฐ๋ฏธ๋„์— ์•„๋ž˜์™€ ๊ฐ™์€ ๋กœ๊ทธ๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค.

[C] ์š”์ฒญ ๋“ค์–ด์˜ด - ์ธ์ฆ ๊ฒ€์‚ฌ ์‹œ์ž‘
[C] ์ธ์ฆ ํ†ต๊ณผ
[B] ์š”์ฒญ ๋“ค์–ด์˜ด - ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ์ธก์ • ์‹œ์ž‘
[A] ์š”์ฒญ ๋“ค์–ด์˜ด - ๋กœ๊น… ์‹œ์ž‘
[A] ์š”์ฒญ ์ •๋ณด: GET /
--- [Route] / ์‹คํ–‰ ---
[A] ์‘๋‹ต ๋‚˜๊ฐ - ๋กœ๊น… ์™„๋ฃŒ
[B] ์‘๋‹ต ๋‚˜๊ฐ - ์ฒ˜๋ฆฌ ์‹œ๊ฐ„: 0.0028์ดˆ
[C] ์‘๋‹ต ๋‚˜๊ฐ

๐Ÿ‘‰ ์š”์ฒญ ์‹œ: C โ†’ B โ†’ A โ†’ Route
๐Ÿ‘‰ ์‘๋‹ต ์‹œ: Route โ†’ A โ†’ B โ†’ C


๐Ÿงญ ์‹คํ–‰ ํ๋ฆ„ ๋‹ค์ด์–ด๊ทธ๋žจ

์š”์ฒญ ํ๋ฆ„
Client
  โ†“
Middleware C (์ธ์ฆ)
  โ†“
Middleware B (์‹œ๊ฐ„ ์ธก์ •)
  โ†“
Middleware A (๋กœ๊น…)
  โ†“
Route

์‘๋‹ต ํ๋ฆ„
Route
  โ†“
Middleware A
  โ†“
Middleware B
  โ†“
Middleware C
  โ†“
Client

๐Ÿ” ๊ฐ„๋‹จํ•œ ์ธ์ฆ ๋ฏธ๋“ค์›จ์–ด

ํŠน์ • ํ—ค๋”๊ฐ€ ์—†์œผ๋ฉด ์š”์ฒญ์„ ์ฐจ๋‹จํ•˜๋Š” ์˜ˆ์ œ

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.middleware("http")
async def check_auth_header(request: Request, call_next):
    # ๋ฌธ์„œ ๊ด€๋ จ ๊ฒฝ๋กœ๋Š” ์ธ์ฆ ์ œ์™ธ
    if request.url.path in ["/docs", "/openapi.json", "/redoc"]:
        return await call_next(request)

    auth_token = request.headers.get("X-Auth-Token")

    if not auth_token:
        return JSONResponse(
            status_code=403,
            content={"detail": "X-Auth-Token ํ—ค๋”๊ฐ€ ํ•„์š”ํ•ด์š”"}
        )

    return await call_next(request)
์„ค๊ณ„ ํฌ์ธํŠธ<

โ€ข ์ธ์ฆ ์‹คํŒจ ์‹œ call_next๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค
โ€ข ์ฆ‰์‹œ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ์š”์ฒญ ํ๋ฆ„์€ ์ข…๋ฃŒ๋œ๋‹ค
โ€ข โ€œ๋“ค์ผ์ง€ ๋ง์ง€โ€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ์œ„์น˜๊ฐ€ ๋ฐ”๋กœ Middleware๋‹ค

๐Ÿงพ ์ •๋ฆฌ

  • Middleware๋Š” ์Šคํƒ(Stack) ๊ตฌ์กฐ๋กœ ์‹คํ–‰๋œ๋‹ค
  • ๋‚˜์ค‘์— ๋“ฑ๋กํ•œ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ๊ฐ€์žฅ ๋ฐ”๊นฅ์ชฝ์ด๋‹ค
  • ์š”์ฒญ๊ณผ ์‘๋‹ต์˜ ์‹คํ–‰ ํ๋ฆ„์€ ์„œ๋กœ ๋ฐ˜๋Œ€๋‹ค
  • ์ธ์ฆยท๋กœ๊น…ยท์„ฑ๋Šฅ ์ธก์ •์€ Middleware์˜ ์ฑ…์ž„์ด๋‹ค
profile
Gongbuhaja

0๊ฐœ์˜ ๋Œ“๊ธ€