Node.js request モジュールで Digest 認証の画像取得時にエラーハンドリングするメモ

見守りカメラ Qwatch TS-NS110W から Node.js で画像を取得する記事で request のサンプル書いていたら、エラーハンドリングがちゃんとできてなかったので、直してみます。

以前のコード

const request = require('request');
const fs = require('fs');
const url = 'http://<IPAddress>:<httpPort>/snapshot.jpg';
const options = {
  'auth': {
    'user': 'username',
    'pass': 'password',
    'sendImmediately': false
  }
};
 
request.get(url, options).pipe(fs.createWriteStream('snapshot-record.jpg'))

前回の記事 ですが、正常系しか試してなくて、仮にエラーが起きたりアカウント認証できなくても、そのエラーをそのまま画像にしてしまって、中が見れないという状況になってました。

修正したコード

といいつつも「エラーを判定しつつ、その時は pipe の前で止めてデータを受け入れない」という処理が、思いのほか、調べるのが大変でした。

紆余曲折を経て、以下の記事を発見しました。

Simplified Stream/Pipe Error Handling · Issue #647 · request/request

ほうっておくと、やっぱり pipe が走ってしまうので pause で止めて今回の request 参照を保持しつつ、レスポンスを待つところが大事なところ。

HTTP ステータスエラーやそのほかのタイムアウトのようなエラーを判定して、もしも、正常に動いたときだけ今回の request 参照 で pipe を再開して保存する動きを作るようです。思ったより複雑だった・・・。

ということで、反映したソースはこちら。

request = require('request');
const fs = require('fs');
const url = 'http://<IPAddress>:<httpPort>/snapshot.jpg';
const options = {
  'auth': {
    'user': 'username',
    'pass': 'password',
    'sendImmediately': false
  }
};

// https://github.com/request/request/issues/647#issuecomment-499518677
let r = request.get(url, options);  // 変数 r で参照を保持
r.pause();  // 一旦リクエストを止めて待つ
r.on('response', function(response) {
    if (response.statusCode === 200) {
        r.pipe(fs.createWriteStream('snapshot-record.jpg'))
            .on('error', function(err) {
                // error handling here
                console.log('createWriteStream error handling here');
                console.log('err: ' + err);
            });
        r.resume();
    } else {
        // status "error" handling here
        console.log('HTTP status "error" handling here');
        // 例 : response.statusCode:401 認証エラーとか
        console.log('response.statusCode: ' + response.statusCode);
    }
})
.on("error", function(err) {
    // error handling here
    // 例 : err: Error: connect ETIMEDOUT 192.168.XXX.XXX:PORT
    console.log('request error handling here');
    console.log('err: ' + err);
});

うーむ、正直、あまり実装の想像つかなかったし、連携するときにはなかなか複雑な印象。こうなると、 await / async や Promise で非同期処理を行うほうが見通しが良いなあと思いつつ、とにもかくにも対処法が分かって良かったです!