NAME EV::Websockets - WebSocket client/server using libwebsockets and EV SYNOPSIS use EV; use EV::Websockets; my $ctx = EV::Websockets::Context->new(loop => EV::default_loop); my $conn = $ctx->connect( url => 'ws://example.com/ws', on_connect => sub { my ($conn) = @_; $conn->send("Hello, WebSocket!"); }, on_message => sub { my ($conn, $data) = @_; print "Got: $data\n"; }, on_close => sub { my ($conn, $code, $reason) = @_; print "Closed: $code " . ($reason // "") . "\n"; }, on_error => sub { my ($conn, $err) = @_; print "Error: $err\n"; }, ); EV::run; DESCRIPTION EV::Websockets provides WebSocket client and server functionality using the libwebsockets C library integrated with the EV event loop. This module uses libwebsockets' foreign loop integration to run within an existing EV event loop, making it suitable for applications already using EV. Important: a context with no active listeners or connections may spin an internal idle watcher, preventing other EV watchers (timers, I/O) from firing. Always create a listener ("$ctx->listen(...)") or connection ("$ctx->connect(...)") before entering "EV::run", or destroy the context when not in use. CLASSES EV::Websockets::Context Manages the libwebsockets context and event loop integration. new(%options) Create a new context. my $ctx = EV::Websockets::Context->new( loop => EV::default_loop, # optional, defaults to EV::default_loop ssl_cert => 'client.pem', # optional, for mTLS client certificates ssl_key => 'client-key.pem', # required if ssl_cert is set ssl_ca => 'ca.pem', # optional CA chain proxy => '192.168.1.1', # optional HTTP proxy host proxy_port => 8080, # optional proxy port (default: 1080) ); If "proxy" is not specified, the module reads "https_proxy", "http_proxy", or "all_proxy" from the environment. Pass "proxy => """ to suppress auto-detection. connect(%options) Create a new WebSocket connection. my $conn = $ctx->connect( url => 'wss://example.com/ws', protocol => 'chat', # optional subprotocol headers => { Authorization => 'Bearer token' }, ssl_verify => 1, # 0 to disable TLS verification max_message_size => 1048576, # optional, 0 = unlimited connect_timeout => 5.0, # optional, seconds on_connect => sub { my ($conn, $headers) = @_; ... }, on_message => sub { my ($conn, $data, $is_binary, $is_final) = @_; ... }, on_close => sub { my ($conn, $code, $reason) = @_; ... }, on_error => sub { my ($conn, $err) = @_; ... }, on_pong => sub { my ($conn, $payload) = @_; ... }, on_drain => sub { my ($conn) = @_; ... }, ); Returns an EV::Websockets::Connection object. $is_final is always 1 (messages are fully reassembled before delivery). "connect_timeout" sets a deadline (in seconds) for the WebSocket handshake. If the connection is not established within this time, "on_error" fires with "connect timeout" and the connection is closed. $headers in "on_connect" is a hashref of response headers from the server (Set-Cookie, Content-Type, Server, Sec-WebSocket-Protocol, and when available Location, WWW-Authenticate). "on_drain" fires when the send queue becomes empty after a write. listen(%options) Create a WebSocket listener. Returns the port number being listened on (useful if port 0 was requested). my $port = $ctx->listen( port => 0, # 0 to let OS pick a port name => 'server', # optional vhost name (default: 'server') protocol => 'chat', # optional WebSocket subprotocol ssl_cert => 'cert.pem', # optional, enables TLS ssl_key => 'key.pem', # required if ssl_cert is set ssl_ca => 'ca.pem', # optional CA chain max_message_size => 1048576, # optional, 0 = unlimited headers => { 'Set-Cookie' => 'session=abc123' }, # response headers on_connect => sub { my ($conn, $headers) = @_; ... }, on_message => sub { my ($conn, $data, $is_binary, $is_final) = @_; ... }, on_close => sub { my ($conn, $code, $reason) = @_; ... }, on_error => sub { my ($conn, $err) = @_; ... }, on_pong => sub { my ($conn, $payload) = @_; ... }, on_drain => sub { my ($conn) = @_; ... }, ); "protocol" sets the WebSocket subprotocol name advertised by the server vhost. The vhost name "default" is reserved and will croak if used. $headers in "on_connect" is a hashref of client request headers (Path, Host, Origin, Cookie, Authorization, Sec-WebSocket-Protocol, User-Agent, X-Forwarded-For). "Path" is the request URI (e.g., "/chat"). "headers" is an optional hashref of headers to inject into the HTTP upgrade response (e.g., "Set-Cookie"). connections Returns a list of all currently connected Connection objects. my @conns = $ctx->connections; $_->send("broadcast!") for @conns; 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, $is_final) = @_; ... }, 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, "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. $headers in "on_connect" is always "undef" for adopted connections. If you already read data from the socket (e.g., the HTTP upgrade request), pass it via "initial_data" so lws can process the handshake. EV::Websockets::Connection Represents a WebSocket connection. send($data) Send text data over the connection. send_binary($data) Send binary data over the connection. send_ping($data) Send a Ping frame. Payload is silently truncated to 125 bytes per RFC 6455. send_pong($data) Send a Pong frame. Payload is silently truncated to 125 bytes per RFC 6455. send_queue_size Returns the number of payload bytes currently queued for sending. get_protocol Returns the negotiated "Sec-WebSocket-Protocol" value, or "undef". peer_address Returns the peer IP address as a string, or "undef". close($code, $reason) Close the connection with the given status code and reason. pause_recv Stop receiving data from this connection (flow control). resume_recv Resume receiving data after "pause_recv". is_connected Returns true if the connection is established and open. is_connecting Returns true if the connection is in progress. state Returns the current state as a string: "connecting", "connected", "closing", "closed", or "destroyed". DEBUGGING EV::Websockets::_set_debug(1); Enables verbose debug output from both the module and libwebsockets. In tests, gate on $ENV{EV_WS_DEBUG}: EV::Websockets::_set_debug(1) if $ENV{EV_WS_DEBUG}; FEERSUM INTEGRATION Adopt WebSocket connections from a Feersum PSGI server via "psgix.io": use Feersum; use EV::Websockets; my $ctx = EV::Websockets::Context->new; # A listener vhost is required for adopt() to work $ctx->listen(port => 0, on_connect => sub {}, on_message => sub {}); 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"; # adopt() holds a ref to $io, preventing Feersum from closing the fd $ctx->adopt(fh => $io, initial_data => $hdr, on_message => sub { $_[0]->send($_[1]) }, # echo ); return; }); See also "eg/feersum_native.pl" and "eg/feersum_psgi.pl" for full examples. BENCHMARKS The "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 perl bench/compare.pl Typical results on Linux (localhost, 1000 round-trips, 64-byte payload): EV::Websockets ~10us avg, ~97k msg/s (C/libwebsockets) Net::WebSocket::EVx ~10us avg, ~96k msg/s (C/wslay) Mojolicious ~83us avg, ~12k msg/s (Pure Perl) Net::Async::WebSocket ~141us avg, ~7k msg/s (Pure Perl) URL FORMATS The module supports both "ws://" and "wss://" (TLS) URLs. SEE ALSO EV, Alien::libwebsockets, libwebsockets , Net::WebSocket::EVx, AnyEvent::WebSocket::Client AUTHOR vividsnow LICENSE This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.