Search code examples
htmlperlwebsocketmojolicious

Mojolicious and delayed WebSocket


I have this code snippet, I'm trying to push a string to the server every X seconds like:

#!/usr/bin/env perl
use Mojolicious::Lite;
use EV;
use AnyEvent;
use POSIX qw(strftime);

get '/' => sub {
    my $self = shift;

    $self->render('main');
};

websocket '/echo' => sub {
        my $self = shift;
        my $w;
        $w = AE::timer 3, 1, sub {
                    $self->send('Got it');
        };
      #  $self->send(strftime("Server $$: %a %b %e %H:%M:%S %Y", localtime));
};
app->start();

__DATA__
@@ main.html.ep
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
</head>
<body>
<table id="tableID">
<tbody>
</tbody>
</table>
<script type="text/javascript">
var ws = new WebSocket('ws://192.168.1.104:3000/echo');
      ws.onopen = function () {
        alert('Connection opened');
      };
      ws.onerror = function() { alert("Error"); };
      ws.onmessage = function (msg) {
          $("#tableID").find('tbody')
                       .append($('<tr>')
                       .append($('<td>')
                       .text(msg.data)
            )
        );

      };
</script>
</body>
</html>

AFAIK Mojo used IO::Loop event loop, which I guess should be fine with AnyEvent

This doesn't work and I'm wondering why. When I remove the AE part and uncomment the simple send, I see the results in the browser.

P.S: Just experimenting with WebSockets and Mojo for some Project I will need to use Both Mojo and AnyEvent + WebSockets


Solution

  • The problem seems to be that the send message is trying to happen before the GET connection is upgraded to a websocket. I'm still trying to track it down, in the meantime this works.

    #!/usr/bin/env perl
    use Mojolicious::Lite;
    use EV;
    use AnyEvent;
    #use POSIX qw(strftime);
    
    get '/' => sub {
        my $self = shift;
    
        $self->render('main');
    };
    
    websocket '/echo' => sub {
            my $self = shift;
            my $w;
    
            $self->on(finish => sub { Mojo::IOLoop->remove($w) });
          #  $self->send(strftime("Server $$: %a %b %e %H:%M:%S %Y", localtime));
    
            $w = Mojo::IOLoop->recurring( 1 => sub{
                        $self->send('Got it');
            });
    };
    app->start();
    
    __DATA__
    @@ main.html.ep
    <html>
    <head>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
    </head>
    <body>
    <table id="tableID">
    <tbody>
    </tbody>
    </table>
    <script type="text/javascript">
    var ws = new WebSocket("<%= url_for('echo')->to_abs %>");
          ws.onopen = function () {
            alert('Connection opened');
          };
          ws.onerror = function() { alert("Error"); };
          ws.onmessage = function (msg) {
              $("#tableID").find('tbody')
                           .append($('<tr>')
                           .append($('<td>')
                           .text(msg.data)
                )
            );
    
          };
    </script>
    </body>
    </html>
    

    notice that in my example the debug output shows 101 switching protocols while your example attempts to autorender echo.html.ep.

    Also I changed the template to generate the websocket url.

    UPDATE:

    It seems that in order to connect to a websocket, its necessary to subscribe to an event. In this example I subscribe to the finish event, which you want to do anyway in order to stop the timer.

    #!/usr/bin/env perl
    use Mojolicious::Lite;
    use EV;
    use AnyEvent;
    #use POSIX qw(strftime);
    
    get '/' => sub {
        my $self = shift;
    
        $self->render('main');
    };
    
    websocket '/echo' => sub {
            my $self = shift;
            my $w;
            $w = AE::timer 3, 1, sub {
                        $self->send('Got it');
            };
            $self->on(finish => sub{ undef $w });
          #  $self->send(L<Mojolicious::Controller/strftime("Server $$: %a %b %e %H:%M:%S %Y", localtime));
    };
    app->start();
    

    UPDATE 2:

    In order to clarify the problem, SRI has added the following documentation to Mojolicious:

    The connection gets established when you respond to the WebSocket handshake with a 101 response status, which happens automatically if you subscribe to an event with "on" or send a message with "send" right away.

    which explains the situation. The original code neither subscribes to an event nor does a send immediately. In almost all cases you do at least one of the two, in this case though :-)