networking advanced

How to create WebSocket client in Perl?

Question

How to create WebSocket client in Perl?

Creating a WebSocket client in Perl requires establishing a TCP connection, performing a proper handshake, and sending/receiving frames according to the WebSocket protocol RFC. The example below demonstrates a minimal synchronous WebSocket client using only core modules IO::Socket::INET, MIME::Base64, and Digest::SHA. It handles a simple text message exchange with a public echo server.

Important corrections for sandboxed usage:

  • Due to sandbox restrictions, network access is disabled, so the original example connecting to an external echo server can't run here.
  • To demonstrate runnable code without networking, this example simulates the handshake and frame construction/parsing logic locally.
  • The core concepts of WebSocket frame masking, processing, and handshake generation are shown.
  • For actual WebSocket client usage, you would remove the simulation and connect to a real server.

Perl-Specific Concepts

  • sigils: Scalars use $, arrays @, hashes %.
  • Context: Reading from sockets (read) and pattern matching behave differently in scalar vs list context.
  • TMTOWTDI: Perl lets you manipulate binary data easily with pack and unpack, helpful for framing WebSocket messages.

Common Gotchas

  • Client-to-server frames must be masked, masking keys are XORed with the payload bytes.
  • Server-to-client frames are not masked.
  • Handshake requires correct generation and verification of Sec-WebSocket-Key and Sec-WebSocket-Accept headers.
  • This example handles only payloads smaller than 126 bytes (no extended length frames).

Runnable Perl Example: WebSocket Frame Masking Demo


use strict;
use warnings;
use MIME::Base64;
use Digest::SHA qw(sha1);
use feature 'say';

# Simulate generating a Sec-WebSocket-Key and corresponding Accept header
sub generate_handshake_keys {
    my $random_bytes = join '', map { chr(int(rand(256))) } 1..16;
    my $ws_key = encode_base64($random_bytes, '');
    my $accept = encode_base64(sha1($ws_key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"), '');
    $accept =~ s/\s//g;
    return ($ws_key, $accept);
}

# Mask or unmask a payload (same operation)
sub mask_payload {
    my ($payload, $mask_ref) = @_;
    my $masked = '';
    for my $i (0 .. length($payload)-1) {
        $masked .= chr(ord(substr($payload, $i, 1)) ^ $mask_ref->[$i % 4]);
    }
    return $masked;
}

# Build a masked client-to-server WebSocket frame (text)
sub build_ws_frame {
    my ($payload) = @_;
    die "Payload too large\n" if length($payload) > 125;

    my @mask = map { int(rand(256)) } 1..4;
    my $masked_payload = mask_payload($payload, \@mask);

    # FIN=1, opcode=1 (text)
    my $frame = chr(0x81);
    # MASK=1 + payload length
    $frame .= chr(0x80 | length($payload));
    $frame .= pack('C4', @mask);
    $frame .= $masked_payload;
    return $frame;
}

# Parse a server-to-client WebSocket frame (unmasked)
sub parse_ws_frame {
    my ($frame) = @_;
    my ($fin_opcode, $len_mask) = unpack('CC', substr($frame, 0, 2));
    my $masked = ($len_mask & 0x80) != 0;
    my $payload_len = $len_mask & 0x7f;
    die "Masked frames from server not supported\n" if $masked;
    my $payload = substr($frame, 2, $payload_len);
    return $payload;
}

sub demo {
    say "Generating handshake keys...";
    my ($client_key, $expected_accept) = generate_handshake_keys();
    say "Sec-WebSocket-Key: $client_key";
    say "Expected Sec-WebSocket-Accept: $expected_accept";

    my $msg = "Hello WebSocket";
    say "Original message: $msg";

    my $frame = build_ws_frame($msg);
    say "Constructed masked WebSocket frame (hex): " . unpack('H*', $frame);

    # Simulate server echoes back unmasked message frame (no mask, just frame)
    my $server_frame = chr(0x81) . chr(length($msg)) . $msg;
    say "Simulated server frame (hex): " . unpack('H*', $server_frame);

    my $payload = parse_ws_frame($server_frame);
    say "Parsed server payload: $payload";
}

demo();

Summary

This example explains the core data transformations involved in a WebSocket client: handshake key generation/validation and frame construction with masking. Due to sandbox restrictions, actual network connections are not possible here, but the code sections that must run locally are demonstrated and ready to extend when networking is allowed.

If you want a real WebSocket client in Perl with networking, you would use the first example (provided earlier) and run outside a sandbox. Alternatively, CPAN modules like AnyEvent::WebSocket::Client or Protocol::WebSocket provide robust solutions.

Verified Code

Executed in a sandbox to capture real output. • v5.34.1 • 19ms

Tip: edit code and use “Run (Browser)”. Server runs always execute the published, verified snippet.
STDOUT
Generating handshake keys...
Sec-WebSocket-Key: 4MLcqdwnzta9Hc5VqK3i4A==
Expected Sec-WebSocket-Accept: Rrn0jConFJN9j7Y9ZQKcMq6gwM4=
Original message: Hello WebSocket
Constructed masked WebSocket frame (hex): 818f963a65cade5f09a6f91a32aff4690aa9fd5f11
Simulated server frame (hex): 810f48656c6c6f20576562536f636b6574
Parsed server payload: Hello WebSocket
STDERR
(empty)

Was this helpful?

Related Questions