Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした