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

I have a pretty simple use case where I want a websocket (or something like it) to send the contents of ZMQ pub/sub messages out to a browser in real time for an indefinite period of time. It's for personal use (home automation) so doesn't need to scale too much, but does need to be robust.

One-way communication (server to browser) is fine, but two ways would be nice, I guess. Not worried about two-way right now, since I can easily use HTTP(s) for browser-to-server.

I would like to use Mojolicious:: Lite (or something I can easily run in hypnotoad, and which allows for in-script hypnotoad configuration).

I found examples that look like they should work, but for me they don't - once the code hits the IO loop (whether the Mojo one or AnyEvent) to receive subscribed messages, I get either an 'operation on closed socket' if I'm using ZMQ::FFI, or a more cryptic error (something like Assertion failed: pfd.revents & POLLIN) if I use the ZMQ library like in the examples.

I decided to take a break and write the code in a plain perl script with Net::WebSocket::Server and AnyEvent, and found that it works fine when run from the command line:

use strict; use IO::Socket::SSL; use ZMQ::FFI; use ZMQ::FFI::Constants qw (ZMQ_SUB); use AnyEvent; use EV; my $wsport = '3000'; my $zmqhost = 'tcp://localhost:5555'; my $subject = 'event'; Net::WebSocket::Server->new( listen => $wsport; on_connect => sub { my ($serv, $conn) = @_; $conn->on( ready => sub { my $context = ZMQ::FFI->new(); my $subsocket = $context->socket(ZMQ_SUB); $subsocket->connect($zmqhost); $subsocket->subscribe($subject); my $fd = $subsocket->get_fd(); my $w = AE::io $fd, 0, sub { while ($subsocket->has_pollin) { $conn->send_utf8($subsocket->recv()); } }; EV::run(); }, ); }, )->start;
which makes me think I should easily be able to make it work in Mojolicious::Lite, but for the life of me I can't seem to do it. It might be that in the code above, I'm able to put a subroutine in the on 'ready' state, but I can't figure out how to do that in the Mojolicious::Lite world.

(I would just use that code above, but it's single-user and not at all fault tolerant, and I'd rather not write a bunch of code to solve those issues if I don't have to)

Anyone with a suggestion on how to get this working in Mojolicious / hypnotoad, or perhaps another approach?

Replies are listed 'Best First'.
Re: Mojolicious websocket question
by Corion (Patriarch) on Aug 26, 2017 at 07:14 UTC

    I'm having a hard time imagining the problems you encounter when using Mojolicious::Lite.

    Can you show us a minimal Mojolicious::Lite example that fails for you?

Re: Mojolicious websocket question
by antichef (Acolyte) on Aug 26, 2017 at 17:14 UTC
    thanks for the replies - here's an example of a Mojolicious::Lite app that is not working for me. This is based on the examples I mentioned, but likley I'm the one who broke it, of course:

    #!/usr/bin/perl use Mojolicious::Lite; use ZMQ; use ZMQ::Constants qw|ZMQ_PUB ZMQ_SUB ZMQ_SUBSCRIBE ZMQ_FD ZMQ_DONTWAI +T ZMQ_NOBLOCK|; use strict; # Template with browser-side code get '/' => 'index'; # WebSocket echo service websocket '/msgs' => sub { my $c = shift; # Opened $c->app->log->debug('WebSocket opened'); # Increase inactivity timeout for connection a bit $c->inactivity_timeout(300); my $ctx = ZMQ::Context->new(10); my $subsocket = $ctx->socket(ZMQ_SUB); $subsocket->setsockopt(ZMQ_SUBSCRIBE,'event'); $subsocket->connect ('tcp://127.0.0.1:5555'); my $subsocket_fd = IO::Handle->new_from_fd($subsocket->getsockopt(ZM +Q_FD),'r'); Mojo::IOLoop->singleton->reactor->io($subsocket_fd=> sub { my ( $r, $w ) = @_; if (my $message = $subsocket->recvmsg(ZMQ_DONTWAIT)) { $c->send($message->data); } undef $w; } ); Mojo::IOLoop->start unless Mojo::IOLoop->is_running; }; app->start; __DATA__ @@ index.html.ep <!DOCTYPE html> <html> <head><title>Echo</title></head> <body> <script> var ws = new WebSocket('<%= url_for('msgs')->to_abs %>'); ws.onmessage = function (msg) { document.body.innerHTML += msg.data + '<br/>'; }; </script> msgs<br> </body> </html>
    when I run this with morbo, it starts up fine, but when I access it with the web browser, I get:

    Assertion failed: pfd.revents & POLLIN (signaler.cpp:242)

    in the console, and the browser complains that

    WebSocket connection to 'ws://myserver:3000/msgs' failed: Connection closed before receiving a handshake response

    I've tried a bunch of variations of this, putting the IO loop in different places, using AnyEvent instead of Mojo::IOLoop, using ZMQ::FFI instead of ZMQ, etc., but all essentially with the same result.

Re: Mojolicious websocket question
by Anonymous Monk on Aug 26, 2017 at 03:20 UTC

    I found examples that look like they should work, but for me they don't ...

    Huh?