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

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.
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を起こすのも迷惑だしどうするのが一番いいかな?

参考

作成したBOT。twitterで指定のキーワードをつぶやくと仮想通貨を投げてくれる。
ソースコード
似たような問題に対処してる方

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です