Files
it-know-how/python/uvicorn.md
T
2026-03-27 12:58:01 +01:00

470 lines
12 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 1. Grundsätzliche Definition: Was ist **uvicorn**?
**uvicorn** ist ein extrem performanter, asynchroner Web-Server für Python, der das **ASGI**Protokoll unterstützt.
- **ASGI** = *Asynchronous Server Gateway Interface*
- uvicorn ist also das Bindeglied zwischen:
- dem Web (HTTP, WebSockets)
- und deiner PythonApplikation (z.B. [[FastAPI]], Starlette, Django mit ASGI)
Uvicorn basiert intern auf sehr schnellen CBibliotheken:
- **uvloop** (schneller Event Loop, Ersatz für `asyncio`Loop)
- **httptools** (schnelles HTTPParsing)
Du verwendest uvicorn typischerweise, um eine ASGIApp „zu starten“:
```bash
uvicorn main:app --reload
```
---
## 2. Wichtige Begriffe: ASGI, WSGI und Web-Frameworks
### 2.1 ASGI vs. WSGI
- **WSGI** (älterer Standard, z.B. für Django (klassisch), Flask):
- synchron
- kein natives WebSocketSupport
- typische Server: `gunicorn`, `uWSGI`, `mod_wsgi`
- **ASGI** (moderner Standard):
- unterstützt **async/await**
- kann **HTTP** und **WebSockets** und Background Tasks
- typische Server: `uvicorn`, `hypercorn`, `daphne`
uvicorn ist also ein **ASGI-Server**, nicht WSGI.
### 2.2 uvicorn vs. Web-Frameworks ([[FastAPI]], Starlette, Django, Flask)
- **Framework** ([[FastAPI]], Starlette, Django, Flask):
- definiert, wie du Routen, Views, Models, etc. schreibst.
- kümmert sich um Request/ResponseLogik
- **uvicorn**:
- kümmert sich um das Annehmen von Verbindungen, HTTPParsing, EventLoopHandling.
- ruft deine Applikation nur gemäß dem ASGIProtokoll auf.
Bildlich:
**Browser** → (HTTP) → **uvicorn** → (ASGI) → **deine App** (z.B. [[FastAPI]])
---
## 3. Abgrenzung zu ähnlichen oder verwandten Begriffen
### 3.1 uvicorn vs. Gunicorn
- **gunicorn**:
- klassischer **WSGI**-Server (für syncApps wie Flask oder Django ohne ASGI).
- kann aber mithilfe von Workern wie `uvicorn.workers.UvicornWorker` auch ASGI-App starten.
Beispiel: [[FastAPI]]App mit gunicorn + uvicorn worker:
```bash
gunicorn -k uvicorn.workers.UvicornWorker main:app -b 0.0.0.0:8000
```
Hier ist:
- gunicorn = Prozessmanager und WorkerSpawner
- uvicorn = eigentlicher ASGIServer pro Worker
### 3.2 uvicorn vs. Hypercorn / Daphne
- **hypercorn**:
- anderer ASGIServer (unterstützt z.B. HTTP/2, verschiedene Event Loops)
- **daphne**:
- ASGIServer aus dem DjangoChannels‑Ökosystem
Alle drei (uvicorn, hypercorn, daphne) machen im Kern das Gleiche:
**ASGIApps ausführen**, unterscheiden sich aber in Features, Performance und Konfigurationsmöglichkeiten.
### 3.3 uvicorn vs. „eingebauter Development-Server“
Viele Frameworks haben eingebaute Dev-Server, z.B.:
- Flask: `app.run(debug=True)`
- Django: `python manage.py runserver`
Diese sind:
- für **Entwicklung** gedacht
- nicht für **Produktion** (Performance, Stabilität, Security)
uvicorn ist ein **richtiger** Webserver, der für **Produktion** geeignet ist (oft zusammen mit einem Reverse Proxy wie [[Nginx]]).
---
## 4. Welche Probleme löst uvicorn?
### 4.1 Asynchrone WebBackends performant betreiben
Mit ASGI kannst du:
- `async def` Endpoints schreiben
- WebSockets nutzen
- viele gleichzeitige Requests mit einem Event Loop bedienen
uvicorn ermöglicht dir, diese **asynchronen** Apps performant auszuliefern.
Praxisnahes Beispiel ([[FastAPI]]):
```python
# main.py
from fastapi import FastAPI
import asyncio
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
await asyncio.sleep(1) # simuliert eine I/O-Operation
return {"item_id": item_id}
```
Starten mit uvicorn:
```bash
uvicorn main:app --reload
```
uvicorn kümmert sich darum, dass mehrere Requests gleichzeitig abgearbeitet werden können, während `asyncio.sleep` nicht blockiert.
### 4.2 WebSockets und Long-Lived Connections
ASGI (und damit uvicorn) unterstützt **WebSockets** nativ, was mit WSGI nicht geht.
Beispiel mit Starlette:
```python
# main.py
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.websockets import WebSocket
from starlette.routing import Route, WebSocketRoute
async def homepage(request):
return JSONResponse({"hello": "world"})
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
await websocket.send_text("Willkommen!")
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Du hast gesendet: {data}")
routes = [
Route("/", endpoint=homepage),
WebSocketRoute("/ws", endpoint=websocket_endpoint),
]
app = Starlette(routes=routes)
```
Start:
```bash
uvicorn main:app
```
Mit WSGI wäre so ein WebSocketEndpoint nicht möglich.
### 4.3 Produktionstauglicher Server gegenüber Entwicklungsservern
- Stabilität bei hoher Last
- Steuerung von:
- Anzahl WorkerProzesse
- Timeouts
- Logging
- Start via CLI, systemd, Docker, Kubernetes etc.
---
## 5. Grundlegende Verwendung von uvicorn
### 5.1 Installation
```bash
pip install uvicorn
# optional: schnellere Variante mit C-Extensions
pip install "uvicorn[standard]"
```
`[standard]` installiert u.a. `uvloop` und `httptools`.
### 5.2 Minimalbeispiel: Plain-ASGI-App
Du kannst eine ASGIApp auch ohne Framework schreiben:
```python
# app.py
async def app(scope, receive, send):
assert scope["type"] == "http"
# Request body lesen (vereinfachter Fall)
await receive()
body = b"Hello, world"
headers = [(b"content-type", b"text/plain")]
await send({
"type": "http.response.start",
"status": 200,
"headers": headers,
})
await send({
"type": "http.response.body",
"body": body,
})
```
Starten:
```bash
uvicorn app:app --reload
```
Erklärung:
- `app:app` = Modul `app.py`, Variable `app`
- `--reload` = automatischer Neustart bei Codeänderung (nur dev)
### 5.3 Beispiel mit [[FastAPI]]
```python
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello from uvicorn + FastAPI"}
```
Start:
```bash
uvicorn main:app --reload --host 0.0.0.0 --port 8000
```
Wichtige CLIOptionen:
- `--reload`: Auto-Reload bei Codeänderungen (Dev)
- `--host`: z.B. `0.0.0.0` um von außen erreichbar zu sein
- `--port`: Port, z.B. `8000`
- `--workers`: Anzahl der Prozesse (für Produktion)
### 5.4 Starten aus Python heraus
```python
# run.py
import uvicorn
if __name__ == "__main__":
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
reload=True,
)
```
Start:
```bash
python run.py
```
---
## 6. Typische Konfigurationen und Szenarien
### 6.1 Entwicklung
- ein Worker
- `--reload` aktiviert
- Logging auf `debug`
```bash
uvicorn main:app --reload --host 0.0.0.0 --port 8000 --log-level debug
```
### 6.2 Produktion (einfach)
- mehrere Worker-Prozesse
- kein `--reload`
- Logging eher `info` oder `warning`
```bash
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 --log-level info
```
Richtwert für Worker:
`Anzahl CPU-Kerne * 2` (abhängig von App und Last; immer testen).
### 6.3 Produktion hinter einem Reverse Proxy (z.B. [[Nginx]])
Typischer Aufbau:
```
Internet → Nginx (TLS, gzip, etc.) → uvicorn → FastAPI/Starlette/Django
```
- [[Nginx]] übernimmt TLS/SSL, Load Balancing, Static Files
- uvicorn macht die Application-Logik
[[Nginx]]-Konfig (stark vereinfacht) könnte so aussehen:
```nginx
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
```
uvicorn wird weiterhin wie oben gestartet.
---
## 7. Herausforderungen und typische Stolpersteine
### 7.1 Async/Synchron-Mix und Blockierungen
**Problem:**
Du verwendest uvicorn (ASGI, async), aber in deinen Endpoints gibt es blockierende Operationen:
- große CPUAufgaben
- synchrones Warten auf externe APIs (z.B. `requests.get(...)`)
- schwere DatenbankQueries, die nicht async sind
Beispiel:
```python
@app.get("/slow")
async def slow():
import time
time.sleep(5) # BLOCKIERT den Event-Loop
return {"status": "ok"}
```
Folge:
- Ein Request blockiert den Event Loop → alle anderen Requests warten mit.
Lösungen:
- I/O: async Libraries benutzen (z.B. `httpx` statt `requests`, `asyncpg` statt syncDBClient)
- CPUlastig: in ThreadPool oder ProcessPool auslagern (`run_in_threadpool` etc.)
### 7.2 Gemeinsamer Zustand über Worker-Prozesse
Wenn du `--workers > 1` nutzt, hast du **mehrere Prozesse**.
Globaler Zustand in Python wird **nicht** zwischen Prozessen geteilt.
Beispiel (Problem):
```python
counter = 0
@app.get("/count")
def count():
global counter
counter += 1
return {"counter": counter}
```
Mit mehreren Workern:
- jeder Worker hat seinen eigenen `counter`
- Ergebnisse sind inkonsistent
Lösung:
- geteilten Zustand über externe Systeme (Redis, Datenbank, etc.)
- oder nur einen Worker nutzen, wenn globaler In-Memory-State unvermeidbar ist (aber meist unsauber).
### 7.3 Datenbankverbindungen und Lebenszyklus
uvicorn unterstützt ASGI`lifespan`Events (startup/shutdown).
Frameworks wie [[FastAPI]]/Starlette nutzen das, um z.B. DBConnections zu öffnen/schließen.
Stolpersteine:
- Verbindungspools pro Worker korrekt initialisieren
- bei Shutdown sauber schließen
- nicht „pro Request“ neue Connections aufmachen
Beispiel mit [[FastAPI]] (vereinfacht):
```python
from fastapi import FastAPI
app = FastAPI()
db = None
@app.on_event("startup")
async def startup():
global db
db = await some_async_db_connect()
@app.on_event("shutdown")
async def shutdown():
await db.close()
```
### 7.4 Logging und Error-Handling
uvicorn hat eigenes Logging; dein Framework ebenso.
Typische Themen:
- Log-Format in Produktion standardisieren
- Fehler-Logs im Zusammenspiel mit Reverse Proxy
- Ausführliche Logs in Dev, weniger in Prod
Beispiel (JSON-Logging in Produktion, nur angedeutet):
```bash
uvicorn main:app \
--host 0.0.0.0 \
--port 8000 \
--log-config logging_config.yaml
```
In `logging_config.yaml` kannst du detailliert das Logging steuern.
### 7.5 Plattformunterschiede (Windows vs. Linux)
- `--reload` nutzt File-Watcher und Signale → unter Linux sehr stabil; unter Windows kann es ein paar Besonderheiten geben.
- In Produktion läuft uvicorn meist auf LinuxServern oder in DockerContainern.
---
## 8. Kurze Checkliste für den praktischen Einstieg
1. **Framework wählen**
- [[FastAPI]] oder Starlette, wenn du intensiv async nutzen willst.
1. **App schreiben**
- `app = FastAPI()`, Endpoints definieren.
3. **In Entwicklung starten**
```bash
uvicorn main:app --reload
```
4. **Vor Produktion**
- BlockingCode prüfen (CPU, I/O)
- Datenbankzugriff sauber konfigurieren (Pools, asyncClient)
- Logging und ErrorHandling aufräumen
5. **In Produktion starten** (einfach)
```bash
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
```
6. **Optional**:
- vor uvicorn einen [[Nginx]] oder Traefik setzen (TLS, Load Balancing)
---
## 9. Zusammenfassung
- **uvicorn** ist ein **ASGIWebserver** für Python, optimiert für **asynchrone** WebApps.
- Er ist **kein WebFramework**, sondern die Laufzeitumgebung für Frameworks wie **[[FastAPI]]**, **Starlette** oder moderne **Django**Konfigurationen.
- Es löst die Probleme klassischer WSGIServer in Bezug auf **Async**, **WebSockets** und **Performance**.
- Typische Herausforderungen liegen im Bereich:
- korrektes AsyncDesign
- Umgang mit mehreren Workern und gemeinsamem Zustand
- saubere Integration von Datenbanken, Logging, Deployment
- Für dich als PythonEntwickler ist uvicorn im Alltag vor allem:
**das Kommando, mit dem du deine moderne WebAPI startest.**