{
    "componentChunkName": "component---src-templates-blog-post-jsx",
    "path": "/post/http-over-quic-on-nodejs15/",
    "result": {"data":{"site":{"siteMetadata":{"title":"WEB EGG","author":"Leko - CTO at Yuimedi"}},"markdownRemark":{"id":"8b639bb8-84b0-5f27-a492-11cd52189fd3","excerpt":"2020/10/20にNode.js v15がリリースされました 🎉 色々新機能や破壊的変更が加わっているので、詳しくは公式のリリースノート等をご参照ください。 — Node.js v15.0.0 is here!. This blog was written by Bethany… | by Node.js…","html":"<p>2020/10/20にNode.js v15がリリースされました 🎉<br>\n色々新機能や破壊的変更が加わっているので、詳しくは公式のリリースノート等をご参照ください。</p>\n<blockquote>\n<p>— <a href=\"https://medium.com/@nodejs/node-js-v15-0-0-is-here-deb00750f278\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Node.js v15.0.0 is here!. This blog was written by Bethany… | by Node.js | Oct, 2020 | Medium</a></p>\n</blockquote>\n<p>また、Node.jsのコラボレータによる日本語のわかりやすい記事もあるのであわせてご覧ください。</p>\n<ul>\n<li><a href=\"https://shisama.hatenablog.com/entry/2020/10/21/004612\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Node.js v15 の主な変更点 - 別にしんどくないブログ</a></li>\n<li><a href=\"https://blog.watilde.com/2020/10/20/node-js-v15/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">10月20日にメジャーアップデートとしてリリースされたNode.js v15の紹介 | watilde’s blog</a></li>\n<li><a href=\"https://blog.watilde.com/2020/10/14/npm-v7%E3%81%AE%E4%B8%BB%E3%81%AA%E5%A4%89%E6%9B%B4%E7%82%B9%E3%81%BE%E3%81%A8%E3%82%81/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">npm v7の主な変更点まとめ | watilde’s blog</a></li>\n</ul>\n<p>まとめは以上にして本題です。本記事はv15の変更点まとめを目的とした記事ではなく、<strong>v15にて新しく追加されたQUICを用いてシンプルなHTTP/3サーバを実装してHTTP over QUIC(HTTP/3)の使用感を掴む</strong>ことを目的としています。この記事を読み終えると以下のものが手に入ります。</p>\n<ul>\n<li>Node.js v15にてQUICを使用できる開発環境</li>\n<li>ファイルをホスティングするシンプルなHTTP/3サーバの実装</li>\n<li>cURLで動作確認する方法</li>\n</ul>\n<p>まずQUICおよびHTTP/3について軽くおさらいし、QUICモジュールを利用する環境構築を構築、HTTP/3サーバのデモコードと簡単な説明をして、最後にcURLを用いて動作確認します。仕様の詳細にはあまり触れずに実装するために必要な情報にフォーカスします。</p>\n<h2 id=\"まえおき\" style=\"position:relative;\"><a href=\"#%E3%81%BE%E3%81%88%E3%81%8A%E3%81%8D\" aria-label=\"まえおき permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>まえおき</h2>\n<p>当記事ではNode.jsのv15.0.1を前提にコードを書いています。またQUICは登場したばかりで現在のStability indexは<a href=\"https://nodejs.org/dist/latest-v15.x/docs/api/documentation.html#documentation_stability_index\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Stability: 1 - Experimental\n</a>、しかもHTTP over QUICについてはUndocumentedです。<br>\nおそらくQUICをラップしたHTTP/3用の高レベルのAPIが今後登場するでしょうし、後方互換のない破壊的変更が予告なく加わる可能性もあります。ここで得た知識は陳腐化する前提でエッジなAPIをシュッと試したい方は読み進めてもらえればと思います。</p>\n<p>なお、当記事ではこれらを前提に書いています。</p>\n<ul>\n<li>Node.jsを手元でビルドしたことがある（経験がない方は<a href=\"https://github.com/nodejs/node/blob/master/BUILDING.md\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">ビルドガイド</a>からビルドしてみてください）</li>\n<li>Node.jsでHTTP/1.1のHTTPサーバを実装したことがある</li>\n</ul>\n<h2 id=\"quichttp-over-quichttp3とは\" style=\"position:relative;\"><a href=\"#quichttp-over-quichttp3%E3%81%A8%E3%81%AF\" aria-label=\"quichttp over quichttp3とは permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>QUIC、HTTP over QUIC（HTTP/3）とは？</h2>\n<p>真面目に解説するとボリュームがありすぎるので参考になったリンクを掲載します。概論をおさえておくと以後の理解がスムーズになると思います。</p>\n<ul>\n<li><a href=\"https://http3-explained.haxx.se/ja\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">日本語 - HTTP/3 explained</a>\n<ul>\n<li>curl作者Daniel StenbergによるHTTP/3の解説を日本語に翻訳したもの。概要を掴むのにとてもいい</li>\n</ul>\n</li>\n<li><a href=\"https://milestone-of-se.nesuke.com/l7protocol/http/http3-over-quic/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">【図解】HTTP/3 (HTTP over QUIC) の仕組み〜UDPのメリット,各バージョンの違い(v1.0/v1.1/v2/v3)〜 | SEの道標</a>\n<ul>\n<li>各バージョンが抱えていた問題点をQUICおよびHTTP/3がどう解決しているのかについて詳しく書いてあります</li>\n</ul>\n</li>\n<li><a href=\"https://asnokaze.hatenablog.com/entry/2018/11/06/025016\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">HTTP over QUICと、その名称について (HTTP3について) *2019年9月更新 - ASnoKaze blog</a>\n<ul>\n<li>言わずと知れたyukiさんによる日本語での解説。詳しく正確に書いてある</li>\n</ul>\n</li>\n</ul>\n<p>少なくとも注意すべきことは、**QUIC＝HTTP/3ではないということです。QUICを利用した新しいHTTPの仕様がHTTP/3です。**QUICはHTTP以外のプロトコルでも使用できるよう設計されています。<br>\nまた、**Node.jsのQUICにおいても同様です。**QUICを扱う＝HTTP/3を扱うではありません。Node.jsで提供されたQUIC APIはQUICそのものを扱う低レイヤのAPIです。そのためQUICを用いてHTTP/3サーバを実装するというイメージを持っておいてください。ここを混同するとドキュメントの読み方やトラブルシューティング時に混乱します。</p>\n<h2 id=\"既存のhttp3のサイトを試す\" style=\"position:relative;\"><a href=\"#%E6%97%A2%E5%AD%98%E3%81%AEhttp3%E3%81%AE%E3%82%B5%E3%82%A4%E3%83%88%E3%82%92%E8%A9%A6%E3%81%99\" aria-label=\"既存のhttp3のサイトを試す permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>既存のHTTP/3のサイトを試す</h2>\n<p>実装を始める前に、既存のHTTP/3のサイトで動作確認します。ユーザエージェントがHTTP/3に対応しているかどうかはこちらのサイトから確認できます。</p>\n<p><a href=\"https://http3.is\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">https://http3.is</a></p>\n<p>現時点ではHTTP/3のブラウザの対応状況はいまひとつです。iOS Safariでフラグつきでサポートされている、Google Chromeにてサポートされているとの情報を得ましたが、私の環境ではどちらも動作しませんでした。</p>\n<p><a href=\"https://caniuse.com/?search=quic\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">https://caniuse.com/?search=quic</a></p>\n<p>本記事では動作確認にcURLを利用します。cURLでHTTP/3を利用するためにcURLをソースコードからビルドする必要があり、<a href=\"https://hub.docker.com/r/curlimages/curl\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">cURL公式のDockerイメージ</a>にもHTTP/3に対応したタグがないためHTTP/3対応したビルドを配布されている<a href=\"https://qiita.com/inductor/items/8d1bc0e95b71e814dbcf\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">curlのHTTP/3通信をDocker上で使ってみる - Qiita</a>を利用させてもらいます。試しに <a href=\"https://http3.is\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">http3.is</a> に対してリクエストを送った結果の抜粋がこちらです。</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">$ docker run -it --rm ymuski/curl-http3 curl -v https://http3.is --http3\n*   Trying 199.232.233.77:443...\n* Sent QUIC client Initial, ALPN: h3-29,h3-28,h3-27\n* Connected to http3.is (199.232.233.77) port 443 (#0)\n* h3 [:method: GET]\n* h3 [:path: /]\n* h3 [:scheme: https]\n* h3 [:authority: http3.is]\n* h3 [user-agent: curl/7.73.0-DEV]\n* h3 [accept: */*]\n* Using HTTP/3 Stream ID: 0 (easy handle 0x55f88c209a20)\n> GET / HTTP/3\n> Host: http3.is\n> user-agent: curl/7.73.0-DEV\n> accept: */*\n>\n&lt; HTTP/3 200\n...\n      Your browser does not support the video tag, but it does support HTTP/3!\n...\n* Connection #0 to host http3.is left intact</code></pre></div>\n<p>HTTP/3に対応してる旨のHTMLが返ってきました。出力からHTTP/3で通信されているのがわかります。\n次にサーバを実装して、このcurlコマンドを使って動作確認をします。</p>\n<h2 id=\"nodejsをビルドする\" style=\"position:relative;\"><a href=\"#nodejs%E3%82%92%E3%83%93%E3%83%AB%E3%83%89%E3%81%99%E3%82%8B\" aria-label=\"nodejsをビルドする permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Node.jsをビルドする</h2>\n<p>QUICはExperimentalな機能のためQUICを使用するには<strong>フラグをつけてNodeをビルドし直す</strong>必要があります。よくあるExperimentalな機能とは違い<code>--experimental-...</code>などのフラグをnodeコマンドに渡しても動作しません。また執筆時点（2020/10/22）ではQUICに対応したDockerイメージもありません。手元でビルドするのは少しハードルが高いかもしれませんが、やることは単にフラグをつけていつも通りNode.jsをビルドするだけです。</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">$ cd /path/to/nodejs/node\n$ ./configure --experimental-quic\n$ make -j4 # コア数を指定すると早くなります\n$ ./node -p -e \"require('net').createQuicSocket\"\n[Function: createQuicSocket] # &lt;-- 表示されたらOK\n$ node -p -e \"require('net').createQuicSocket\"\nundefined # &lt;-- グローバルなnodeだとundefinedになる</code></pre></div>\n<p><code>require('net').createQuicSocket</code>が存在していれば成功です。**以後、<code>./node</code>と書いてある場合はいまビルドしたNode.jsを実行するという意味を持ちます。**グローバルにインストールされているnodeコマンドを起動しないようご注意ください。</p>\n<h2 id=\"自己証明書の作成\" style=\"position:relative;\"><a href=\"#%E8%87%AA%E5%B7%B1%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%81%AE%E4%BD%9C%E6%88%90\" aria-label=\"自己証明書の作成 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>自己証明書の作成</h2>\n<p>QUICを使用するにはlocalhostであっても証明書が必須です。適当に自己証明書を作成しておきます。</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">$ mkdir .certs\n$ cd .certs\n$ openssl genrsa 2024 > server.key\n$ openssl req -new -key server.key -subj \"/C=JP\" > server.csr\n$ openssl x509 -req -days 3650 -signkey server.key &lt; server.csr > server.crt\n$ cd -\n$ ls .certs\nserver.crt      server.csr      server.key</code></pre></div>\n<h2 id=\"サーバを実装する\" style=\"position:relative;\"><a href=\"#%E3%82%B5%E3%83%BC%E3%83%90%E3%82%92%E5%AE%9F%E8%A3%85%E3%81%99%E3%82%8B\" aria-label=\"サーバを実装する permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>サーバを実装する</h2>\n<p>環境構築が終わったので本題です。さっそくサーバを実装します。</p>\n<h3 id=\"要件定義\" style=\"position:relative;\"><a href=\"#%E8%A6%81%E4%BB%B6%E5%AE%9A%E7%BE%A9\" aria-label=\"要件定義 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>要件定義</h3>\n<p>今回は静的なファイルを配信するサーバを実装します。このように起動できるhttp3-serve.jsを実装します。</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">PORT=8888 PUBLIC_ROOT=$PWD ./node http3-serve.js</code></pre></div>\n<p>パラメータは２つです。設定値はすべて環境変数で与えます。</p>\n<ul>\n<li>PORT: ポート番号</li>\n<li>PUBLIC_ROOT: 静的ファイルを配信するルートファイル\n<ul>\n<li>ファイルが存在すればそれを200で返す。<code>content-length</code>と<code>content-type</code>ヘッダも返す</li>\n<li>リクエストされたパスが存在しなければ404を返す</li>\n<li>リクエストされたパスがファイル以外（ex. ディレクトリ）だったら403を返す</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"実装\" style=\"position:relative;\"><a href=\"#%E5%AE%9F%E8%A3%85\" aria-label=\"実装 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>実装</h3>\n<p>いよいよ実装です。先にコードを載せます。このjsはES Modules形式で記述しています。package.jsonに<code>\"type\": \"module\"</code>フィールドが設定されている前提で読んでください。</p>\n<p><code>// [数字] ...</code>と書いてあるところを順に触れていきます。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">import</span> fs <span class=\"token keyword\">from</span> <span class=\"token string\">'fs'</span>\n<span class=\"token keyword\">import</span> fsPromises <span class=\"token keyword\">from</span> <span class=\"token string\">'fs/promises'</span>\n<span class=\"token keyword\">import</span> path <span class=\"token keyword\">from</span> <span class=\"token string\">'path'</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> createQuicSocket <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'net'</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> lookup <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'mime-types'</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> <span class=\"token constant\">PORT</span><span class=\"token punctuation\">,</span> <span class=\"token constant\">DOCUMENT_ROOT</span> <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> process<span class=\"token punctuation\">.</span>env\n\n<span class=\"token keyword\">const</span> key <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> fs<span class=\"token punctuation\">.</span><span class=\"token function\">readFileSync</span><span class=\"token punctuation\">(</span><span class=\"token string\">'./.certs/server.key'</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">const</span> cert <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> fs<span class=\"token punctuation\">.</span><span class=\"token function\">readFileSync</span><span class=\"token punctuation\">(</span><span class=\"token string\">'./.certs/server.crt'</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">// [1] QUICソケットの初期化</span>\n<span class=\"token keyword\">const</span> server <span class=\"token operator\">=</span> <span class=\"token function\">createQuicSocket</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  endpoint<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> port<span class=\"token operator\">:</span> <span class=\"token constant\">PORT</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  server<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> key<span class=\"token punctuation\">,</span> cert<span class=\"token punctuation\">,</span> alpn<span class=\"token operator\">:</span> <span class=\"token string\">'h3-29'</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n\nserver<span class=\"token punctuation\">.</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token string\">'session'</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">session</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// [2] session, streamイベント</span>\n  session<span class=\"token punctuation\">.</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token string\">'stream'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">stream</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// [3] リクエストヘッダを受け取る</span>\n    stream<span class=\"token punctuation\">.</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token string\">'initialHeaders'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">rawHeaders</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">const</span> headers <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Map</span><span class=\"token punctuation\">(</span>rawHeaders<span class=\"token punctuation\">)</span>\n      <span class=\"token keyword\">const</span> url <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">URL</span><span class=\"token punctuation\">(</span>headers<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">':path'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'https://localhost'</span><span class=\"token punctuation\">)</span>\n      <span class=\"token keyword\">const</span> requestPath <span class=\"token operator\">=</span> path<span class=\"token punctuation\">.</span><span class=\"token function\">join</span><span class=\"token punctuation\">(</span><span class=\"token constant\">DOCUMENT_ROOT</span><span class=\"token punctuation\">,</span> url<span class=\"token punctuation\">.</span>pathname<span class=\"token punctuation\">)</span>\n      fsPromises\n        <span class=\"token punctuation\">.</span><span class=\"token function\">stat</span><span class=\"token punctuation\">(</span>requestPath<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">stats</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n          <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>stats<span class=\"token punctuation\">.</span><span class=\"token function\">isFile</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token comment\">// [4] レスポンスヘッダを返す</span>\n            stream<span class=\"token punctuation\">.</span><span class=\"token function\">submitInitialHeaders</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n              <span class=\"token string\">':status'</span><span class=\"token operator\">:</span> <span class=\"token string\">'403'</span><span class=\"token punctuation\">,</span>\n            <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n            stream<span class=\"token punctuation\">.</span><span class=\"token function\">end</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n            <span class=\"token keyword\">return</span>\n          <span class=\"token punctuation\">}</span>\n\n          stream<span class=\"token punctuation\">.</span><span class=\"token function\">submitInitialHeaders</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n            <span class=\"token string\">':status'</span><span class=\"token operator\">:</span> <span class=\"token string\">'200'</span><span class=\"token punctuation\">,</span>\n            <span class=\"token string\">'content-length'</span><span class=\"token operator\">:</span> stats<span class=\"token punctuation\">.</span>size<span class=\"token punctuation\">,</span>\n            <span class=\"token string\">'content-type'</span><span class=\"token operator\">:</span> <span class=\"token function\">lookup</span><span class=\"token punctuation\">(</span>requestPath<span class=\"token punctuation\">)</span> <span class=\"token operator\">||</span> <span class=\"token string\">'application/octet-stream'</span><span class=\"token punctuation\">,</span>\n          <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n          <span class=\"token comment\">// [5] レスポンスボディを返す</span>\n          fs<span class=\"token punctuation\">.</span><span class=\"token function\">createReadStream</span><span class=\"token punctuation\">(</span>requestPath<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">pipe</span><span class=\"token punctuation\">(</span>stream<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">catch</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">e</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n          stream<span class=\"token punctuation\">.</span><span class=\"token function\">submitInitialHeaders</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n            <span class=\"token string\">':status'</span><span class=\"token operator\">:</span> <span class=\"token string\">'404'</span><span class=\"token punctuation\">,</span>\n          <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n          stream<span class=\"token punctuation\">.</span><span class=\"token function\">end</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token keyword\">await</span> server<span class=\"token punctuation\">.</span><span class=\"token function\">listen</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">The socket is listening on :</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span><span class=\"token constant\">PORT</span><span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span></code></pre></div>\n<h4 id=\"1-quicソケットの初期化\" style=\"position:relative;\"><a href=\"#1-quic%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96\" aria-label=\"1 quicソケットの初期化 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>[1] QUICソケットの初期化</h4>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// [1] QUICソケットの初期化</span>\n<span class=\"token keyword\">const</span> server <span class=\"token operator\">=</span> <span class=\"token function\">createQuicSocket</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  endpoint<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> port<span class=\"token operator\">:</span> <span class=\"token constant\">PORT</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  server<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> key<span class=\"token punctuation\">,</span> cert<span class=\"token punctuation\">,</span> alpn<span class=\"token operator\">:</span> <span class=\"token string\">'h3-29'</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>特に<code>server.alpn</code>が重要です。単にQUICを扱うなら任意の値が指定可能ですが、HTTP/3のサーバを立てるなら値を（現バージョンにおいては）<code>h3-29</code>にする必要があります。</p>\n<blockquote>\n<p>ALPN identifiers that are known to Node.js (such as the ALPN identifier for HTTP/3) will alter how the QuicSession and QuicStream objects operate internally, but the QUIC implementation for Node.js has been designed to allow any ALPN to be specified and used.</p>\n<p>— <a href=\"https://nodejs.org/dist/latest-v15.x/docs/api/quic.html#quic_quicsession_and_alpn\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">QuicSession and ALPN | Node.js v15.0.1 Documentation</a></p>\n</blockquote>\n<p>createQuicSocketに指定可能な全てのオプションは公式ドキュメントをご確認ください。</p>\n<blockquote>\n<p>— <a href=\"https://nodejs.org/dist/latest-v15.x/docs/api/quic.html#quic_net_createquicsocket_options\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">createQuicSocket | Node.js v15.0.1 Documentation</a></p>\n</blockquote>\n<h4 id=\"2-session-streamイベント\" style=\"position:relative;\"><a href=\"#2-session-stream%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88\" aria-label=\"2 session streamイベント permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>[2] <code>session</code>, <code>stream</code>イベント</h4>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\">server<span class=\"token punctuation\">.</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token string\">'session'</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">session</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// [2] session, streamイベント</span>\n  session<span class=\"token punctuation\">.</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token string\">'stream'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">stream</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span></code></pre></div>\n<p>QUICのセッションが開始されたときに<code>session</code>イベントが呼び出されます。コールバックの引数は<a href=\"https://nodejs.org/dist/latest-v15.x/docs/api/quic.html#quic_class_quicsession_extends_eventemitter\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">QuicSession</a>のインスタンスです。QuicSessionが確立した後にクライアントがストリームを作成した時に<code>stream</code>イベントが呼び出されます。コールバックの引数は<a href=\"https://nodejs.org/dist/latest-v15.x/docs/api/quic.html#quic_class_quicstream_extends_stream_duplex\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">QuicStream</a>のインスタンスです。基本的に１リクエストにつき１回<code>stream</code>イベントが呼び出されます。</p>\n<p>QuicSessionは以下の４つの状態のいずれかをとります。このうち<code>Initial</code>に相当するのが<code>session</code>イベントです。<code>Ready</code>に相当するのが次に紹介する<code>stream</code>イベントです。</p>\n<blockquote>\n<ul>\n<li>Initial - Entered as soon as the QuicSession is created</li>\n<li>Handshake - Entered as soon as the TLS 1.3 handshake between the client and server begins. The handshake is always initiated by the client</li>\n<li>Ready - Entered as soon as the TLS 1.3 handshake completes. Once the QuicSession enters the Ready state, it may be used to exchange application data using QuicStream instances</li>\n<li>Closed - Entered as soon as the QuicSession connection has been terminated</li>\n</ul>\n<p>— <a href=\"https://nodejs.org/dist/latest-v15.x/docs/api/quic.html#quic_client_and_server_quicsessions\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Client and server QuicSessions | Node.js v15.0.1 Documentation</a></p>\n</blockquote>\n<p>QuicStreamはstream.Duplexを継承しており、リクエストボディは<code>stream</code>から読み込めます。</p>\n<h4 id=\"3-リクエストヘッダを受け取る\" style=\"position:relative;\"><a href=\"#3-%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%83%98%E3%83%83%E3%83%80%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%82%8B\" aria-label=\"3 リクエストヘッダを受け取る permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>[3] リクエストヘッダを受け取る</h4>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// [3] リクエストヘッダを受け取る</span>\nstream<span class=\"token punctuation\">.</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token string\">'initialHeaders'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">rawHeaders</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span></code></pre></div>\n<p>QuicSessionが確立した後にクライアントがストリームを作成し、リクエストヘッダが届いた時に呼び出されます。rawHeadersにはこのような値が格納されています。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token punctuation\">[</span>\n  <span class=\"token punctuation\">[</span> <span class=\"token string\">':method'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'GET'</span> <span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">[</span> <span class=\"token string\">':path'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'/hoge'</span> <span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">[</span> <span class=\"token string\">':scheme'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'https'</span> <span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">[</span> <span class=\"token string\">':authority'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'host.docker.internal:8080'</span> <span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">[</span> <span class=\"token string\">'user-agent'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'curl/7.73.0-DEV'</span> <span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">[</span> <span class=\"token string\">'accept'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'*/*'</span> <span class=\"token punctuation\">]</span>\n<span class=\"token punctuation\">]</span></code></pre></div>\n<p><code>:</code>からはじまるヘッダは擬似ヘッダ（<a href=\"https://tools.ietf.org/html/draft-ietf-quic-http-29#section-4.1.1.1\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Pseudo-Header Fields</a>）と呼ぶそうです。よく利用するであろう擬似ヘッダは<code>:method</code>と<code>:path</code>です。名前から察する通り<code>:method</code>はHTTPメソッド、<code>:path</code>はリクエストされたパス（クエリ文字列含む）が格納されています。リクエストボディが不要ならこの時点でリクエストを処理できます。</p>\n<p>他にもヘッダに関するメソッド、イベントがありますが、使い分けは以下の通りです。</p>\n<blockquote>\n<ul>\n<li>Informational Headers: Any response headers transmitted within a block of headers using a 1xx status code</li>\n<li>Initial Headers: HTTP request or response headers</li>\n<li>Trailing Headers: A block of headers that follow the body of a request or response</li>\n<li>Push Promise Headers: A block of headers included in a promised push stream</li>\n</ul>\n<p>— <a href=\"https://nodejs.org/dist/latest-v15.x/docs/api/quic.html#quic_quicstream_headers\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">QuicStream headers | Node.js v15.0.1 Documentation</a></p>\n</blockquote>\n<h4 id=\"4-レスポンスヘッダを返す\" style=\"position:relative;\"><a href=\"#4-%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%83%98%E3%83%83%E3%83%80%E3%82%92%E8%BF%94%E3%81%99\" aria-label=\"4 レスポンスヘッダを返す permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>[4] レスポンスヘッダを返す</h4>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// [4] レスポンスヘッダを返す</span>\nstream<span class=\"token punctuation\">.</span><span class=\"token function\">submitInitialHeaders</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  <span class=\"token string\">':status'</span><span class=\"token operator\">:</span> <span class=\"token string\">'403'</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\nstream<span class=\"token punctuation\">.</span><span class=\"token function\">end</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p><code>stream</code>イベントで受け取ったQuicStreamに対して<code>submitInitialHeaders</code>メソッドをコールすることでレスポンスヘッダを返せます。<br>\nHTTPステータスコードは<code>:status</code>という擬似ヘッダでセットします。</p>\n<p>ボディを返さずにレスポンスを終了する場合は<code>end</code>でストリームを閉じます。</p>\n<h4 id=\"5-レスポンスボディを返す\" style=\"position:relative;\"><a href=\"#5-%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%83%9C%E3%83%87%E3%82%A3%E3%82%92%E8%BF%94%E3%81%99\" aria-label=\"5 レスポンスボディを返す permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>[5] レスポンスボディを返す</h4>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// [5] レスポンスボディを返す</span>\nfs<span class=\"token punctuation\">.</span><span class=\"token function\">createReadStream</span><span class=\"token punctuation\">(</span>requestPath<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">pipe</span><span class=\"token punctuation\">(</span>stream<span class=\"token punctuation\">)</span></code></pre></div>\n<p>QuicStreamはストリームです。リクエストボディの読み取りとレスポンスボディの書き込み両方に対応するためにstream.Duplexを継承しています。<br>\nファイルの内容をレスポンスするならReadableなストリームを作りパイプするだけです。ストリームではない文字列を書き込む場合は<code>stream.write('...')</code>などを使用できます。この辺はストリームの基礎的な話なので詳しくは割愛します。</p>\n<p>駆け足ですが解説は以上です。次に作ったサーバの動作確認をします。</p>\n<h3 id=\"動作確認\" style=\"position:relative;\"><a href=\"#%E5%8B%95%E4%BD%9C%E7%A2%BA%E8%AA%8D\" aria-label=\"動作確認 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>動作確認</h3>\n<p>最後に起動したサーバの動作確認をします。サーバが正常に起動すると以下のような出力になると思います。</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">$ PORT=8080 DOCUMENT_ROOT=$PWD ./node http3-serve.js \nThe socket is listening on :8080\n(node:10968) ExperimentalWarning: The QUIC protocol is experimental and not yet supported for production use\n(Use `node --trace-warnings ...` to show where the warning was created)</code></pre></div>\n<p>curlコマンドを利用していくつかリクエストを飛ばしてみます。<code>host.docker.internal</code>はホスト側のlocalhostを参照するDockerの特殊なホスト名です。Linuxの方は適宜読み替えてください。</p>\n<h4 id=\"正常系\" style=\"position:relative;\"><a href=\"#%E6%AD%A3%E5%B8%B8%E7%B3%BB\" aria-label=\"正常系 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>正常系</h4>\n<p>まずは正常系です。ファイルが存在しているので200が返されます。</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">$ echo 'Hello world' > hello.txt # サーバを起動したディレクトリで適当なファイルを作る\n$ docker run -it --rm ymuski/curl-http3 curl -v 'https://host.docker.internal:8080/hello.txt' --http3\n*   Trying 192.168.65.2:8080...\n* Sent QUIC client Initial, ALPN: h3-29,h3-28,h3-27\n* Connected to host.docker.internal (192.168.65.2) port 8080 (#0)\n* h3 [:method: GET]\n* h3 [:path: /hello.txt]\n* h3 [:scheme: https]\n* h3 [:authority: host.docker.internal:8080]\n* h3 [user-agent: curl/7.73.0-DEV]\n* h3 [accept: */*]\n* Using HTTP/3 Stream ID: 0 (easy handle 0x562874d14a40)\n> GET /hello.txt HTTP/3\n> Host: host.docker.internal:8080\n> user-agent: curl/7.73.0-DEV\n> accept: */*\n>\n&lt; HTTP/3 200\n&lt; content-length: 12\n&lt; content-type: text/plain\n&lt;\nHello world\n* Connection #0 to host host.docker.internal left intact</code></pre></div>\n<h4 id=\"異常系404\" style=\"position:relative;\"><a href=\"#%E7%95%B0%E5%B8%B8%E7%B3%BB404\" aria-label=\"異常系404 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>異常系（404）</h4>\n<p>ファイルが存在しないパスにリクエストします。404が返されます。</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">$ docker run -it --rm ymuski/curl-http3 curl -v 'https://host.docker.internal:8080/xxx' --http3\n*   Trying 192.168.65.2:8080...\n* Sent QUIC client Initial, ALPN: h3-29,h3-28,h3-27\n* Connected to host.docker.internal (192.168.65.2) port 8080 (#0)\n* h3 [:method: GET]\n* h3 [:path: /xxx]\n* h3 [:scheme: https]\n* h3 [:authority: host.docker.internal:8080]\n* h3 [user-agent: curl/7.73.0-DEV]\n* h3 [accept: */*]\n* Using HTTP/3 Stream ID: 0 (easy handle 0x56541c3d9a30)\n> GET /xxx HTTP/3\n> Host: host.docker.internal:8080\n> user-agent: curl/7.73.0-DEV\n> accept: */*\n>\n&lt; HTTP/3 404\n* Connection #0 to host host.docker.internal left intact</code></pre></div>\n<h4 id=\"異常系２403\" style=\"position:relative;\"><a href=\"#%E7%95%B0%E5%B8%B8%E7%B3%BB%EF%BC%92403\" aria-label=\"異常系２403 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>異常系２（403）</h4>\n<p>最後にリクエストしたパスがファイルではない場合のテストです。403が返されます。</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">$ mkdir dir # サーバを起動したディレクトリでディレクトリを作成する\n$ docker run -it --rm ymuski/curl-http3 curl -v 'https://host.docker.internal:8080/dir' --http3\n*   Trying 192.168.65.2:8080...\n* Sent QUIC client Initial, ALPN: h3-29,h3-28,h3-27\n* Connected to host.docker.internal (192.168.65.2) port 8080 (#0)\n* h3 [:method: GET]\n* h3 [:path: /dir]\n* h3 [:scheme: https]\n* h3 [:authority: host.docker.internal:8080]\n* h3 [user-agent: curl/7.73.0-DEV]\n* h3 [accept: */*]\n* Using HTTP/3 Stream ID: 0 (easy handle 0x562fe362ba30)\n> GET /dir HTTP/3\n> Host: host.docker.internal:8080\n> user-agent: curl/7.73.0-DEV\n> accept: */*\n>\n&lt; HTTP/3 403\n* Connection #0 to host host.docker.internal left intact</code></pre></div>\n<h2 id=\"さいごに\" style=\"position:relative;\"><a href=\"#%E3%81%95%E3%81%84%E3%81%94%E3%81%AB\" aria-label=\"さいごに permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>さいごに</h2>\n<p>本記事では本当に最低限の処理しかしてないので、あとはドキュメントやソースコード、QUIC、HTTP/3の仕様書を読みながらいろいろ試してみてください。Node.jsの内部実装の話や、0-RTT・Server pushなどのHTTP/3の他の機能を試す記事も書けたら書こうと思います。ただ、冒頭にも書いた通り現時点ではまだUndocumentedなAPIなので深く使い込むのは時期尚早だと思います。今後QUICをラップした高レベルのAPIがおそらく登場するので、しっかり学ぶのはそれを待ってからでも遅くないと思います。</p>\n<p>Node.jsのコミュニティはオープンで誰でも開発・議論に参加できます。例えばドキュメントの誤字脱字やAPIに対するフィードバック、仕様と実装が乖離しているなどの何かしらの問題を見つけたらチャンスと思ってコントリビュートしてもらえればと思います。興味のある方は<a href=\"https://github.com/nodejs/node\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">nodejs/node</a>リポジトリからぜひ参加してみてください。</p>\n<h2 id=\"参考情報\" style=\"position:relative;\"><a href=\"#%E5%8F%82%E8%80%83%E6%83%85%E5%A0%B1\" aria-label=\"参考情報 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>参考情報</h2>\n<p>これらの一次情報が参考になりました。</p>\n<ul>\n<li><a href=\"https://nodejs.org/dist/latest-v15.x/docs/api/quic.html\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">QUIC | Node.js v15.0.1 Documentation</a>\n<ul>\n<li>公式ドキュメント</li>\n</ul>\n</li>\n<li><a href=\"https://github.com/nodejs/node/blob/7657f62b1810b94acbe7db68089b608213b34749/test/parallel/test-quic-http3-client-server.js\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">test/parallel/test-quic-http3-client-server.js</a>\n<ul>\n<li>nodejs/nodeの中にあるテストコード</li>\n</ul>\n</li>\n</ul>\n<p>これらの二次情報も参考になりました。</p>\n<ul>\n<li><a href=\"https://www.nearform.com/blog/a-quic-update-for-node-js/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">A QUIC Update for Node.js</a>\n<ul>\n<li>Node.jsにQUICを実装したJames Snellによる紹介記事、ただし記事内のAPIが古い</li>\n</ul>\n</li>\n</ul>","timeToRead":19,"frontmatter":{"title":"Node.jsのHTTP over QUIC(HTTP/3)を試す","tags":["JavaScript","Node.js","HTTP/3","QUIC"],"date":"October 25, 2020","featuredImage":{"childImageSharp":{"fluid":{"tracedSVG":"data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='400'%20height='136'%20viewBox='0%200%20400%20136'%20preserveAspectRatio='none'%3e%3cpath%20d='M63%2026L45%2037l6%204c6%204%206%204%205%206-2%201-3%200-8-3l-9-3c-2%200-5%202-3%203l6%203c6%204%206%204%204%205s-3%201-7-2c-7-4-12-4-15%200-3%203-3%203-3%2018l1%2017c2%203%2058%2036%2062%2036l39-22a492%20492%200%2000-56-33c0-4%206-1%2031%2013%2026%2016%2032%2018%2034%2015l-28-17C79%2063%2076%2061%2078%2059l2-1a11942%2011942%200%200157%2032c3%200%208-3%209-6%202-5%201-31-1-34-4-4-58-34-61-34s-8%203-21%2010m183%2018h-6v18l1%2018%204%204c5%203%206%203%2017%203s11%200%2017-3l5-3V44h-12v32h-19l-1-16V44h-6m66%200h-6v43h12V43l-6%201m52%200c-14%200-15%201-19%203l-5%203v15l1%2015%204%204%205%203h28V76h-26V55h26V43l-14%201m-190%203l-4%204-1%2014c0%2016%200%2016%207%2020%204%202%206%202%2015%202%2010%200%2011%200%2016%203%206%204%209%204%2015%200%206-3%206-3%201-6l-5-2V51l-5-4-5-3h-29l-5%203m8%2019v10h23V55h-23v11'%20fill='%23d3d3d3'%20fill-rule='evenodd'/%3e%3c/svg%3e","aspectRatio":2.9557522123893807,"src":"/static/0e40d75dae683074c1af61e73faf0fd2/8eab8/2020-10-26-21-50-02.png","srcSet":"/static/0e40d75dae683074c1af61e73faf0fd2/1ec58/2020-10-26-21-50-02.png 334w,\n/static/0e40d75dae683074c1af61e73faf0fd2/ccb4a/2020-10-26-21-50-02.png 668w,\n/static/0e40d75dae683074c1af61e73faf0fd2/8eab8/2020-10-26-21-50-02.png 1336w,\n/static/0e40d75dae683074c1af61e73faf0fd2/ed396/2020-10-26-21-50-02.png 2000w","srcWebp":"/static/0e40d75dae683074c1af61e73faf0fd2/f7e47/2020-10-26-21-50-02.webp","srcSetWebp":"/static/0e40d75dae683074c1af61e73faf0fd2/cd98f/2020-10-26-21-50-02.webp 334w,\n/static/0e40d75dae683074c1af61e73faf0fd2/7535d/2020-10-26-21-50-02.webp 668w,\n/static/0e40d75dae683074c1af61e73faf0fd2/f7e47/2020-10-26-21-50-02.webp 1336w,\n/static/0e40d75dae683074c1af61e73faf0fd2/37117/2020-10-26-21-50-02.webp 2000w","sizes":"(max-width: 1336px) 100vw, 1336px"}}}}}},"pageContext":{"slug":"/http-over-quic-on-nodejs15/","previous":{"fields":{"slug":"/hack-spy-family-advanced-missions/"},"frontmatter":{"title":"SPY×FAMILY SPECIAL MISSION上級編を一切謎を解かずに突破する","tags":["JavaScript"]}},"next":{"fields":{"slug":"/2020-js-ts-trending-history/"},"frontmatter":{"title":"GitHubのトレンドで振り返る2020年のJavaScript","tags":["JavaScript","TypeScript","GitHub"]}}}},
    "staticQueryHashes": ["2585454260","2954598359"]}