SSR StreamingをHTTPフレーム単位で見る
概要
SSR Streamingは、使いやすく素晴らしい機能ですが、
その通信がどのような仕組みで動いてるかの記事が少ないため、HTTPフレームの通信を見ていきます。
また、この記事では、HTTP/1.1での通信は行わず、HTTP/2のみ関心を寄せています。
(本来は、HTTP/3の通信も確認したいですが、vercelがHTTP/2のみ対応しているので...)
Streaming
まず、大前提としてストリーミングですが、
今回はNext.js 14のストリーミングを基準に考えていきます。
Google Chrome上で確認すると、画像のようにダウンロードが長く見えるのが特徴です。
サーバー側でのレンダリング処理がデータフェッチなどの非同期処理によって長時間かかる場合、
ブラウザ側ではHTMLを受け取れず、ブラウザ側はネットワークもCPUなどのリソースを持て余してしまいます。
例えば、Early Hintsなどを利用しなければpreloadリソースなどの、リソースの先読みが行えません。
そのような時に、重たい処理をsuspenseでwrapし、ストリーミングを行えるようにすると、すぐにブラウザ側にHTMLを送信することができ、無駄な待ち時間の解消につながります。
streamingの通信を見る
/streaming/[ms]/でms秒を指定することができます。
ザックリ実装されている。アプリの解説をします。
streaming-frame-test/streaming/[ms]/の説明
フロント側から受け取った[ms]をそのままAPIへリクエストし、
API側では、指定ms
、sleep処理します
また、/streaming/[ms]/では、
サーバーキャッシュを行わないようにしています
chromeの開発者ツールでの確認
ms=0の時と、ms=500の違いでは、コンテンツのダウンロードがその分伸びているのがわかります。
nghttp2でみる
nghttp2は、HTTP/2の通信をHTTP/2フレーム単位で見ることができます。
brew install nghttp2
nghttp -nva https://streaming-frame-test.vercel.app/dynamic/500
実行結果
nghttp -nva https://streaming-frame-test.vercel.app/streaming/500
[ 0.032] Connected
The negotiated protocol: h2
[ 0.050] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.050] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
(dep_stream_id=0, weight=201, exclusive=0)
[ 0.050] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
(dep_stream_id=0, weight=101, exclusive=0)
[ 0.050] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
(dep_stream_id=0, weight=1, exclusive=0)
[ 0.050] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
(dep_stream_id=7, weight=1, exclusive=0)
[ 0.050] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
(dep_stream_id=3, weight=1, exclusive=0)
[ 0.050] send HEADERS frame <length=61, flags=0x25, stream_id=13>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=11, weight=16, exclusive=0)
; Open new stream
:method: GET
:path: /streaming/500
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 0.057] recv SETTINGS frame <length=30, flags=0x00, stream_id=0>
(niv=5)
[SETTINGS_MAX_FRAME_SIZE(0x05):1048576]
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):250]
[SETTINGS_MAX_HEADER_LIST_SIZE(0x06):2097472]
[SETTINGS_HEADER_TABLE_SIZE(0x01):4096]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):1048576]
[ 0.057] recv WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=983041)
[ 0.057] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.057] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 1.264] recv (stream_id=13) :status: 200
[ 1.264] recv (stream_id=13) age: 0
[ 1.264] recv (stream_id=13) cache-control: private, no-cache, no-store, max-age=0, must-revalidate
[ 1.264] recv (stream_id=13) content-encoding: gzip
[ 1.264] recv (stream_id=13) content-type: text/html; charset=utf-8
[ 1.264] recv (stream_id=13) date: Tue, 05 Dec 2023 19:53:10 GMT
[ 1.264] recv (stream_id=13) server: Vercel
[ 1.264] recv (stream_id=13) strict-transport-security: max-age=63072000; includeSubDomains; preload
[ 1.264] recv (stream_id=13) vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url
[ 1.264] recv (stream_id=13) x-matched-path: /streaming/[ms]
[ 1.264] recv (stream_id=13) x-powered-by: Next.js
[ 1.264] recv (stream_id=13) x-vercel-cache: MISS
[ 1.264] recv (stream_id=13) x-vercel-execution-region: iad1
[ 1.264] recv (stream_id=13) x-vercel-id: hnd1::iad1::55gcs-1701805988927-5da5bfba3555
[ 1.264] recv HEADERS frame <length=311, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
[ 1.264] recv DATA frame <length=750, flags=0x00, stream_id=13>
[ 1.264] recv DATA frame <length=1108, flags=0x00, stream_id=13>
[ 1.264] send HEADERS frame <length=49, flags=0x25, stream_id=15>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=11, weight=32, exclusive=0)
; Open new stream
:method: GET
:path: /_next/static/media/c9a5bc6a7c948fb0-s.p.woff2
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 1.264] send HEADERS frame <length=40, flags=0x25, stream_id=17>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=3, weight=32, exclusive=0)
; Open new stream
:method: GET
:path: /_next/static/css/e7d3bab42d9af29d.css
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 1.264] send HEADERS frame <length=48, flags=0x25, stream_id=19>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=5, weight=32, exclusive=0)
; Open new stream
:method: GET
:path: /_next/static/chunks/webpack-d9f8a4f0dd52fadf.js
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 1.265] send HEADERS frame <length=48, flags=0x25, stream_id=21>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=3, weight=32, exclusive=0)
; Open new stream
:method: GET
:path: /_next/static/chunks/fd9d1056-7b52db27cfdaff1f.js
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 1.265] send HEADERS frame <length=45, flags=0x25, stream_id=23>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=3, weight=32, exclusive=0)
; Open new stream
:method: GET
:path: /_next/static/chunks/472-b67f79dbdd2c1fe1.js
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 1.275] send HEADERS frame <length=48, flags=0x25, stream_id=25>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=3, weight=32, exclusive=0)
; Open new stream
:method: GET
:path: /_next/static/chunks/main-app-892c3dff08e9cd4c.js
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 1.275] send HEADERS frame <length=49, flags=0x25, stream_id=27>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=3, weight=32, exclusive=0)
; Open new stream
:method: GET
:path: /_next/static/chunks/polyfills-c67a75d1b6f99dc8.js
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 1.286] recv (stream_id=19) :status: 200
[ 1.286] recv (stream_id=19) access-control-allow-origin: *
[ 1.286] recv (stream_id=19) age: 172821
[ 1.286] recv (stream_id=19) cache-control: public,max-age=31536000,immutable
[ 1.286] recv (stream_id=19) content-disposition: inline; filename="webpack-d9f8a4f0dd52fadf.js"
[ 1.286] recv (stream_id=19) content-encoding: gzip
[ 1.286] recv (stream_id=19) content-type: application/javascript; charset=utf-8
[ 1.286] recv (stream_id=19) date: Tue, 05 Dec 2023 19:53:10 GMT
[ 1.286] recv (stream_id=19) etag: W/"f550bbe13e984c7ddb673492f04171d0"
[ 1.286] recv (stream_id=19) server: Vercel
[ 1.286] recv (stream_id=19) strict-transport-security: max-age=63072000; includeSubDomains; preload
[ 1.286] recv (stream_id=19) x-matched-path: /_next/static/chunks/webpack-d9f8a4f0dd52fadf.js
[ 1.286] recv (stream_id=19) x-vercel-cache: HIT
[ 1.286] recv (stream_id=19) x-vercel-id: hnd1::sszdh-1701805990152-25c867bb0852
[ 1.286] recv HEADERS frame <length=209, flags=0x04, stream_id=19>
; END_HEADERS
(padlen=0)
; First response header
[ 1.287] recv DATA frame <length=1643, flags=0x00, stream_id=19>
[ 1.287] recv (stream_id=25) :status: 200
[ 1.287] recv (stream_id=25) accept-ranges: bytes
[ 1.287] recv (stream_id=25) access-control-allow-origin: *
[ 1.287] recv (stream_id=25) age: 172821
[ 1.287] recv (stream_id=25) cache-control: public,max-age=31536000,immutable
[ 1.287] recv (stream_id=25) content-disposition: inline; filename="main-app-892c3dff08e9cd4c.js"
[ 1.287] recv (stream_id=25) content-type: application/javascript; charset=utf-8
[ 1.287] recv (stream_id=25) date: Tue, 05 Dec 2023 19:53:10 GMT
[ 1.287] recv (stream_id=25) etag: "c9a92bcd7028363edf7f2ff618d57922"
[ 1.287] recv (stream_id=25) server: Vercel
[ 1.287] recv (stream_id=25) strict-transport-security: max-age=63072000; includeSubDomains; preload
[ 1.287] recv (stream_id=25) x-matched-path: /_next/static/chunks/main-app-892c3dff08e9cd4c.js
[ 1.287] recv (stream_id=25) x-vercel-cache: HIT
[ 1.287] recv (stream_id=25) x-vercel-id: hnd1::frxb8-1701805990152-8237c55906be
[ 1.287] recv (stream_id=25) content-length: 463
[ 1.287] recv HEADERS frame <length=154, flags=0x04, stream_id=25>
; END_HEADERS
(padlen=0)
; First response header
[ 1.287] recv DATA frame <length=0, flags=0x01, stream_id=19>
; END_STREAM
[ 1.287] recv DATA frame <length=463, flags=0x00, stream_id=25>
[ 1.287] recv DATA frame <length=0, flags=0x01, stream_id=25>
; END_STREAM
[ 1.288] recv (stream_id=17) :status: 200
[ 1.288] recv (stream_id=17) access-control-allow-origin: *
[ 1.288] recv (stream_id=17) age: 172821
[ 1.288] recv (stream_id=17) cache-control: public,max-age=31536000,immutable
[ 1.288] recv (stream_id=17) content-disposition: inline; filename="e7d3bab42d9af29d.css"
[ 1.288] recv (stream_id=17) content-encoding: gzip
[ 1.288] recv (stream_id=17) content-type: text/css; charset=utf-8
[ 1.288] recv (stream_id=17) date: Tue, 05 Dec 2023 19:53:10 GMT
[ 1.288] recv (stream_id=17) etag: W/"8fd6a223d7ac13f3dfc7bde4e2de01cb"
[ 1.288] recv (stream_id=17) server: Vercel
[ 1.288] recv (stream_id=17) strict-transport-security: max-age=63072000; includeSubDomains; preload
[ 1.288] recv (stream_id=17) x-matched-path: /_next/static/css/e7d3bab42d9af29d.css
[ 1.288] recv (stream_id=17) x-vercel-cache: HIT
[ 1.288] recv (stream_id=17) x-vercel-id: hnd1::d4d2d-1701805990152-db8589e45365
[ 1.288] recv HEADERS frame <length=149, flags=0x04, stream_id=17>
; END_HEADERS
(padlen=0)
; First response header
[ 1.288] recv DATA frame <length=615, flags=0x00, stream_id=17>
[ 1.288] recv DATA frame <length=0, flags=0x01, stream_id=17>
; END_STREAM
[ 1.288] recv (stream_id=15) :status: 200
[ 1.288] recv (stream_id=15) accept-ranges: bytes
[ 1.288] recv (stream_id=15) access-control-allow-origin: *
[ 1.299] recv (stream_id=15) age: 172821
[ 1.299] recv (stream_id=15) cache-control: public,max-age=31536000,immutable
[ 1.299] recv (stream_id=15) content-disposition: inline; filename="c9a5bc6a7c948fb0-s.p.woff2"
[ 1.299] recv (stream_id=15) content-type: font/woff2
[ 1.299] recv (stream_id=15) date: Tue, 05 Dec 2023 19:53:10 GMT
[ 1.299] recv (stream_id=15) etag: "74c3556b9dad12fb76f84af53ba69410"
[ 1.299] recv (stream_id=15) server: Vercel
[ 1.299] recv (stream_id=15) strict-transport-security: max-age=63072000; includeSubDomains; preload
[ 1.299] recv (stream_id=15) x-matched-path: /_next/static/media/c9a5bc6a7c948fb0-s.p.woff2
[ 1.299] recv (stream_id=15) x-vercel-cache: HIT
[ 1.299] recv (stream_id=15) x-vercel-id: hnd1::frxb8-1701805990152-bbfbb030acf6
[ 1.299] recv (stream_id=15) content-length: 46552
[ 1.299] recv HEADERS frame <length=156, flags=0x04, stream_id=15>
; END_HEADERS
(padlen=0)
; First response header
[ 1.299] recv DATA frame <length=3513, flags=0x00, stream_id=15>
[ 1.299] recv DATA frame <length=16384, flags=0x00, stream_id=15>
[ 1.299] recv DATA frame <length=16384, flags=0x00, stream_id=15>
[ 1.299] recv DATA frame <length=10271, flags=0x00, stream_id=15>
[ 1.299] recv DATA frame <length=0, flags=0x01, stream_id=15>
; END_STREAM
[ 1.299] recv (stream_id=27) :status: 200
[ 1.299] recv (stream_id=27) access-control-allow-origin: *
[ 1.299] recv (stream_id=27) age: 2092
[ 1.299] recv (stream_id=27) cache-control: public,max-age=31536000,immutable
[ 1.299] recv (stream_id=27) content-disposition: inline; filename="polyfills-c67a75d1b6f99dc8.js"
[ 1.299] recv (stream_id=27) content-encoding: gzip
[ 1.299] recv (stream_id=27) content-type: application/javascript; charset=utf-8
[ 1.299] recv (stream_id=27) date: Tue, 05 Dec 2023 19:53:10 GMT
[ 1.299] recv (stream_id=27) etag: W/"837c0df77fd5009c9e46d446188ecfd0"
[ 1.299] recv (stream_id=27) server: Vercel
[ 1.299] recv (stream_id=27) strict-transport-security: max-age=63072000; includeSubDomains; preload
[ 1.299] recv (stream_id=27) x-matched-path: /_next/static/chunks/polyfills-c67a75d1b6f99dc8.js
[ 1.299] recv (stream_id=27) x-vercel-cache: HIT
[ 1.299] recv (stream_id=27) x-vercel-id: hnd1::mc7xd-1701805990152-7a229614cc4b
[ 1.299] recv HEADERS frame <length=150, flags=0x04, stream_id=27>
; END_HEADERS
(padlen=0)
; First response header
[ 1.299] recv DATA frame <length=14404, flags=0x00, stream_id=27>
[ 1.299] recv (stream_id=23) :status: 200
[ 1.299] recv (stream_id=23) access-control-allow-origin: *
[ 1.299] recv (stream_id=23) age: 172821
[ 1.299] recv (stream_id=23) cache-control: public,max-age=31536000,immutable
[ 1.299] recv (stream_id=23) content-disposition: inline; filename="472-b67f79dbdd2c1fe1.js"
[ 1.299] recv (stream_id=23) content-encoding: gzip
[ 1.299] recv (stream_id=23) content-type: application/javascript; charset=utf-8
[ 1.299] recv (stream_id=23) date: Tue, 05 Dec 2023 19:53:10 GMT
[ 1.299] recv (stream_id=23) etag: W/"cb18edb92ec72f27e6d4e762a70d4128"
[ 1.299] recv (stream_id=23) server: Vercel
[ 1.315] recv (stream_id=23) strict-transport-security: max-age=63072000; includeSubDomains; preload
[ 1.315] recv (stream_id=23) x-matched-path: /_next/static/chunks/472-b67f79dbdd2c1fe1.js
[ 1.315] recv (stream_id=23) x-vercel-cache: HIT
[ 1.315] recv (stream_id=23) x-vercel-id: hnd1::dxcxr-1701805990152-bcddc3a4871b
[ 1.315] recv HEADERS frame <length=139, flags=0x04, stream_id=23>
; END_HEADERS
(padlen=0)
; First response header
[ 1.315] recv (stream_id=21) :status: 200
[ 1.315] recv (stream_id=21) access-control-allow-origin: *
[ 1.315] recv (stream_id=21) age: 172821
[ 1.315] recv (stream_id=21) cache-control: public,max-age=31536000,immutable
[ 1.315] recv (stream_id=21) content-disposition: inline; filename="fd9d1056-7b52db27cfdaff1f.js"
[ 1.315] recv (stream_id=21) content-encoding: gzip
[ 1.315] recv (stream_id=21) content-type: application/javascript; charset=utf-8
[ 1.315] recv (stream_id=21) date: Tue, 05 Dec 2023 19:53:10 GMT
[ 1.315] recv (stream_id=21) etag: W/"9dee4994f9e89448ff05c84f6bb40b96"
[ 1.315] recv (stream_id=21) server: Vercel
[ 1.315] recv (stream_id=21) strict-transport-security: max-age=63072000; includeSubDomains; preload
[ 1.315] recv (stream_id=21) x-matched-path: /_next/static/chunks/fd9d1056-7b52db27cfdaff1f.js
[ 1.315] recv (stream_id=21) x-vercel-cache: HIT
[ 1.315] recv (stream_id=21) x-vercel-id: hnd1::6f7kl-1701805990152-d247e4623042
[ 1.315] recv HEADERS frame <length=146, flags=0x04, stream_id=21>
; END_HEADERS
(padlen=0)
; First response header
[ 1.315] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=40851)
[ 1.323] recv DATA frame <length=1980, flags=0x00, stream_id=27>
[ 1.323] recv DATA frame <length=16384, flags=0x00, stream_id=23>
[ 1.323] recv DATA frame <length=14981, flags=0x00, stream_id=27>
[ 1.323] recv DATA frame <length=7506, flags=0x00, stream_id=21>
[ 1.323] recv DATA frame <length=0, flags=0x01, stream_id=27>
; END_STREAM
[ 1.323] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=36954)
[ 1.334] recv DATA frame <length=12765, flags=0x00, stream_id=23>
[ 1.334] recv DATA frame <length=16384, flags=0x00, stream_id=21>
[ 1.334] recv DATA frame <length=7805, flags=0x00, stream_id=21>
[ 1.334] recv DATA frame <length=0, flags=0x01, stream_id=23>
; END_STREAM
[ 1.334] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=36764)
[ 1.345] recv DATA frame <length=1073, flags=0x00, stream_id=21>
[ 1.345] recv DATA frame <length=16384, flags=0x00, stream_id=21>
[ 1.345] recv DATA frame <length=4777, flags=0x00, stream_id=21>
[ 1.345] recv DATA frame <length=0, flags=0x01, stream_id=21>
; END_STREAM
[ 1.345] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=38027)
[ 2.476] recv DATA frame <length=28, flags=0x00, stream_id=13>
[ 2.476] recv DATA frame <length=11, flags=0x00, stream_id=13>
[ 2.493] recv DATA frame <length=327, flags=0x00, stream_id=13>
[ 2.493] recv DATA frame <length=17, flags=0x00, stream_id=13>
[ 2.493] recv DATA frame <length=10, flags=0x00, stream_id=13>
[ 2.493] recv DATA frame <length=0, flags=0x01, stream_id=13>
; END_STREAM
[ 2.493] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])
今回は上記のコマンドを実行します。
オプションやnghttpの詳細の説明は省きます。
send SETTINGS
nghttp -nva https://streaming-frame-test.vercel.app/streaming/500
[ 0.024] Connected
The negotiated protocol: h2
[ 0.039] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.048] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
(dep_stream_id=0, weight=201, exclusive=0)
[ 0.048] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
(dep_stream_id=0, weight=101, exclusive=0)
[ 0.048] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
(dep_stream_id=0, weight=1, exclusive=0)
[ 0.048] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
(dep_stream_id=7, weight=1, exclusive=0)
[ 0.048] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
(dep_stream_id=3, weight=1, exclusive=0)
-nva
で実際のレスポンス結果を非表示にしてくれます
レスポンスと合わせて見たい場合は、オプションを-va
としてください。
まず、
SETTINGSフレームを送信し、サーバーとのやりとりを開始します。
PRIORITYフレームは従来のHTTP/2での優先度制御であり、今回は扱いません。
参照:
RFC7540 日本語訳
RFC9218
recv HEADERS (HTML)
次にHEADERSフレームを送信します。
これはHTTPリクエストのヘッダーのようなもので、その後にしばらくすると、recv HEADERS
します
HTTP/2では、特定のデータのまとまり(ストリーム)がstream_id
としてidが振り分けられます。
例えば、ここのstream_id=13
はHTMLを示しています。
ストリームでの通信が開始する場合(リクエストする場合)
Open new stream
フラグが設定されます
[ 0.050] send HEADERS frame <length=61, flags=0x25, stream_id=13>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=11, weight=16, exclusive=0)
; Open new stream
:method: GET
:path: /streaming/500
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 0.057] recv SETTINGS frame <length=30, flags=0x00, stream_id=0>
(niv=5)
[SETTINGS_MAX_FRAME_SIZE(0x05):1048576]
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):250]
[SETTINGS_MAX_HEADER_LIST_SIZE(0x06):2097472]
[SETTINGS_HEADER_TABLE_SIZE(0x01):4096]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):1048576]
[ 0.057] recv WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=983041)
[ 0.057] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.057] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 1.264] recv (stream_id=13) :status: 200
[ 1.264] recv (stream_id=13) age: 0
[ 1.264] recv (stream_id=13) cache-control: private, no-cache, no-store, max-age=0, must-revalidate
[ 1.264] recv (stream_id=13) content-encoding: gzip
[ 1.264] recv (stream_id=13) content-type: text/html; charset=utf-8
[ 1.264] recv (stream_id=13) date: Tue, 05 Dec 2023 19:53:10 GMT
[ 1.264] recv (stream_id=13) server: Vercel
[ 1.264] recv (stream_id=13) strict-transport-security: max-age=63072000; includeSubDomains; preload
[ 1.264] recv (stream_id=13) vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url
[ 1.264] recv (stream_id=13) x-matched-path: /streaming/[ms]
[ 1.264] recv (stream_id=13) x-powered-by: Next.js
[ 1.264] recv (stream_id=13) x-vercel-cache: MISS
[ 1.264] recv (stream_id=13) x-vercel-execution-region: iad1
[ 1.264] recv (stream_id=13) x-vercel-id: hnd1::iad1::55gcs-1701805988927-5da5bfba3555
[ 1.264] recv HEADERS frame <length=311, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
recv DATA (HTML)
その次に、DATAフレームを取得します。
これはHTTP/2でのデータのやり取りで、stream_id=13
ですのでHTMLをフレーム単位で受け取っています。
[ 1.264] recv DATA frame <length=750, flags=0x00, stream_id=13>
[ 1.264] recv DATA frame <length=1108, flags=0x00, stream_id=13>
試しに、-va
オプションで実行すると下記のようになります。
HTMLが断片的に取得されているのがわかります。
`-va`での実行
nghttp -va https://streaming-frame-test.vercel.app/streaming/500
...省略
[ 0.464] recv (stream_id=13) :status: 200
[ 0.464] recv (stream_id=13) age: 0
[ 0.464] recv (stream_id=13) cache-control: private, no-cache, no-store, max-age=0, must-revalidate
[ 0.464] recv (stream_id=13) content-encoding: gzip
[ 0.464] recv (stream_id=13) content-type: text/html; charset=utf-8
[ 0.464] recv (stream_id=13) date: Tue, 05 Dec 2023 20:14:01 GMT
[ 0.464] recv (stream_id=13) server: Vercel
[ 0.464] recv (stream_id=13) strict-transport-security: max-age=63072000; includeSubDomains; preload
[ 0.464] recv (stream_id=13) vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url
[ 0.464] recv (stream_id=13) x-matched-path: /streaming/[ms]
[ 0.464] recv (stream_id=13) x-powered-by: Next.js
[ 0.464] recv (stream_id=13) x-vercel-cache: MISS
[ 0.464] recv (stream_id=13) x-vercel-execution-region: iad1
[ 0.464] recv (stream_id=13) x-vercel-id: hnd1::iad1::55gcs-1701807241685-c028066d78eb
[ 0.464] recv HEADERS frame <length=311, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
<!DOCTYPE html><html lang="ja"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/e7d3bab42d9af29d.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-d9f8a4f0dd52fadf.js"/><script src="/_next/static/chunks/fd9d1056-7b52db27cfdaff1f.js" async=""></script><script src="/_next/static/chunks/472-b67f79dbdd2c1fe1.js" async=""></script><script src="/_next/static/chunks/main-app-892c3dff08e9cd4c.js" async=""></script><title>streaming-test</title><meta name="description" content="check streaming"/><link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16"/><meta name="next-size-adjust"/><script src="/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js" noModule=""></script></head><body class="__className_e66fe9"><main><div>Streaming-test<!--$?--><template id="B:0"></template><p>Loading...</p><!--/$--></div></main><script src="/_next/static/chunks/webpack-d9f8a4f0dd52fadf.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n2:HL[\"/_next/static/css/e7d3bab42d9af29d.css\",\"style\"]\n0:\"$L3\"\n"])</script>[ 0.465] recv DATA frame <length=700, flags=0x00, stream_id=13>
<script>self.__next_f.push([1,"4:I[3728,[],\"\"]\n6:I[9928,[],\"\"]\n7:I[6954,[],\"\"]\n8:I[7264,[],\"\"]\na:\"$Sreact.suspense\"\n"])</script><script>self.__next_f.push([1,"3:[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/e7d3bab42d9af29d.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\"}]],[\"$\",\"$L4\",null,{\"buildId\":\"mJgjBQrRTVvbQPibOFT9R\",\"assetPrefix\":\"\",\"initialCanonicalUrl\":\"/streaming/500\",\"initialTree\":[\"\",{\"children\":[\"streaming\",{\"children\":[[\"ms\",\"500\",\"d\"],{\"children\":[\"__PAGE__\",{}]}]}]},\"$undefined\",\"$undefined\",true],\"initialHead\":[false,\"$L5\"],\"globalErrorComponent\":\"$6\",\"children\":[null,[\"$\",\"html\",null,{\"lang\":\"ja\",\"children\":[\"$\",\"body\",null,{\"className\":\"__className_e66fe9\",\"children\":[\"$\",\"$L7\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"loading\":\"$undefined\",\"loadingStyles\":\"$undefined\",\"loadingScripts\":\"$undefined\",\"hasLoading\":false,\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L8\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[],\"initialChildNode\":[\"$\",\"$L7\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"streaming\",\"children\"],\"loading\":\"$undefined\",\"loadingStyles\":\"$undefined\",\"loadingScripts\":\"$undefined\",\"hasLoading\":false,\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L8\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\",\"initialChildNode\":[\"$\",\"$L7\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"streaming\",\"children\",[\"ms\",\"500\",\"d\"],\"children\"],\"loading\":\"$undefined\",\"loadingStyles\":\"$undefined\",\"loadingScripts\":\"$undefined\",\"hasLoading\":false,\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L8\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":
\"$undefined\",\"notFoundStyles\":\"$undefined\",\"initialChildNode\":[\"$L9\",[\"$\",\"main\",null,{\"children\":[\"$\",\"div\",null,{\"children\":[\"Streaming-test\",[\"$\",\"$a\",n
ull,{\"fallback\":[\"$\",\"p\",null,{\"children\":\"Loading...\"}],\"children\":\"$Lb\"}]]}]}],null],\"childPropSegment\":\"__PAGE__\",\"styles\":null}],\"childPropSegment\":[\"ms\",\
"500\",\"d\"],\"styles\":null}],\"childPropSegment\":\"streaming\",\"styles\":null}]}]}],null]}]]\n"])</script><script>self.__next_f.push([1,"5:[[\"$\",\"meta\",\"0\",{\"name\":\"view
port\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"streaming-test\"}],[\"$\",\"meta\",\
"3\",{\"name\":\"description\",\"content\":\"check streaming\"}],[\"$\",\"link\",\"4\",{\"rel\":\"icon\",\"href\":\"/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$
\",\"meta\",\"5\",{\"name\":\"next-size-adjust\"}]]\n9:null\n"])</script>[ 0.465] recv DATA frame <length=1161, flags=0x00, stream_id=13>
send HEADERS (resources)
断片的なHTMLが取得されたことで、rel=preload
などに紐づいたリソースの取得をリクエストすることができます。
[ 1.264] send HEADERS frame <length=49, flags=0x25, stream_id=15>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=11, weight=32, exclusive=0)
; Open new stream
:method: GET
:path: /_next/static/media/c9a5bc6a7c948fb0-s.p.woff2
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 1.264] send HEADERS frame <length=40, flags=0x25, stream_id=17>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=3, weight=32, exclusive=0)
; Open new stream
:method: GET
:path: /_next/static/css/e7d3bab42d9af29d.css
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 1.264] send HEADERS frame <length=48, flags=0x25, stream_id=19>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=5, weight=32, exclusive=0)
; Open new stream
:method: GET
:path: /_next/static/chunks/webpack-d9f8a4f0dd52fadf.js
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 1.265] send HEADERS frame <length=48, flags=0x25, stream_id=21>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=3, weight=32, exclusive=0)
; Open new stream
:method: GET
:path: /_next/static/chunks/fd9d1056-7b52db27cfdaff1f.js
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 1.265] send HEADERS frame <length=45, flags=0x25, stream_id=23>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=3, weight=32, exclusive=0)
; Open new stream
:method: GET
:path: /_next/static/chunks/472-b67f79dbdd2c1fe1.js
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 1.275] send HEADERS frame <length=48, flags=0x25, stream_id=25>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=3, weight=32, exclusive=0)
; Open new stream
:method: GET
:path: /_next/static/chunks/main-app-892c3dff08e9cd4c.js
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
[ 1.275] send HEADERS frame <length=49, flags=0x25, stream_id=27>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=3, weight=32, exclusive=0)
; Open new stream
:method: GET
:path: /_next/static/chunks/polyfills-c67a75d1b6f99dc8.js
:scheme: https
:authority: streaming-frame-test.vercel.app
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.58.0
recv DATA (resources)
リソースのリクエストを行いましたので、リソースの取得が行われます。
例えば、stream_id=15
は下記のようになります。
そのフレームがストリームの最後の通信の場合、
END_STREAM
フラグが設定されます。
[ 1.288] recv (stream_id=15) :status: 200
[ 1.288] recv (stream_id=15) accept-ranges: bytes
[ 1.288] recv (stream_id=15) access-control-allow-origin: *
[ 1.299] recv (stream_id=15) age: 172821
[ 1.299] recv (stream_id=15) cache-control: public,max-age=31536000,immutable
[ 1.299] recv (stream_id=15) content-disposition: inline; filename="c9a5bc6a7c948fb0-s.p.woff2"
[ 1.299] recv (stream_id=15) content-type: font/woff2
[ 1.299] recv (stream_id=15) date: Tue, 05 Dec 2023 19:53:10 GMT
[ 1.299] recv (stream_id=15) etag: "74c3556b9dad12fb76f84af53ba69410"
[ 1.299] recv (stream_id=15) server: Vercel
[ 1.299] recv (stream_id=15) strict-transport-security: max-age=63072000; includeSubDomains; preload
[ 1.299] recv (stream_id=15) x-matched-path: /_next/static/media/c9a5bc6a7c948fb0-s.p.woff2
[ 1.299] recv (stream_id=15) x-vercel-cache: HIT
[ 1.299] recv (stream_id=15) x-vercel-id: hnd1::frxb8-1701805990152-bbfbb030acf6
[ 1.299] recv (stream_id=15) content-length: 46552
[ 1.299] recv HEADERS frame <length=156, flags=0x04, stream_id=15>
; END_HEADERS
(padlen=0)
; First response header
[ 1.299] recv DATA frame <length=3513, flags=0x00, stream_id=15>
[ 1.299] recv DATA frame <length=16384, flags=0x00, stream_id=15>
[ 1.299] recv DATA frame <length=16384, flags=0x00, stream_id=15>
[ 1.299] recv DATA frame <length=10271, flags=0x00, stream_id=15>
[ 1.299] recv DATA frame <length=0, flags=0x01, stream_id=15>
; END_STREAM
recv DATA (HTML)
しばらくして、 HTML(stream_id=13)のDATA frameが受け取れます。
最後にEND_STREAMが設定され、ここでHTMLのストリームが閉じていることがわかります。
[ 1.345] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=38027)
[ 2.476] recv DATA frame <length=28, flags=0x00, stream_id=13>
[ 2.476] recv DATA frame <length=11, flags=0x00, stream_id=13>
[ 2.493] recv DATA frame <length=327, flags=0x00, stream_id=13>
[ 2.493] recv DATA frame <length=17, flags=0x00, stream_id=13>
[ 2.493] recv DATA frame <length=10, flags=0x00, stream_id=13>
[ 2.493] recv DATA frame <length=0, flags=0x01, stream_id=13>
; END_STREAM
送信されているHTMLを-va
オプションで実行し見てみると、
scriptを挿入することで、Streamingを行えるようにしているのがわかります。
[ 0.538] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=39670)
<script>self.__next_f.push([1,"b:[\"$\",\"div\",null,{\"children\":\"ok!hogehoge\"}]\n"])</script>[ 0.968] recv DATA frame <length=28, flags=0x00, stream_id=13>
<script>self.__next_f.push([1,""])</script>[ 0.968] recv DATA frame <length=11, flags=0x00, stream_id=13>
<div hidden id="S:0"><div>ok!hogehoge</div></div><script>$RC=function(b,c,e){c=document.getElementById(c);c.parentNode.removeChild(c);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var d=a.data;if("/$"===d)if(0===f)break;else f--;else"$"!==d&&"$?"!==d&&"$!"!==d||f++}d=a.nextSibling;e.removeChild(a);a=d}while(a);for(;c.firstChild;)e.insertBefore(c.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}};$RC("B:0","S:0")</script>[ 0.969] recv DATA frame <length=327, flags=0x00, stream_id=13>
</body></html>[ 0.969] recv DATA frame <length=17, flags=0x00, stream_id=13>
[ 0.970] recv DATA frame <length=10, flags=0x00, stream_id=13>
[ 0.970] recv DATA frame <length=0, flags=0x01, stream_id=13>
; END_STREAM
まとめ
streamingをHTTP/2フレーム単位で見ると、
HTMLのHEADERフレームを送信し、Open new stream
します。
その後HTMLのDATAフレームの一部を先に受け取り、リソースの先読みが可能な場合は、
resourceリクエストHEADERを送信します。
Suspense内部の処理が準備完了すると<script>self.__next_f.push(.*)</script>
の形で、
DATAフレームを順次受信し、全てが完了した後で、END_STREAMを行い、ストリームを閉じます。
HTMLのレスポンスヘッダーについて
次に、suspense内部でErrorを発生させるページ(/error/[ms]
)と、
suspenseを利用せずにErrorを発生させるページ(/error-sync/[ms]
)で、ステータスコードがどうなるかを見ると、suspense内部に例外を投げる処理を実装しても、ステータスコードとして500を返してくれません。
`/error/[ms]`code
import { Suspense } from "react";
async function SuspenseComponent ({ms}: {ms: string}) {
const fetched = await fetch(https://streaming-frame-test.vercel.app/api/?ms=${ms}&is_error=true
)
if (fetched.ok) {
const obj = await fetched.json();
return <div>{obj.response}</div>
} else {
if ("true" === "true") { // 自明なtrue
console.log('error発生')
throw Error("エラー実行")
}
return <div>ng</div>
}
}
export default function Home({params}: {params: {ms: string}}) {
const ms = params.ms;
return (
<main>
<div>
Streaming-test
<Suspense fallback={<p>Loading...</p>}>
<SuspenseComponent ms={ms}/>
</Suspense>
</div>
</main>
)
}
export const dynamic = 'force-dynamic'
`/error-sync/[ms]`code
function SuspenseComponent () {
if ("true" === "true") { // 自明なtrue
throw "エラー実行"
}
return <div>error</div>
}
export default function Home() {
return (
<main>
<div>
error-test
{/* <Suspense fallback={<p>Loading...</p>}> /}
<SuspenseComponent />
{/ </Suspense> */}
</div>
</main>
)
}
export const dynamic = 'force-dynamic'
エラーをnghttp2でみる
/error/[ms]
のステータスコードがどうなるかを見ます。
ステータスコードはHEADERフレームに紐づきますが、下記の実行結果を見ていただくと
/streaming/[ms]
同様最初にHEADERフレームを受信し、DATAフレームによってSTREAMを終了し、
それ以降に、HEADERフレームを受信していません。
つまり、リクエストヘッダー情報は、Suspenseでwrapされてないコンポーネントのみで計算されレスポンスされます。
nghttp -nva https://streaming-frame-test.vercel.app/error/500
#...省略
[ 0.282] recv (stream_id=13) :status: 200
[ 0.282] recv (stream_id=13) age: 0
[ 0.282] recv (stream_id=13) cache-control: private, no-cache, no-store, max-age=0, must-revalidate
[ 0.282] recv (stream_id=13) content-encoding: gzip
[ 0.282] recv (stream_id=13) content-type: text/html; charset=utf-8
[ 0.282] recv (stream_id=13) date: Thu, 07 Dec 2023 20:30:19 GMT
[ 0.282] recv (stream_id=13) server: Vercel
[ 0.282] recv (stream_id=13) strict-transport-security: max-age=63072000; includeSubDomains; preload
[ 0.282] recv (stream_id=13) vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url
[ 0.282] recv (stream_id=13) x-matched-path: /error/[ms]
[ 0.282] recv (stream_id=13) x-powered-by: Next.js
[ 0.282] recv (stream_id=13) x-vercel-cache: MISS
[ 0.282] recv (stream_id=13) x-vercel-execution-region: iad1
[ 0.282] recv (stream_id=13) x-vercel-id: hnd1::iad1::6d8b7-1701981018995-57af4cfcf377
[ 0.282] recv HEADERS frame <length=308, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
#...省略
[ 0.371] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=32890)
[ 0.948] recv DATA frame <length=42, flags=0x00, stream_id=13>
[ 0.948] recv DATA frame <length=11, flags=0x00, stream_id=13>
[ 0.948] recv DATA frame <length=169, flags=0x00, stream_id=13>
[ 0.948] recv DATA frame <length=17, flags=0x00, stream_id=13>
[ 0.948] recv DATA frame <length=10, flags=0x00, stream_id=13>
[ 0.948] recv DATA frame <length=0, flags=0x01, stream_id=13>
; END_STREAM
[ 0.948] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])
error-sync/[ms]
の方は、例外処理も含めて最初に計算されるため
ステータスコードとして500を返します。
nghttp -nva https://streaming-frame-test.vercel.app/error-sync/500
# ...省略
[ 0.386] recv (stream_id=13) :status: 500
[ 0.386] recv (stream_id=13) age: 0
[ 0.386] recv (stream_id=13) cache-control: private, no-cache, no-store, max-age=0, must-revalidate
[ 0.386] recv (stream_id=13) content-type: text/html; charset=utf-8
[ 0.386] recv (stream_id=13) date: Thu, 07 Dec 2023 20:35:15 GMT
[ 0.386] recv (stream_id=13) server: Vercel
[ 0.386] recv (stream_id=13) strict-transport-security: max-age=63072000; includeSubDomains; preload
[ 0.386] recv (stream_id=13) vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url
[ 0.386] recv (stream_id=13) x-matched-path: /error-sync/[ms]
[ 0.386] recv (stream_id=13) x-powered-by: Next.js
[ 0.386] recv (stream_id=13) x-vercel-cache: MISS
[ 0.387] recv (stream_id=13) x-vercel-execution-region: iad1
[ 0.387] recv (stream_id=13) x-vercel-id: hnd1::iad1::bx25k-1701981315100-7ffe07148d20
[ 0.387] recv HEADERS frame <length=307, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
Discussion