node-twitterでstreamAPIに再接続するときの実装
Posted: 2018-06-06

node-twitterを使用すると tweet はもちろん filter/stream の取得も簡単に行えてテスト実行レベルでは何の問題もない。
ただ実際に BOT を稼働させてみるとたまに stream が切断されて即時再接続を行おうとしてエラーコード 420(速度制限)が返ってくる挙動がみられる。例えばアプリがクラッシュしたときに service 化などで自動再起動設定にしていて即時再接続を行うと、エラーコード 420 を延々繰り返してゾンビ化してしまう(しまった)。
解決策
twitter のドキュメントを調べてみると 420 が返ってきた場合(と他のエラーの場合)の再接続のベストプラクティスが示されているのでそれに従う。また stream も一度にひとつだけ開くようにする。
- Back off exponentially for HTTP 420 errors. Start with a 1 minute wait and double each attempt. Note that every HTTP 420 received increases the time you must wait until rate limiting will no longer will be in effect for your account.
参考: https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/connecting
HTTP 420 error の場合インターバルを置き再々接続までの時間を最大値まで指数関数的に増やすようにと指示されている。(例:60 秒,120 秒,240 秒,...3600 秒)
node-twitter では実装の例などは無いので自分で実装する必要があるが、issue/159 にコードの例があり参考にできる。 https://github.com/desmondmorris/node-twitter/issues/159
ただしここにおける stream destroy()の問題は解決しているようだし setTimeout()も Promise でラップして書きたいので書き直してみた。
実装
'use strict';
const Twitter = require('twitter');
const client = new Twitter({
consumer_key:process.env.TWITTER_CONSUMER_KEY,
consumer_secret:process.env.TWITTER_CONSUMER_SECRET,
access_token_key:process.env.TWITTER_ACCESS_TOKEN_KEY,
access_token_secret:process.env.TWITTER_ACCESS_TOKEN_SECRET
});
var timeintervalsec = 1;
var exponent = 0;
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
// インターバルを 60* 2^0SEC,60*2^1SEC,60*2^2SEC...として main task を実行する実装
function main() {
timeintervalsec=60*Math.pow(2, exponent);
wait(timeintervalsec*1000)
.then(() => {
letdate=newDate();
console.log(date+' Reconnecting... Interval Time = '+timeintervalsec+" sec")
varstream=client.stream('statuses/filter', { track:'something' });
stream.on('data', function (event) {
exponent=0; //初期化
console.log('Maintask');
});
stream.on('error', function (error) {
if (exponent>6) { // exponent が 6 以上つまり 2^12=3840 > 3600sec=1hour の場合処理を終了する
console.log('再接続間隔が 1 時間以上となったため終了');
throwerror;
} else if (error.message == 'Status Code: 420') { // 420 の場合はインターバルを増やして再接続する
stream.destroy(); //多重起動を防止するため
exponent++;
main();
} else {
console.log('420 以外のエラーによる終了');
throwerror;
}
});
})
.catch();
};
main();
(recconect.js)
議論
- コードは 420 の場合のみなので他のエラーの場合も実装してもいいかもしれないが、素直にクラッシュさせたほうがいいと思ってこのまま使用していたりする。
- twitter から 420 が返ってこないとテストできない。そこで邪道かもしれないがソースコードの 420 のところを 401 にして KEY を一部変更して起動すると twitter から 401 error が返ってくるので指数関数的に間隔をあけて、けなげに再接続を試みているのを観察できる。わざと 420 を起こすのも迷惑だしどうするのが一番いいかな?
参考
https://twitter.com/tipnem_faucet/with_replies
作成した BOT。twitter で指定のキーワードをつぶやくと仮想通貨を投げてくれる。
ソースコード
https://github.com/ocknamo/tipnem_faucet
似たような問題に対処してる方