5

I'm trying to reuse a previously established websocket connection to avoid the websocket handshake. I found that a custom websocket transaction can be built using build_websocket_tx (more details here), and there's a connection identifier for every websocket connection which can be retrieved using connection subroutine defined in Mojo::Transaction (more details here). Can I somehow combine both of these to re use the connection? Is there another way to do so?

PS: Websocket connections are supposed to be consistent and reusable. But Mojolicoious doesn't provide any such options for websocket connections.

EDIT

Example code without connection re-use.

#!/usr/bin/perl

use strict;
use warnings;

use Mojo::UserAgent;
use JSON qw |encode_json|;

my $ua = Mojo::UserAgent->new;
my $url = "wss://trello.com/1/Session/socket";

$| = 1;

sub _connect {
    my $req = {
        type => "ping",
        reqid=> 0
    };
    $ua->websocket(
        $url => sub {
            my ($ua, $tx) = @_;

            die "error: ", $tx->res->error->{message}, "\n" if $tx->res->error;
            die 'Not a websocket connection' unless $tx->is_websocket;

            # Connection established.
            $tx->on(
                message => sub {
                    my ($tx, $msg) = @_;
                    print "$msg\n";
                    $tx->closed; #Close connection
                });
            $tx->send(encode_json($req));
        });
}

sub reuse_conn {
    # Re use connection
}

_connect();

Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
Ed.
  • 1,992
  • 1
  • 13
  • 30
Apoorv Joshi
  • 389
  • 3
  • 15
  • What do you want to accomplish by 'reusing' connection? – mpapec Aug 04 '17 at 05:21
  • @Сухой27 Since websocket is a statefull protocol. I want to keep track of all the websocket connections that are already authorized, so that I don't have to through the overhead of re-authorizing every connection again and again for every client requests. This might not make sense to you, but it requires that to overcome a lot of overhead of resending the same requests again and again. – Apoorv Joshi Aug 04 '17 at 06:14
  • 1
    I'm not sure if I understand you correctly but once you have established client connection and event handlers put in place, you use this connection until network breaks down, or other side closes it. At this point you have to initiate new client connection. 'Reuse' doesn't quite fit in such scheme. – mpapec Aug 04 '17 at 06:37
  • @Сухой27 Actually, you got it right. Consider the whole process asynchronous, let's assume the connection is established and after a while the user sends another request, what I want to do is use the previously established connection (if it is not closed), instead of creating a new connection for every request that the user makes. Doesn't it sound wrong that we create a connection for every request that the user sends? At least in the context of websocket it does. Websockets connections are supposed to be persistent and re-usable. – Apoorv Joshi Aug 04 '17 at 07:15
  • I think what you need is basic server/client example for mojolicious. Doesn't wiki already cover this? – mpapec Aug 04 '17 at 07:55
  • No, the wiki doesn't cover anything on reusing the connection. – Apoorv Joshi Aug 04 '17 at 08:02
  • I'm interested in working on this but without a basic bit of code that works *without* reuse, that you want to modify to work *with* reuse, it's difficult. Please add such code :-) – Ed. Aug 29 '17 at 04:39
  • @Ed. https://github.com/kraih/mojo/wiki/Writing-websocket-chat-using-Mojolicious-Lite – mpapec Aug 29 '17 at 09:58
  • @Сухой27 I'm asking the OP (or someone who edits the question) to please add the actual code being used, to the question for posterity. GitHub code can change or go away :-) – Ed. Aug 29 '17 at 16:30
  • 1
    @Ed. I have added the code snippet which doesn't re-use connection. The only way I was able to reuse connection was by storing the transaction object and use it in subsequent calls. – Apoorv Joshi Aug 30 '17 at 02:06
  • @Сухой27 Just for future reference, the client/server example you provided was a JavaScript client / Mojolicious server. What OP is after is a Mojolicious client :-) – Ed. Sep 03 '17 at 05:13

1 Answers1

3

Preliminaries: to run a Mojolicious client script with debugging information:

MOJO_EVENTEMITTER_DEBUG=1 MOJO_USERAGENT_DEBUG=1 perl mua.pl

As at version 7.43, Mojo::UserAgent has built-in connection pooling but specifically refuses to use it for WebSockets. This may well be because as you said, WebSockets is stateful, and if the UserAgent blindly reused connections, that could cause chaos. However, if your application knows how to safely reuse them, that is a different matter.

This question came up recently in the IRC channel for Mojolicious, and sri, the author, said:

16:28   sri     mohawk: some frameworks like phoenix have their own higher level protocol on top of websockets to multiplex multiple channels https://hexdocs.pm/phoenix/channels.html
16:28       sounds like that's what you want
16:28       mojolicious should have something like that, but doesn't yet
[...]
16:42   sri     it's not hard to build on top of mojolicious, but for now you have to do that yourself
16:42       ultimately i'd hope for us to have it in core, without the message bus part
16:43       but channel management and routing
[...]
16:50   jberger mohawk I did write Mojolicious::Plugin::Multiplex which might help
16:51       For an example of a higher level tool

I acknowledge OP said:

The only way I was able to reuse connection was by storing the transaction object and use it in subsequent calls.

However, as the code currently is, it appears this is the only way. This code demonstrates how to make, maintain, and use your own connection pool:

#!/usr/bin/perl

use strict;
use warnings;
use Mojo::UserAgent;
use Time::HiRes qw(time);
$| = 1;
my $REQ = {
    type => "ping",
    reqid => 0,
};
my $URL = "wss://trello.com/1/Session/socket";
my $SECONDS = 2;
my $POOL_SIZE = 5;

my $ua = Mojo::UserAgent->new;
my @pool;

sub make_conn {
  my ($ua, $url, $pool) = @_;
  $ua->websocket($URL => sub {
    my (undef, $tx) = @_;
    die "error: ", $tx->res->error->{message}, "\n" if $tx->res->error;
    die 'Not a websocket connection' unless $tx->is_websocket;
    push @$pool, $tx;
  });
}

# pool gets pushed onto, shifted off, so using recently-used connection
sub send_message {
  my ($pool, $request, $start) = @_;
  my $tx = shift @$pool;
  die "got bad connection" unless $tx; # error checking needs improving
  $tx->once(message => sub {
    my (undef, $msg) = @_;
    print "got back: $msg\n";
print "took: ", time - $start, "\n";
    push @$pool, $tx;
  });
  $tx->send({json => $request});
}

make_conn($ua, $URL, \@pool) for (1..5); # establish pool

# every 2 secs, send a message
my $timer_cb;
$timer_cb = sub {
  my $loop = shift;
  print "every $SECONDS\n";
  send_message(\@pool, $REQ, time);
  $loop->timer($SECONDS => $timer_cb);
};
Mojo::IOLoop->timer($SECONDS => $timer_cb);

Mojo::IOLoop->start unless Mojo::IOLoop->is_running;

At a high level, it works like this:

  • make 5 connections in the pool
  • send_message uses the least-recently-used connection in that pool
  • send a message every two seconds, registering a one-time "on message" callback to deal with the response

For the sake of simplicity, it does not check connections are still working when it gets them from the pool, and relies on the first two-second delay to initialise all 5 connections.

The use of time calls demonstrates the speed gain from using this pool. Your provided code takes (on my system) around 300ms to start up the connection, then send and receive. Using the pool, it is taking around 120ms.

Ed.
  • 1,992
  • 1
  • 13
  • 30
  • 1
    Thanks for an elaborate answer. But I'm already aware of this. The two code references that you attached could be helpful in future research. – Apoorv Joshi Sep 05 '17 at 01:28
  • I've added jberger's note about https://metacpan.org/pod/Mojolicious::Plugin::Multiplex which may also be useful. – Ed. Sep 05 '17 at 16:58