Node.js v15の次世代プロトコル「QUIC」を実際に試してみた!
エンジニアになろう!
Node.js v15 の次世代プロトコル「QUIC」を使ってみる
先日、Node.js の新バージョンの v15 がリリースされましたね!
このとき、「QUIC」が試験的にサポートされました。
QUIC は、HTTP の新バージョン「HTTP/3」で使われているプロトコルです。
となると、「QIUC は従来のものとどういった点が違うのか」「どういう機能が新たに導入されているか」といった疑問が浮かびます。
そこで今回は、HTTP/3 や QUIC の解説から、Node.js v15 で QUIC を導入する方法まで、具体的に解説していきたいと思います!
HTTP/3 とは?
HTTP/3 は、HTTP の最新バージョンのことで、Google や YouTube、Facebook でも導入されているプロトコル。
最近では、Google Chrome にも、段階的に導入され始めました。
また HTTP/3 は、2019年の年末から現在にかけて、Web サイトでの導入数が3倍以上にもなっています。
このように、HTTP/3 は現在多くの Web サービスで導入され始めていますが、前バージョンの HTTP とは何が違うのでしょうか。
HTTP はバージョンによって通信方法が違う
HTTP は、HTTP/1.1 で標準化され、HTTP/2・HTTP/3 へとバージョンアップしていきました。
HTTP のバージョンは上がっても、「HTTP リクエストを受け取って HTTP レスポンスを返す」という、基本的な仕組みは変わっていません。
では、バージョンによって何が違うのかというと、HTTP メッセージの通信方法が違います。
- HTTP/1.1:TCP 上で ASCII 文字のまま送受信
- HTTP/2:ストリームで送受信(形式はフレーム形式)
- HTTP/3:QUIC 上のストリームで送受信(形式はフレーム形式)
送受信する HTTP メッセージの意味は変わりません。
たとえ HTTP のバージョンが違っても、ブラウザやサーバサイドのコードを変更せずに、どのバージョンでも動作できます。
HTTP/3 の特徴
HTTP/3 には、QUIC という新しいプロトコルが導入されています。
UDP 上で、動作するプロトコルなので、信頼性の高い通信を高速に行うことが可能です。
HTTP/2 までは、TCP コネクションの確立に、時間を要していました。
HTTP/3 では、その問題が解決されるようになっています。
QUIC とは?
QUIC(クイック)は、Google が開発している、UDP 上で動作するトランスポート層のプロトコルです。
QUIC は、HTTP/3 のプロトコルとして採用され、Node.js v15 (2020年10月20日リリース)でも試験的に導入されています。
QUIC の特徴は、以下のとおり。
- TCP ではなく、UDP を採用している
- 信頼性の高い通信をトランスポート層で実現
- QUIC では、暗号化しない通信方法は定義されない(必然的に暗号化される)
- ストリーム上の整合性を、ストリーム毎に担保(他のストリームに影響を与えない)
- 送信者や受信者の IP アドレスやポート番号には依存しない(UDP 上で動作しているため、それぞれのコネクションはコネクション ID で管理されている)
- コネクションの確立も速い(1-RTT ハンドシェイクを採用)
HTTP/2 では、実現できなかったことを、QUIC で実現できるようになっています。
このように、QUIC は HTTP/2 よりもメリットが大きいので、HTTP/3 を導入しているサービスが増えてきているわけですね!
Docker を使った QUIC の試し方(環境構築)
では、実際に QUIC を試していきましょう!
実は、QUIC を Node.js v15 で試すには、普通に node を実行しただけでは試せません。
QUIC を試すには、Node.js v15 を--experimental-quic フラグをつけてビルドし直す必要があります。
以下の公式サイトから、バイナリをダウンロードして、ローカルでビルドする方法もあります。
【Node.js:公式サイト】
https://nodejs.org/ja/download/current/
ですが、ここでは Dockerfile を用いて環境を構築していきましょう!
以下のコードを、Dockerfile に記述して、Docker コンテナをビルドします。
1FROM ubuntu:18.04
2
3RUN apt update && \
4 apt install -y software-properties-common && \
5 add-apt-repository ppa:ubuntu-toolchain-r/test && \
6 apt update && \
7 apt install -y \
8 g++ \
9 python \
10 ccache \
11 build-essential \
12 git \
13 curl \
14 python3-distutils && \
15 apt-get clean && \
16 rm -rf /var/lib/apt/lists/*
17
18RUN mkdir -p quic-build && \
19 cd quic-build && \
20 curl -LkvOf https://nodejs.org/dist/v15.0.0/node-v15.0.0.tar.gz && \
21 tar zxf node-v15.0.0.tar.gz && \
22 cd node-v15.0.0 && \
23 ./configure --experimental-quic && \
24 make -j4 && \
25 mv node /usr/local/bin/ && \
26 rm -rf /build
27CMD [ "node" ]
ポイントは、Node のソースコードを curl でダウンロードしてきて、以下の2行でビルドする点。
1./configure --experimental-quic && \
2make -j4
次に、以下のコマンドを打って、Dockerfile をビルドしましょう。(※ビルドには結構時間がかかります)
1$ docker build -t quic-test .
2$ docker run -it quic-test
あとは、createQuicSocket と入力したときに、[Function: createQuicSocket] と出てくれば、QUIC が使えるようになっています。
1> const { createQuicSocket } = require('net');
2undefined
3> createQuicSocket
4[Function: createQuicSocket]
Docker コンテナをビルドするとき、以下のようなエラーが表示される場合があります。
1g++: internal compiler error: Killed (program cc1plus)
これは、メモリ不足が原因な場合が多いので、Mac をお使いの方は Docker の Preference からメモリを増やすようにしましょう。
上手くビルドができない時は、node-quicというDockerイメージを使いましょう
ビルドに時間があまりにもかかりすぎる場合や、上手くビルドできない場合は、「node-quic」という Docker イメージを使うのも手です。
こちらのイメージは、「Node.js v14.0.0-pre」でビルドされていますが、Node.js v15 と同様に QUIC を使用することができます。
以下のコマンドを打てば、実行可能です。
1$ docker run -it nwtgck/node-quic
createQuicSocket を試してみると、上手く動きます。
1Welcome to Node.js v14.0.0-pre.
2Type ".help" for more information.
3> const { createQuicSocket } = require('net');
4undefined
5> createQuicSocket
6[Function: createQuicSocket]
これだけで動くので、非常に簡単で便利ですね!
QUICを使ったサーバを作ってみる
では、実際に QUIC を使ったサーバを作ってみましょう!
今回は、単純な echo サーバを、以下の流れで作ってみます。
- 自己証明書の作成
- サーバサイドの実装
- サーバに接続するクライアントコードの作成
- 該当ファイルを Docker コンテナにマウント
- コードの実行
自己証明書の作成
まず、QUIC は localhost で試す場合であっても、自己証明書の作成をする必要があります。
以下のコマンドで、ローカルに自己証明書を作成しましょう。
1$ mkdir ssl_certs
2$ cd ssl_certs
3$ openssl genrsa 2024 > server.key
4$ openssl req -new -key server.key -subj "/C=JP" > server.csr
5$ openssl x509 -req -days 3650 -signkey server.key < server.csr > server.crt
サーバサイドの実装
サーバは、以下のコードで実現できます。
ここでは、「echo_server.js」というファイル名で、ローカルに保存しておきましょう。
1const { createQuicSocket } = require('net');
2 const fs = require('fs');
3
4 const key = fs.readFileSync('./ssl_certs/server.key');
5 const cert = fs.readFileSync('./ssl_certs/server.crt');
6 const ca = fs.readFileSync('./ssl_certs/server.csr');
7 const port = 4567;
8
9 const server = createQuicSocket({ endpoint: { port } });
10 server.listen({ key, cert, alpn: 'hello' });
11
12 server.on('session', (session) => {
13 session.on('stream', (stream) => {
14 console.log('requested!!');
15
16 stream.setEncoding('utf8');
17 stream.on('data', console.log); // データを受け取ったとき
18 stream.on('end', () => console.log('stream ended')); // ストリームが終了したとき
19 });
20 });
21
22 server.on('listening', () => {
23 // The socket is listening for sessions!
24 console.log(`listening on ${port}...`);
25 });
サーバに接続するクライアントコードの作成
次に、クライアントサイドのコードを、「echo_server_client.js」というファイル名で保存します。
先ほど作成した、echo_server.js と同じディレクトリに置くようにしましょう。
1const { createQuicSocket } = require('net');
2const fs = require('fs');
3const key = fs.readFileSync('./ssl_certs/server.key');
4const cert = fs.readFileSync('./ssl_certs/server.crt');
5const ca = fs.readFileSync('./ssl_certs/server.csr');
6const port = 4567;
7
8 let socket = createQuicSocket({
9 client: {
10 key,
11 cert,
12 ca,
13 requestCert: true,
14 alpn: 'h3-29',
15 servername: 'localhost'
16 }
17 });
18
19 let req = socket.connect({
20 address: 'localhost',
21 port,
22 });
23
24req.on('secure', () => {
25 console.log('何か入力してください');
26 const stream = socket.connect({
27 address: 'localhost',
28 port,
29 }).openStream();
30 process.stdin.pipe(stream);
31 stream.on('close', () => {
32 socket.close();
33 });
34 });
該当ファイルを Docker コンテナにマウント
以下のコマンドを使って、該当ファイルを Docker コンテナにマウントして、コンテナ内に入ります。
ここでは、コンテナ内の「/home ディレクトリ」にマウントしています。
1$ docker run -it -v $PWD:/home -p 4567:4567 nwtgck/node-quic bash
コンテナ内に入れたら、以下のコマンドを使って、実際にファイルがマウントされているか確認しましょう。
1root@228566e5c0eb:/# cd /home
2root@228566e5c0eb:/home# ls
コードを実行して確認してみよう
あとは、サーバサイドのコードとクライアントサイドのコードを、それぞれ実行するだけです。
サーバサイドのコードの実行
ホームディレクトリまで移動できたら、サーバサイドのコードを、node で実行しましょう。
1root@228566e5c0eb:/home# node echo_server.js
2(node:16) ExperimentalWarning: The QUIC protocol is experimental and not yet supported for production use
3listening on 4567...
これで、サーバが listen している状態になります。
クライアントサイドのコードの実行
続いて、クライアントサイドのコードの実行ですが、ここでもう1つターミナルを立ち上げましょう。
そのターミナルで、動作している Docker コンテナ ID を調べて、コンテナ内に入ります。
1$ docker ps -a
2$ docker exec -it コンテナID bash
コンテナ内に入ったら、ホームディレクトリへ移動し、クライアントサイドのコードを実行します。
1root@228566e5c0eb:/# cd /home
2root@228566e5c0eb:/home# node echo_server_client.js
文字を入力をして確認してみよう
これで準備は整ったので、あとはクライアントサイドから、文字を入力してみます。
1(node:18) ExperimentalWarning: The QUIC protocol is experimental and not yet supported for production use
2何か入力してください
3aa
4bbb
すると、サーバサイドで以下のように表示されます。
1(node:18) ExperimentalWarning: The QUIC protocol is experimental and not yet supported for production use
2listening on 4567...
3
4requested!!
5aa
6
7bbb
ちゃんと同じ文字列が表示されていますね!
これで、echo サーバの実装は完了です。
ファイルを取得したり、curl コマンドを使って、HTTP/3 でアクセスしたりすると面白いかもしれません。
さいごに
今回は、QUIC の解説から Node.js の最新版を使って、echo サーバを作るところまで見ていきました。
QUIC は、HTTP/3 で導入されているプロトコルで、高速かつ信頼性の高い次世代プロトコルです。
Node.js v15 では、QUIC はまだ試験的な導入ですが、その機能を先取りして試してみるのも興味深いと思います。
今後、QUIC の導入が進んでいくとは思います。
ただ、まだ正式導入までには時間を要すると思われるので、今の内に QUIC を使って色々試してみると面白いかもしれませんよ!
こちらの記事もオススメ!
2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
2020.08.07JavaScript 特集知識編JavaScriptを使ってできることをわかりやすく解説!JavaScriptの歴史【紆余曲折を経たプログラミン...
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit