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

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:

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.