目次
今回は、WebSocketの勉強として、複数のブラウザでリアルタイムに同期されるチャット機能を実装してみます。
WebSocketとは?
サーバとクライアント間で相互通信を行うためのプロトコル
Socket.IOって何?
Socket.IOはWebSocketで通信を行うためのライブラリ。(厳密にはWebSocketの実装ではないらしいが)
ブラウザ用のJavaScriptライブラリと、サーバサイド用のNode.jsライブラリがある。
LaravelでWebSocketを使うには?
ブロードキャスト(Broadcast)という機能を利用する。
この仕組みにより、Laravelでイベントがトリガーされるとフロントにもイベントが通知される様になるとの事。
ブロードキャストドライバとして、PusherというSaaSサービスを使う方式とRedisのPub/Sub機能を使用する方式がある。
Laravel-Echoって何?
Laravelのブロードキャストをブラウザで受け取るためのJavaScriptのライブラリ。
フロントのライブラリまで提供してくれるLaravelコミュニティの厚さがやばい。
Laravel-Echo-Serverって何?
Laravelのイベントをブロードキャストするミドルウェア(NodeJSサーバ)。
Socket.IOを用いて作成されている。laravel-echo-server start
で起動して6001
ポートでWebSocket接続を待ち受け、Laravelアプリケーションで発生したイベントをブラウザにブロードキャストする。
どんな構成になるのか?
今回はRedisを用いた方式でやってみる。
ブラウザのPOST送信からイベントブロードキャスト受信までを図にするとこんな感じになる。
ちなみにPusherを使う方式だとこうなる。
RedisサーバとLaravel-Echo-Serverが不要になる。
実装していく。
基本的には公式ドキュメントに従って進める。
まずは画面を実装しておく。
現状はこんな感じ。
フロント側でsocket.io-client
とlaravel-echo
を読み込む必要がある
フロントのコードはこんな感じになった。
import Echo from 'laravel-echo'; window.io = require('socket.io-client'); window.Echo = new Echo({ broadcaster: 'socket.io', host: window.location.hostname + ':6001' }); window.Echo.channel('messages') .listen('MessageSend', (e) => { console.log(e.order.name); });
Laravel Echo Serverを用意する
Laravel Echo Serverの設定ファイルをプロジェクトルートに配置する。laravel-echo-server init
コマンドでlaravel-echo-server.json
が生成されるので、これをプロジェクトルートに配置する。
Laravel Echo ServerのREADMEを参考にDockerイメージを作っていく。
こんな感じのDockerfileを作った。
FROM node:14.2.0-alpine3.10 RUN npm install -g laravel-echo-server CMD [ "laravel-echo-server", "start" ]
で、docker-compose
にも追加して、コンテナを起動する。
ポート6001を外から接続できる様にするのを忘れずに。
socket.io:
container_name: ${COMPOSE_PROJECT_NAME}-socket.io
build: socket.io
working_dir: "/var/www/app"
volumes:
- ${SRC_DIR}:/var/www/app
ports:
- "6001:6001"
restart: always
コンテナのログを確認して以下の様に表示されていればOK。
project-name-socket.io | L A R A V E L E C H O S E R V E R
project-name-socket.io |
project-name-socket.io | version 1.6.2
project-name-socket.io |
project-name-socket.io | ⚠ Starting server in DEV mode...
project-name-socket.io |
project-name-socket.io | ✔ Running at localhost on port 6001
project-name-socket.io | ✔ Channels are ready.
project-name-socket.io | ✔ Listening for http events...
project-name-socket.io | ✔ Listening for redis events...
project-name-socket.io |
project-name-socket.io | Server ready!
メッセージ送信処理を作る(構成図の①)
画面からWebサーバにメッセージをPOSTする処理を作る
(この辺はWebSocketに直接関係ないので省略。DBにメッセージを保存したりイベントを発行したりしてます。)
で、POSTしたら以下のエラーが発生した。
redisのPHP拡張がインストールされていないとの事。
ドキュメントにはcomposer require predis/predis
しろとしか書かれていないが・・・?
Please make sure the PHP Redis extension is installed and enabled.
predis
ではなくphpredis
に変えてみる。
phpのDockerfile
に以下を追記して再ビルドする。
RUN pecl install redis
RUN docker-php-ext-enable redis
改めてPOSTしてみるとエラーが変わった。.env
のREDIS_HOST
を設定していなかったので設定したら解決した。
Connection refused", exception: "RedisException"
これでメッセージが送信できる様になった。
ブラウザでイベントが受信できない(構成図の④)
一通り必要な物を用意して、POST(イベントをトリガー)しても、laravel-echoのlisten
が呼び出されない。
window.Echo.channel('messages') .listen('MessageSend', (e) => { console.log('receive MSG', e); // ここが呼ばれない });
調査のため、redisでsubscribeしてみる。redis-cli
で接続してsubscribe [チャンネル名]
する。
イベント待ちになるのでブラウザからPOSTを送ってみるも反応なし。
127.0.0.1:6379> subscribe messages
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "messages"
3) (integer) 1
逆にpublishしてみる。
ブラウザに通知は来ない(console.logに出力なし)
127.0.0.1:6379> publish messages aasdf
(integer) 1
LaravelのEventListenerが呼ばれているのか確認してみる。
ブレークポイントを掛けてPOSTしてみると、ブレークが掛かるので、問題なく呼ばれている。
原因が分からないのでBroadcastの実装を追いかけるIlluminate/Broadcasting/Broadcasters/RedisBroadcaster.php
のformatChannels
でチャンネル名を組み立てているのだが、ここでprefixが付与されている。
/** * Format the channel array into an array of strings. * * @param array $channels * @return array */ protected function formatChannels(array $channels) { return array_map(function ($channel) { return $this->prefix.$channel; }, parent::formatChannels($channels)); }
prefixはconfig/database.php
で定義されていてデフォルトはlaravel_database_
になっている
あらためてredis-cli
からprefix付きでsubscribe
してPOSTしてみるとメッセージが出力された。
redisまでは到達していることがわかった。
Laravel Echo Serverのログを確認するdocker-compose logs socket.io
で確認できる。
以下の様なエラーが発生している。
Laravel Echo Serverからredisに接続できていない模様。
project-name-socket.io | [ioredis] Unhandled error event: Error: connect ECONNREFUSED 127.0.0.1:6379
project-name-socket.io | at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1142:16)
laravel-echo-server.json
のdatabaseConfig
にホスト名やprefixを設定する
"database": "redis",
"databaseConfig": {
"redis": {
"host": "kvs",
"keyPrefix": "laravel_database_"
}
},
改めてLaravel Echo Serverのログを確認すると接続できているっぽいログが表示された。
project-name-socket.io | [3:40:13 PM] - XXXX left channel: messages (transport close)
project-name-socket.io | [3:40:14 PM] - XXXX joined channel: laravel_database_messages
project-name-socket.io | Channel: laravel_database_messages
project-name-socket.io | Event: App\Events\MessageSend
一応chromeの開発者ツールのネットワークタブでも確認してみると、こんな感じで表示された
送受信の詳細も確認できる。
こんな感じでイベントの受信もできる様になった。
完成!
左右に2つのブラウザを立ち上げて、お互いにメッセージを送り合える事を確認して完成!
所感
Pusherの料金表を見ると最安で月額約5000円〜との事なので、Laravel Echo ServerやRedisをメンテするコストを考えるとPusherを使ったほうが安そうだなぁって思いました・・・。
Webエンジニアをやっています
UX/UIデザインからプログラミング、DB設計、SEO、インフラ構築など幅広く対応してます
PHP/PHPUnit/Laravel/Vue/Nuxt/Docker/Terraform
ご連絡はTwitterのDMまで。