NAME

Unblock::WebSocket::Handshake - WebSocket handshake helpers using Uniform::HTTP

SYNOPSIS

The opening handshake happens over HTTP, before any WebSocket messages.

Client:

use Unblock::WebSocket::Handshake;
use Unblock::WebSocket::Client;

my ($handshake, $request) =
    Unblock::WebSocket::Handshake->client_request(
        'wss://example.com/chat',
        http_version => '1.1',
    );

# Your HTTP client sends $request and receives $response.
$handshake->validate_client_response($response);

my $ws = Unblock::WebSocket::Client->new(
    %{ $handshake->connection_options },
    on_message => sub { ... },
);

Server:

use Unblock::WebSocket::Handshake;
use Unblock::WebSocket::Server;

my $handshake =
    Unblock::WebSocket::Handshake->server_accept($request);
my $response = $handshake->server_response;

# Your HTTP server sends $response and switches the connection.
my $ws = Unblock::WebSocket::Server->new(
    %{ $handshake->connection_options },
    on_message => sub { ... },
);

These are the WebSocket-specific steps, not a complete HTTP client or server. See examples/handshake-memory.pl for an executable in-memory handshake and message exchange.

DESCRIPTION

This module owns WebSocket-specific HTTP handshake rules.

It does not send HTTP and it does not own the HTTP connection. Instead it creates and validates Uniform::HTTP request and response objects.

An invalid handshake throws an exception. The surrounding HTTP application decides how to send an HTTP error response or close the connection.

Three opening handshake forms are supported:

  • HTTP/1.1 RFC 6455 Upgrade

  • HTTP/2 Extended CONNECT

  • HTTP/3 Extended CONNECT

CLIENT

Create a request:

my ($handshake, $request) =
    Unblock::WebSocket::Handshake->client_request(
        'wss://example.com/chat',
        http_version => '1.1',
    );

Send $request through the HTTP implementation of your choice. The default HTTP version is 1.1. The URL scheme must be ws or wss; the helper does not open the destination.

After receiving the HTTP response:

$handshake->validate_client_response($response);

Then create the established protocol engine:

my $ws = Unblock::WebSocket::Client->new(
    %{ $handshake->connection_options },
    on_message => sub {
        my ($ws, $payload, $type) = @_;
    },
);

For HTTP/2 or HTTP/3, use http_version => '2' or http_version => '3'. The generated request uses Extended CONNECT with :protocol = websocket.

Subprotocols

Offer subprotocols with:

subprotocols => [ 'chat', 'superchat' ]

After response validation:

my $selected = $handshake->subprotocol;

permessage-deflate

Offer RFC 7692 compression with:

permessage_deflate => 1

Or provide negotiation preferences:

permessage_deflate => {
    server_no_context_takeover => 1,
    client_no_context_takeover => 1,
    server_max_window_bits     => 12,
    client_max_window_bits     => undef,
}

A valueless client_max_window_bits offer is represented by undef.

After response validation, connection_options() contains the compression parameters that should be passed to the established Client object.

SERVER

Validate an incoming Uniform request:

my $handshake =
    Unblock::WebSocket::Handshake->server_accept(
        $request,
        subprotocols       => [ 'chat' ],
        permessage_deflate => 1,
    );

Build the HTTP response:

my $response = $handshake->server_response;

Send that response through your HTTP implementation. For HTTP/1.1, the accepted response has status 101 and the entire connection changes from HTTP to WebSocket. For HTTP/2 and HTTP/3, the accepted response is 200 and only that streaming transaction becomes WebSocket. Do not feed HTTP handshake bytes to the established WebSocket engine.

Then create the established server engine:

my $ws = Unblock::WebSocket::Server->new(
    %{ $handshake->connection_options },
    on_message => sub {
        my ($ws, $payload, $type) = @_;
    },
);

The server may supply a compression policy instead of a simple true value:

permessage_deflate => {
    server_no_context_takeover => 1,
    client_max_window_bits     => 12,
}

METHODS

client_request

my ($handshake, $request) =
    Unblock::WebSocket::Handshake->client_request($url, %options);

Creates a client handshake and a canonical Uniform::HTTP request. The http_version option accepts 1.1, 2, or 3. Optional subprotocols is an array reference of names to offer. permessage_deflate offers compression. origin adds the Origin header. headers adds extra name/value header pairs. For HTTP/1.1 testing, key can supply a valid fixed Sec-WebSocket-Key; normal calls generate a random key.

server_accept

my $handshake =
    Unblock::WebSocket::Handshake->server_accept($request, %options);

Validates an incoming WebSocket handshake request. Optional subprotocols lists protocols the server supports; permessage_deflate enables or configures compression. The result records the negotiated values used by server_response() and connection_options().

server_response

my $response = $handshake->server_response;

Returns the successful Uniform::HTTP response for a validated server handshake.

validate_client_response

$handshake->validate_client_response($response);

Validates a server response to a client handshake. Throws on a rejected, malformed, or unexpected HTTP response. Call this before creating a WebSocket::Client object or sending WebSocket messages.

connection_options

my $options = $handshake->connection_options;

Returns a hash reference of negotiated established-connection options.

At present this contains permessage_deflate when RFC 7692 was negotiated.

permessage_deflate

my $config = $handshake->permessage_deflate;

Returns the negotiated RFC 7692 parameters, or undef when compression was not negotiated.

subprotocol

Returns the selected WebSocket subprotocol, or undef.

http_version

Returns 1.1, 2, or 3.

accept_key

my $accept =
    Unblock::WebSocket::Handshake->accept_key($sec_websocket_key);

Calculates the RFC 6455 Sec-WebSocket-Accept value for a valid client key.

FASTPATH

Exact canonical Uniform::HTTP objects may be inspected through Uniform::HTTP::FastPath during handshake validation.

This is only an optimization. Uniform subclasses and adapters continue to use the normal portable Uniform::HTTP methods.

WebSocket-generated handshake objects still use normal validated Uniform constructors.