Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mojolicious re-using a previously established connection

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;
like image 499
Apoorv Joshi Avatar asked Oct 18 '22 08:10

Apoorv Joshi


1 Answers

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.

like image 158
Ed. Avatar answered Oct 21 '22 03:10

Ed.