先日、WEBサイト制作者さん向けのウェブサービスをリリースしたのですが、制作の際に得た知見をシリーズで共有していこうと思います。
WebSocket の自動再接続が行われない
Crew'sHubには、リアルタイムのチャット機能があり、Web Socketを利用しています。
また、データ管理画面でも、プロジェクトメンバーが編集したら即座に更新されるようになっており、やはりここでもWebSocketを利用しています。
開発中に遭遇した問題として、WebSocketが切断された際に、自動再接続がうまく行われないということがありました。
クライアント側では、Reconnecting WebSocketという、WebSocketがクローズされた際に自動的に再接続が行われるモジュールを採用しています。
reconnecting-websocket
https://www.npmjs.com/package/reconnecting-websocket
自動再接続されるはずのモジュールではありますが、ノートPCでスリープから復帰した際など、切断されたままの状態になってしまう不具合が度々発生しました。
そこで、SetTimeoutで定期的にWebSocketのreadyStateを確認して、コネクションが切断されていたら再接続するようにしてみたのですが、それもうまく機能しませんでした。どうもコネクションが切断されているにも関わらず、readyStateにそれが反映しないことがあるようです。
Ping Pong で接続状況を確認する
WebSocket.readyStateが使えないので、実際にサーバと定期的に通信を行って接続状況を確認する方法に切り替えました。
クライアント側はこんな感じです。
let pingPongTimer = null
const ws = new ReconnectingWebSocket(`wss://${process.env.API_HOST}/api/v1/`)
const checkConnection = () => {
setTimeout(() => {
ws.send('ping')
pingPongTimer = setTimeout(() => {
console.log('再接続を試みます')
pingPongTimer = null
ws.reconnect()
}, 1000)
}, 30000)
}
ws.onopen = () => {
checkConnection()
...
}
ws.onmessage = ({ data }) => {
if (data === 'pong') {
if (pingPongTimer) {
clearTimeout(pingPongTimer)
}
return checkConnection()
}
...
}
以下のような処理が行われます。
- WebSocketがopenしたら、checkConnectionをコールします。
- checkConnectionは、呼ばれてから30秒後にサーバにpingを送信します。
- ping送信後1秒以内にpongが返って来なかったら再接続を試みます。
- ping送信後1秒以内にpongが返って来たら、checkConnectionを再度コールします。
サーバ側はNodeで構成されており、以下のように実装しています。(一部抜粋)
// ユーザーからのメッセージを処理する
ws.on('message', (message) => {
// pingの処理
if (message === 'ping') {
return ws.send('pong')
}
...
}
以上のような処理に変更してからは、コネクションが維持されるようになりました。
現在は、コネクションの確認を30秒ごとに行っていますが、目的によって間隔を調整すると良いと思います。
最後に
繰り返しになりますが、WEBサイト制作者さん向けのサービスをリリースしましたので、お気軽にお試しいただけると幸いです。(無料で使えます)
元々は自分たち向けに作ったシステムでして、WEBサイト再作時に大量の情報やファイルを管理・共有するのに疲れ果て、それを解消するために作ったウェブアプリです。なかなか評判が良かったので、一般向けに作り直してリリースしました。
ウェブサイト制作に携わる人達に使っていただきたいウェブサービスをリリースしました。
— 橋本技研@売れるネットショップ研究所 WEB技術×販促 (@hashimotosubaru) April 5, 2020
ウェブサイトを作る際、大量の情報やデータを管理する必要がありますが、それをスッキリ整理整頓できて、簡単にチームで共有できます。
昨年の夏から作り始めて、やっと公開です。https://t.co/kFOCip3eUD