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:"
モジュールが別れているとおり、メソッドの作りなんかは似ているものの、内部的には完全に別物のようだ。
オプションの連想配列は、http
や https
モジュールの request()
メソッドにほぼそのまま渡している。タイムアウトとリクエストボディを渡すために、独自に timeout
・body
プロパティを受け付けてハンドリングするように記述している。
一応レスポンスエンコーディングも指定できるようにしてみたが、UTF-8 以外は未検証。
HTTP ステータスコードは見ていないので、401 などでレスポンスされても Reject はされない。その辺凝り始めると axios の再発明だなーと思って止めた。ハンドリングしたければ res.on('end')
内で判定ロジックを実装するか、resolve({ req, res, data })
と全部ぶん投げちゃって外でやることにすれば良いかと。