Skip to main content

kemal-websocket

Implementing real-time bi-directional communication with WebSockets in Kemal, origin security, and lifecycle management.

Informations de source

Dépôt
kemalcr/kemal
Dernière activité de la source
24 août 2026 à 11:28
Langue détectée de SKILL.md
anglais
Étoiles
3 924
Forks
206

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
kemal-websocket
description
Implementing real-time bi-directional communication with WebSockets in Kemal, origin security, and lifecycle management.
license
MIT
# Kemal WebSocket Integration This skill provides expert guidance on using WebSockets for real-time features in Kemal, with version-aware origin security guidance, strictly following patterns from [`kemal-by-example/real-time-dashboard`](https://github.com/sdogruyol/kemal-by-example/tree/master/real-time-dashboard) and [`kemal-by-example/twitter-clone`](https://github.com/sdogruyol/kemal-by-example/tree/master/twitter-clone). ## Version Notes - **Default Origin policy changed in 1.13.0:** with an empty `websocket_allowed_origins` (the default), Kemal 1.13.0+ enforces same-origin and rejects missing or mismatched `Origin` headers with `403 Forbidden`; releases up to 1.12.0 allow **all** origins (CSWSH risk — configure an allowlist explicitly, see below). - `websocket_allowed_origins = ["*"]` opt-in allow-all: since Kemal 1.13.0. - Non-GET upgrade rejection (RFC 6455 §4.1, `405 Method Not Allowed` + `Allow: GET`) and explicit `Connection: close` after a rejected handshake: since Kemal 1.13.0. ## Core Mandates - **WebSocket Route Definition:** Use the `ws` helper to define WebSocket endpoints: ```crystal ws "/socket" do |socket, env| # Socket lifecycle callbacks end ``` - **Origin Validation & CSWSH Security:** - **CRITICAL ON STABLE RELEASES (1.12.0 and earlier):** An empty `Kemal.config.websocket_allowed_origins = [] of String` (default) **permits all origins** (`return true if allowed.empty?`). To protect against Cross-Site WebSocket Hijacking (CSWSH) in production on these releases, you **MUST explicitly configure allowed origins**: ```crystal # MANDATORY on 1.12.0 and earlier to protect against CSWSH: Kemal.config.websocket_allowed_origins = %w[https://myapp.com http://localhost:3000] ``` - **ON KEMAL 1.13.0+:** An empty `websocket_allowed_origins` defaults to strict **same-origin** enforcement (matching request `Host`). Missing or mismatched `Origin` headers are rejected with `403 Forbidden`. To explicitly allow all origins: ```crystal # Kemal 1.13.0+ — opt-in allow-all: Kemal.config.websocket_allowed_origins = ["*"] ``` - **Protocol Compliance (RFC 6455 §4.1) — *[Kemal 1.13.0+]*:** - On Kemal 1.13.0+, WebSocket upgrade handshakes require the `GET` method. Non-GET upgrade requests (`POST`, `QUERY`, etc.) are automatically rejected with `405 Method Not Allowed`, `Allow: GET`, and `Connection: close`. - Failed handshakes close the connection immediately to prevent WebSocket connection smuggling. - **Socket Lifecycle Callbacks:** - `socket.on_message`: Handle incoming text messages from client - `socket.on_close`: Handle socket disconnection and perform cleanup - `socket.on_ping` / `socket.on_pong`: Handle heartbeat signals - **Concurrency Safety:** Always protect shared socket collections with a `Mutex`: ```crystal class Hub @sockets = Array(HTTP::WebSocket).new @mutex = Mutex.new def register(socket) @mutex.synchronize { @sockets << socket } end def unregister(socket) @mutex.synchronize { @sockets.delete(socket) } end end ``` - **Error Handling:** Rescue `IO::Error | Socket::Error` when sending to sockets and unregister closed sockets. ## Patterns from Source Code ### Centralized WebSocket Hub (real-time-dashboard/src/services/dashboard_hub.cr) ```crystal require "kemal" module RealTimeDashboard class DashboardHub # NOTE: Do not `include JSON::Serializable` here — it removes the default # constructor, and sockets/mutexes are not serializable anyway. Serialize a # plain stats payload (e.g. `{"total_requests" => total_requests}.to_json`) # when broadcasting instead. getter total_requests : Int64 = 0_i64 getter active_users : Int32 = 0 @sockets = Array(HTTP::WebSocket).new @mutex = Mutex.new def register(socket : HTTP::WebSocket) @mutex.synchronize { @sockets << socket } end def unregister(socket : HTTP::WebSocket) @mutex.synchronize { @sockets.delete(socket) } end def broadcast(message : String) @mutex.synchronize do @sockets.reject! do |socket| begin socket.send(message) false rescue IO::Error | Socket::Error true # Remove failed socket end end end end end end ``` ### WebSocket Route Setup with Origin Security ```crystal require "kemal" # Configure allowed origins explicitly (mandatory on 1.12.0 and earlier for security, recommended everywhere) Kemal.config.websocket_allowed_origins = %w[http://127.0.0.1:3000 http://localhost:3000] HUB = RealTimeDashboard::DashboardHub.new ws "/dashboard/socket" do |socket, env| HUB.register(socket) socket.on_message do |msg| # Handle incoming client messages (spawn long-running work to avoid blocking fiber) spawn do # process msg end end socket.on_close do HUB.unregister(socket) end end ``` ## Best Practices - **Security First:** Always configure `Kemal.config.websocket_allowed_origins` explicitly in production. Origin validation is a browser boundary, not authentication/authorization. - **Mutex Synchronization:** Wrap all modifications and iterations over socket lists in `@mutex.synchronize`. - **Selective Rejection:** Use `@sockets.reject!` inside broadcast loops to cleanly strip dead sockets without deadlocks. - **Graceful Teardown:** Unregister sockets in `on_close` callbacks. ## When to Use - When building real-time bi-directional chat applications, collaborative tools, or active telemetry dashboards. - When client and server communicate via continuous socket connections.
Voir sur GitHub