Edited at

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

More than 1 year has passed since last update.

最近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優先にする

現時点ではChromeやFirefoxであれば1の対処だけでOK。

Mac/iOS Safariやnode.jsのsocket.io-clientなどの場合は2の対処までしてやる必要があります。


HerokuのHttp Session AffinityをONにする

詳しくは コチラ

heroku features:enable http-session-affinity

この問題を調べるとsiticky sessionを使え!って出てくるのだけど、 herokusticky session使えないよ!というスタックオーバーフローのページが沢山出てくる。公式的には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非対応のオールドブラウザでの接続確立までの時間が短くなるという理由らしいです。