Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
allow WebSocket constants to be imported
  • Loading branch information
kraih committed Jan 10, 2016
1 parent f7a76a9 commit 3614b9d
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 53 deletions.
47 changes: 15 additions & 32 deletions lib/Mojo/Transaction/WebSocket.pm
Expand Up @@ -7,19 +7,8 @@ use List::Util 'first';
use Mojo::JSON qw(encode_json j);
use Mojo::Transaction::HTTP;
use Mojo::Util qw(decode deprecated encode trim);
use Mojo::WebSocket;

use constant DEBUG => $ENV{MOJO_WEBSOCKET_DEBUG} || 0;

# Opcodes
use constant {
CONTINUATION => 0x0,
TEXT => 0x1,
BINARY => 0x2,
CLOSE => 0x8,
PING => 0x9,
PONG => 0xa
};
use Mojo::WebSocket
qw(WS_BINARY WS_CLOSE WS_CONTINUATION WS_PING WS_PONG WS_TEXT);

has [qw(compressed masked)];
has handshake => sub { Mojo::Transaction::HTTP->new };
Expand All @@ -41,8 +30,8 @@ sub build_message {
$frame->{text} = encode_json($frame->{json}) if exists $frame->{json};

# Raw text or binary
if (exists $frame->{text}) { $frame = [1, 0, 0, 0, TEXT, $frame->{text}] }
else { $frame = [1, 0, 0, 0, BINARY, $frame->{binary}] }
if (exists $frame->{text}) { $frame = [1, 0, 0, 0, WS_TEXT, $frame->{text}] }
else { $frame = [1, 0, 0, 0, WS_BINARY, $frame->{binary}] }

# "permessage-deflate" extension
return Mojo::WebSocket::build_frame($self->masked, @$frame)
Expand Down Expand Up @@ -70,7 +59,7 @@ sub finish {
my $payload = $close->[0] ? pack('n', $close->[0]) : '';
$payload .= encode 'UTF-8', $close->[1] if defined $close->[1];
$close->[0] //= 1005;
$self->send([1, 0, 0, 0, CLOSE, $payload])->{finished} = 1;
$self->send([1, 0, 0, 0, WS_CLOSE, $payload])->{finished} = 1;

return $self;
}
Expand Down Expand Up @@ -173,14 +162,14 @@ sub _message {
my ($self, $frame) = @_;

# Assume continuation
my $op = $frame->[4] || CONTINUATION;
my $op = $frame->[4] || WS_CONTINUATION;

# Ping/Pong
return $self->send([1, 0, 0, 0, PONG, $frame->[5]]) if $op == PING;
return if $op == PONG;
return $self->send([1, 0, 0, 0, WS_PONG, $frame->[5]]) if $op == WS_PING;
return if $op == WS_PONG;

# Close
if ($op == CLOSE) {
if ($op == WS_CLOSE) {
return $self->finish unless length $frame->[5] >= 2;
return $self->finish(unpack('n', substr($frame->[5], 0, 2, '')),
decode('UTF-8', $frame->[5]));
Expand Down Expand Up @@ -210,8 +199,8 @@ sub _message {

$self->emit(json => j($msg)) if $self->has_subscribers('json');
$op = delete $self->{op};
$self->emit($op == TEXT ? 'text' : 'binary' => $msg);
$self->emit(message => $op == TEXT ? decode 'UTF-8', $msg : $msg)
$self->emit($op == WS_TEXT ? 'text' : 'binary' => $msg);
$self->emit(message => $op == WS_TEXT ? decode 'UTF-8', $msg : $msg)
if $self->has_subscribers('message');
}

Expand Down Expand Up @@ -466,8 +455,8 @@ Local interface port.
my $ws = Mojo::Transaction::WebSocket->new({compressed => 1});
Construct a new L<Mojo::Transaction::WebSocket> object and subscribe to
L</"frame"> event with default message parser, which also handles C<PING> and
C<CLOSE> frames automatically.
L</"frame"> event with default message parser, which also handles C<Ping> and
C<Close> frames automatically.
=head2 protocol
Expand Down Expand Up @@ -518,7 +507,8 @@ Send message or frame non-blocking via WebSocket, the optional drain callback
will be invoked once all data has been written.
# Send "Ping" frame
$ws->send([1, 0, 0, 0, 9, 'Hello World!']);
use Mojo::WebSocket 'WS_PING';
$ws->send([1, 0, 0, 0, WS_PING, 'Hello World!']);
=head2 server_close
Expand Down Expand Up @@ -560,13 +550,6 @@ Negotiate C<permessage-deflate> extension for this WebSocket connection.
Negotiate subprotocol for this WebSocket connection.
=head1 DEBUGGING
You can set the C<MOJO_WEBSOCKET_DEBUG> environment variable to get some
advanced diagnostics information printed to C<STDERR>.
MOJO_WEBSOCKET_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicious.org>.
Expand Down
63 changes: 52 additions & 11 deletions lib/Mojo/WebSocket.pm
Expand Up @@ -14,8 +14,20 @@ use constant GUID => '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
use constant MODERN =>
(($Config{use64bitint} // '') eq 'define' || $Config{longsize} >= 8);

our @EXPORT_OK
= qw(build_frame challenge client_handshake parse_frame server_handshake);
# Opcodes
use constant {
WS_CONTINUATION => 0x0,
WS_TEXT => 0x1,
WS_BINARY => 0x2,
WS_CLOSE => 0x8,
WS_PING => 0x9,
WS_PONG => 0xa
};

our @EXPORT_OK = (
qw(WS_BINARY WS_CLOSE WS_CONTINUATION WS_PING WS_PONG WS_TEXT),
qw(build_frame challenge client_handshake parse_frame server_handshake)
);

sub build_frame {
my ($masked, $fin, $rsv1, $rsv2, $rsv3, $op, $payload) = @_;
Expand Down Expand Up @@ -163,9 +175,9 @@ Mojo::WebSocket - The WebSocket Protocol
=head1 SYNOPSIS
use Mojo::WebSocket qw(build_frame parse_frame);
use Mojo::WebSocket qw(WS_TEXT build_frame parse_frame);
my $bytes = build_frame 0, 1, 0, 0, 0, 2, 'Hello World!';
my $bytes = build_frame 0, 1, 0, 0, 0, WS_TEXT, 'Hello World!';
my $frame = parse_frame \$bytes, 262144;
=head1 DESCRIPTION
Expand All @@ -184,23 +196,23 @@ individually.
Build WebSocket frame.
# Binary frame with FIN bit and payload
say build_frame 0, 1, 0, 0, 0, 2, 'Hello World!';
# Maked binary frame with FIN bit and payload
say build_frame 1, 1, 0, 0, 0, WS_BINARY, 'Hello World!';
# Text frame with payload but without FIN bit
say build_frame 0, 0, 0, 0, 0, 1, 'Hello ';
say build_frame 0, 0, 0, 0, 0, WS_TEXT, 'Hello ';
# Continuation frame with FIN bit and payload
say build_frame 0, 1, 0, 0, 0, 0, 'World!';
say build_frame 0, 1, 0, 0, 0, WS_CONTINUATION, 'World!';
# Close frame with FIN bit and without payload
say build_frame 0, 1, 0, 0, 0, 8, '';
say build_frame 0, 1, 0, 0, 0, WS_CLOSE, '';
# Ping frame with FIN bit and payload
say build_frame 0, 1, 0, 0, 0, 9, 'Test 123';
say build_frame 0, 1, 0, 0, 0, WS_PING, 'Test 123';
# Pong frame with FIN bit and payload
say build_frame 0, 1, 0, 0, 0, 10, 'Test 123';
say build_frame 0, 1, 0, 0, 0, WS_PONG, 'Test 123';
=head2 challenge
Expand Down Expand Up @@ -235,6 +247,35 @@ Parse WebSocket frame.
Perform WebSocket handshake server-side.
=head1 CONSTANTS
L<Mojo::WebSocket> implements the following constants, which can be imported
individually.
=head2 WS_BINARY
Opcode for C<Binary> frames.
=head2 WS_CLOSE
Opcode for C<Close> frames.
=head2 WS_CONTINUATION
Opcode for C<Continuation> frames.
=head2 WS_PING
Opcode for C<Ping> frames.
=head2 WS_PONG
Opcode for C<Pong> frames.
=head2 WS_TEXT
Opcode for C<Text> frames.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicious.org>.
Expand Down
3 changes: 2 additions & 1 deletion lib/Mojolicious/Controller.pm
Expand Up @@ -810,7 +810,8 @@ establish the WebSocket connection.
$c->send({binary => encode_json({test => 'I ♥ Mojolicious!'})});
# Send "Ping" frame
$c->send([1, 0, 0, 0, 9, 'Hello World!']);
use Mojo::WebSocket 'WS_PING';
$c->send([1, 0, 0, 0, WS_PING, 'Hello World!']);
# Make sure previous message has been written before continuing
$c->send('First message!' => sub {
Expand Down
44 changes: 35 additions & 9 deletions t/mojo/websocket_frames.t
Expand Up @@ -2,10 +2,12 @@ use Mojo::Base -strict;

use Test::More;
use Mojo::Transaction::WebSocket;
use Mojo::WebSocket qw(build_frame parse_frame);
use Mojo::WebSocket
qw(WS_BINARY WS_CLOSE WS_CONTINUATION WS_PING WS_PONG WS_TEXT),
qw(build_frame parse_frame);

# Simple text frame roundtrip
my $bytes = build_frame 0, 1, 0, 0, 0, 1, 'whatever';
my $bytes = build_frame 0, 1, 0, 0, 0, WS_TEXT, 'whatever';
is $bytes, "\x81\x08\x77\x68\x61\x74\x65\x76\x65\x72", 'right frame';
my $frame = parse_frame \(my $dummy = $bytes), 262144;
is $frame->[0], 1, 'fin flag is set';
Expand All @@ -16,6 +18,30 @@ is $frame->[4], 1, 'text frame';
is $frame->[5], 'whatever', 'right payload';
is build_frame(0, 1, 0, 0, 0, 1, 'whatever'), $bytes, 'frames are equal';

# Simple ping frame roundtrip
$bytes = build_frame 0, 1, 0, 0, 0, WS_PING, 'whatever';
is $bytes, "\x89\x08\x77\x68\x61\x74\x65\x76\x65\x72", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1, 'fin flag is set';
is $frame->[1], 0, 'rsv1 flag is not set';
is $frame->[2], 0, 'rsv2 flag is not set';
is $frame->[3], 0, 'rsv3 flag is not set';
is $frame->[4], 9, 'ping frame';
is $frame->[5], 'whatever', 'right payload';
is build_frame(0, 1, 0, 0, 0, 9, 'whatever'), $bytes, 'frames are equal';

# Simple pong frame roundtrip
$bytes = build_frame 0, 1, 0, 0, 0, WS_PONG, 'whatever';
is $bytes, "\x8a\x08\x77\x68\x61\x74\x65\x76\x65\x72", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1, 'fin flag is set';
is $frame->[1], 0, 'rsv1 flag is not set';
is $frame->[2], 0, 'rsv2 flag is not set';
is $frame->[3], 0, 'rsv3 flag is not set';
is $frame->[4], 10, 'pong frame';
is $frame->[5], 'whatever', 'right payload';
is build_frame(0, 1, 0, 0, 0, 10, 'whatever'), $bytes, 'frames are equal';

# Simple text frame roundtrip with all flags set
$bytes = build_frame 0, 1, 1, 1, 1, 1, 'whatever';
is $bytes, "\xf1\x08\x77\x68\x61\x74\x65\x76\x65\x72", 'right frame';
Expand Down Expand Up @@ -52,17 +78,17 @@ is $frame->[4], 1, 'text frame';
is $frame->[5], 'whatever', 'right payload';
is build_frame(0, 1, 1, 0, 0, 1, 'whatever'), $bytes, 'frames are equal';

# Simple text frame roundtrip with RSV2 flags set
$bytes = build_frame(0, 1, 0, 1, 0, 1, 'whatever');
is $bytes, "\xa1\x08\x77\x68\x61\x74\x65\x76\x65\x72", 'right frame';
# Simple continuation frame roundtrip with RSV2 flags set
$bytes = build_frame(0, 1, 0, 1, 0, WS_CONTINUATION, 'whatever');
is $bytes, "\xa0\x08\x77\x68\x61\x74\x65\x76\x65\x72", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1, 'fin flag is set';
is $frame->[1], 0, 'rsv1 flag is not set';
is $frame->[2], 1, 'rsv2 flag is set';
is $frame->[3], 0, 'rsv3 flag is not set';
is $frame->[4], 1, 'text frame';
is $frame->[4], 0, 'continuation frame';
is $frame->[5], 'whatever', 'right payload';
is build_frame(0, 1, 0, 1, 0, 1, 'whatever'), $bytes, 'frames are equal';
is build_frame(0, 1, 0, 1, 0, 0, 'whatever'), $bytes, 'frames are equal';

# Simple text frame roundtrip with RSV3 flags set
$bytes = build_frame(0, 1, 0, 0, 1, 1, 'whatever');
Expand All @@ -77,7 +103,7 @@ is $frame->[5], 'whatever', 'right payload';
is build_frame(0, 1, 0, 0, 1, 1, 'whatever'), $bytes, 'frames are equal';

# Simple binary frame roundtrip
$bytes = build_frame(0, 1, 0, 0, 0, 2, 'works');
$bytes = build_frame(0, 1, 0, 0, 0, WS_BINARY, 'works');
is $bytes, "\x82\x05\x77\x6f\x72\x6b\x73", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1, 'fin flag is set';
Expand Down Expand Up @@ -174,7 +200,7 @@ is $frame->[5], '', 'no payload';
is build_frame(0, 1, 0, 0, 0, 1, ''), $bytes, 'frames are equal';

# Empty close frame roundtrip
$bytes = build_frame(0, 1, 0, 0, 0, 8, '');
$bytes = build_frame(0, 1, 0, 0, 0, WS_CLOSE, '');
is $bytes, "\x88\x00", 'right frame';
$frame = parse_frame \($dummy = $bytes), 262144;
is $frame->[0], 1, 'fin flag is set';
Expand Down

0 comments on commit 3614b9d

Please sign in to comment.