LoginSignup
7
7

More than 5 years have passed since last update.

Mojolicious::LiteでWebSocketを使った簡単なチャットを作る

Last updated at Posted at 2016-04-21

MojoliciousでWebSocketでjsonをやりとりする簡単なチャットを作ってみました。
名前とメッセージを入力して送信→表示って感じです。
MojoChat.png

WebSocketあまり詳しくないので変な解説してある所があったらごめんなさい(

ルーティング&コントローラ部分

chat.pl
use Mojolicious::Lite;
use Mojo::EventEmitter; # 必要

# ヘルパー定義
helper events => sub {
  state $events = Mojo::EventEmitter->new
};

my @data; # チャットログデータを格納する配列

# チャット画面
get '/' => sub {
  my ($c) = @_;
  $c->stash(message => \@data);
  $c->render(template => 'chat');
};

# WebSocket用
websocket '/channel' => sub {
  my ($c) = @_;

  # デフォルトのヘルパーでタイムアウトの設定
  $c->inactivity_timeout(300);

  # jsonをWebSocketを通して受ける
  $c->on(json => sub {
    my ($c, $json) = @_;
    $json->{message} =~ s/\n/<br>/g;
    unshift(@data, $json);
    $c->events->emit(chat => $json); # イベントを発行
  });

  # jsonをクライアント側に送る
  my $cb = $c->events->on( # イベントを読む
    chat => sub {
      my ($event, $json) = @_;
      $c->send({json => $json}); # jsonを送信
    }
  );

  # トランザクション終了後の処理(クライアント側との接続が切れた時)
  $c->on(finish => sub {
    my ($c) = @_;
    $c->events->unsubscribe(chat => $cb); # イベントを読むのをやめる
  });

};

app->start;
__DATA__

@@ chat.html.ep
(略)

解説

タイムアウトの設定, WebSocketからデータを受け取る処理, クライアント側にデータを送る処理, クライアント側との接続が切れた時の処理, と書いています。

Mojo::EventEmitterのインスタンスをヘルパーに登録してシングルトンみたく使うとクライアント側にデータを送る処理が楽に実装できます。

あとはまあコードとコメントを見て頂ければなんとなくわかると思います!

テンプレート部分

chat.pl
(上のコードの続き)
@@ chat.html.ep
<!DOCTYPE html>
<html>
<head>
  <title>Chat!</title>
</head>
<body>
Name : <%= text_field name => '', size => 20, id => 'name' %><br>
<%= text_area profile => '', cols => 50, rows => 5, id => 'form' %>
<input type="button" id="submit" value="送信">
<div id="log">
  % for (@$message) {
      <p><%= $_->{name} %> say: <br><%== $_->{message} %></p>
  % }
</div>
<script>
  'use strict';

  // WebSocketオブジェクト作成
  var ws = new WebSocket('<%= url_for('channel')->to_abs %>');

  // WebSocket受信部
  ws.onmessage = function (eve) {
    var json = JSON.parse(eve.data);
    var parentDom = document.getElementById('log');
    var newDom = document.createElement('p');
    newDom.innerHTML = json.name + ' say: <br>' + json.message;
    parentDom.insertBefore(newDom, parentDom.firstChild);
  };

  /* ws送信イベント発火部 */
  document.getElementById('submit').addEventListener('click', function(eve) {
    sendChat(document.getElementById('name'), document.getElementById('form'));
  },false);

  /* WebSocket送信部 */
  function sendChat(name, input) {
    if (!input.value || !name.value) { return false; }
    var json = {
      'name' : name.value,
      'message' : input.value
    };
    ws.send(JSON.stringify(json));
    input.value = '';
  }

</script>
</body>
</html>

解説

送信ボタンがクリックされて名前欄とメッセージ欄が空でなければ/channelにjsonを送信、
jsonを受け取ったら、<div id="log">の下に受け取ったデータをもとに新しいDOMを追加しています。
改行させるためにメッセージ表示部分をエスケープしていないのでこのコードのまま使うのはやめてください。
(もしいい感じで改行させれる方法を知っている方がいれば是非教えてください!)

参考

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7