Azure Functions 内で追加モジュールを使わずに HTTP Client を動かす方法


何に使えるのか?

Azure Functions から SaaS 等の REST API が呼べるようになります。

論よりコード

※ サーバ側には WebHook.site を利用しています

const http = require('https');
module.exports = function (context, req) {
    const url = `https://webhook.site/9e3f8370-c5e8-4771-89c3-71846257a7c7`;
    const postData = {"k1": 1, "k2": "v2"};

    const postBody = JSON.stringify(postData);
    const options = {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Content-Length': Buffer.byteLength(postBody)
        }
    };

    const client = http.request(url, options, (res) => {
        context.log(res.statusCode);
        context.log(res.headers);
        let resBody = '';
        res.on('data', (chunk) => { resBody += chunk; });
        res.on('end', () => {
            context.log(resBody);
            context.res = {status: 204};
            context.done();
        });
    });
    client.on('error', (e) => {
        context.log.error(e);
        context.res = {status: 500};
        context.done();
    });
    client.write(postBody);
    client.end();
};

実行結果

2019-10-18T05:10:36.631 [Information] Executing 'Functions.HttpTrigger1' (Reason='This function was programmatically called via the host APIs.', Id=c5825099-7df7-4315-999a-fd863b3b157a)
2019-10-18T05:10:38.005 [Information] 201
2019-10-18T05:10:38.006 [Information] { server: 'nginx/1.10.3',
  'content-type': 'text/plain; charset=UTF-8',
  'transfer-encoding': 'chunked',
  connection: 'close',
  'x-request-id': '7a4ed117-26a0-4051-880e-a4378fb00a19',
  'x-token-id': '9e3f8370-c5e8-4771-89c3-71846257a7c7',
  'cache-control': 'no-cache, private',
  date: 'Fri, 18 Oct 2019 05:10:37 GMT',
  'x-ratelimit-limit': '100',
  'x-ratelimit-remaining': '99' }
2019-10-18T05:10:38.006 [Information] GET-Wild
2019-10-18T05:10:38.007 [Information] Executed 'Functions.HttpTrigger1' (Succeeded, Id=c5825099-7df7-4315-999a-fd863b3b157a)

実装のポイント

  • exports へは、同期関数として宣言している (async が無い)
    • 理由は後述
  • end イベントハンドラ内、および error イベントハンドラ内で context.done() を呼び出して関数の終了を Azure Functions に伝達している
  • レスポンスボディに対する処理を end イベントハンドラに移している
    • 正直これはどうでもいい

async 関数内では request メソッドの callback が実行されない

なぜこんなエントリーを書いたのかというと、Azure Functions における Node.js の最近のランタイムでは async/await を用いた非同期処理が推奨されており、 util.promisify を用いた async/await 化の方法も掲載されているのですが、これが適用可能なメソッドの条件が様々ある中、残念なことに (私は) HTTP/HTTPS モジュールの request メソッドを async/await 対応させることができませんでした。

もちろん Promise を使えばいいのですが、はっきり言ってデカイ。ちょこっとやりたい事を実現するにはデカすぎる。 (だから util.promisify が産まれたのだから)
ということで、最初のコードを見ていただくと module.exports = function (...async が無くなっていることにお気づきになるでしょう。

もし、Node.js の HTTP モジュールのページにあるサンプル実装 を、そのまま Azure Functions で async 宣言内への実装をすると以下のようなコードになると思いますが、結論から言えば POST は送信されるけど、そのレスポンスを処理する callback は動きません

const http = require('https');
module.exports = async function (context, req) {
    const url = `https://webhook.site/9e3f8370-c5e8-4771-89c3-71846257a7c7`;
    const postData = {"k1": 1, "k2": "v2"};

    const postBody = JSON.stringify(postData);
    const options = {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Content-Length': Buffer.byteLength(postBody)
        }
    };

    const client = http.request(url, options, (res) => {
        context.log(res.statusCode);
        context.log(res.headers);
        res.on('data', (chunk) => {
            context.log(chunk);
            context.res = {status: 204};
        });
    });
    client.on('error', (e) => {
        context.log.error(e);
        context.res = {status: 500};
    });
    client.write(postBody);
    client.end();
};

実行結果

2019-10-18T05:09:22.257 [Information] Executing 'Functions.HttpTrigger1' (Reason='This function was programmatically called via the host APIs.', Id=539cfd67-bcd9-4af6-90fd-cf6405fbe085)
2019-10-18T05:09:23.243 [Information] Executed 'Functions.HttpTrigger1' (Succeeded, Id=539cfd67-bcd9-4af6-90fd-cf6405fbe085)

POST メソッド以外は?

options.method'GET' とか入れれば動くよ。

https なのに http とか書いてるのは、何?

const http = require('https');
// でも
const http = require('http');
// でも、後ろのコードに影響がないようにしただけ。

あとがき

前に request npm パッケージを入れて HTTP リクエストする方法 を書いたけど、なんだか動かなくなっちゃったから調べたの。標準モジュールだけで動くのはありがたいけれど、async/await がわからんね。

EoT