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

MojoliciousでWebSocketでjsonをやりとりする簡単なチャットを作ってみました。
名前とメッセージを入力して送信→表示って感じです。
![MojoChat.png](https://qiita-image-store.s3.amazonaws.com/0/110976/752b5844-d4df-5ca4-92c8-ef8aea963549.png)

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

# ルーティング&コントローラ部分
```perl: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のインスタンスをヘルパーに登録してシングルトンみたく使うとクライアント側にデータを送る処理が楽に実装できます。

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

# テンプレート部分
```erb: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を追加しています。
改行させるためにメッセージ表示部分をエスケープしていないのでこのコードのまま使うのはやめてください。
（もしいい感じで改行させれる方法を知っている方がいれば是非教えてください！）

# 参考
- [Mojo/examples/chat.pl](https://github.com/kraih/mojo/tree/master/examples)  -  これを元にコード書いていきました。
- [Mojo::EventEmitter](https://github.com/yuki-kimoto/mojolicious-guides-japanese/wiki/Mojo::EventEmitter)
- [Mojolicious::Controller](https://github.com/yuki-kimoto/mojolicious-guides-japanese/wiki/Mojolicious::Controller)  -  Mojoliciousのコントローラ部分のドキュメントです。WebSocketに関係する物も多いので読んだ方がいいと思います。
- [WebSocket](https://developer.mozilla.org/ja/docs/Web/API/WebSocket)  -  javascript側を書く時に参考になると思います。英語ですが！
- [Writing WebSocket client applications](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications) - 同上