Real-time chat (Mojolicious::Lite)

This is a sample of real-time chat using WebSocket. Even if you open multiple screens, the writing will be reflected immediately on the other screens.

[f: id: perlcodesample: 20100418234248p: image]

Source code

use strict;
use warnings;

use Mojolicious::Lite;
my $clients = {};

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

  #Client id
  my $cid = "$self";
  
  #Resistance controller
  $clients->{$cid} {controller} = $self;
  
  #Receive message
  $self->on('message' => sub {
    my ($self, $message) = @_;
    
    #Send message to all clients
    foreach my $cid (keys%$clients) {
      $clients->{$cid} {controller}->send($message);
    }
  });
  
  # Finish
  $self->on('finish' => sub {
      
    # Remove client
    delete $clients->{$cid};
  });
};;

get'/' =>'index';

app->start;

__DATA__

@@index.html.ep
%my $url = $self->req->url->to_abs->scheme($self->req->is_secure?'Wss':'ws')->path('/');
<! doctype html>
<html>
<head>
  <meta http-equiv = "Content-Type" content = "text / html; charset = UTF-8">
  <title> Mojo Websocket Demo </title>

  <script type = "text / javascript">
    // only load the flash fallback when needed
    if (! ('WebSocket' in window)) {
      document.write ([[
        '<scr' +'ipt type = "text / javascript" src = "web-socket-js / swfobject.js"> </scr'+'ipt>',
        '<scr' +'ipt type = "text / javascript" src = "web-socket-js / FABridge.js"> </scr'+'ipt>',
        '<scr' +'ipt type = "text / javascript" src = "web-socket-js / web_socket.js"> </scr'+'ipt>'
      ] .join(''));
    }
  </script>
  <script type = "text / javascript">
    if (WebSocket.__initialize) {
      // Set URL of your WebSocketMain.swf here:
      WebSocket.__swfLocation ='web-socket-js / WebSocketMain.swf';
    }

    // example copied from web-socket-js / sample.html
    var ws, input, clock;

    function init () {
      
      // Connect to Web Socket.
      ws = new WebSocket ('<%= $url%>');
      
      // Receive message
      ws.onmessage = function (e) {
        // Write message
        var message = document.createElement ('div');
        message.appendChild (document.createTextNode (e.data));
        var display = document.getElementById ('display');
        display.appendChild (message);
      };;
    }

    function sendChatMessage () {
      var input = document.getElementById ('message-box');
      var message = input.value;
      
      // Send message
      ws.send (message);
      input.value = "";
    }
    
    window.onload = init;
  </script>
</head>
<div id = "display" style = "width: 500px; height: 200px; border: 1px solid black"> </div>

<form onsubmit = "sendChatMessage (); return false;">
  <input size = "60" type = "text" id = "message-box">
  <input type = "submit" onclick = "sendChatMessage (); return false;" value = Send>
</form>
</html>

Real-time chat version using Redis that also supports production environment + WebSocket reverse proxy

I will also introduce the logic of the real-time chat of the version using Redis that also supports the production environment WebSocket reverse proxy.

A version that runs on Mojolicious's production server hypnotoad, using Apache and nginx WebSocket reverse proxies.

It is realized by combining the pub / sub function of Redis.

Please modify the path part from "/" to the required one if necessary. Please also correct the path "/" on the first line of index.html.ep. For Apache, you need to separate the paths for WebSockets.

use strict;
use warnings;

use Mojolicious::Lite;

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

  my $tx = $self->tx;
  my $redis = Mojo::Redis->new;
  my $pub = $redis->pubsub;
  
  # message from Redis
  my $sub = $pub->listen('messages', sub {
    my ($sub, $message) = @_; # $channel == messages
    $tx->send($message);
  });
 
  # message from websocket
  $self->on(message => sub {
    my ($self, $message) = @_;
    $pub->notify(messages => $message);
  });
 
  #need to clean up after websocket close
  $self->on(finish => sub {
    undef $redis;
    undef $pub;
    undef $sub;
    undef $tx;
  });
};;

get'/' =>'index';

app->start;

Associated Information