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:
- User points their web browser to a static page served up by Mojolicious
- This static page uses JavaScript to open a websocket connection and send a single JSON packet to Mojolicious to kickstart the next step
- 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');
}
};
});