Lynkio · Real‑Time Event Engine

Pure Python, zero dependencies — HTTP routing, WebSockets (RFC6455), UDP datagram server, and AUTO mode on one port. Rooms, binary events, background tasks, scheduler, soketDB logging, and full client SDKs.

What is Lynkio?

Lynkio is a lightweight, high‑performance framework for building real‑time web applications using only the Python standard library. It combines a native HTTP router, a full WebSocket implementation (RFC6455), a UDP datagram server, and an innovative AUTO mode that serves TCP (HTTP/WebSocket) and UDP concurrently on the same port. Designed for simplicity and control, Lynkio has no external dependencies except optional integration with soketDB for logging.

Pure Python, async/await
Leverages asyncio for high concurrency.
Zero external deps
Only standard library – easy to deploy.
Standards compliant
HTTP/1.1, WebSocket (RFC6455), UDP.
Production ready
Graceful shutdown, rate limiting, CORS.
Core – Lynk() constructor

The Lynk class is the main entry point. It configures the server and exposes all routing decorators and methods.

ParameterTypeDefaultDescription
hoststr"0.0.0.0"Bind address
portint8765Listen port
protocolstr"TCP""TCP" (HTTP+WS), "UDP", or "AUTO" (TCP+UDP same port)
max_payload_sizeint256*1024Max WebSocket frame / UDP datagram size
max_message_sizeint1024*1024Max fragmented WebSocket message
max_body_sizeint1024*1024Max HTTP request body
max_connectionsint/NoneNoneMax concurrent WebSocket clients
rate_limitint/NoneNoneMessages per second per client/UDP token
enable_keep_aliveboolFalseHTTP keep‑alive connections
debugboolFalseEnable debug logging
enable_databaseboolFalseEnable soketDB integration
database_configdictNonesoketDB options (primary_storage, backups, cloud)
serve_clientboolFalseServe built‑in JS client at /lynkio/client.js
client_pathstr"/lynkio/client.js"Custom path for JS client
from lynkio import Lynk
app = Lynk(host="0.0.0.0", port=8080, protocol="AUTO", rate_limit=30, debug=True)

The server is started with app.run() which installs signal handlers for graceful shutdown.

HTTP Routing & Responses
Decorators

@app.get(path), @app.post(path), @app.put(path), @app.delete(path), @app.patch(path), @app.route(path, methods).
Path parameters: /user/<user_id> or /post/{post_id}. They become keyword arguments in the handler.

@app.get("/users/<user_id>/posts/<post_id>")
async def get_post(req, user_id, post_id):
    return {"user": user_id, "post": post_id}
Request object

req.method, req.path, req.headers (dict), req.body (bytes), req.client_ip, req.query_params (dict), req.cookies (dict), await req.json(), await req.form().

Response helpers
  • json_response(data, status=200) – returns JSON response.
  • redirect(location, status=302) – HTTP redirect.
  • abort(status_code, message) – raises HTTPError.
  • send_file(filepath, as_attachment=False, cache_control=None, ...) – streams a file with support for Content-Disposition, Last‑Modified, Cache‑Control.
  • render_template(template_name, context=None, template_dir="templates") – basic template with {{ variable.nested }} syntax.
  • FileResponse and StreamingResponse classes for custom streaming.
from lynkio import send_file, render_template

@app.get("/download")
async def download(req):
    return send_file("report.pdf", as_attachment=True, cache_control="max-age=3600")

@app.get("/page")
async def page(req):
    return render_template("index.html", {"title": "Lynkio"})
Static files & route groups

app.static(prefix, directory) – serves files from a directory, prevents directory traversal.
app.group(prefix) – returns a RouteGroup that can have its own middleware and routes.

app.static("/assets", "public")
api = app.group("/api/v1")
@api.get("/health")
async def health(req):
    return {"status": "ok"}
HTTP Middleware & CORS

Add functions to app._http_middleware (list) – each receives req and can return None or a response (bytes).
Built‑in CORS: app.enable_cors(allowed_origins, allow_credentials) adds the middleware and handles preflight.

app.enable_cors(["https://example.com"], allow_credentials=True)
WebSocket Server

WebSocket upgrade is automatic when the client sends an Upgrade: websocket header. Lynkio implements RFC6455 with fragmentation, ping/pong heartbeat, and binary frames.

Connection object

Inside event handlers, client is a Connection instance with:
client.id (unique UUID), client.session (dict for custom data), client.rooms (set of rooms), await client.send(payload, text=True), await client.ping(), await client.close(code, reason).

Event handlers

@app.on(event_name) – receives JSON messages with {"event": ..., "data": ...}.
@app.on_binary – receives raw binary payloads.
@app.on_binary_event(name) – receives named binary events (client sends a 2‑byte length header + name).

@app.on("chat")
async def chat(client, data):
    await app.emit_to_room(data["room"], "chat", data["text"])

@app.on_binary_event("image")
async def image_stream(client, payload: bytes):
    print(f"Received image chunk {len(payload)} bytes")
WebSocket middleware

@app.middleware – each middleware receives (client, event, data) and can return modified data or raise StopProcessing to reject the event.

@app.middleware
async def auth_mw(client, event, data):
    if event == "admin" and not client.session.get("auth"):
        raise StopProcessing
    return data
Rooms & Pub/Sub

Rooms allow grouping clients. Methods: app.join_room(client_id, room), app.leave_room(client_id, room), app.emit_to_room(room, event, data, exclude=None) (batched to avoid event loop overload), app.get_room_clients(room) -> Set[str]. Broadcasting to all clients: await app.emit(event, data) (client_id=None).

@app.on("join")
async def join_room(client, data):
    app.join_room(client.id, data["room"])
    await app.emit_to_room(data["room"], "system", f"{client.id} joined")
UDP Datagram Server & AUTO Mode

@app.udp(path) – registers a handler for UDP JSON messages. The message must contain a "path" field, optionally "data" and "client_id" for rate limiting. The handler receives a Request object (method="UDP"). Response is sent back as a UDP datagram. AUTO mode runs both TCP (HTTP/WebSocket) and UDP on the same port simultaneously.

@app.udp("/sensor")
async def sensor(req):
    payload = await req.json()
    return {"status": "ok", "echo": payload}

# Client example:
# echo '{"path":"/sensor","data":{"temp":22.5},"client_id":"sensor1"}' | nc -u localhost 8765

UDP supports the same HTTP middleware chain, rate limiting (based on client_id or IP:port), and max payload enforcement.

Background Tasks & Scheduler

@app.task – registers an async function that starts when the server runs and should loop until app._running becomes False.
@app.schedule(interval) – runs the coroutine periodically every interval seconds (cron‑like). Both are automatically cancelled on graceful shutdown.

@app.task
async def monitor():
    while app._running:
        await asyncio.sleep(30)
        print(f"Active connections: {len(app._clients)}")

@app.schedule(3600)
async def hourly_cleanup():
    await app.query_database("logs", "DELETE FROM old_entries")
soketDB Integration & Logging

Lynkio includes soketDB, a lightweight file‑based database with async support. Set enable_database=True and optionally provide database_config (primary_storage, backups, cloud providers).

app = Lynk(enable_database=True)
db = app.create_database("myapp", create_log_table=True, auto_sync_log=True)

When create_log_table=True, three tables are created: http_logs, wss_logs, runtime_logs. If auto_sync_log=True, every HTTP request, WebSocket message, connect/disconnect, and runtime event is automatically inserted asynchronously.

Manual logging: await app.add_log(table, **kwargs) where table is 'http', 'wss', or 'runtime'.
Distributed queries: await app.query_database(db_name, sql, params=None) – runs on any registered soketDB instance in a thread executor (non‑blocking).

await app.add_log("runtime", level="INFO", message="Custom event", source="auth")
rows = await app.query_database("myapp", "SELECT * FROM http_logs WHERE status_code = $1", (200,))
Official Clients
JavaScript Client (built‑in)

Include <script src="/lynkio/client.js"></script> (set serve_client=True). The global LynkClient class provides auto‑reconnect, binary events, rooms, and task scheduling.

const client = new LynkClient("ws://localhost:8765", { maxReconnectAttempts: 10 });
client.on("message", (data) => console.log(data));
client.onBinary("audio", (payload) => { /* Uint8Array */ });
client.connect().then(() => {
  client.joinRoom("lobby");
  client.emit("chat", { text: "hi" });
  client.sendBinaryEvent("image", new Uint8Array([1,2,3]));
  let tid = client.createTask(() => client.emit("ping", {}), 5000);
});
client.close();
Python Client (lynkio_client)

Full async client with WebSocket, HTTP, and UDP support. Import from lynkio.

from lynkio import LynkClient
import asyncio

async def main():
    client = LynkClient("localhost", 8765)
    await client.ws.connect()
    client.on("greet", print)
    await client.ws.emit("ping", "hello")
    await client.ws.send_binary_event("data", b'\x01\x02')
    # HTTP
    status, headers, body = await client.http.get("/api/status")
    # UDP
    resp = await client.udp.send(b'{"path":"/ping"}')
    await client.ws.close()
asyncio.run(main())

Separate classes: WebSocketClient, HTTPClient, UDPClient are also available.

Plugins, Fetch & Internal Hooks

Plugin system: app.use(plugin) where plugin is a callable that receives the app instance – can add routes, middleware, etc.

def metrics_plugin(app):
    @app.get("/metrics")
    async def metrics(req):
        return {"clients": len(app._clients)}
app.use(metrics_plugin)

Internal HTTP client: await app.fetch(url, method, headers, body, json, timeout) – returns a FetchResponse with .json(), .text(), .content.

resp = await app.fetch("https://api.example.com/data")
data = await resp.json()

Internal events: @app.on_internal(event) for "connect", "disconnect", "subscribe", "unsubscribe", "message" – useful for analytics.

@app.on_internal("connect")
async def on_connect(client, _):
    print(f"New client {client.id}")
Complete Method Reference
Method / DecoratorDescription
@app.get(path), .post, .put, .delete, .patchHTTP route with path parameters (<id>).
@app.route(path, methods)Explicit HTTP methods list.
app.static(prefix, dir)Serve static files from directory.
app.group(prefix)Route group with shared middleware.
@app.on(event)WebSocket JSON event handler.
@app.on_binaryGeneric binary WebSocket handler.
@app.on_binary_event(name)Named binary event handler.
@app.middlewareWebSocket middleware (can raise StopProcessing).
@app.udp(path)UDP datagram route (JSON required).
app.join_room(cid, room)Add client to room.
app.leave_room(cid, room)Remove client from room.
app.emit_to_room(room, event, data, exclude)Batch broadcast to all clients in room.
app.get_room_clients(room) -> Set[str]Return client IDs in room.
app.emit(event, data, client_id=None)Send to one client or broadcast if client_id=None.
app.send_binary_event(cid, name, data)Send named binary event to client.
@app.taskBackground coroutine that runs at server start.
@app.schedule(interval)Periodic scheduled task (seconds).
app.create_database(name, create_log_table, auto_sync_log)Initialize soketDB and optionally log tables.
app.add_log(table, **kwargs)Manual log entry (http/wss/runtime).
app.query_database(db_name, sql, params=None)Async query on any registered soketDB.
app.enable_cors(origins, credentials)Enable CORS globally.
app.use(plugin)Register a plugin (callable).
app.fetch(url, ...)Async HTTP client inside server.
app.run()Start the server (blocking).
app.stop()Graceful shutdown.
Complete Real‑Time Chat Server
from lynkio import Lynk, render_template

app = Lynk(protocol="AUTO", serve_client=True, enable_database=True)
app.create_database("chat_logs", create_log_table=True, auto_sync_log=True)

@app.get("/")
async def index(req):
    return render_template("chat.html", {"title": "Lynkio Chat"})

@app.on("join")
async def join(client, data):
    name = data.get("name", "anon")
    client.session["name"] = name
    app.join_room(client.id, "main")
    await app.emit_to_room("main", "system", f"{name} joined")

@app.on("msg")
async def message(client, data):
    await app.emit_to_room("main", "chat", {
        "name": client.session.get("name"),
        "text": data["text"]
    })

@app.task
async def stats():
    while app._running:
        await asyncio.sleep(60)
        print(f"Active connections: {len(app._clients)}")

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)