Opanuj middleware FastAPI od podstaw. Ten szczeg贸艂owy przewodnik obejmuje niestandardowe middleware, uwierzytelnianie, logowanie, obs艂ug臋 b艂臋d贸w i najlepsze praktyki.
Middleware w Python FastAPI: Kompleksowy Przewodnik po Przetwarzaniu 呕膮da艅 i Odpowiedzi
W 艣wiecie nowoczesnego rozwoju web, wydajno艣膰, bezpiecze艅stwo i 艂atwo艣膰 utrzymania s膮 sprawami najwy偶szej wagi. Pythonowy framework FastAPI szybko zyska艂 popularno艣膰 dzi臋ki swojej niesamowitej szybko艣ci i funkcjom przyjaznym dla programist贸w. Jedn膮 z jego najpot臋偶niejszych, cho膰 czasem niedocenianych funkcji jest middleware. Middleware dzia艂a jako kluczowe ogniwo w 艂a艅cuchu przetwarzania 偶膮da艅 i odpowiedzi, umo偶liwiaj膮c programistom wykonywanie kodu, modyfikowanie danych i egzekwowanie regu艂, zanim 偶膮danie dotrze do celu lub zanim odpowied藕 zostanie odes艂ana do klienta.
Ten kompleksowy przewodnik jest przeznaczony dla globalnej publiczno艣ci programist贸w, od tych, kt贸rzy dopiero zaczynaj膮 z FastAPI, po do艣wiadczonych profesjonalist贸w pragn膮cych pog艂臋bi膰 swoj膮 wiedz臋. Zbadamy podstawowe koncepcje middleware, zademonstrujemy, jak tworzy膰 niestandardowe rozwi膮zania, i przejdziemy przez praktyczne, rzeczywiste przypadki u偶ycia. Do ko艅ca b臋dziesz wyposa偶ony do wykorzystania middleware do tworzenia bardziej solidnych, bezpiecznych i wydajnych API.
Czym jest Middleware w Kontek艣cie Framework贸w Webowych?
Zanim zag艂臋bimy si臋 w kod, wa偶ne jest, aby zrozumie膰 koncepcj臋. Wyobra藕 sobie cykl 偶膮danie-odpowied藕 swojej aplikacji jako potok lub lini臋 monta偶ow膮. Kiedy klient wysy艂a 偶膮danie do Twojego API, nie dociera ono natychmiast do logiki endpointu. Zamiast tego przechodzi przez szereg krok贸w przetwarzania. Podobnie, gdy Tw贸j endpoint generuje odpowied藕, wraca przez te same kroki, zanim dotrze do klienta. Komponenty middleware to w艂a艣nie te kroki w potoku.
Popularn膮 analogi膮 jest model cebuli. Rdzeniem cebuli jest logika biznesowa Twojej aplikacji (endpoint). Ka偶da warstwa cebuli otaczaj膮ca rdze艅 to kawa艂ek middleware. 呕膮danie musi przej艣膰 przez ka偶d膮 zewn臋trzn膮 warstw臋, aby dotrze膰 do rdzenia, a odpowied藕 wraca przez te same warstwy. Ka偶da warstwa mo偶e analizowa膰 i modyfikowa膰 偶膮danie w drodze do 艣rodka i odpowied藕 w drodze na zewn膮trz.
W zasadzie middleware to funkcja lub klasa, kt贸ra ma dost臋p do obiektu 偶膮dania, obiektu odpowiedzi i nast臋pnego middleware w cyklu 偶膮danie-odpowied藕 aplikacji. Jego g艂贸wne cele to:
- Wykonywanie kodu: Wykonuj akcje dla ka偶dego przychodz膮cego 偶膮dania, takie jak logowanie lub monitorowanie wydajno艣ci.
- Modyfikowanie 偶膮dania i odpowiedzi: Dodawaj nag艂贸wki, kompresuj cia艂a odpowiedzi lub konwertuj formaty danych.
- Przerwanie cyklu: Wcze艣nie zako艅cz cykl 偶膮danie-odpowied藕. Na przyk艂ad middleware uwierzytelniaj膮cy mo偶e zablokowa膰 nieuwierzytelnione 偶膮danie, zanim dotrze ono do zamierzonego endpointu.
- Zarz膮dzanie globalnymi kwestiami: Obs艂uguj og贸lne problemy, takie jak obs艂uga b艂臋d贸w, CORS (Cross-Origin Resource Sharing) i zarz膮dzanie sesjami w scentralizowanym miejscu.
FastAPI jest zbudowany na zestawie narz臋dzi Starlette, kt贸ry zapewnia solidn膮 implementacj臋 standardu ASGI (Asynchronous Server Gateway Interface). Middleware jest fundamentaln膮 koncepcj膮 w ASGI, co czyni je obywatelem pierwszej kategorii w ekosystemie FastAPI.
Najprostsza Forma: Middleware FastAPI z Dekoratemorem
FastAPI zapewnia prosty spos贸b dodawania middleware za pomoc膮 dekoratora @app.middleware("http"). Jest to idealne rozwi膮zanie dla prostej, samodzielnej logiki, kt贸ra musi dzia艂a膰 dla ka偶dego 偶膮dania HTTP.
Stw贸rzmy klasyczny przyk艂ad: middleware do obliczania czasu przetwarzania ka偶dego 偶膮dania i dodawania go do nag艂贸wk贸w odpowiedzi. Jest to niezwykle przydatne do monitorowania wydajno艣ci.
Przyk艂ad: Middleware Process-Time
Najpierw upewnij si臋, 偶e masz zainstalowane FastAPI i serwer ASGI, taki jak Uvicorn:
pip install fastapi uvicorn
Teraz napiszmy kod w pliku o nazwie main.py:
import time
from fastapi import FastAPI, Request
app = FastAPI()
# Zdefiniuj funkcj臋 middleware
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
# Zapisz czas rozpocz臋cia, gdy przychodzi 偶膮danie
start_time = time.time()
# Przejd藕 do nast臋pnego middleware lub endpointu
response = await call_next(request)
# Oblicz czas przetwarzania
process_time = time.time() - start_time
# Dodaj niestandardowy nag艂贸wek do odpowiedzi
response.headers["X-Process-Time"] = str(process_time)
return response
@app.get("/")
async def root():
# Symuluj troch臋 pracy
time.sleep(0.5)
return {"message": "Hello, World!"}
Aby uruchomi膰 t臋 aplikacj臋, u偶yj polecenia:
uvicorn main:app --reload
Teraz, je艣li wy艣lesz 偶膮danie do http://127.0.0.1:8000 za pomoc膮 narz臋dzia takiego jak cURL lub klienta API jak Postman, zobaczysz nowy nag艂贸wek w odpowiedzi, X-Process-Time, z warto艣ci膮 oko艂o 0,5 sekundy.
Rozbieramy kod:
@app.middleware("http"): Ten dekorator rejestruje nasz膮 funkcj臋 jako element middleware HTTP.async def add_process_time_header(request: Request, call_next):: Funkcja middleware musi by膰 asynchroniczna. Otrzymuje przychodz膮cy obiektRequesti specjaln膮 funkcj臋,call_next.response = await call_next(request): To jest najwa偶niejsza linia.call_nextprzekazuje 偶膮danie do nast臋pnego kroku w potoku (innego middleware lub rzeczywistej operacji 艣cie偶ki). Musisz `await` to wywo艂anie. Wynikiem jest obiektResponsewygenerowany przez endpoint.response.headers[...] = ...: Po otrzymaniu odpowiedzi od endpointu mo偶emy j膮 zmodyfikowa膰, w tym przypadku dodaj膮c niestandardowy nag艂贸wek.return response: Na koniec zmodyfikowana odpowied藕 jest zwracana, aby zosta艂a wys艂ana do klienta.
Tworzenie W艂asnego Niestandardowego Middleware za Pomoc膮 Klas
Chocia偶 podej艣cie z dekoratorem jest proste, mo偶e by膰 ograniczone w bardziej z艂o偶onych scenariuszach, szczeg贸lnie gdy middleware wymaga konfiguracji lub musi zarz膮dza膰 wewn臋trznym stanem. W takich przypadkach FastAPI (za po艣rednictwem Starlette) obs艂uguje middleware oparte na klasach przy u偶yciu BaseHTTPMiddleware.
Podej艣cie oparte na klasach oferuje lepsz膮 struktur臋, pozwala na wstrzykiwanie zale偶no艣ci w jego konstruktorze i jest og贸lnie 艂atwiejsze w utrzymaniu dla z艂o偶onej logiki. Podstawowa logika znajduje si臋 w asynchronicznej metodzie dispatch.
Przyk艂ad: Middleware Uwierzytelniania Kluczem API Oparte na Klasach
Zbudujmy bardziej praktyczne middleware, kt贸re zabezpieczy nasze API. B臋dzie ono sprawdza膰 obecno艣膰 okre艣lonego nag艂贸wka, X-API-Key, a je艣li klucz nie zostanie znaleziony lub jest nieprawid艂owy, natychmiast zwr贸ci odpowied藕 z b艂臋dem 403 Forbidden. Jest to przyk艂ad "przerwania" 偶膮dania.
W main.py:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.responses import Response
# Lista prawid艂owych kluczy API. W prawdziwej aplikacji pochodzi艂aby z bazy danych lub bezpiecznego magazynu.
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:
# Przerwij 偶膮danie i zwr贸膰 odpowied藕 z b艂臋dem
return JSONResponse(
status_code=403,
content={"detail": "Forbidden: Invalid or missing API Key"}
)
# Je艣li klucz jest prawid艂owy, kontynuuj przetwarzanie 偶膮dania
response = await call_next(request)
return response
app = FastAPI()
# Dodaj middleware do aplikacji
app.add_middleware(APIKeyMiddleware)
@app.get("/")
async def root():
return {"message": "Welcome to the secure zone!"}
Teraz, gdy uruchomisz t臋 aplikacj臋:
- 呕膮danie bez nag艂贸wka
X-API-Key(lub z nieprawid艂ow膮 warto艣ci膮) otrzyma kod statusu 403 i komunikat o b艂臋dzie w formacie JSON. - 呕膮danie z nag艂贸wkiem
X-API-Key: my-super-secret-keyzako艅czy si臋 sukcesem i otrzyma odpowied藕 200 OK.
Ten wzorzec jest niezwykle pot臋偶ny. Kod endpointu pod adresem / nie musi nic wiedzie膰 o walidacji klucza API; ta kwestia jest ca艂kowicie oddzielona do warstwy middleware.
Cz臋ste i Pot臋偶ne Przypadki U偶ycia Middleware
Middleware to idealne narz臋dzie do obs艂ugi og贸lnych problem贸w. Przyjrzyjmy si臋 niekt贸rym z najcz臋stszych i najbardziej wp艂ywowych przypadk贸w u偶ycia.
1. Scentralizowane Logowanie
Kompleksowe logowanie jest niezb臋dne dla aplikacji produkcyjnych. Middleware pozwala na stworzenie jednego miejsca, w kt贸rym logujesz krytyczne informacje o ka偶dym 偶膮daniu i jego odpowiedniej odpowiedzi.
Przyk艂ad Middleware Logowania:
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()
# Loguj szczeg贸艂y 偶膮dania
logger.info(f"Incoming request: {request.method} {request.url.path}")
response = await call_next(request)
process_time = time.time() - start_time
# Loguj szczeg贸艂y odpowiedzi
logger.info(f"Response status: {response.status_code} | Process time: {process_time:.4f}s")
return response
To middleware loguje metod臋 i 艣cie偶k臋 偶膮dania w drodze do 艣rodka, oraz kod statusu odpowiedzi i ca艂kowity czas przetwarzania w drodze na zewn膮trz. Zapewnia to nieocenion膮 widoczno艣膰 ruchu aplikacji.
2. Globalna Obs艂uga B艂臋d贸w
Domy艣lnie nieobs艂u偶ony wyj膮tek w kodzie skutkuje b艂臋dem 500 Internal Server Error, potencjalnie ujawniaj膮c 艣lady stosu i szczeg贸艂y implementacji klientowi. Globalne middleware obs艂ugi b艂臋d贸w mo偶e przechwytywa膰 wszystkie wyj膮tki, logowa膰 je do wewn臋trznego przegl膮du i zwraca膰 wystandaryzowan膮, przyjazn膮 dla u偶ytkownika odpowied藕 o b艂臋dzie.
Przyk艂ad Middleware Obs艂ugi B艂臋d贸w:
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 # Spowoduje to podniesienie b艂臋du ZeroDivisionError
Z tym middleware, 偶膮danie do /error nie spowoduje ju偶 awarii serwera ani nie ujawni 艣ladu stosu. Zamiast tego, b臋dzie ono elegancko zwraca膰 kod statusu 500 z czystym cia艂em JSON, podczas gdy pe艂ny b艂膮d b臋dzie logowany po stronie serwera do zbadania przez programist贸w.
3. CORS (Cross-Origin Resource Sharing)
Je艣li Twoja aplikacja frontendowa jest serwowana z innej domeny, protoko艂u lub portu ni偶 backend FastAPI, przegl膮darki zablokuj膮 偶膮dania z powodu Polityki tej samej domeny. CORS to mechanizm 艂agodzenia tej polityki. FastAPI zapewnia dedykowane, wysoce konfigurowalne `CORSMiddleware` do tego w艂a艣nie celu.
Przyk艂ad Konfiguracji CORS:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# Zdefiniuj list臋 dozwolonych 藕r贸de艂. U偶yj "*" dla publicznych API, ale b膮d藕 konkretny dla lepszego bezpiecze艅stwa.
origins = [
"http://localhost:3000",
"https://my-production-frontend.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True, # Pozw贸l na do艂膮czanie ciasteczek do 偶膮da艅 mi臋dzy domenami
allow_methods=["*"] # Pozw贸l na wszystkie standardowe metody HTTP
allow_headers=["*"] # Pozw贸l na wszystkie nag艂贸wki
)
Jest to jeden z pierwszych element贸w middleware, kt贸ry prawdopodobnie dodasz do ka偶dego projektu z od艂膮czonym frontendem, co u艂atwi zarz膮dzanie politykami mi臋dzy domenami z jednego, centralnego miejsca.
4. Kompresja GZip
Kompresja odpowiedzi HTTP mo偶e znacznie zmniejszy膰 ich rozmiar, prowadz膮c do szybszego 艂adowania dla klient贸w i ni偶szych koszt贸w przepustowo艣ci. FastAPI zawiera `GZipMiddleware` do automatycznego obs艂ugiwania tego.
Przyk艂ad Middleware GZip:
from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware
app = FastAPI()
# Dodaj middleware GZip. Mo偶esz ustawi膰 minimalny rozmiar do kompresji.
app.add_middleware(GZipMiddleware, minimum_size=1000)
@app.get("/")
async def root():
# Ta odpowied藕 jest ma艂a i nie zostanie skompresowana.
return {"message": "Hello World"}
@app.get("/large-data")
async def large_data():
# Ta du偶a odpowied藕 zostanie automatycznie skompresowana przez middleware.
return {"data": "a_very_long_string..." * 1000}
Dzi臋ki temu middleware, ka偶da odpowied藕 wi臋ksza ni偶 1000 bajt贸w zostanie skompresowana, je艣li klient wska偶e, 偶e akceptuje kodowanie GZip (co robi膮 praktycznie wszystkie nowoczesne przegl膮darki i klienci).
Zaawansowane Koncepcje i Najlepsze Praktyki
Gdy staniesz si臋 bardziej bieg艂y w middleware, wa偶ne jest, aby zrozumie膰 pewne niuanse i najlepsze praktyki, aby pisa膰 czysty, wydajny i przewidywalny kod.
1. Kolejno艣膰 Middleware Ma Znaczenie!
To najwa偶niejsza zasada, kt贸r膮 nale偶y pami臋ta膰. Middleware jest przetwarzane w kolejno艣ci, w jakiej jest dodawane do aplikacji. Pierwsze dodane middleware jest najbardziej zewn臋trzn膮 warstw膮 "cebuli".
Rozwa偶 t臋 konfiguracj臋:
app.add_middleware(ErrorHandlingMiddleware) # Najbardziej zewn臋trzna
app.add_middleware(LoggingMiddleware)
app.add_middleware(AuthenticationMiddleware) # Najbardziej wewn臋trzna
Przep艂yw 偶膮dania wygl膮da艂by nast臋puj膮co:
ErrorHandlingMiddlewareotrzymuje 偶膮danie. Opakowuje swoje `call_next` w blok `try...except`.- Wywo艂uje `next`, przekazuj膮c 偶膮danie do `LoggingMiddleware`.
LoggingMiddlewareotrzymuje 偶膮danie, loguje je i wywo艂uje `next`.AuthenticationMiddlewareotrzymuje 偶膮danie, waliduje dane uwierzytelniaj膮ce i wywo艂uje `next`.- 呕膮danie w ko艅cu dociera do endpointu.
- Endpoint zwraca odpowied藕.
AuthenticationMiddlewareotrzymuje odpowied藕 i przekazuje j膮 dalej.LoggingMiddlewareotrzymuje odpowied藕, loguje j膮 i przekazuje dalej.ErrorHandlingMiddlewareotrzymuje ostateczn膮 odpowied藕 i zwraca j膮 do klienta.
Ta kolejno艣膰 jest logiczna: program do obs艂ugi b艂臋d贸w znajduje si臋 na zewn膮trz, dzi臋ki czemu mo偶e przechwytywa膰 b艂臋dy z dowolnej kolejnej warstwy, w tym z innych middleware. Warstwa uwierzytelniania znajduje si臋 g艂臋boko w 艣rodku, wi臋c nie przejmujemy si臋 logowaniem ani przetwarzaniem 偶膮da艅, kt贸re i tak zostan膮 odrzucone.
2. Przekazywanie Danych za Pomoc膮 `request.state`
Czasami middleware musi przekazywa膰 informacje do endpointu. Na przyk艂ad middleware uwierzytelniaj膮cy mo偶e dekodowa膰 JWT i wyodr臋bnia膰 identyfikator u偶ytkownika. Jak mo偶e udost臋pni膰 ten identyfikator u偶ytkownika funkcji obs艂ugi 艣cie偶ki?
Z艂ym sposobem jest bezpo艣rednia modyfikacja obiektu 偶膮dania. W艂a艣ciwym sposobem jest u偶ycie obiektu request.state. Jest to prosty, pusty obiekt dostarczony dok艂adnie w tym celu.
Przyk艂ad: Przekazywanie Danych U偶ytkownika z Middleware
# W metodzie dispatch Twojego middleware uwierzytelniaj膮cego:
# ... po walidacji tokenu i dekodowaniu u偶ytkownika ...
user_data = {"id": 123, "username": "global_dev"}
request.state.user = user_data
response = await call_next(request)
# W Twoim endpointcie:
@app.get("/profile")
async def get_user_profile(request: Request):
current_user = request.state.user
return {"profile_for": current_user}
To utrzymuje logik臋 w czysto艣ci i unika zanieczyszczania przestrzeni nazw obiektu `Request`.
3. Wzgl臋dy Wydajno艣ciowe
Chocia偶 middleware jest pot臋偶ne, ka偶da warstwa dodaje niewielki narzut. W przypadku aplikacji o wysokiej wydajno艣ci pami臋taj o tych punktach:
- Zachowaj zwi臋z艂o艣膰: Logika middleware powinna by膰 jak najszybsza i najbardziej wydajna.
- B膮d藕 asynchroniczny: Je艣li Twoje middleware musi wykonywa膰 operacje wej艣cia/wyj艣cia (takie jak sprawdzenie bazy danych), upewnij si臋, 偶e jest w pe艂ni `async`, aby unikn膮膰 blokowania p臋tli zdarze艅 serwera.
- U偶ywaj z celem: Nie dodawaj middleware, kt贸rego nie potrzebujesz. Ka偶de z nich zwi臋ksza g艂臋boko艣膰 stosu wywo艂a艅 i czas przetwarzania.
4. Testowanie Middleware
Middleware jest krytyczn膮 cz臋艣ci膮 logiki aplikacji i powinno by膰 dok艂adnie testowane. `TestClient` FastAPI sprawia, 偶e jest to proste. Mo偶esz napisa膰 testy, kt贸re wysy艂aj膮 偶膮dania z wymaganymi warunkami i bez nich (np. z prawid艂owym kluczem API lub bez niego) i sprawdza膰, czy middleware dzia艂a zgodnie z oczekiwaniami.
Przyk艂ad Testu dla APIKeyMiddleware:
from fastapi.testclient import TestClient
from .main import app # Zaimportuj swoj膮 aplikacj臋 FastAPI
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!"}
Wniosek
Middleware FastAPI to fundamentalne i pot臋偶ne narz臋dzie dla ka偶dego programisty tworz膮cego nowoczesne API webowe. Zapewnia elegancki i wielokrotnego u偶ytku spos贸b obs艂ugi og贸lnych problem贸w, oddzielaj膮c je od rdzenia logiki biznesowej. Przechwytuj膮c i przetwarzaj膮c ka偶de 偶膮danie i odpowied藕, middleware pozwala na implementacj臋 solidnego logowania, scentralizowanej obs艂ugi b艂臋d贸w, rygorystycznych zasad bezpiecze艅stwa i ulepsze艅 wydajno艣ci, takich jak kompresja.
Od prostego dekoratora @app.middleware("http") po zaawansowane rozwi膮zania oparte na klasach, masz elastyczno艣膰 wyboru odpowiedniego podej艣cia do swoich potrzeb. Zrozumienie podstawowych koncepcji, typowych przypadk贸w u偶ycia i najlepszych praktyk, takich jak kolejno艣膰 middleware i zarz膮dzanie stanem, pozwala na tworzenie czystszych, bezpieczniejszych i wysoce 艂atwych w utrzymaniu aplikacji FastAPI.
Teraz Twoja kolej. Zacznij integrowa膰 niestandardowe middleware w swoim nast臋pnym projekcie FastAPI i odblokuj nowy poziom kontroli i elegancji w projektowaniu swojego API. Mo偶liwo艣ci s膮 ogromne, a opanowanie tej funkcji bez w膮tpienia sprawi, 偶e staniesz si臋 bardziej efektywnym i wydajnym programist膮.