470 lines
12 KiB
Markdown
Executable File
470 lines
12 KiB
Markdown
Executable File
## 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 Python‑Applikation (z.B. [[FastAPI]], Starlette, Django mit ASGI)
|
||
|
||
Uvicorn basiert intern auf sehr schnellen C‑Bibliotheken:
|
||
- **uvloop** (schneller Event Loop, Ersatz für `asyncio`‑Loop)
|
||
- **httptools** (schnelles HTTP‑Parsing)
|
||
|
||
Du verwendest uvicorn typischerweise, um eine ASGI‑App „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 WebSocket‑Support
|
||
- 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/Response‑Logik
|
||
|
||
- **uvicorn**:
|
||
- kümmert sich um das Annehmen von Verbindungen, HTTP‑Parsing, Event‑Loop‑Handling.
|
||
- ruft deine Applikation nur gemäß dem ASGI‑Protokoll 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 sync‑Apps 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 Worker‑Spawner
|
||
- uvicorn = eigentlicher ASGI‑Server pro Worker
|
||
|
||
### 3.2 uvicorn vs. Hypercorn / Daphne
|
||
|
||
- **hypercorn**:
|
||
- anderer ASGI‑Server (unterstützt z.B. HTTP/2, verschiedene Event Loops)
|
||
- **daphne**:
|
||
- ASGI‑Server aus dem Django‑Channels‑Ökosystem
|
||
|
||
Alle drei (uvicorn, hypercorn, daphne) machen im Kern das Gleiche:
|
||
**ASGI‑Apps 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 Web‑Backends 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 WebSocket‑Endpoint nicht möglich.
|
||
|
||
### 4.3 Produktionstauglicher Server gegenüber Entwicklungsservern
|
||
|
||
- Stabilität bei hoher Last
|
||
- Steuerung von:
|
||
- Anzahl Worker‑Prozesse
|
||
- 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 ASGI‑App 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 CLI‑Optionen:
|
||
- `--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 CPU‑Aufgaben
|
||
- synchrones Warten auf externe APIs (z.B. `requests.get(...)`)
|
||
- schwere Datenbank‑Queries, 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 sync‑DB‑Client)
|
||
- CPU‑lastig: in Thread‑Pool oder Process‑Pool 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. DB‑Connections 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 Linux‑Servern oder in Docker‑Containern.
|
||
|
||
---
|
||
|
||
## 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**
|
||
- Blocking‑Code prüfen (CPU, I/O)
|
||
- Datenbankzugriff sauber konfigurieren (Pools, async‑Client)
|
||
- Logging und Error‑Handling 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 **ASGI‑Webserver** für Python, optimiert für **asynchrone** Web‑Apps.
|
||
- Er ist **kein Web‑Framework**, sondern die Laufzeitumgebung für Frameworks wie **[[FastAPI]]**, **Starlette** oder moderne **Django**‑Konfigurationen.
|
||
- Es löst die Probleme klassischer WSGI‑Server in Bezug auf **Async**, **WebSockets** und **Performance**.
|
||
- Typische Herausforderungen liegen im Bereich:
|
||
- korrektes Async‑Design
|
||
- Umgang mit mehreren Workern und gemeinsamem Zustand
|
||
- saubere Integration von Datenbanken, Logging, Deployment
|
||
- Für dich als Python‑Entwickler ist uvicorn im Alltag vor allem:
|
||
**das Kommando, mit dem du deine moderne Web‑API startest.**
|
||
|