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

12 KiB
Executable File
Raw Blame History

1. Grundsätzliche Definition: Was ist uvicorn?

uvicorn ist ein extrem performanter, asynchroner Web-Server für Python, der das ASGIProtokoll 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 asyncioLoop)
  • httptools (schnelles HTTPParsing)

Du verwendest uvicorn typischerweise, um eine ASGIApp „zu starten“:

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: FastAPIApp mit gunicorn + uvicorn worker:

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):

# 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:

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:

# 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:

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

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:

# 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:

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

# main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello from uvicorn + FastAPI"}

Start:

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

# run.py
import uvicorn

if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=8000,
        reload=True,
    )

Start:

python run.py

6. Typische Konfigurationen und Szenarien

6.1 Entwicklung

  • ein Worker
  • --reload aktiviert
  • Logging auf debug
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
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:

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:

@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):

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 ASGIlifespanEvents (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):

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):

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.
  2. App schreiben
    • app = FastAPI(), Endpoints definieren.
  3. In Entwicklung starten
    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)
    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 DjangoKonfigurationen.
  • 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.