Linux::Event::WebSocket
High-performance WebSocket client and server for Linux::Event, with a simple callback-first API.
Status
Release version: 0.001.
The distribution provides working ws:// and wss:// client/server paths,
a native RFC 6455 data engine, and a production test suite. Version 0.001 is the
first CPAN release.
The GitHub Actions test matrix targets Perl 5.36 and Perl 5.44.0.
What works today
- WebSocket client and server APIs.
ws://andwss://.- Text and binary messages.
- UTF-8 validation and decoding for text messages.
- Client masking and server unmasked output.
- Fragmented WebSocket messages with interleaved control frames.
- Automatic ping/pong control handling plus explicit
ping(). - Graceful WebSocket close handshake with a configurable close timeout.
- Hard transport abort when graceful close is not appropriate.
- Subprotocol negotiation.
- Access to the HTTP handshake request and response.
- Configurable message-size protection, defaulting to 16 MiB.
- Linux::Event Stream output buffering and backpressure.
- Linux::Event TLS transport retained across the HTTP-to-WebSocket transition.
- Same-read handoff: WebSocket bytes that arrive immediately after the HTTP Upgrade headers are preserved across the protocol transition.
Server
use v5.36;
use Linux::Event::Loop;
use Linux::Event::WebSocket::Server;
my $loop = Linux::Event::Loop->new;
my $server = Linux::Event::WebSocket::Server->new(
loop => $loop,
host => '127.0.0.1',
port => 8080,
on_open => sub ($ws) {
$ws->send_text('hello');
},
on_message => sub ($ws, $payload, $type) {
if ($type eq 'text') {
$ws->send_text("echo: $payload");
}
},
on_close => sub ($ws, $code, $reason) {
# Connection finished.
},
);
$loop->run;
For wss://, pass the normal Linux::Event HTTP server tls policy containing
the certificate and key.
Client
use v5.36;
use Linux::Event::Loop;
use Linux::Event::WebSocket::Client;
my $loop = Linux::Event::Loop->new;
my $client = Linux::Event::WebSocket::Client->new(
loop => $loop,
on_open => sub ($ws) {
$ws->send_text('hello');
},
on_message => sub ($ws, $payload, $type) {
say $payload if $type eq 'text';
},
on_close => sub ($ws, $code, $reason) {
$loop->stop;
},
);
$client->connect('wss://example.com/socket');
$loop->run;
Connection API
Established client and server connections share the common
Linux::Event::WebSocket::Connection API. Important operations include:
$ws->send_text($text);
$ws->send_binary($bytes);
$ws->ping($bytes);
$ws->close(code => 'SUCCESS', reason => 'done');
$ws->abort;
$ws->is_open;
$ws->is_closing;
$ws->subprotocol;
$ws->secure;
$ws->url;
$ws->handshake_request;
$ws->handshake_response;
$ws->data;
close() starts the WebSocket close handshake. abort() closes the underlying
transport immediately.
Architecture
Linux::Event::WebSocket deliberately composes the existing ecosystem instead of reimplementing each layer:
Linux::Event
socket, TLS, ordered bytes, buffering, backpressure, lifecycle
|
Linux::Event::HTTP
HTTP/1.1 opening Upgrade and live-stream handoff
|
Linux::Event::WebSocket
WebSocket connection API, RFC 6455 framing, messages, masking,
fragmentation, control semantics, and protocol policy
A successful HTTP Upgrade calls Linux::Event's in-place transition_to() on the
same live connection. The socket, TLS transport, queued output, application
state, and already-read post-HTTP bytes stay attached.
There is no second miniature HTTP parser in this distribution.
Inheritance policy
Connection classes use ordinary single inheritance only:
Linux::Event::WebSocket::Client::Connection
-> Linux::Event::WebSocket::Connection
-> Linux::Event::IO::Sock::Stream
and separately:
Linux::Event::WebSocket::Server::Connection
-> Linux::Event::WebSocket::Connection
-> Linux::Event::IO::Sock::Stream
There are no Perl roles, mixins, multiple-inheritance trees, or method injection in the connection design.
Protocol engine
The production RFC 6455 data engine is native code kept behind private modules.
A small, vendored copy of bq_websocket performs framing, masking,
fragmentation, message assembly, and control-frame processing. A thin XS
adapter connects it to Linux::Event's existing Stream; bq does not own the
socket, TLS, HTTP Upgrade, timers, or event loop.
Inbound and outbound text use Perl's C UTF-8 API from XS to enforce the RFC
3629 boundary without a Perl-level validation pass. Client frame masks use
Linux getrandom(2). The opening HTTP handshake remains entirely owned by
Linux::Event::HTTP and this distribution's handshake policy.
The older private _Frame and _Parser helpers remain useful for tests and
developer benchmarks, but they are not the production data-path parser.
Protocol policy
The wrapper enforces peer-side RFC 6455 rules that should not depend on a generic transport-neutral parser, including:
- clients must mask frames sent to servers;
- servers must not mask frames sent to clients;
- RSV bits are rejected while no extensions are negotiated;
- control frames must be final and no larger than 125 bytes;
- close payload, status-code, and UTF-8 reason validation;
- frame/message size limits before accepting large advertised payloads.
Native-code policy
Measured end-to-end, Unicode, concurrency, and broadcast workloads justified a WebSocket-specific native engine in this distribution. The native code stays here rather than in Linux::Event core because RFC 6455 framing and policy are WebSocket-specific.
The vendored engine is intentionally transport-neutral in this integration: Linux::Event continues to own epoll, sockets, TLS, buffering, backpressure, and lifecycle. No external bq system library is required.
The vendored bq source is based on upstream commit
6c188d3f0edca38d7a8926e0d30f4c145414ba4c and is carried under the MIT
license. Linux::Event-specific fixes are documented in
vendor/bq_websocket/README.md; the full third-party license text is in
vendor/bq_websocket/LICENSE.
See docs/ARCHITECTURE.md for the detailed design, docs/BENCHMARKS.md
for the measurement rationale, and handoff.md for the current development
state.