リアルタイムチャット (Mojolicious::Lite)

WebSocketを使ったリアルタイムチャットのサンプルです。複数の画面を開いても他の画面に書き込みがすぐに反映されます。

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

ソースコード

use strict;
use warnings;

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

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

  # Client id
  my $cid = "$self";
  
  # Resist 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>

本番環境+WebSocketリバースプロキシにも対応したRedisを使うバージョンのリアルタイムチャット

本番環境WebSocketリバースプロキシにも対応したRedisを使うバージョンのリアルタイムチャットのロジックも紹介しておきます。

ApacheやnginxのWebSocketリバースプロキシを使って、Mojoliciousの本番サーバーhypnotoadで動くバージョンです。

Redisのpub/sub機能を組み合わせて実現しています。

パスの部分は必要に応じて「/」から必要なものに修正してください。index.html.epの一行目のパス「/」も合わせて修正してください。Apacheの場合は、WebSocket用のパスに分ける必要があります。

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;

関連情報