NAME

PAGI::Endpoint::WebSocket - Class-based WebSocket endpoint handler

SYNOPSIS

package MyApp::Chat;
use parent 'PAGI::Endpoint::WebSocket';
use Future::AsyncAwait;

sub encoding { 'json' }  # or 'text', 'bytes'
sub ping_interval { 25 } # Send ping every 25 seconds

async sub on_connect {
    my ($self, $ws) = @_;
    await $ws->accept;
    await $ws->send_json({ type => 'welcome' });
}

async sub on_receive {
    my ($self, $ws, $data) = @_;
    # $data is auto-decoded from JSON (per encoding above)
    # For sending, explicitly choose: send_json, send_text, send_bytes
    await $ws->send_json({ type => 'echo', message => $data });
}

sub on_disconnect {
    my ($self, $ws, $code) = @_;
    cleanup_user($ws->stash->{user_id});
}

# Use with PAGI server
my $app = MyApp::Chat->to_app;

DESCRIPTION

PAGI::Endpoint::WebSocket provides a Starlette-inspired class-based approach to handling WebSocket connections with lifecycle hooks.

Connection Keepalive

WebSocket connections may be closed by proxies, load balancers, or NAT devices after periods of inactivity (typically 30-60 seconds). To prevent this, enable server-side ping by overriding ping_interval:

sub ping_interval { 25 }  # Send ping every 25 seconds

The server sends JSON messages { type => 'ping', ts => <timestamp> } at the specified interval. Clients can optionally respond with { type => 'pong' } to confirm receipt, though this is not required for keepalive purposes.

LIFECYCLE METHODS

on_connect

async sub on_connect {
    my ($self, $ws) = @_;
    await $ws->accept;
}

Called when a client connects. You should call $ws->accept to accept the connection. If not defined, connection is auto-accepted.

on_receive

async sub on_receive {
    my ($self, $ws, $data) = @_;
    await $ws->send_text("Got: $data");
}

Called for each message received. The $data format depends on the encoding() setting.

on_disconnect

sub on_disconnect {
    my ($self, $ws, $code, $reason) = @_;
    # Cleanup
}

Called when connection closes. This is synchronous (not async).

CLASS METHODS

encoding

sub encoding { 'json' }  # 'text', 'bytes', or 'json'

Controls how incoming messages are decoded before being passed to on_receive. This does not affect outgoing messages - you always explicitly choose the send method (send_json, send_text, send_bytes).

text - Messages passed as strings (default)
bytes - Messages passed as raw bytes
json - Messages automatically decoded from JSON to Perl data structures

Example - JSON encoding:

package MyEndpoint;
use parent 'PAGI::Endpoint::WebSocket';

sub encoding { 'json' }  # Incoming messages auto-decoded from JSON

async sub on_receive {
    my ($self, $ws, $data) = @_;
    # $data is already a Perl hashref/arrayref (decoded from JSON)
    my $name = $data->{name};

    # For sending, you still explicitly choose the method:
    await $ws->send_json({ greeting => "Hello, $name" });
    await $ws->send_text("Raw text message");
}

Example - Text encoding:

sub encoding { 'text' }  # Incoming messages as raw strings

async sub on_receive {
    my ($self, $ws, $text) = @_;
    # $text is a plain string, decode JSON yourself if needed
    my $data = JSON::MaybeXS::decode_json($text);
    await $ws->send_text("Echo: $text");
}

This follows the same pattern as Starlette's WebSocketEndpoint.

ping_interval

sub ping_interval { 25 }  # seconds, 0 = disabled (default)

Seconds between server-initiated ping messages. Set to a positive value to enable keepalive pings that prevent proxy/NAT timeouts on idle connections.

When enabled, the server sends JSON messages at the specified interval:

{ "type": "ping", "ts": 1703275200 }

Common values:

25 - Safe for most proxies (30s timeout common)
55 - Safe for aggressive proxies (60s timeout)
0 - Disabled (default) - connection may timeout if idle

Note: Requires the PAGI event loop to be available in the scope (automatically provided by PAGI::Server).

websocket_class

sub websocket_class { 'PAGI::WebSocket' }

Override to use a custom WebSocket wrapper.

to_app

my $app = MyEndpoint->to_app;

Returns a PAGI-compatible async coderef.

SEE ALSO

PAGI::WebSocket, PAGI::Endpoint::HTTP, PAGI::Endpoint::SSE