EV-Websockets

 view release on metacpan or  search on metacpan

lib/EV/Websockets.pm  view on Meta::CPAN

per-connection response headers into the upgrade response. Return a false
value (C<undef>, C<0>, C<"">) to reject the connection (the client receives
a 403).

=head3 connections

Returns a list of Connection objects whose state is C<"connected"> or
C<"closing"> (i.e. the WebSocket handshake completed and the underlying
wsi still exists). Conns still in C<"connecting"> and conns already
C<"closed">/C<"destroyed"> are omitted.

    my @conns = $ctx->connections;
    $_->send("broadcast!") for @conns;

=head3 adopt(%options)

Adopt an existing IO handle (socket).

    my $conn = $ctx->adopt(
        fh               => $socket_handle,
        initial_data     => $already_read_bytes, # optional pre-read data
        max_message_size => 1048576,
        on_connect => sub { my ($conn, $headers) = @_; ... },
        on_message => sub { my ($conn, $data, $is_binary) = @_; ... },
        on_close   => sub { my ($conn, $code, $reason) = @_; ... },
        on_error   => sub { my ($conn, $err) = @_; ... },
        on_pong    => sub { my ($conn, $payload) = @_; ... },
        on_drain   => sub { my ($conn) = @_; ... },
    );

Once adopted, C<libwebsockets> takes ownership of the file descriptor.
The module holds a reference to the Perl handle until the connection is
destroyed, preventing premature fd closure. C<$headers> in C<on_connect>
is always C<undef> for adopted connections.

If you already read data from the socket (e.g., the HTTP upgrade request),
pass it via C<initial_data> so lws can process the handshake.

=head2 EV::Websockets::Connection

Represents a WebSocket connection.

=head3 send($data)

Queue a text frame. Croaks if the connection is not open.

=head3 send_binary($data)

Queue a binary frame. Croaks if the connection is not open.

=head3 send_ping([$payload])

Queue a Ping frame. C<$payload> is optional; if supplied it is silently
truncated to 125 bytes per RFC 6455 §5.5. Croaks if the connection is not
open.

=head3 send_pong([$payload])

Queue a Pong frame. Same payload rules as C<send_ping>. Most peers send Pong
automatically in response to Ping; you only need this to send an unsolicited
Pong (e.g. as a one-way keepalive).

=head3 send_fragment($data, $is_binary = 0, $is_final = 1)

Send one fragment of a streaming message. The first call starts a new
fragmented message (text or binary per C<$is_binary>); subsequent calls send
continuation frames. Set C<$is_final> true on the last fragment.

    $conn->send_fragment("part1", 0, 0);   # text, not final
    $conn->send_fragment("part2", 0, 0);   # continuation, not final
    $conn->send_fragment("part3", 0, 1);   # continuation, final

Use this only if you need to interleave outbound writes with other I/O while
streaming a single message. For ordinary sends, prefer C<send>/C<send_binary>.

=head3 send_queue_size

Returns the number of payload bytes currently queued for sending (excludes
WebSocket framing overhead). Useful for backpressure monitoring; pair with
C<on_drain> to gate further sends.

=head3 stash

Returns a hashref for storing arbitrary per-connection metadata. The hashref
is lazily created on first access and lives until the connection is freed.

    $conn->stash->{user_id} = 42;
    my $uid = $conn->stash->{user_id};

=head3 get_protocol

Returns the negotiated C<Sec-WebSocket-Protocol> value, or C<undef> if no
subprotocol was negotiated or the connection is closed.

=head3 peer_address

Returns the peer's IP address as a printable string (IPv4 dotted-quad or
IPv6 colon notation, no brackets, no port), or C<undef> if unavailable.

=head3 close([$code = 1000], [$reason])

Initiate a clean WebSocket close. Sends a Close frame with C<$code> (default
1000, normal closure) and an optional UTF-8 C<$reason> (truncated by lws to
fit the frame). Pending sends are drained first, then the connection is torn
down and C<on_close> fires.

This is a no-op (does not croak) if the connection is already closed,
closing, or destroyed. It is also a no-op while the connection is still
in the C<"connecting"> state - calling C<close()> before the handshake
completes does not cancel the in-flight connect; use C<connect_timeout>
to bound the handshake instead.

=head3 pause_recv

Stop reading frames from this connection (TCP flow control). New incoming
frames will back up in the kernel's socket buffer until C<resume_recv> is
called. Silently does nothing on a closed or destroyed connection.

=head3 resume_recv

Resume receiving after C<pause_recv>. Silently does nothing on a closed or
destroyed connection.

=head3 is_connected

Returns true while C<state> is C<"connected">.

=head3 is_connecting

Returns true while C<state> is C<"connecting">. Returns false once the
connection is established, closing, closed, or destroyed.

=head3 state

Returns the current state as one of:

=over 4

=item C<"connecting"> - TCP/TLS handshake or HTTP upgrade in progress

=item C<"connected"> - open and ready to send/receive

=item C<"closing"> - C<close()> has been called; pending sends still draining

=item C<"closed"> - the underlying wsi is gone but the Perl object is still alive

=item C<"destroyed"> - the C struct has been freed (further method calls will croak)

=back

=head1 DEBUGGING

    EV::Websockets::_set_debug(1);

Enables verbose debug output from both the module and libwebsockets.
In tests, gate on C<$ENV{EV_WS_DEBUG}>:

    EV::Websockets::_set_debug(1) if $ENV{EV_WS_DEBUG};

=head1 FEERSUM INTEGRATION

Adopt WebSocket connections from a Feersum PSGI server via C<psgix.io>:

    use Feersum;
    use EV::Websockets;

    my $ctx = EV::Websockets::Context->new;
    my $feersum = Feersum->endjinn;
    $feersum->set_psgix_io(1);

    $feersum->psgi_request_handler(sub {
        my $env = shift;
        return [400,[],[]] unless ($env->{HTTP_UPGRADE}//'') =~ /websocket/i;

        my $io = $env->{'psgix.io'};

        # Reconstruct HTTP upgrade for lws
        my $path = $env->{REQUEST_URI} // '/';
        my $hdr = "GET $path HTTP/1.1\r\n";
        for (sort keys %$env) {
            next unless /^HTTP_(.+)/;
            (my $h=$1) =~ s/_/-/g;
            $hdr .= "$h: $env->{$_}\r\n";
        }
        $hdr .= "\r\n";

        $ctx->adopt(fh => $io, initial_data => $hdr,
            on_message => sub { $_[0]->send($_[1]) },  # echo
        );
        return;
    });

See also C<eg/feersum_native.pl> and C<eg/feersum_psgi.pl> for full examples.

=head1 BENCHMARKS

The C<bench/> directory contains latency and throughput benchmarks.

    # Echo round-trip latency (native client + native server)
    perl bench/latency.pl

    # Throughput (messages/sec)
    perl bench/throughput.pl

    # Comparison with AnyEvent::WebSocket and Net::WebSocket::EVx



( run in 1.134 second using v1.01-cache-2.11-cpan-98e64b0badf )