Skip to main content

kemal-websocket

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

Quellinformationen

Repository
kemalcr/kemal
Letzte Quellaktivität
24. August 2026 um 11:28
Erkannte Sprache von SKILL.md
Englisch
Sterne
3.923
Forks
206

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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.
Auf GitHub ansehen