Documented streaming route support.
+26
-2
@@ -20,6 +20,7 @@ async def stats(ctx: RouteContext) -> dict:
|
||||
|-----------|------|---------|-------------|
|
||||
| `path` | `str` | *(required)* | URL path relative to the module's namespace. |
|
||||
| `methods` | `list[str] \| None` | `["GET"]` | HTTP methods to accept. |
|
||||
| `streaming` | `bool` | `False` | If `True`, the handler runs without a timeout. Use for long-lived connections like SSE streams. The handler is responsible for its own keepalives, idle detection, and cleanup. |
|
||||
|
||||
A route that accepts multiple methods:
|
||||
|
||||
@@ -49,6 +50,28 @@ async def create_data(ctx: RouteContext) -> dict:
|
||||
|
||||
Each handler's method set must not overlap with any other handler on the same path. Registering a handler whose methods conflict with an existing one raises `ValueError`.
|
||||
|
||||
### Streaming Routes
|
||||
|
||||
Routes that hold a connection open for a long time (e.g. Server-Sent Events) should set `streaming=True` to bypass the handler timeout (default 30 seconds):
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from aiohttp import web
|
||||
from owlbot.api import RouteContext, on_route
|
||||
|
||||
@on_route("/events", streaming=True)
|
||||
async def sse_handler(ctx: RouteContext) -> web.StreamResponse:
|
||||
resp = web.StreamResponse()
|
||||
resp.content_type = "text/event-stream"
|
||||
await resp.prepare(ctx.request)
|
||||
while True:
|
||||
await resp.write(b"data: ping\n\n")
|
||||
await asyncio.sleep(30)
|
||||
```
|
||||
|
||||
Streaming handlers manage their own lifecycle. Owlbot does not impose any timeout or keepalive, so a handler that never writes will run indefinitely. Write periodically to detect closed connections, as the next write after a client disconnects will raise `ConnectionResetError`.
|
||||
|
||||
## Namespacing
|
||||
|
||||
Routes are automatically prefixed under `/owlbot/<module_name>/`. So if the module `stats` registers `@on_route("/dashboard")`, the actual URL is:
|
||||
@@ -155,7 +178,7 @@ async def incoming_webhook(ctx: RouteContext) -> None:
|
||||
|
||||
## Error Handling
|
||||
|
||||
If a route handler raises an exception, the error is logged and the client receives an empty `500` response. The same applies if the handler exceeds the configured `handler_timeout` (default 30 seconds); it is cancelled and the client receives a `500`.
|
||||
If a route handler raises an exception, the error is logged and the client receives an empty `500` response. The same applies if the handler exceeds the configured `handler_timeout` (default 30 seconds); it is cancelled and the client receives a `500`. Streaming handlers (`streaming=True`) are exempt from the timeout.
|
||||
|
||||
## URL Building
|
||||
|
||||
@@ -276,7 +299,7 @@ async def setup(ctx: ModuleContext) -> None:
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `ctx.routes.register(path, handler, methods=...)` | Register a route. Accepts the same parameters as `@on_route`. Returns `RouteInfo`. |
|
||||
| `ctx.routes.register(path, handler, methods=..., streaming=...)` | Register a route. Accepts the same parameters as `@on_route`. Returns `RouteInfo`. |
|
||||
| `ctx.routes.unregister(path)` | Remove all handlers at the path. Returns `True` if found. |
|
||||
| `ctx.routes.unregister(path, method=...)` | Remove only the handler covering that method. Returns `True` if found. |
|
||||
| `ctx.routes.get(path)` | Get all handlers at the path. Returns `list[RouteInfo]`. |
|
||||
@@ -295,3 +318,4 @@ async def setup(ctx: ModuleContext) -> None:
|
||||
| `methods` | `frozenset[str]` | HTTP methods. |
|
||||
| `handler` | `RouteHandler` | The handler function. |
|
||||
| `module_name` | `str` | Module that owns this route. |
|
||||
| `streaming` | `bool` | Whether the handler bypasses the timeout. |
|
||||
|
||||
Reference in New Issue
Block a user