Skip to content

Commit

Permalink
added support for "client_no_context_takeover"
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Nov 28, 2013
1 parent 0a777a0 commit ceb4799
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 27 deletions.
3 changes: 2 additions & 1 deletion Changes
@@ -1,7 +1,8 @@

4.59 2013-11-28
- Added support for permessage-deflate WebSocket compression.
- Added has_compression method to Mojo::Transaction::WebSocket.
- Added compressed and context_takeover attributes to
Mojo::Transaction::WebSocket.
- Relicensed all artwork to CC-SA version 4.0.

4.58 2013-11-19
Expand Down
48 changes: 30 additions & 18 deletions lib/Mojo/Transaction/WebSocket.pm
Expand Up @@ -26,8 +26,9 @@ use constant {
PONG => 10
};

has handshake => sub { Mojo::Transaction::HTTP->new };
has 'masked';
has [qw(compressed masked)];
has context_takeover => 1;
has handshake => sub { Mojo::Transaction::HTTP->new };
has max_websocket_size => sub { $ENV{MOJO_MAX_WEBSOCKET_SIZE} || 262144 };

sub new {
Expand Down Expand Up @@ -87,7 +88,10 @@ sub client_challenge {

# "permessage-deflate" extension
my $headers = $self->res->headers;
$self->_deflate($headers->sec_websocket_extensions);
my $extensions = $headers->sec_websocket_extensions // '';
$self->context_takeover(0)
if $self->_deflate($extensions)
&& $extensions =~ /client_no_context_takeover/i;

return _challenge($self->req->headers->sec_websocket_key) eq
$headers->sec_websocket_accept;
Expand Down Expand Up @@ -125,8 +129,6 @@ sub finish {
return $self;
}

sub has_compression { !!shift->{compress} }

sub is_websocket {1}

sub kept_alive { shift->handshake->kept_alive }
Expand Down Expand Up @@ -223,11 +225,11 @@ sub send {
else { $frame = [1, 0, 0, 0, BINARY, $frame->{binary}] }

# "permessage-deflate" extension
if ($self->has_compression) {
if ($self->compressed) {
$frame->[1] = 1;
my $deflate = $self->{deflate}
||= Compress::Raw::Zlib::Deflate->new(WindowBits => -15,
MemLevel => 8);
|| Compress::Raw::Zlib::Deflate->new(WindowBits => -15, MemLevel => 8);
$self->{deflate} = $deflate if $self->context_takeover;
$deflate->deflate(\$frame->[5], my $out);
$deflate->flush($out, Z_SYNC_FLUSH);
$frame->[5] = substr $out, 0, length($out) - 4;
Expand Down Expand Up @@ -260,7 +262,7 @@ sub server_handshake {

# "permessage-deflate" extension
$res_headers->sec_websocket_extensions('permessage-deflate')
if $self->_deflate($req_headers->sec_websocket_extensions);
if $self->_deflate($req_headers->sec_websocket_extensions // '');
}

sub server_read {
Expand All @@ -287,7 +289,7 @@ sub server_write {

sub _challenge { b64_encode(sha1_bytes(($_[0] || '') . GUID), '') }

sub _deflate { ($_[1] // '') =~ /permessage-deflate/i && ++$_[0]->{compress} }
sub _deflate { $_[1] =~ /permessage-deflate/i && $_[0]->compressed(1) }

sub _message {
my ($self, $frame) = @_;
Expand Down Expand Up @@ -317,9 +319,10 @@ sub _message {

# "permessage-deflate" extension (handshake and RSV1)
my $msg = delete $self->{message};
if ($self->has_compression && $frame->[1]) {
if ($self->compressed && $frame->[1]) {
my $inflate = $self->{inflate}
||= Compress::Raw::Zlib::Inflate->new(WindowBits => -15);
|| Compress::Raw::Zlib::Inflate->new(WindowBits => -15);
$self->{inflate} = $inflate if $self->context_takeover;
$inflate->inflate(\($msg .= "\x00\x00\xff\xff"), my $out);
$msg = $out;
}
Expand Down Expand Up @@ -475,6 +478,21 @@ Emitted when a complete WebSocket text message has been received.
L<Mojo::Transaction::WebSocket> inherits all attributes from
L<Mojo::Transaction> and implements the following new ones.
=head2 compressed
my $bool = $ws->compressed;
$ws = $ws->compressed(1);
Messages will be compressed with C<permessage-deflate> extension.
=head2 context_takeover
my $bool = $ws->context_takeover;
$ws = $ws->context_takeover(0);
Reuse LZ77 sliding window for C<permessage-deflate> extension, defaults to
true.
=head2 handshake
my $handshake = $ws->handshake;
Expand Down Expand Up @@ -574,12 +592,6 @@ Connection identifier or socket.
Close WebSocket connection gracefully.
=head2 has_compression
my $bool = $ws->has_compression;
Check if messages will be compressed with C<permessage-deflate> extension.
=head2 is_websocket
my $true = $ws->is_websocket;
Expand Down
10 changes: 5 additions & 5 deletions t/mojo/websocket.t
Expand Up @@ -456,16 +456,16 @@ like $log, qr/Inactivity timeout\./, 'right log message';
app->log->unsubscribe(message => $msg);

# Ping/pong (with negotiated compression)
my ($pong, $compression, $extensions);
my ($pong, $compressed, $extensions);
$ua->websocket(
'/echo' => sub {
my ($ua, $tx) = @_;
$tx->on(
frame => sub {
my ($tx, $frame) = @_;
$pong = $frame->[5] if $frame->[4] == 10;
$compression = $tx->has_compression;
$extensions = $tx->res->headers->sec_websocket_extensions;
$pong = $frame->[5] if $frame->[4] == 10;
$compressed = $tx->compressed;
$extensions = $tx->res->headers->sec_websocket_extensions;
Mojo::IOLoop->stop;
}
);
Expand All @@ -474,7 +474,7 @@ $ua->websocket(
);
Mojo::IOLoop->start;
is $pong, 'test', 'received pong with payload';
ok $compression, 'WebSocket has compression';
ok $compressed, 'WebSocket has compression';
is $extensions, 'permessage-deflate', 'right "Sec-WebSocket-Extensions" value';

done_testing();
26 changes: 23 additions & 3 deletions t/mojolicious/websocket_lite_app.t
Expand Up @@ -55,6 +55,14 @@ websocket '/unicode' => sub {
);
};

websocket '/no_context_takeover' => sub {
my $self = shift;
$self->tx->context_takeover(0);
$self->res->headers->sec_websocket_extensions(
'permessage-deflate;client_no_context_takeover');
$self->on(binary => sub { shift->send({binary => shift}) });
};

websocket '/bytes' => sub {
my $self = shift;
$self->on(
Expand Down Expand Up @@ -120,8 +128,11 @@ is $t->tx->req->headers->dnt, 1, 'right "DNT" value';
is $t->tx->req->headers->sec_websocket_protocol, 'foo, bar, baz',
'right "Sec-WebSocket-Protocol" value';

# Bytes
$t->websocket_ok('/echo')->send_ok({binary => 'bytes!'})
# Bytes with compression
$t->websocket_ok('/echo');
ok $t->tx->compressed, 'WebSocket has compression';
ok $t->tx->context_takeover, 'with context takeover';
$t->send_ok({binary => 'bytes!'})
->message_ok->message_is({binary => 'bytes!'})
->send_ok({binary => 'bytes!'})
->message_ok->message_isnt({text => 'bytes!'})->finish_ok;
Expand Down Expand Up @@ -212,10 +223,19 @@ $t->websocket_ok('/unicode')->send_ok('hello again')
->send_ok('and one ☃ more time')
->message_ok->message_is('♥: and one ☃ more time')->finish_ok;

# Compression with forced "client_no_context_takeover"
$t->websocket_ok('/no_context_takeover');
ok $t->tx->compressed, 'WebSocket has compression';
ok !$t->tx->context_takeover, 'no context takeover';
$t->send_ok({binary => 'a' x 500})
->message_ok->message_is({binary => 'a' x 500})
->send_ok({binary => 'a' x 500})
->message_ok->message_is({binary => 'a' x 500})->finish_ok;

# Binary frame and events (no compression)
my $bytes = b("I ♥ Mojolicious")->encode('UTF-16LE')->to_string;
$t->websocket_ok('/bytes' => {'Sec-WebSocket-Extensions' => 'nothing'});
ok !$t->tx->has_compression, 'WebSocket has no compression';
ok !$t->tx->compressed, 'WebSocket has no compression';
ok !$t->tx->res->headers->sec_websocket_extensions,
'no "Sec-WebSocket-Extensions" value';
my $binary;
Expand Down

0 comments on commit ceb4799

Please sign in to comment.