Skip to main content

clika_runtime.http

Serve HTTP from Python: routes, middleware, static files, streamed replies.

An :class:HttpServer takes handlers for exact paths and for patterns with captures, runs them on its own threads, and answers with the :class:Response a handler returns::

from clika_runtime import http

server = http.HttpServer()

@server.get("/health")
def health(request: http.Request) -> dict[str, str]:
return {"status": "ok"} # a dict is sent as JSON

@server.route("GET", r"/models/(?P<name>[^/]+)", pattern=True)
def model(request: http.Request) -> str:
return f"model {request.param('name')}" # a str is sent as text/plain

@server.post("/echo")
def echo(request: http.Request) -> http.Response:
return http.Response.bytes(request.body, "application/octet-stream")

with server:
port = server.bind_to_any_port("127.0.0.1")
... # the block ends with server.stop()

A handler returns a :class:Response (built with Response.text, .json, .html, .bytes, .error, .redirect, .file, .stream or .sse), a str (sent as text/plain) or a dict / list (serialized with json.dumps and sent as application/json). A streamed body pulls its chunks from a generator::

@server.get("/events")
def events(request: http.Request) -> http.Response:
def frames():
for i in range(3):
yield {"event": "tick", "data": str(i)}
return http.Response.sse(frames())

The :class:Request a handler receives is a view over the request the server is handling: read it during the call; a read after the handler returned raises RuntimeError.

Middleware wraps every route: middleware(request, next) inspects the request, then returns next() (the downstream response, changed or not) or its own response without calling next. Register middleware, routes and mounts before the server listens.

A handler that raises answers an opaque 500; the exception's text reaches the handler installed with :meth:HttpServer.set_exception_handler as what (do not echo it to clients) and the server keeps serving.

Threading: listen blocks its thread until stop is called from another; listen_async and bind_to_any_port serve on the server's own thread. Every one of them, and stop and wait_until_ready, releases the interpreter lock while it waits, so other Python threads keep running. A handler holds the lock only while it runs Python code, so several handlers make progress at once whenever they wait on I/O or on the runtime.

NameKind
FormFileclass
FormPartclass
HttpServerclass
Methodclass
Nextclass
Requestclass
Responseclass
RouteGroupclass