Node.js の開発版で http2 標準モジュールを試す


## 概要

Node.js v8.4 から http2 モジュールが実験的な機能として利用できるようになりました ([プルリクエスト](https://github.com/nodejs/node/pull/14239))。スクリプトの実行時にフラグを指定する必要があります。

## nghttp2 の採用

http2 モジュールの実装には nghttp2 が採用されています。nghttp2 は curl、Apache HTTP Server (mod_http2) で採用されています。

## HTTP/2 の普及状況

W3Techs の[統計](https://w3techs.com/technologies/details/ce-http2/all/all)によれば、2017年8月1日時点でHTTP/2 に対応したウェブサイトは全体の15％を超えるとのことです。同じく W3techs のサーバーシェア[統計](https://w3techs.com/technologies/overview/web_server/all)によれば Node.js は全体の0.3％を占めます。

日本国内において大手ホスティングサービスのロリポップが HTTP/2 に対応し、Let's Encrypt を利用することで独自ドメインの SSL/TLS を無料で提供しています。Chrome や Firefox は HTTP/2 通信の際に SSL/TLS を必須としています。

## 開発版のインストール

Node.js v8.4 がリリースされる以前の2017年8月5日から Node.js の開発版 (nightly 版) で http2 が利用できるようになったので、NVS (Node Version Switcher) を導入することにしました。NVS は Windows のためのインストーラーが用意されています。

```bash
nvs add nightly
nvs use nightly
```

バージョン番号のつけられた最新版をインストールするのであれば、次のコマンドを実行します。

```bash
nvs add latest
nvs use latest
```

## 実行時のコマンドフラグ

記事の執筆時点で http2 は実験的な段階にあるので、実行時に `--expose-http2` フラグを指定する必要があります。

```bash
node --expose-http2 client.js
```

## ドキュメント

Node.js のソースコードに含まれる [doc/api/http2.md](https://github.com/nodejs/node/blob/master/doc/api/http2.md) に　http2 モジュールの使い方が記載されています。

## HTTP クライアント

http2 の[マニュアル](https://github.com/nodejs/node/blob/master/doc/api/http2.md) にいくつかのコードの例が記載されています。HTTP/2 に対応したサイトに対して GET リクエストを送信してみましょう。

```js:client.js
const http2 = require('http2');
const client = http2.connect('https://stackoverflow.com');

const req = client.request({ ':method': 'GET', ':path': '/' });
req.setEncoding('utf8');

req.on('response', (headers, flags) => {
  console.log(headers);
});

let data = '';
req.on('data', (d) => data += d);
req.on('end', () => client.destroy());
req.end();
```

いろいろな HTTP メソッドやレスポンスを試したいのであれば、https://nghttp2.org/httpbin/ を利用するとよいでしょう。

## HTTP サーバー

今度は HTTP サーバーを用意してみましょう。

```js:server.js
const http2 = require('http2');
const fs = require('fs');

const options = {
  key: fs.readFileSync(__dirname + '/server.key'),
  cert: fs.readFileSync(__dirname + '/server.crt')
};

const server = http2.createSecureServer(options);
server.on('stream', (stream, headers) => {
  stream.respond({
    ':status': 200,
    'content-type': 'text/html; charset=utf-8',
  });
  stream.end('hello world!');
});
server.listen(3000);
```

Chrome や　Firefox が SSL/TLS を必須としているので、これらのブラウザーで自己署名証明書を用意します。


## 定数

HTTP メソッドやヘッダーの名前が定数として定義されています。

```js
const {
  HTTP2_HEADER_STATUS,
  HTTP2_HEADER_CONTENT_TYPE
} = http2.constants;


server.on('stream', (stream, headers) => {
  stream.respond({
    [HTTP2_HEADER_STATUS]: 200,
    [HTTP2_HEADER_CONTENT_TYPE]: 'text/html; charset=utf-8'
  });
  stream.end('hello world!');
});
```

定義されている定数をすべて表示させてみましょう。

```bash
> node --expose-http2
> const http2 = require('http2')
> http2.constants
```

## サーバープッシュ

クライアントからのリクエストが来る前にサーバー側から JavaScript、画像、CSS のアセットをプッシュすることができます。2回目以降にアセットがサーバーにキャッシュされているかを調べるのに Cookie を使います。

クライアントのキャッシュの状態を判断できるサーバープッシュを CASPer (cache-aware server-push) と呼びます。Cookie を使うことの問題はファイルのパスやファイルのバージョンを示すのに使われる Etag によって Cookie の容量が増えてしまうことです。

HTTP サーバーの h2o の場合、Cookie の容量を減らすために Bloom フィルタ をもとに判定します。Apache の場合、`H2PushDiarySize` でサーバープッシュした URL の数の上限を指定できます。

## その他
### 自己署名証明書の生成

TLS/SSL の自己署名証明書は次のワンライナーで生成できます。

```bash
# https://stackoverflow.com/a/41366949/531320
openssl req -x509 -newkey rsa:4096 -sha256 \
-nodes -keyout server.key -out server.crt \
-subj "/CN=example.com" -days 3650
```

### `@std/esm` による import

`@std/esm` を導入すれば import/export が使えるようになります。

```js
import http2 from 'http2';
```

モジュールバンドラーの Webpack と比べてスクリプトを修正するたびにビルドしなくてすむので手軽です。Yarn で導入するには次のコマンドを実行します。

```bash
yarn add @std/esm
```

コマンドラインからスクリプトを実行するには `-r` (require) オプションで ``std/esm` を読み込みます。

```bash
node --expose-http2 -r @std/esm client.mjs
```

デフォルトではスクリプトの拡張子を `mjs` にする必要があります。`js` を読み込むことができるようにするには　`package.json` に次の設定を追加する必要があります。


```js:package.json
{
  "@std/esm": { "esm": "js" }
}
```



```bash
node --expose-http2 -r @std/esm client.js
```