Skip to content

wireform-websocket

wireform-websocket is a native RFC 6455 WebSocket implementation built on wireform-core’s streaming Wireform.Parser and Wireform.Builder primitives. The magic-ring transport comes from wireform-network and TLS from Wireform.Network.TLS.OpenSSL, the same backend wireform-http uses, so wss:// is a one-line opt-in.

The package integrates directly with the wireform-http stack: the server-side handshake parser consumes a unified Request and replies with a Response, while the standalone listener (runWebSocketServer) accepts both plain TCP and TLS. Per-connection acceptWebSocketOnSocket / acceptWebSocketOnTls hooks let you upgrade a connection from your own dispatch loop.

  • RFC 6455 framing with a streaming parser and builder, mode-polymorphic over Wireform.Parser
  • Handshake (RFC 6455 §4) with SHA-1 + base64 Sec-WebSocket-Accept via Wireform.Base64
  • Reassembled text / binary messages across continuation frames
  • Standalone TCP / TLS listener plus per-connection hand-off for wireform-http upgrade handlers
  • Client connect over ws:// and wss://, including connecting straight from a URL string
  • Sub-protocol negotiation on both client and server sides
  • permessage-deflate (RFC 7692) negotiation and compression via Network.WebSocket.PerMessageDeflate (backed by zlib through cbits/wf_pmd.c)
import Network.WebSocket
main :: IO ()
main =
runWebSocketServer
defaultWebSocketServerConfig
{ wscPort = "8443"
, wscHandler = echo
, wscTls = Just WebSocketTlsConfig
{ wstCertPath = "cert.pem"
, wstKeyPath = "key.pem"
, wstAlpn = [] -- ALPN optional for wss://
}
}
where
echo _ conn = forEachMessage conn defaultMessageLimit $ \m ->
case m of
TextMessage t -> sendTextMessage conn t
BinaryMessage bs -> sendBinaryMessage conn bs
import Network.WebSocket
main :: IO ()
main = do
let cfg = (defaultWebSocketClientConfig "echo.example" "443" "/echo")
{ wcTls = Just wsTlsDefault }
withWebSocketClient cfg $ \conn -> do
sendTextMessage conn "hi"
TextMessage reply <- receiveMessage conn defaultMessageLimit
print reply

You can also connect straight from a URL string:

withWebSocketClientURI "wss://echo.example/echo" $ \conn -> do
sendTextMessage conn "hi"
TextMessage reply <- receiveMessage conn defaultMessageLimit
print reply

For sub-protocol negotiation, withWebSocketClient' exposes the server’s ServerHandshakeResult (selected sub-protocol, extensions, response headers); the server side mirrors this through wscSelectSubProtocol.

ModulePurpose
Network.WebSocketUmbrella re-export
Network.WebSocket.FrameFrame ADT, streaming parser, builder
Network.WebSocket.HandshakeRFC 6455 §4 handshake (SHA-1 + base64)
Network.WebSocket.ConnectionConnection over send/receive transports; ping/pong/close
Network.WebSocket.MessageReassembled text / binary messages across continuation frames
Network.WebSocket.PerMessageDeflateRFC 7692 permessage-deflate negotiation and codec
Network.WebSocket.ServerStandalone TCP / TLS listener + per-connection hand-off
Network.WebSocket.Clientws:// / wss:// connect (withWebSocketClient, withWebSocketClient')
Network.WebSocket.URIparseWebSocketURI / renderWebSocketURI / clientConfigFromURI

The package ships an Autobahn conformance harness that runs the upstream crossbario/autobahn-testsuite Docker image against the included echo server (wireform-websocket/scripts/run-autobahn.sh). The README’s last full run reports 247 / 247 passed across sections 1 (framing), 2 (ping/pong), 3 (reserved bits), 4 (opcodes), 5 (fragmentation), 6 (UTF-8 handling), 7 (close handling), and 10 (misc); the 9.* performance section is excluded as it is not a conformance check.

The README reports end-to-end loopback echo round-trips (single persistent connection, post-handshake measurement window) against the canonical Haskell websockets package and Rust’s tungstenite-rs. wireform-websocket beats websockets on every benched shape (8 to 32% faster) and beats tungstenite-rs at large payloads (33% faster on 128 KiB binary), while tungstenite-rs keeps roughly a 2× edge under 16 KiB. Reproduce with:

cabal bench wireform-websocket:wireform-websocket-bench \
--benchmark-options='--time-limit 5 +RTS -N1 -RTS'