kakts-log

programming について調べたことを整理していきます

node.js nightlyバージョンで使えるようになった http2モジュールについて

概要

先日 node.jsにおいて http/2の実装がマージされました。
experimentalな機能で、現在だとnode nightlyバージョン(9.x系)で実行時にオプションを付けることで http2 コアモジュールが利用可能です。
近々node.js 8.xに取り込まれるようで年内にはちゃんと使えるようです。
今回は、このhttp2コアモジュールについて簡単な使い方と内部実装について調べたことをまとめます。

ドキュメントはnodeのmasterブランチにマージされており、見ることができます。
- github.com

http2コアモジュールの実装について

http2の実装はnghttp2というCライブラリが採用されております。 このライブラリはhttp/2の実装のなかで人気であり、 RFC7540 HTTP/2 とRFC7541の仕様に基づいて実装されています。
- github.com

http2 コアモジュールのJS apiは下記で定義されています。
- node/http2.js at master · nodejs/node · GitHub

http/2のJS apiの各実装は lib/internal/http2配下のファイルにあります。内部の実装を見たい人はここで見ることができます。
- node/lib/internal/http2 at master · nodejs/node · GitHub

nghttp2ライブラリへのインターフェースはsrc/node_http2.ccと src/node_http2.hで定義されています
nghttp2とコアモジュールのやり取りはこのあたりを見ればよいかと思います。
- node/node_http2_core.cc at master · nodejs/node · GitHub
- node/node_http2.h at master · nodejs/node · GitHub

上記のhttp2コアモジュールを使うにあたり、nodeの実行時に –expose-http2 フラグを追加することで、 require(‘http2’) が使えるようになります。
これは、すでに http2というnpmモジュールが存在しているため、 –expose-http2フラグを付けないと下記のnpmモジュールをrequireするようになり、既存のnpm http2モジュールを使っているプロジェクトは注意が必要です。
- www.npmjs.com

node.js nightly版のインストール

http2のコアモジュールを使うために、node.jsの最も最新のnightly版(現時点で9.x系)を使えるようにします。
mac OSの環境が前提ですが、 node.js nightly版を簡単にインストールできる node-nightlyというnpmモジュールを使います。

node-nightlyモジュールインストール
$npm install --global node-nightly

nightly最新版のnodeインストール
$ node-nightly 
Downloading the nightly version, hang on...
node-nightly is available on your CLI!

nightlyバージョンの確認
$ node-nightly -v
 New nightly available. To upgrade: `node-nightly --upgrade` 
v9.0.0-nightly20170806e96ca62480

これでnode.js nightly版を簡単にインストールすることができました。

http2.createSecureServerを使ってhttp2サーバを作る

ここでさっそくhttp2のTLS/SSLを使ったセキュアなサーバを作ってみます。
http2.createSecureServer()というメソッドで、サーバのインスタンスを作成することができます。

http2.createSecureServer()について

http2.createSecureServer()の内部の実装は、主要なところだけ抜粋すると以下のようになっています。 - https://github.com/nodejs/node/blob/master/lib/internal/http2/core.js#L2427-L2434

function createSecureServer(options, handler) {
  if (typeof options === 'function') {
    handler = options;
    options = Object.create(null);
  }
  debug('creating http2secureserver');
  // Http2SecureServerのインスタンスを返す
  return new Http2SecureServer(options, handler);
}

・・・

// Http2SecureServerクラス
class Http2SecureServer extends TLSServer {
  // コンストラクタ
  constructor(options, requestListener) {
    options = initializeTLSOptions(options);
    super(options, connectionListener);
    this[kOptions] = options;
    this.timeout = kDefaultSocketTimeout;
    this.on('newListener', setupCompat);
    if (typeof requestListener === 'function')
      this.on('request', requestListener);
    this.on('tlsClientError', onErrorSecureServerSession);
    debug('http2secureserver created');
  }

  // setTImeoutメソッド
  setTimeout(msecs, callback) {
    this.timeout = msecs;
    if (callback !== undefined) {
      if (typeof callback !== 'function')
        throw new errors.TypeError('ERR_INVALID_CALLBACK');
      this.on('timeout', callback);
    }
    return this;
  }
}

このメソッドの戻り値として、Http2SecureServerクラスのインスタンスを返します。
Http2SecureServerクラスはTLSServerクラスを継承しており、接続においてSSL/TLSを使う事となります。

TLSServerクラスはtlsというコアモジュールで定義され、実装は下記になります。

tls_wrapでTLSServerクラスの実装が見れます。
[https://github.com/nodejs/node/blob/master/lib/
tls_wrap.js#L784-L893]

かなり階層が深くなりますが、TLS/SSLを使ったセキュアなサーバを作る場合、http2.createSecureServerの引数にオプションを渡すことができ、 接続において使うTLS/SSLの証明書や秘密鍵を設定することができます。 optionとして、オブジェクトのkey, certにそれぞれ指定してあげます。
node/_tls_wrap.js at master · nodejs/node · GitHub

TLS/SSL証明書 秘密鍵の作成

今回はここで証明書と秘密鍵を指定するために、ローカルで自前で準備していきます。
ここではあくまでローカル環境でのhttp2サーバを作ることが目的のため、自前でローカルpcでopensslコマンドを使って作成します。
- stackoverflow.com

mac OSでopensslを使って以下のコマンドを実行することでカレントディレクトリに下記2つのファイルが生成されるので、 サーバインスタンス生成時にこれを指定します。
- server.crt: SSLサーバ証明書 - server.key: SSL公開鍵暗号用の秘密鍵

サーバを立ててみる

さっそく上記の説明を踏まえてhttp2サーバを立ててみます。
ここではあくまでhttp2コアモジュールのサンプルコードをそのまま使用します。

http2サーバインスタンス生成時に 証明書・秘密鍵を指定し、 3000番ポートで待ち受けます。
https://localhost:3000をブラウザでアクセスしてhello worldを表示させる簡単なものを作ります。

サーバを立てた後、アクセスがくると サーバインスタンスに対して"stream"イベントが発火されるので、 ハンドラ関数を設定します。
ここではresponse statusと hello worldを記述した簡単なhtmlをstreamで返します。

// http2コアモジュールをrequire
const http2 = require('http2');

// createSecureServerでのオプションを指定する
const options = {
  key: fs.readFileSync('server.crt'), // 公開鍵暗号の秘密鍵
  cert: fs.readFileSync('server.key') // TLS/SSL サーバ証明書
};

// Create a plain-text HTTP/2 server
const server = http2.createSecureServer(options);

// アクセスが来た場合、streamイベントが発火されう
server.on('stream', (stream, headers) => {
  stream.respond({
    'content-type': 'text/html',
    ':status': 200
  });
  stream.end('<h1>Hello World</h1>');
});

server.listen(3000);

chromeでアクセスする

ここで、サーバを立ち上げる事ができたので、chromeブラウザで https://localhost:3000へアクセスしてみます。
f:id:kakts:20170809005348p:plain
上述したように、今回はTLS/SSL証明書を自前で用意したため、chromeでwarning画面がでますが、無視して、「詳細設定」→「localhost にアクセスする(安全ではありません)」をクリックすることでアクセスが可能です。

f:id:kakts:20170809005555p:plain

まとめ

これでhttp/2のセキュアなサーバにアクセスできました。 今回は簡単な開設のため、http/2固有の機能を使ったサーバの作り方は解説せず、ここで終わります。
http/2固有の機能を使うことでよりパフォーマンスが向上するため、時間があるときに、http/2RFCをちゃんと読んで仕様を把握していきたいと思います。

参考

qiita.com

d.hatena.ne.jp