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

    メディアチームメディアチーム
    2020.11.10

    エンジニアになろう!

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

    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]

    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の歴史【紆余曲折を経たプログラミン...

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    メディアチーム
    メディアチーム
    Show more...

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background