Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Mojolicious websocket with server-side repeating events

by gri6507 (Deacon)
on Jan 09, 2014 at 17:07 UTC ( [id://1070002]=perlquestion: print w/replies, xml ) Need Help??

gri6507 has asked for the wisdom of the Perl Monks concerning the following question:

Fellow monks,

I am creating a Mojolicious based webapp which uses a websocket connection to periodically serve up JSON data to the client browser, which in turn uses JavaScript to render that JSON data on the webpage. My implementation assumes the following sequence of operation:

  1. User points their web browser to a static page served up by Mojolicious
  2. This static page uses JavaScript to open a websocket connection and send a single JSON packet to Mojolicious to kickstart the next step
  3. Mojolicious should periodically send JSON data back to the browser with new data
I am having problems with the last step. I think I am not understanding how Mojo::IOLoop is supposed to work. When I run my current implementation, I get one Event "read" failed: Mojo::IOLoop already running error which closes the websocket, then I get a single iteration of a delay, and then Mojolicious crashes. The documentation for Mojo is not the best, or I am missing something. Any ideas?

Here is the minimal set of code showing the problem.

use warnings; use strict; use Mojolicious::Lite; use Mojo::IOLoop; # Define which template is rendered when the client goes to the base U +RLs. get '/' => 'deleteme'; # WebSocket reading device objects websocket '/ws' => sub { my $self = shift; # Opened $self->app->log->debug('WebSocket opened.'); # Increase inactivity timeout for connection a bit my $loop = Mojo::IOLoop->singleton; $loop->stream($self->tx->connection)->timeout(1000); # Closed $self->on(finish => sub { my ($self, $code, $reason) = @_; $self->app->log->debug("WebSocket closed with status $code."); }); # Incoming message $self->on(message => sub { # safely catch abort signals local $SIG{INT} = $SIG{TERM} = $SIG{__DIE__} = $SIG{__WARN__} += sub { $self->app->log->debug("Safely caught a signal in WebSocke +t message handler"); exit 0; }; }); $self->on(json => sub { my ($self, $hash) = @_; $self->app->log->debug("Got WebSocket JSON request"); # simmulate a handful of iterations of sitting in what otherwi +se would be an infinite loop for my $i (0 .. 10) { my $delay = $loop->delay( # First delay a bit sub { my $theDelay = shift; $loop->timer(5 => $theDelay->begin); $self->app->log->debug("Delay started"); }, # next do the Read and send back the data sub { $self->app->log->debug("Doing periodic useful thin +g"); } ); $delay->wait; exit if ($i > 10); } }); }; app->start;

Here is the required supporting deleteme.html.ep file:

<!DOCTYPE html> <html> <head> <title>Test</title> %= javascript "/jquery-2.0.3.js" %= javascript "/deleteme.js" </head> <body> <center> Status of connection to <label id="connection"><%= url_with('ws')->to_abs %></labe +l> - <label id="status">Disconnected</label> </center><p/> <div id="container"></div> </body> </html>

This uses two JavaScript files. The jQuery.js is the standard source from http://jquery.com/download/. The deleteme.js is below:

$(function () { // figure out where we need to connect to, and then do so var server = $('#connection').text(); var ws = new WebSocket(server); // Figure out where to log the connection status and setup the hoo +ks to log // the current status. Also kick off the initial message to the de +vice var lblStatus = $('#status'); ws.onopen = function () { lblStatus.text('Connected'); ws.send( JSON.stringify({ obj: "AV", num: 1 }) ); }; ws.onclose = function() { lblStatus.text('Disconnected'); }; ws.onerror = function(e) { lblStatus.text('Error: ' + e.data); }; // Parse incoming response and post it to the screen ws.onmessage = function (msg) { var res = JSON.parse(msg.data); if (res.type == "name") { $("<label/>", { 'class': 'objId', 'id' : 'lbl_' + res.obj + '_' + res.num, 'text' : res.obj + res.num }).appendTo('#container'); $("<label/>", { 'class': 'objName', 'id' : 'lblName_' + res.obj + '_' + res.num, 'text' : res.value }).appendTo('#container'); } else { $("<input/>", { 'class' : 'objPv', 'name' : 'inPv_' + res.obj + '_' + res.num, 'type' : 'text', 'value' : res.value }).appendTo('#container'); } }; });

Replies are listed 'Best First'.
Re: Mojolicious websocket with server-side repeating events
by gri6507 (Deacon) on Jan 09, 2014 at 21:19 UTC
    After many failed attempts to solve it myself, I have stumbled on the desired solution. For the sake of completeness, here's what I had to do is replace the
    # simmulate a handful of iterations of sitting in what otherwise would + be an infinite loop for my $i (0 .. 10) { ... }
    with the following
    $loop->recurring(1 => sub { $self->app->log->debug("Doing periodic useful thing"); });
    Of course, the new code is truly a while(1) loop, constantly firing the repeating event, rather than the original example which was only supposed to fire 11 times. However, I actually wanted this behavior anyways.
Re: Mojolicious websocket with server-side repeating events
by zentara (Archbishop) on Jan 10, 2014 at 12:03 UTC
    I came back today to check on this node because I feel it is a state of the art question, which I have been trying to figure out myself. It seems that they are pushing Web 2.0, with all the jquery, ajax, and backend socket connections.

    Great question, and thanks for the solution.


    I'm not really a human, but I play one on earth.
    Old Perl Programmer Haiku ................... flash japh

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1070002]
Approved by karlgoethebier
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (4)
As of 2024-04-24 03:03 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found