Skip to content

Writing websocket chat using Mojolicious Lite

irondog edited this page Dec 7, 2011 · 2 revisions

=head1 Writing websocket chat using Mojolicious::Lite

Note: This page is a translation of Lhttp://d.hatena.ne.jp/naoya/20101011/1286778922.

I read the article (real time web with websocket Lhttp://www.atmarkit.co.jp/fcoding/articles/websocket/01/websocket01a.html) The article says, you can easily write event driven web server when you use Javascript framework, which is using V8 and named C<node.js>. Additionally, using C<node-websocket-server.js>, you can easily implement websocket server with C<node.js>. The article compares architecture of websocket, polling with Ajax and long polling.

(Please look at the reference material at the end of this article for links)

Websocket enables us to write server-pushing application easily. But the problem is how to prepare the HTTP server which processing websocket. It is interesting that node.js is one of the answer.

=head2 Websocket server with Perl ... Mojolicious::Lite

The article provoked me into writing server-side program in Perl, not Javascript. I wrote a sample using Mojolicious::Lite. Mojolicious includes an implementation of a server that can handle the WebSocket protocol.

So you can write it like below.

use Mojolicious::Lite;

websocket '/echo' => sub {
    my $self = shift;
        $self->receive_message(sub {
        my ($self, $message) = @_;
        $self->send_message("echo: $message");
    });
};

app->start;

Then, starting the server,

% perl app.pl --daemon

Cws://localhost:3000/echo is the end point of websocket. The callback passed to C<receive_message()> will be kicked on every event from client-side. Other processes like handshaking are treated appropriately by Mojo. It's offhandedness.

=head2 The code of websocket chat sample

The following is the chat I made as a sample. C</> returns HTML chat page. C is the websocket endpoint. When it recieves a message, it sends the time and the recieved message to all connected clients.

#!/usr/bin/env perl
use utf8;
use Mojolicious::Lite;
use DateTime;
use Mojo::JSON;

get '/' => 'index';

my $clients = {};

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

    app->log->debug(sprintf 'Client connected: %s', $self->tx);
    my $id = sprintf "%s", $self->tx;
    $clients->{$id} = $self->tx;

    $self->on(message =>
        sub {
            my ($self, $msg) = @_;

            my $json = Mojo::JSON->new;
            my $dt   = DateTime->now( time_zone => 'Asia/Tokyo');

            for (keys %$clients) {
                $clients->{$_}->send_message(
                    $json->encode({
                        hms  => $dt->hms,
                        text => $msg,
                    })
                );
            }
        }
    );

    $self->on(finish =>
        sub {
            app->log->debug('Client disconnected');
            delete $clients->{$id};
        }
    );
};

app->start;

__DATA__
@@ index.html.ep
<html>
  <head>
    <title>WebSocket Client</title>
    <script
      type="text/javascript"
      src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"
    ></script>
    <script type="text/javascript" src="/js/ws.js"></script>
    <style type="text/css">
      textarea {
          width: 40em;
          height:10em;
      }
    </style>
  </head>
<body>

<h1>Mojolicious + WebSocket</h1>

<p><input type="text" id="msg" /></p>
<textarea id="log" readonly></textarea>

</body>
</html>

The client side js code is below. Connecting to C with websocket API, processes input and output appropriately.

$(function () {
  $('#msg').focus();

  var log = function (text) {
    $('#log').val( $('#log').val() + text + "\n");
  };

  var ws = new WebSocket('ws://localhost:3000/echo');
  ws.onopen = function () {
    log('Connection opened');
  };

  ws.onmessage = function (msg) {
    var res = JSON.parse(msg.data);
    log('[' + res.hms + '] ' + res.text); 
  };

$('#msg').keydown(function (e) {
    if (e.keyCode == 13 && $('#msg').val()) {
        ws.send($('#msg').val());
        $('#msg').val('');
    }
  });
});

Access to Llocalhost:3000 with Google Chrome. It works fine.

=begin html

<img src="http://img.f.hatena.ne.jp/images/fotolife/n/naoya/20101011/20101011153308.png" alt="Screenshot" width="465" height="232"

=end html

Screenshot: Lhttp://f.hatena.ne.jp/naoya/20101011153308

In this way you can implement a WebSocket server with Perl. As I end on that, there is a wealth of existing information to make use of which give rise to a number of different ideas. Such as implementing on a server in Perl something like the Activity Monitor that article mentioned. I'm never out of ideas, such as displaying a remote server's status graphically.

=head2 Other articles and references

=over 4

=item More Mojolicious WebSocket example / No time to wait

Lhttp://vti.showmetheco.de/articles/2010/05/more-mojolicious-websocket-examples.html

You can find more gorgeous samples using Mojolicious + websocket.

=item Twiggy sample

Lhttp://cpansearch.perl.org/src/MIYAGAWA/Twiggy-0.1007/eg/chat-websocket/chat.psgi

When I search another server-side example, I found Twiggy (AnyEvent + PSGI based) sample. It's happy to process websocket with Plack/PSGI.

=item "The Realtime Web"

Lhttp://www.atmarkit.co.jp/fcoding/articles/websocket/01/websocket01a.html

An eye on WebSocket "The Realtime Web" - original article (jp)

=item node.js

Lhttp://nodejs.org/

Server-side JavaScript framework

=item node-websocket-server.js

Lhttp://github.com/miksago/node-websocket-server

Server for the websocket protocol

=back

Clone this wiki locally