• トップ
  • ブログ一覧
  • Node.js v15の次世代プロトコル「QUIC」を実際に試してみた!
  • Node.js v15の次世代プロトコル「QUIC」を実際に試してみた!

    広告メディア事業部広告メディア事業部
    2020.11.10

    エンジニアになろう!

    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 メッセージの通信方法が違います。

    1. HTTP/1.1:TCP 上で ASCII 文字のまま送受信
    2. HTTP/2:ストリームで送受信(形式はフレーム形式)
    3. 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 の特徴は、以下のとおり。

    1. TCP ではなく、UDP を採用している
    2. 信頼性の高い通信をトランスポート層で実現
    3. QUIC では、暗号化しない通信方法は定義されない(必然的に暗号化される)
    4. ストリーム上の整合性を、ストリーム毎に担保(他のストリームに影響を与えない)
    5. 送信者や受信者の IP アドレスやポート番号には依存しない(UDP 上で動作しているため、それぞれのコネクションはコネクション ID で管理されている)
    6. コネクションの確立も速い(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]

    MacでDockerコンテナをビルドする際のエラー対処法

    Docker コンテナをビルドするとき、以下のようなエラーが表示される場合があります。

    1g++: internal compiler error: Killed (program cc1plus)

    これは、メモリ不足が原因な場合が多いので、Mac をお使いの方は  Docker の Preference からメモリを増やすようにしましょう。

    MacでDockerコンテナをビルドする際のエラー対処法

    上手くビルドができない時は、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 サーバを、以下の流れで作ってみます。

    1. 自己証明書の作成
    2. サーバサイドの実装
    3. サーバに接続するクライアントコードの作成
    4. 該当ファイルを Docker コンテナにマウント
    5. コードの実行

    自己証明書の作成

    まず、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 を使って色々試してみると面白いかもしれませんよ!

    こちらの記事もオススメ!

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
    featureImg2020.08.07JavaScript 特集知識編JavaScriptを使ってできることをわかりやすく解説!JavaScriptの歴史【紆余曲折を経たプログラミン...

    広告メディア事業部

    広告メディア事業部

    おすすめ記事