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

  • 12
    いいね
  • 0
    コメント

概要

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

nghttp2 の採用

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

HTTP/2 の普及状況

W3Techs の統計によれば、2017年8月1日時点でHTTP/2 に対応したウェブサイトは全体の15%を超えるとのことです。同じく W3techs のサーバーシェア統計によれば 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 のためのインストーラーが用意されています。

nvs add nightly
nvs use nightly

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

nvs add latest
nvs use latest

実行時のコマンドフラグ

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

node --expose-http2 client.js

ドキュメント

Node.js のソースコードに含まれる doc/api/http2.md に http2 モジュールの使い方が記載されています。

HTTP クライアント

http2 のマニュアル にいくつかのコードの例が記載されています。HTTP/2 に対応したサイトに対して GET リクエストを送信してみましょう。

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 サーバーを用意してみましょう。

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 メソッドやヘッダーの名前が定数として定義されています。

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!');
});

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

> 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 の自己署名証明書は次のワンライナーで生成できます。

# 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 が使えるようになります。

import http2 from 'http2';

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

yarn add @std/esm

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

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

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

package.json
{
  "@std/esm": { "esm": "js" }
}
node --expose-http2 -r @std/esm client.js