Skip to main content

kemal-websocket

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

소스 정보

저장소
kemalcr/kemal
최근 소스 활동
2026년 8월 24일 11:28
감지된 SKILL.md 언어
영어
스타
3,936
포크
207

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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.
GitHub에서 보기