アプリケーション開発ポータルサイト
ServerNote.NET
Amazon.co.jpでPC関連商品タイムセール開催中!
カテゴリー【Node.jsJavaScript
【Node.js】標準httpsモジュールで同期的にURLコンテンツを取得する
POSTED BY
2023-10-04

Node/javascriptは処理が非同期で行われるので、普通にやると結果を待たずに次の処理が実行されてしまう。

<やりたいこと>
・標準httpsモジュールで外部コンテンツを取得、「取得が終わってから」次の処理へ行きたい。
・タイムアウト値を設定してタイムアウトしたらhttps通信を中止したい。
・ステータスHTTP 200番台以外はエラーとしたい。

<解決方法>
・同期したい(待ちたい)処理をまるまるPromiseクラスで囲ってしまえばよい。
・必要なデータの取得が済んだらPromise内でresolve関数に渡せば呼んだ側の戻り値としてデータが返る。
・エラーが発生した場合Errorオブジェクトを生成してreject関数に渡せば呼んだ側のcatchブロックのerrとしてエラーが返る。
・そのようにしてPromiseから返ったデータの型によって処理を分けたい場合res.constructor.nameを見ればクラスがわかるので分岐可能。
optionsにtimeoutをミリ秒で指定可能。タイムアウトイベントが発生するのでそのときabortを呼べばよい。

<ソースコード>

JavaScriptpromise_https.jsGitHub Source
// 同期的にhttps通信するサンプル
const https = require('https');

function promiseRequest(url, options, postData) {
return new Promise(function (resolve, reject) {

  var req = https.request(url, options, (res) => {
    console.log('statusCode:', res.statusCode);
    console.log('headers:', res.headers);

    //200台以外はエラーとする場合
    if(res.statusCode >= 300) {
      reject(new Error('res.statusCode >= 300'));
    }

    var chunks = []; //チャンクレスポンスストック配列

    res.on('data', chunk => chunks.push(Buffer.from(chunk)))
    .on('end', () => { //チャンクレスポンスストリーム終了
      var buffer = Buffer.concat(chunks);
      resolve(buffer);
    });
  });

  req.on('error', (err) => {
    reject(err);
  });

  req.on('timeout', () => {
    req.abort();
    reject(new Error('request timed out'));
  });

  if(postData != null) {
    req.write(postData);
  }

  req.end();

});
}

async function main() {

  const url = 'https://www.yahoo.co.jp/';

  var options = {
    method: 'GET',
    timeout: 5000,
    //headers: {
      //'Content-Type': 'application/json',
    //},
  };

  //var postData = JSON.stringify({
  //  'limit' : 50
  //});
  var postData = null;

  try {
    const res = await promiseRequest(url, options, postData);
    //Promiseがresolveするとresがここに返る
    console.log(res.constructor.name); // =Buffer
    console.log(res.toString());
  }
  catch(err) { //Promiseがrejectするとerrがここに返る
    console.log(err.constructor.name); // =Error
    console.log(err.stack);
  }

}

main();

・async宣言した関数内でawait promiseRequestとし、https通信完了まで待つ。try~catchで囲わないと正常に動作しないので注意。
・HTTP 200番台以外をエラーとし、自分でErrorオブジェクトを生成しrejectで抜けている。
・HTTPS通信のデータ取得はチャンク(chunk)という細切れで数回に分けてストリーム取得されるため、chunks配列に都度格納し、endイベント発生時に一括Buffer変換し、処理成功としてresolveで呼び出し元へデータを返す。
・通常HTMLなどのテキストデータなら、受け取った側はtoStringで文字列変換して表示できる。
・optionsをPOSTにしてContent-Typeをjson、postData JSONデータをセットすれば、POSTメソッドによるREST APIの呼び出しにも対応する。
・optionsにtimeout5000=5秒でreq.on(timetout)が呼ばれる。その時忘れずabortして処理を中止し自前でErrorオブジェクトを生成しrejectで抜ければOK。

<実行結果>

node promise_https.js

statusCode: 200
headers: {
  server: 'ATS',
  date: 'Sat, 17 Sep 2022 01:53:50 GMT',
  'content-type': 'text/html; charset=UTF-8',
  'accept-ranges': 'none',
  'cache-control': 'private, no-cache, no-store, must-revalidate',
  expires: '-1',
  pragma: 'no-cache',
  'set-cookie': [
    'B=1medvfthiaa5e&b=3&s=th; expires=Tue, 17-Sep-2024 01:53:50 GMT; path=/; domain=.yahoo.co.jp',
    'XB=1medvfthiaa5e&b=3&s=th; expires=Tue, 17-Sep-2024 01:53:50 GMT; path=/; domain=.yahoo.co.jp; secure; samesite=none'
  ],
  vary: 'Accept-Encoding',
  'x-content-type-options': 'nosniff',
  'x-frame-options': 'SAMEORIGIN',
  'x-vcap-request-id': '07baffbf-08d0-4fa1-593d-38c6fbd75061',
  'x-xss-protection': '1; mode=block',
  age: '0',
  'transfer-encoding': 'chunked',
  connection: 'close'
}
Buffer
<!DOCTYPE html><html lang="ja"><head><meta charSet="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/><title>Yahoo! JAPAN</title><meta name="description" content="あなたの毎日をアップデートする情報ポータル。検索、ニュース、天気、スポーツ、メール、ショッピング、オークションなど便利なサービスを展開しています。"/>
....中略....
})();</script></body></html>

<テストでtimeout: 1としてタイムアウトを発生させた場合>

node promise_https.js

Error
Error: request timed out
    at ClientRequest.<anonymous> (/home/hogeuser/promise_https.js:31:10)
    at ClientRequest.emit (node:events:394:28)
    at TLSSocket.emitRequestTimeout (node:_http_client:761:9)
    at Object.onceWrapper (node:events:513:28)
    at TLSSocket.emit (node:events:406:35)
    at TLSSocket.Socket._onTimeout (node:net:486:8)
    at listOnTimeout (node:internal/timers:557:17)
    at processTimers (node:internal/timers:500:7)
※本記事は当サイト管理人の個人的な備忘録です。本記事の参照又は付随ソースコード利用後にいかなる損害が発生しても当サイト及び管理人は一切責任を負いません。
※本記事内容の無断転載を禁じます。
【WEBMASTER/管理人】
自営業プログラマーです。お仕事ください!
ご連絡は以下アドレスまでお願いします★

☆ServerNote.NETショッピング↓
ShoppingNote / Amazon.co.jp
☆お仲間ブログ↓
一人社長の不動産業務日誌
【キーワード検索】