概要
HTTP/2は、ウェブ通信の速度、効率、およびパフォーマンスを向上させるために設計されたHTTPの主要な改訂版である。HTTP/1.1を基盤としながら、以下のような重要な改善点を導入している。
- マルチプレクシング (Multiplexing): 複数のリクエストとレスポンスを1つの接続で同時に送信できるようにし、レイテンシを削減し、ページの読み込み速度を向上させる。
- ヘッダー圧縮: HPACK圧縮を使用してHTTPヘッダーのサイズを縮小し、データ伝送を高速化する。
- サーバプッシュ: サーバが必要なリソースを事前にクライアントに送信できるようにし、ページの読み込みをさらに迅速にする。
Ref: https://coolicehost.com/http2-protocol.html
httpプロトコルの各バージョンの歴史
バージョン | リリース年 | 主な特徴 |
---|---|---|
HTTP/1.0 | 1996年 | - 初期のHTTPバージョン - 各リクエストごとに新しいTCP接続を確立 |
HTTP/1.1 | 1997年 | - パーシステント接続を導入(複数のリクエストで同じTCP接続を使用) - ホストヘッダーの必須化 - チャンク転送エンコーディングのサポート |
HTTP/2 | 2015年 | - マルチプレクシングによる複数リクエストの同時処理 - ヘッダー圧縮(HPACK) - サーバプッシュ機能の追加 |
http/2のサーバ間通信の検証
以下のような構成で検証したい。
検証したいこと
- Clientからhttp/2のプロトコルで、webServer.jsに通信する
- webServer.jsはボディとヘッダーをそのままapiServer.jsに転送
- apiServer.jsは受信したヘッダーとボティを出力し、簡単なメッセージをClientに返す
- webServer.jsはapiServer.jsから受信したレスポンスをClientに返す
今回はnode.js
を利用して検証する。
手順
webServer
最初はSSL/TLS
を使わずにサーバを立てて検証したが、Client側でcurl
にてリクエストを送信したところ
curl: (1) Received HTTP/0.9 when not allowed
という訳の分からないエラーメッセージが表示された。いろいろ調べた所、どうやらSSL/TLS
を使えば解決できそうということで、自己証明書を作ることにした。
$ openssl req -x509 -newkey rsa:2048 -nodes -keyout server-key.pem -out server-cert.pem -days 365
各オプションとその意味を以下に説明する
- -x509: X.509形式の証明書を生成するオプション。X.509は証明書の標準フォーマット。
- -newkey rsa:2048: 新しいRSA鍵ペアを生成し、鍵の長さを2048ビットに設定。
- -nodes: 秘密鍵をパスフレーズなしで生成。
- -keyout server-key.pem: 秘密鍵を server-key.pem というファイルに保存。
- -out server-cert.pem: 証明書を server-cert.pem というファイルに保存。
- -days 365: 証明書の有効期限を365日間に設定オプション。
以下はwebServer.js
のソース
const http2 = require('http2');
const fs = require('fs');
// サーバ用の証明書とキーを読み込み
const server = http2.createSecureServer({
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem')
});
server.on('stream', (stream, headers) => {
// ローカルのAPIサーバへの接続を確立
const client = http2.connect('http://localhost:7001');
// クライアントからのリクエストヘッダーをAPIサーバに転送
const req = client.request(headers);
req.on('response', (headers, flags) => {
// APIサーバからのレスポンスヘッダーをクライアントに転送
stream.respond(headers);
});
req.setEncoding('utf8');
req.on('data', (chunk) => {
// APIサーバからのレスポンスボディをクライアントに転送
stream.write(chunk);
});
req.on('end', () => {
// レスポンスが完了したら、クライントとの接続を終了
stream.end();
client.close();
});
// クライアントからのリクエストボティがあれば、APIサーバに転送
stream.on('data', (chunk) => {
req.write(chunk);
});
stream.on('end', () => {
req.end();
});
});
server.listen(7000, () => {
console.log('Secure HTTP/2 server listening on port 7000');
});
webServer
がSSL/TLS
の終端になるので、apiServer
へは普通のhttp
プロトコルで行ける。
Webサーバを立ち上げる。
$ node webServer.js
Secure HTTP/2 server listening on port 7000
apiServer
const http2 = require('http2');
// HTTP/2サーバを作成
const apiServer = http2.createServer();
apiServer.on('stream', (stream, headers) => {
// HTTP/2プロトコルであることを前提に、リクエストの詳細をログに表示
console.log('Stream ID:', stream.id); // ストリームID
// ヘッダーの情報を直接確認
console.log('Stream Headers:', headers);
// リクエストボディを受け取るための変数
let body = '';
// クライアントから送られてくるデータを受け取る
stream.on('data', (chunk) => {
body += chunk; // データを連結
});
// データ受け取りが完了した時にボディをログに出力
stream.on('end', () => {
console.log('Request Body:', body); // リクエストボディの出力
// 応答を送信
stream.respond({
':status': 200,
'content-type': 'text/plain'
});
stream.end('Hello from HTTP/2 API server!\n');
});
});
apiServer.listen(7001, () => console.log('API server listening on port 7001'));
APIサーバを立ち上げる。
$ node apiServer.js
API server listening on port 7001
Client
curl
コマンドのバージョンがhttp/2
に対応しているかを確認しておく
$ curl --version
curl 8.8.0 (x86_64-apple-darwin19.6.0) libcurl/8.8.0 (SecureTransport) OpenSSL/3.3.0 zlib/1.2.11 brotli/1.1.0 zstd/1.5.6 libidn2/2.3.7 libssh2/1.11.0 nghttp2/1.61.0 librtmp/2.3 OpenLDAP/2.6.8
Release-Date: 2024-05-22
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz MultiSSL NTLM SPNEGO SSL threadsafe TLS-SRP UnixSockets zstd
Features:
にHTTP2
が含まれているので問題ない。
以下のコマンドでwebServer
にヘッダーとボディを送信する
$ curl --http2 -k "https://localhost:7000" -H "Client-Set-Header: Header Value" -d "This is the body data"
Hello from HTTP/2 API server!
Clientからは正しくapiServer
からのレスポンスを受信できた。
この時、apiServer
を立てたターミナルには以下のような情報が出力された
Stream ID: 1
Stream Headers: [Object: null prototype] {
':method': 'POST',
':scheme': 'https',
':authority': 'localhost:7000',
':path': '/',
'user-agent': 'curl/8.8.0',
accept: '*/*',
'client-set-header': 'Header Value',
'content-length': '21',
'content-type': 'application/x-www-form-urlencoded',
[Symbol(nodejs.http2.sensitiveHeaders)]: []
}
Request Body: This is the body data
Clientから受信してヘッダーとボディが、webServer
経由(プロキシ)し、apiServer
に転送されることが検証できた。また、それがhttp2
のプロトコルで、ストリームIDが1であることも分かった。
まとめ
http/2
について触り程度で、stream
イベントにてマルチプレクシング
での処理を検証した。http/1.1
のような単純なreq
とres
の扱い方ほど直感的ではない感覚もあるが、リクエスト間の待機時間が短縮でき、通信の効率が向上することに繋がる。
サーバプッシュ
の検証はしなかったが、今後時間があれば試したい。