Herokuで複数Dynoにするとsocket.ioが繋がらなくなる問題への対処

最近HerokuでSocket.io依存のサービスを運用する機会があったので手順だけメモ。 (日本語記事あまり見つからないので、もしかしたら socket.io-redisがこの問題をも解決してくれているのかな…)

症状

Heroku上でクラスタリングしたsocket.io(node.js)との接続が確立できない。 エラーメッセージは下記2種類。ポーリングで接続成功した後に、websocketにupgradeしようとして失敗しているような感じ。最初に接続確立したときと違うサーバにつなぎに行こうとして怒られてしまっているみたい。

Failed to load resource: the server responded with a status of 400 (Bad Request)
WebSocket connection to ......... failed: WebSocket is closed before the connection is established.

手順

  1. heroku側でhttp-session-affinity をONにする
  2. socket.ioのfallback optionをwebsocket優先にする

現時点ではChromeFirefoxであれば1の対処だけでOK。 Mac/iOS Safariやnode.jsのsocket.io-clientなどの場合は2の対処までしてやる必要があります。

HerokuのHttp Session AffinityをONにする

詳しくは コチラ

heroku features:enable http-session-affinity

client側のsocket.ioのfallback optionをwebsocket優先にする

デフォルトではpolllingwebsocket の順番で接続をしていくsocket.ioですが、クラスタリングされたサーバに接続する場合は順番を指定して、websocketから繋ぐようにする。

const options = { 
  transports: ['websocket', 'polling' ]
};

const socket = io.connect( "URL" ,options);

付録

socket系 x クラスタリングで発生する問題としては、セッションが各nodeで共有されないことくらいで、broadcastのときに困るくらいだろうとタカをくくっていました。Broadcastの問題に関しては別のソリューションで解決していたので、今回はたまたまsocket.io-redisを使っていませんでした。もしかしたらこの問題自体、socket.io-redisが一緒に解決しちゃってくれる可能性もあります。

ちなみにsocket.io、以前はwebsocketから順番にfallbackしていっていたのを、どこかのバージョンで逆にするようにしています。そのほうがwebsocket非対応のオールドブラウザでの接続確立までの時間が短くなるという理由らしいです。