PHPでチャットしたい
現在1php websocket
とかでググるとRatchetというライブラリが真っ先にヒットします。私も最初はこれを弄っていました。
ですがRatchetはWAMPv2に対応していないという致命的な欠点を抱えています。
そのため何気なくnpm install autobahn
とかして最新のautobahn.js
(WAMPv2対応)を突っ込んだりするとプロトコル違いが原因でhandshakeに失敗します。公式のtutorialで使ってるのに!
困って他をあたったところ、Thruwayというライブラリを見つけました。これが触ってみたらとてもいい感じでした。
しかし日本語の情報がわずかしかないので、ここではチャットアプリの実装を通じてThruwayの使い方を広めようと思います。
Thruwayだけでclientサイドまでphpオンリーで実装できますが、今回はclientサイドはautobahn
で行くことにします。
前提条件
下記ツールを用いますので、持ってない人は/(apt-get|yum|dnf|インストール)/
してください。
-
必須
- composer
-
autobahn使わないならいらない
- node.js
- bower
インストール
composer require voryx/thruway
composer require thruway/pawl-transport
以上。
autobahn.jsなどを、クライアントサイドに使う場合はcomposer require voryx/thruway
のみでOKです。
今回はphpだけで動作確認までしたいのでthruway/pawl-transport
まで入れましょう。
動作確認
php vendor/voryx/thruway/Examples/SimpleWsRouter.php
こんな感じでRouterが走り出します。
2018-09-08T07:15:54.0880690 debug [Thruway\Peer\Router 32351] New router created
2018-09-08T07:15:54.0896410 info [Thruway\Peer\Router 32351] Starting router
2018-09-08T07:15:54.0903750 info [Thruway\Transport\RatchetTransportProvider 32351] Websocket listening on 127.0.0.1:9090
2018-09-08T07:15:54.0906830 info [Thruway\Peer\Router 32351] Starting loop
Starting loopしたらもう一個terminalを立ち上げてclient側を動かします。
php vendor/voryx/thruway/Examples/SimpleClient.php
最初に動かしたRouterがこんな感じの出力を吐けばちゃんと動いています。最後の方のonMessageが大事。
2018-09-08T07:20:34.2795810 debug [Thruway\Transport\RatchetTransportProvider 1812] RatchetTransportProvider::onOpen
2018-09-08T07:20:34.2858580 debug [Thruway\Transport\RatchetTransportProvider 1812] onMessage: ([1,"realm1",{"roles":{"publisher":{"features":{"subscriber_blackwhite_listing":true,"publisher_exclusion":true}},"subscriber":{"features":{}},"caller":{"features":{"caller_identification":true,"progressive_call_results":true,"call_canceling":true}},"callee":{"features":{"caller_identification":true,"progressive_call_results":true,"call_canceling":true}}},"authmethods":[],"authid":"anonymous"}])
2018-09-08T07:20:34.2863310 info [Thruway\RealmManager 1812] Got prehello...
2018-09-08T07:20:34.2863440 debug [Thruway\RealmManager 1812] Creating new realm 'realm1'
2018-09-08T07:20:34.2875190 debug [Thruway\RealmManager 1812] Adding realm 'realm1'
2018-09-08T07:20:34.2898050 debug [Thruway\Transport\RatchetTransportProvider 1812] onMessage: ([32,5799645875876444,{},"com.myapp.hello"])
2018-09-08T07:20:34.2904330 debug [Thruway\Subscription\SubscriptionGroup 1812] Added subscription to 'exact':'com.myapp.hello'
2018-09-08T07:20:34.2905620 debug [Thruway\Transport\RatchetTransportProvider 1812] onMessage: ([16,3149386944896931,{"acknowledge":true},"com.myapp.hello",["Hello, world from PHP!!!"]])
2018-09-08T07:20:34.2907040 debug [Thruway\Transport\RatchetTransportProvider 1812] onMessage: ([64,3904229972015909,{},"com.myapp.add2"])
2018-09-08T07:20:34.2915070 debug [Thruway\Transport\RatchetTransportProvider 1812] onMessage: ([48,4597463936048137,{},"com.myapp.add2",[2,3]])
2018-09-08T07:20:34.2930660 debug [Thruway\Transport\RatchetTransportProvider 1812] onMessage: ([70,6219899842080365,{},[5]])
Routerの実装
<?php
require_once './vendor/autoload.php';
use Thruway\Peer\Router;
use Thruway\Transport\RatchetTransportProvider;
$router = new Router();
$transportProvider = new RatchetTransportProvider("127.0.0.1", 9090);
$router->addTransportProvider($transportProvider);
$router->start();
ThruwayのExampleにあるSimpleWsServer.phpのほぼ全コピーです。
これだけでws://127.0.0.1:9090
でアクセス可能なRouterの実装が完了します。
Clientの実装
Clientはautobahn.jsに任せるのでひたすらhtmlとjsです。
client用のautobahn.min.jsを手に入れる
bower install autobahn
bower
でinstallしたパッケージはbower_components
に入ってくれるのでこれを<script src='...'>
で読み込めば使えるようになります。
というわけでチャット欄を設けてjsを読むhtmlを用意しましょう。
<html>
<head>
<meta charset='utf-8'>
<title>chat</title>
</head>
<body>
<h3>Chat</h3>
<ul id='chat-list'></ul>
<input id='speech' type='text'>
<button onclick='chat()'>chat</button>
<script src="/bower_components/autobahn/autobahn.min.js"></script>
<script src="/chat.js"></script>
</body>
</html>
最低限。
最後にchat.htmlに読ませるjsを作って完成です。
const chat_url = 'php.chat';
var conn;
(function(){
conn = new autobahn.Connection({url: 'ws://127.0.0.1:9090', realm: 'realm1'});
conn.onopen = function(session){
function received_chat(message){
document.getElementById('chat-list').innerHTML += '<li>' + message[0] + '</li>';
}
session.subscribe(chat_url, received_chat);
}
conn.open();
}());
function chat(){
const chat = document.getElementById('speech').value;
conn.session.publish(chat_url, [chat], null, {exclude_me: false});
}
autobahn
はautobahn.min.js
内で定義された変数です。autobahnが定義している各メソッドやクラスはこいつ経由で参照します。
new autobahn.Connection()
でWebSocket通信のurlやrealmを指定します。実際にconnectするにはconn.open()
です。
conn.onopen
で、connectionを確立した時に処理する関数を指定します。今回はsession.subscribe
でphp.chat
チャンネルでpublishイベントがあった際の挙動を指定しています。
function chat()
はchatボタンを押したときの挙動です。入力フォームの値をphp.chat
チャンネルにpublishします。
{exclude_me: false}
というのは、publishした本人もpublishイベントを受け取るためのオプションです。これがないとpublishした本人は自分の発言がチャット欄に流れて来なくなります。
以上で実装終了です。
動かす
php -S localhost:9999 | php router.php
任意のブラウザでhttp://localhost:9999/chat.html
に行けばもうそこはチャットルームです。
まとめ
ほとんどautobahnの説明でしたがたったこれだけのコードでWAMPv2に則ったチャットアプリを実装できてしまいました。
後は発言をredisに保存するなりxss対策するなり部屋を分割するなりすればいいと思います。
phpによるclientサイドの実装はThruwayのREADME.mdの実例で分かりやすく説明しています。autobahnと見た目はあまり変わりません。
phpでWebsocket通信したいときには是非Thruwayの採用をご検討ください。
-
2018/09/08 ↩