Contenu connexe
Similaire à Node の HTTP/2.0 モジュール iij-http2 の実装苦労話 (20)
Node の HTTP/2.0 モジュール iij-http2 の実装苦労話
- 1. Node の HTTP/2.0 モジュール
iij-http2 の実装苦労話
IIJ 大津 繁樹
2013年8月21日
第10回東京Node学園
- 2. 自己紹介
• 株式会社インターネットイニシアティブ(IIJ)
プロダクト本部 戦略的開発部所属
• twitter: @jovi0608
• github: https://github.com/shigeki/
• ブログ: http://d.hatena.ne.jp/jovi0608/
• Node とか、HTML5とか、HTTP/2.0とか、流行そうな
技術の評価検証してます。
• 最近、HTTP/2.0仕様修正やNode.js のHTTP/2.0モ
ジュールの開発をしています。
- 5. もう時代は先に (v0.11.5~)
• old mode/new mode はなくなり flowing mode/paused mode に、
• stream1 + stream2 = streams3
• data イベント復活
• ただし以前のAPIと互換性はありますのでご安心を
今回のiij-http2は Streams3 を利用
- 8. 年 月 トピック
2012年1月 IETF httpbis WGでHTTP/2.0の仕様検討開始することを決定
2012年11月 3つの候補案からSPDY仕様をベースにすることを決定
draft-00(SPDY/3仕様をそのまま)リリース
2013年1月 第1回中間会議(東京)
draft-01リリース(HTTPからのUpgrade方法を追加)
2013年4月 draft-02リリース(フレームフォーマット・タイプの大幅な変更)
2013年5月 draft-03リリース(中間会議に向けて修正点の整理・まとめ)
2013年6月 第2回中間会議(サンフランシスコ)
2候補案を合わせたヘッダ圧縮仕様の採用を決定
2013年7月 draft-04リリース(最初の実装仕様)
2013年8月 第3回中間会議(ハンブルグ)
最初のHTTP/2.0相互接続試験を実施
draft-05リリース(接続試験結果を反映)
これまでの HTTP/2.0 仕様策定作業の主な歩み
今ここ、
iij-http2 を持ち込んだ
2014年春に仕様化完了を目指す
- 10. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
Length(16) Type(8) Flags(8)
R Stream Identifier(31)
Frame Payload
HTTP/2.0 Frame Format
Type Name 役割
0x0 DATA リクエスト・レスポンスボディ
0x1 HEADERS リクエスト・レスポンスヘッダ
0x2 PRIORITY レスポンスの優先度設定
0x3 RST_STREAM ストリームのリセット
0x4 SETTINGS 設定情報
0x5 PUSH_PROMISE サーバプッシュの予約
0x6 PING 生死確認
0x7 GOAWAY 終了宣言
0x9 WINDOW_UPDATE フロー制御ウィンドウの更新
0x8は欠番
重要なのは
この二つ
最初の8バイト分を
見れば大丈夫
HTTP/2.0のフレー
ムヘッダは8バイト
- 11. iij-http2の設計方針
• 7/8に draft-04がリリース。実装期間は約4週間弱。
• node-spdyはクライアントがない。spdy/3フレーム
フォーマットに大きく依存した実装なのでヤメ。
• Chromeのspdyスタック(Visitorパターン)を参考
– コネクション生成→フレーム解析→コールバック→ストリー
ム処理
• Nodeのhttpモジュールを最大限に流用
(Outgoing/Incomingクラスを利用、後で後悔)
• HTTP/2.0サーバの初期ハンドシェイク(後述)
は、とりあえずTLS+NPNとDirectの2種だけに。
- 14. HTTP Serverのコア
lib/_http_server.js (v0.11)
function Server(requestListener) {
(中略)
this.addListener('connection', connectionListener);
}
util.inherits(Server, net.Server);
function connectionListener(socket) {
// とんでもない処理
// (おそらくNode.jsコアの中で最難関の一つ)
}
HTTP/2.0向けにconnectionListener をどう書くかがキモ
HTTPSサーバと共通化しているのでSSL化は楽
- 15. Hello World! with HTTP/1.1
var http = require(‘http’);
var server = http.createServer(function(req, res) {
res.writeHead(200, {‘content-type’: ‘text/plain’});
res.end(‘Hello World!’);
});
server.listen(8080);
目標:HTTP/2.0でこれと同じ処理ができるようにしよう
- 18. iij-http2コネクションリスナ実装概要
function connectionListener(socket) {
var self = this;
var connection = new Connection(socket, 'http_server', {});
socket.pipe(connection);
connection.on('Incoming', function(req) {
var res = new ServerResponse(req, req._stream);
res.assignSocket(connection);
self.emit('request', req, res);
});
}
pipeでソケットデータを渡す
リクエストヘッダの処理完了
Hello Worldのコールバックを emit
レスポンスオブジェクト生成
- 19. Connectionクラス
function Connection(socket, endpoint_type, opts) {
node_stream.Writable.call(this, opts);
(中略)
this.on('ConnectionHeader', OnConnectionHeader);
this.on('FrameHeader', OnFrameHeader);
this.on('FramePayload', OnFramePayload);
this.on('DATA', OnDATA);
this.on('HEADERS', OnHEADERS);
this.on('PRIORITY', OnPRIORITY);
this.on('RST_STREAM', OnRST_STREAM);
this.on('SETTINGS', OnSETTINGS);
this.on('PUSH_PROMISE', OnPUSH_PROMISE);
this.on('PING', OnPING);
this.on('GOAWAY', OnGOAWAY);
this.on('WINDOW_UPDATE', OnWINDOW_UPDATE);
}
結局バカでかいクラスに。 Chrome も同じでした。
- 20. 初期接続処理
Connection.prototype._write = function(chunk, encoding, cb) {
this._buf_list.push(chunk);
if(!this._recv_connection_header) {
if(this._buf_list.total >= connection_header.length) {
var b = Buffer.concat(this._buf_list);
this.emit('ConnectionHeader', b.slice(0, connection_header.length));
back = b.slice(connection_header.length);
backBuffer(this, back);
} else {
cb();
return;
}
}
(いろいろ後略)
};
マジックコード長だけ
切り出し、コールバッ
ク内でHTTP/2.0の文
字列チェック
初期接続済フラグ
WritableStream
- 21. HTTP/2.0フレームヘッダのパース
(バイナリーデータの解析)
function parseFrameHeader(buf) {
var offset = 0;
var length = buf.readUInt16BE(offset);
offset += 2;
var type = buf.readUInt8(offset);
offset++;
var flags = buf.readUInt8(offset);
offset++;
var stream_id = buf.readUInt32BE(offset) & kUInt31;
return {'length': length, 'type': type, 'flags': flags, 'stream_id': stream_id};
}
バイナリーフレームのデータ解析は Buffer API が必須
ペイロードをパースしたら各フレーム型に応じたコールバックを起動
- 23. で、 Hello World! with iij-http2
var http2 = require(‘./lib/http2.js’);
var server = http2.createServer(function(req, res) {
res.writeHead(200, {‘content-type’: ‘text/plain’});
res.end(‘Hello World!’);
});
server.listen(8080);
(SSLサーバでは、証明書とNPN関係のオプション指定を行う)