
Node.js v15の次世代プロトコル「QUIC」を実際に試してみた!
2021.12.20
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 コンテナをビルドします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | FROM ubuntu:18.04 RUN apt update && \ apt install -y software-properties-common && \ add-apt-repository ppa:ubuntu-toolchain-r/test && \ apt update && \ apt install -y \ g++ \ python \ ccache \ build-essential \ git \ curl \ python3-distutils && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* RUN mkdir -p quic-build && \ cd quic-build && \ curl -LkvOf https://nodejs.org/dist/v15.0.0/node-v15.0.0.tar.gz && \ tar zxf node-v15.0.0.tar.gz && \ cd node-v15.0.0 && \ ./configure --experimental-quic && \ make -j4 && \ mv node /usr/local/bin/ && \ rm -rf /build CMD [ "node" ] |
ポイントは、Node のソースコードを curl でダウンロードしてきて、以下の2行でビルドする点。
1 2 | ./configure --experimental-quic && \ make -j4 |
次に、以下のコマンドを打って、Dockerfile をビルドしましょう。(※ビルドには結構時間がかかります)
1 2 | $ docker build -t quic-test . $ docker run -it quic-test |
あとは、createQuicSocket と入力したときに、 [Function: createQuicSocket] と出てくれば、QUIC が使えるようになっています。
1 2 3 4 | > const { createQuicSocket } = require('net'); undefined > createQuicSocket [Function: createQuicSocket] |
MacでDockerコンテナをビルドする際のエラー対処法
Docker コンテナをビルドするとき、以下のようなエラーが表示される場合があります。
1 | g++: 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 を試してみると、上手く動きます。
1 2 3 4 5 6 | Welcome to Node.js v14.0.0-pre. Type ".help" for more information. > const { createQuicSocket } = require('net'); undefined > createQuicSocket [Function: createQuicSocket] |
これだけで動くので、非常に簡単で便利ですね!
QUICを使ったサーバを作ってみる
では、実際に QUIC を使ったサーバを作ってみましょう!
今回は、単純な echo サーバを、以下の流れで作ってみます。
- 自己証明書の作成
- サーバサイドの実装
- サーバに接続するクライアントコードの作成
- 該当ファイルを Docker コンテナにマウント
- コードの実行
自己証明書の作成
まず、QUIC は localhost で試す場合であっても、自己証明書の作成をする必要があります。
以下のコマンドで、ローカルに自己証明書を作成しましょう。
1 2 3 4 5 | $ mkdir ssl_certs $ cd ssl_certs $ openssl genrsa 2024 > server.key $ openssl req -new -key server.key -subj "/C=JP" > server.csr $ openssl x509 -req -days 3650 -signkey server.key < server.csr > server.crt |
サーバサイドの実装
サーバは、以下のコードで実現できます。
ここでは、「echo_server.js」というファイル名で、ローカルに保存しておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | const { createQuicSocket } = require('net'); const fs = require('fs'); const key = fs.readFileSync('./ssl_certs/server.key'); const cert = fs.readFileSync('./ssl_certs/server.crt'); const ca = fs.readFileSync('./ssl_certs/server.csr'); const port = 4567; const server = createQuicSocket({ endpoint: { port } }); server.listen({ key, cert, alpn: 'hello' }); server.on('session', (session) => { session.on('stream', (stream) => { console.log('requested!!'); stream.setEncoding('utf8'); stream.on('data', console.log); // データを受け取ったとき stream.on('end', () => console.log('stream ended')); // ストリームが終了したとき }); }); server.on('listening', () => { // The socket is listening for sessions! console.log(`listening on ${port}...`); }); |
サーバに接続するクライアントコードの作成
次に、クライアントサイドのコードを、「echo_server_client.js」というファイル名で保存します。
先ほど作成した、echo_server.js と同じディレクトリに置くようにしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | const { createQuicSocket } = require('net'); const fs = require('fs'); const key = fs.readFileSync('./ssl_certs/server.key'); const cert = fs.readFileSync('./ssl_certs/server.crt'); const ca = fs.readFileSync('./ssl_certs/server.csr'); const port = 4567; let socket = createQuicSocket({ client: { key, cert, ca, requestCert: true, alpn: 'h3-29', servername: 'localhost' } }); let req = socket.connect({ address: 'localhost', port, }); req.on('secure', () => { console.log('何か入力してください'); const stream = socket.connect({ address: 'localhost', port, }).openStream(); process.stdin.pipe(stream); stream.on('close', () => { socket.close(); }); }); |
該当ファイルを Docker コンテナにマウント
以下のコマンドを使って、該当ファイルを Docker コンテナにマウントして、コンテナ内に入ります。
ここでは、コンテナ内の「/home ディレクトリ」にマウントしています。
1 | $ docker run -it -v $PWD:/home -p 4567:4567 nwtgck/node-quic bash |
コンテナ内に入れたら、以下のコマンドを使って、実際にファイルがマウントされているか確認しましょう。
1 2 | root@228566e5c0eb:/# cd /home root@228566e5c0eb:/home# ls |
コードを実行して確認してみよう
あとは、サーバサイドのコードとクライアントサイドのコードを、それぞれ実行するだけです。
サーバサイドのコードの実行
ホームディレクトリまで移動できたら、サーバサイドのコードを、node で実行しましょう。
1 2 3 | root@228566e5c0eb:/home# node echo_server.js (node:16) ExperimentalWarning: The QUIC protocol is experimental and not yet supported for production use listening on 4567... |
これで、サーバが listen している状態になります。
クライアントサイドのコードの実行
続いて、クライアントサイドのコードの実行ですが、ここでもう1つターミナルを立ち上げましょう。
そのターミナルで、動作している Docker コンテナ ID を調べて、コンテナ内に入ります。
1 2 | $ docker ps -a $ docker exec -it コンテナID bash |
コンテナ内に入ったら、ホームディレクトリへ移動し、クライアントサイドのコードを実行します。
1 2 | root@228566e5c0eb:/# cd /home root@228566e5c0eb:/home# node echo_server_client.js |
文字を入力をして確認してみよう
これで準備は整ったので、あとはクライアントサイドから、文字を入力してみます。
1 2 3 4 | (node:18) ExperimentalWarning: The QUIC protocol is experimental and not yet supported for production use 何か入力してください aa bbb |
すると、サーバサイドで以下のように表示されます。
1 2 3 4 5 6 7 | (node:18) ExperimentalWarning: The QUIC protocol is experimental and not yet supported for production use listening on 4567... requested!! aa bbb |
ちゃんと同じ文字列が表示されていますね!
これで、echo サーバの実装は完了です。
ファイルを取得したり、curl コマンドを使って、HTTP/3 でアクセスしたりすると面白いかもしれません。
さいごに
今回は、QUIC の解説から Node.js の最新版を使って、echo サーバを作るところまで見ていきました。
QUIC は、HTTP/3 で導入されているプロトコルで、高速かつ信頼性の高い次世代プロトコルです。
Node.js v15 では、QUIC はまだ試験的な導入ですが、その機能を先取りして試してみるのも興味深いと思います。
今後、QUIC の導入が進んでいくとは思います。
ただ、まだ正式導入までには時間を要すると思われるので、今の内に QUIC を使って色々試してみると面白いかもしれませんよ!
こちらの記事もオススメ!
書いた人はこんな人

- 「好きを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もり大歓迎!
また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」「WEBディレクター」を積極採用中です!
インターンや新卒採用も行っております。
以下よりご応募をお待ちしております!
https://rightcode.co.jp/recruit
ITエンタメ10月 13, 2023Netflixの成功はレコメンドエンジン?
ライトコードの日常8月 30, 2023退職者の最終出社日に密着してみた!
ITエンタメ8月 3, 2023世界初の量産型ポータブルコンピュータを開発したのに倒産!?アダム・オズボーン
ITエンタメ7月 14, 2023【クリス・ワンストラス】GitHubが出来るまでとソフトウェアの未来