FAkka.WebSocket
1.569.101.302
dotnet add package FAkka.WebSocket --version 1.569.101.302
NuGet\Install-Package FAkka.WebSocket -Version 1.569.101.302
<PackageReference Include="FAkka.WebSocket" Version="1.569.101.302" />
<PackageVersion Include="FAkka.WebSocket" Version="1.569.101.302" />
<PackageReference Include="FAkka.WebSocket" />
paket add FAkka.WebSocket --version 1.569.101.302
#r "nuget: FAkka.WebSocket, 1.569.101.302"
#:package FAkka.WebSocket@1.569.101.302
#addin nuget:?package=FAkka.WebSocket&version=1.569.101.302
#tool nuget:?package=FAkka.WebSocket&version=1.569.101.302
Akka.WebSocket
Akka.WebSocket is a tiny bridge that wires a running Akka.NET actor system to a Suave WebSocket endpoint. It targets both net9.0 and netstandard2.0, so the same package works for .NET 9 applications and legacy .NET Framework hosts.
The library exposes a single entry point: start Suave, hand it an ActorSystem plus the actor that should receive WebSocket JSON/text payloads, and the bridge will keep forwarding messages asynchronously. It also provides a REST-style health check that round-trips through your actor, so downstream load balancers can observe the same infrastructure used by WebSocket clients.
Disconnect Handling
1.569.101.301-win17 bounds each connection's outbound backlog to 128 messages / 16 MiB UTF-8. A slow consumer now receives Close and the response proxy terminates, allowing durable callers to reconnect/replay instead of retaining an unbounded in-memory queue. Regression gate: scripts/verify.boundedOutbound.fsx plus the existing mapper/disconnect/stack-safe gates.
1.569.101.301-win16 fixes clean WebSocket close on Suave 3.4.3. After replying to a client Close frame, the bridge keeps reading until the client closes the TCP connection;returning immediately allowed Suave's handshake continuation to append a default HTTP/1.1 404 response after the valid Close frame, which Chromium reported as Invalid frame header。The server echoes the client's Close payload and uses status 1000 for server-initiated close。Regression gate: scripts/verify.expectedDisconnect.fsx plus the PTCS Dynamic true-live Playwright gate。
1.569.101.301-win13 adds opt-in transport-level WebSocketMessageMapper support. Callers can use mapper-aware overloads to rewrite or drop inbound decoded WebSocket messages before handler.Tell, and rewrite or drop outbound text replies before the per-client proxy actor writes to the browser. This is intentionally not a PTCS/RN routing hook; domain orchestration remains in caller packages. Regression gate: scripts/verify.messageMapper.fsx, plus the existing expected-disconnect and stack-safe loop gates.
1.569.101.301-win12 replaces the recursive Suave SocketMonad WebSocket read loop with an iterative task-backed loop. This prevents stack overflow on long-lived or busy browser sessions while preserving expected-disconnect cleanup behavior. Regression gate: scripts/verify.stackSafeLoop.fsx.
1.569.101.301-win11 keeps the narrowly-scoped console filter from the shared loop path and exposes WebSocketServer.tryObserveExpectedDisconnectTaskException so direct-loop consumers such as PTCS can mark normal Suave shutdown/abort TaskScheduler.UnobservedTaskException events as observed without adding host-specific workarounds. The classifier includes normal browser refresh/tab close/client abort families such as WebSocket disconnected, SocketError ConnectionAborted, I/O operation has been aborted, and thread exit or an application request, while keeping unrelated stdout/stderr and unhandled failures visible.
1.569.101.301-win9 installed the narrowly-scoped console filter from the shared websocketLoopWithActorRegistry path. Suave 3.4.3 can write normal close events directly to Console.Out as WebSocket disconnected ... before caller code observes the error; win9 suppresses only that expected normal-close family for both package-owned hosts and direct-loop consumers such as PTCS, while keeping unrelated stdout/stderr visible.
1.569.101.301-win8 installed the same filter only from the package start path. It covered WebSocketServer.startWithActorRegistry, but not consumers that call websocketLoop directly.
1.569.101.301-win6 fixes the remaining expected-disconnect noise path seen by PTCS/Dynamic POC hosts. Suave may report a normal browser refresh, tab close, or client abort as ConnectionError "short read: expected 2 bytes, got 0" before the send-side proxy sees ConnectionAborted; the bridge now classifies that read-loop error as expected cleanup and exits the WebSocket loop silently. Regression gate: scripts/verify.expectedDisconnect.fsx.
1.569.101.301-win1 keeps expected browser disconnects silent. Browser tab changes, page reloads, service shutdown, and normal client aborts may still surface from Suave as ConnectionAborted / WebSocket disconnected, but the bridge classifies those as expected cleanup and does not log them as debug, warning, or error events.
1.564.101.203-win1 treats browser reload, abort, and disposed websocket sends as normal connection lifecycle. The per-client response proxy catches ObjectDisposedException, OperationCanceledException, and disposed AggregateException, then stops the proxy actor so upstream actors can clean subscriptions via Terminated.
1.564.101.203-win2 serializes outbound text-frame sends per browser client. Earlier versions started each send as fire-and-forget async work; high-frequency multi-source push could overlap writes on the same Suave websocket and corrupt the protocol frame, observed as browser Could not decode a text frame as UTF-8 or .NET ClientWebSocket reporting The WebSocket server sent a masked frame.
Journal / Snapshot / DB
Akka.WebSocket does not use Akka.Persistence journal/snapshot tables and does not own any SQL database.
The optional WebSocketActorRegistryOptions only emits actor lifecycle events to a caller-supplied PulseTrade.Comm.Actor.Registry sink. Persistence for those events is owned by the caller; this package does not create tables or JSONL files.
Features
- Actor-first design – no
Ask/blocking calls; messages areTelled to the supplied actor. - Opt-in message mapper – mapper-aware overloads can normalize/drop inbound frames and outbound text responses without changing existing no-op behavior.
- Health probe support –
/healthsends"healthcheck"to the handler actor and returns its response. - Multi-target build – ships as a single package for
net9.0andnetstandard2.0. - Graceful shutdown – disposing the returned
ServerHandlestops Suave and cancels the WebSocket loop. - Explicit licensing – ships under MIT while clearly documenting bundled Apache‑licensed dependencies (Suave 2.5.6 and Akka.NET 1.5.54).
Getting Started
Install the package from NuGet (package id pending):
dotnet add package Akka.WebSocket
Create an actor that understands your payloads (and optionally the "healthcheck" message) and start the bridge:
open System
open Akka.Actor
open Akka.WebSocketBridge
type EchoActor() =
inherit UntypedActor()
override _.OnReceive msg =
match msg with
| :? string as text when text = "healthcheck" ->
base.Sender.Tell("ok", base.Self)
| :? string as text ->
printfn "client said: %s" text
| _ -> ()
let system = ActorSystem.Create("WebSocketCluster")
let echo = system.ActorOf(Props.Create<EchoActor>(), "ws-echo")
let server =
WebSocketServer.start
system
"ws"
None
(Choice2Of2 ("0.0.0.0", 8080))
(fun _ -> Some echo)
printfn "Listening on ws://0.0.0.0:8080/ws"
Console.ReadLine() |> ignore
server.Stop()
system.Terminate() |> Async.AwaitTask |> Async.RunSynchronously
Connect with any WebSocket client (browser, PowerShell, wscat, etc.) and send UTF-8 text frames; they appear in your actor. Pings are answered automatically, and fragmented/binary frames close the connection.
Demo Host
The repository contains Program.fs, a sample console host that starts:
- an Akka.NET cluster seed node (defaults:
127.0.0.1:4053), - a single
EchoActor, and - the WebSocket bridge bound to
ws://0.0.0.0:8080/ws.
Run it with:
dotnet run -f net9.0 --project akka.websocket.fsproj \
-- 0.0.0.0 8080 127.0.0.1 4053 "akka.tcp://WebSocketCluster@127.0.0.1:4053"
Arguments (all optional):
| Position | Meaning | Default |
|---|---|---|
| 0 | HTTP bind address | 0.0.0.0 |
| 1 | HTTP port | 8080 |
| 2 | Cluster hostname | 127.0.0.1 |
| 3 | Cluster port | 4053 |
| 4 | Seed node list | self address |
Health Check
The demo actor replies "ok" to "healthcheck". The /health endpoint calls into that actor, so you can verify end-to-end readiness:
curl http://localhost:8080/health
# => ok
If the actor is unavailable or throws, the endpoint returns an HTTP 500 with the failure message.
Building and Packing
Restore and build:
dotnet restore
dotnet build
Create a NuGet package (produces both target frameworks):
dotnet pack -c Release -o nupkgs
Publish the resulting .nupkg to NuGet with your preferred workflow (dotnet nuget push, GitHub Actions, etc.).
License
This project is licensed under the terms of the included LICENSE file (MIT).
Suave 2.5.6 and Akka.NET 1.5.54 are distributed under the Apache License 2.0; see THIRD-PARTY-NOTICES.md for details and required notices.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Akka (>= 1.5.69)
- Akka.Cluster (>= 1.5.69)
- FSharp.Core (>= 10.1.302)
- Newtonsoft.Json (>= 13.0.4)
- Suave (>= 2.5.6)
-
net10.0
- Akka (>= 1.5.69)
- Akka.Cluster (>= 1.5.69)
- FSharp.Core (>= 10.1.302)
- Newtonsoft.Json (>= 13.0.4)
- PulseTrade.Comm.Actor.Registry (= 0.1.0-alpha5)
- Suave (>= 3.4.3)
-
net9.0
- Akka (>= 1.5.69)
- Akka.Cluster (>= 1.5.69)
- FSharp.Core (>= 10.1.302)
- Newtonsoft.Json (>= 13.0.4)
- Suave (>= 2.5.6)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on FAkka.WebSocket:
| Package | Downloads |
|---|---|
|
FAkka.Proc.Supervisor
Package Description |
|
|
PulseTrade.Comm.Spa
Small Suave + WebSharper SPA shell for key-set based chat, participant, set, and actor views. |
|
|
PulseTrade.Shared.fs
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.569.101.302 | 75 | 8/2/2026 |
| 1.569.101.302-win2 | 78 | 8/2/2026 |
| 1.569.101.301 | 279 | 6/19/2026 |
| 1.569.101.301-win9 | 112 | 6/30/2026 |
| 1.569.101.301-win8 | 102 | 6/30/2026 |
| 1.569.101.301-win7 | 159 | 6/29/2026 |
| 1.569.101.301-win6 | 124 | 6/29/2026 |
| 1.569.101.301-win5 | 202 | 6/28/2026 |
| 1.569.101.301-win4 | 109 | 6/27/2026 |
| 1.569.101.301-win3 | 101 | 6/27/2026 |
| 1.569.101.301-win17 | 472 | 7/14/2026 |
| 1.569.101.301-win16 | 366 | 7/14/2026 |
| 1.569.101.301-win13 | 418 | 7/6/2026 |
| 1.569.101.301-win12 | 139 | 7/3/2026 |
| 1.569.101.301-win11 | 294 | 6/30/2026 |
| 1.569.101.301-win10 | 119 | 6/30/2026 |
| 1.569.101.301-win1 | 189 | 6/27/2026 |
| 1.569.101.300 | 107 | 6/19/2026 |
| 1.568.101.300 | 289 | 6/7/2026 |
| 1.564.101.203-win2 | 152 | 5/4/2026 |