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

## 何に使えるのか？

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

## 論よりコード

※ サーバ側には [WebHook.site](http://webhook.site) を利用しています

```js
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 に伝達している
    - 同期関数として宣言した場合は[`context.done()`を呼び出す必要がある](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-reference-node#contextdone-method)
-  レスポンスボディに対する処理を `end` イベントハンドラに移している
    - 正直これはどうでもいい

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

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

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

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

```js
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 とか書いてるのは、何？

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

## あとがき

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

EoT