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

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:

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.