Corredor

ウェブ、プログラミングの勉強メモ。

axios を使わず Node.js 標準モジュールの http・https だけでリクエストを投げる

request・request-promise・axios・node-fetch などに頼らず、Node.js スクリプトから外部 API をコールするリクエストを投げてみたくなった。

Node.js においては、http モジュールおよび https モジュールを利用すればリクエストを投げられる。

const http  = require('http');
const https = require('https');

/**
 * リクエストする
 * 
 * @param {string} url URL
 * @param {object} options オプション
 * @return {Promise<*>} Promise
 */
function request(url, options) {
  return new Promise((resolve, reject) => {
    // 引数の確認・調整
    if(!url || typeof url !== 'string') { return reject('Invalid URL Argument'); }
    options = options || {};
    
    // タイムアウト指定があれば控える
    const timeout = options.timeout || null;
    if(options.timeout) { delete options.timeout; }
    
    // リクエストボディがあれば控える
    const body = options.body || null;
    if(options.body) { delete options.body; }
    
    // レスポンスエンコーディング指定があれば控える
    const responseEncoding = options.responseEncoding || 'utf8';
    if(options.responseEncoding) { delete options.responseEncoding; }
    
    // プロトコルに合わせて使用するモジュールを決める
    const agent = url.startsWith('https:') ? https : http;
    
    const req = agent.request(url, options, (res) => {
      res.setEncoding(responseEncoding);
      let data = '';
      res.on('data', (chunk) => {
        data += chunk;
      })
        .on('end', () => {
          resolve(data);
        });
    })
      .on('error', (error) => {
        reject(error);
      })
      .on('timeout', () => {
        req.abort();
        reject('Request Timeout');
      });
    
    // プロパティがあれば指定する
    if(timeout) { req.setTimeout(timeout); }
    if(body) { req.write(body); }
    req.end();
  });
}

こんな関数を作れば良い。

使用する時はこんな感じで。

(async () => {
  // 通常の GET リクエスト
  const getResult = await request('http://example.com/');
  
  // API に POST リクエストする
  const postResult = await request('https://example.com/api/write', {
    method: 'POST',
    headers: {
      'Accept'      : 'application/json',
      'Content-Type': 'application/json;charset=utf-8',
      'User-Agent'  : 'my-node-js-script'
    },
    // タイムアウトを指定する
    timeout: 5000,
    // リクエストボディは以下のように書く
    body: JSON.stringify({
      id  : 'HOGE',
      name: 'FUGA'
    })
  });
  // レスポンスは文字列なので、JSON 文字列の場合は適宜パースする
  const jsonResult = JSON.parse(postResult);
})();

なんで http モジュールと https モジュールを使い分けないといけないのかというと、エラーが出るから。

  • http.request()https:// な URL を渡すと…
    • TypeError [ERR_INVALID_PROTOCOL]: Protocol "https:" not supported. Expected "http:"
  • https.request()http:// な URL を渡すと…
    • TypeError [ERR_INVALID_PROTOCOL]: Protocol "http:" not supported. Expected "https:"

モジュールが別れているとおり、メソッドの作りなんかは似ているものの、内部的には完全に別物のようだ。

オプションの連想配列は、httphttps モジュールの request() メソッドにほぼそのまま渡している。タイムアウトとリクエストボディを渡すために、独自に timeoutbody プロパティを受け付けてハンドリングするように記述している。

一応レスポンスエンコーディングも指定できるようにしてみたが、UTF-8 以外は未検証。

HTTP ステータスコードは見ていないので、401 などでレスポンスされても Reject はされない。その辺凝り始めると axios の再発明だなーと思って止めた。ハンドリングしたければ res.on('end') 内で判定ロジックを実装するか、resolve({ req, res, data }) と全部ぶん投げちゃって外でやることにすれば良いかと。