Linux::Event::WebSocket
High-performance WebSocket client and server for Linux::Event, with a simple callback-first API.
Status
Prepared version: 0.003.
The distribution provides working ws:// and wss:// client/server paths,
a native RFC 6455 data engine, and a production test suite. The 0.003 release
replaces the old HTTP internals with the published, event-loop-neutral
Unblock::HTTP1 engine for the opening handshake.
The GitHub Actions test matrix targets Perl 5.36 and Perl 5.44.0.
See 0.003 release notes for the coordinated WebSocket/HTTP upgrade order.
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 Linux::Event listener tls options with 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.
Choosing a WebSocket API
Use Linux::Event::WebSocket::Server or Linux::Event::WebSocket::Client
when your application is primarily WebSocket-based. The examples above are
complete: no HTTP application handler or framework adapter is required.
If you already serve ordinary HTTP traffic with Linux::Event::HTTP, use
its on_websocket or connect_websocket convenience instead. That is an
HTTP application using WebSocket, not a second required WebSocket setup.
Neither approach requires you to configure a protocol parser or framing
engine. The existing Linux::Event::WebSocket callbacks remain unchanged.
Version 0.003 can coexist with Linux::Event::HTTP 0.004, but no longer depends on the HTTP distribution: the opening handshake belongs to Unblock::HTTP1. The native WebSocket engine remains private to this distribution.
Architecture
Linux::Event::WebSocket deliberately composes the existing ecosystem instead of reimplementing each layer:
Linux::Event
socket, TLS, ordered bytes, buffering, backpressure, lifecycle
|
Unblock::HTTP1
HTTP/1.1 opening Upgrade
|
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 uses Unblock::HTTP1 and this distribution's
WebSocket-specific validation and 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.