🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

`new Response(null, { status: 101 })`は、実行環境によってエラーになる

ことの経緯としては、

  • とあるHTTP関連のOSSにPRを出そうとしていた
  • コード書いてテストを実行すると、どうやらPRを出す前からコケてるケースがあった
  • 原因は、`new Response(null, { status: 101 })`が`RangeError`で落ちてたから

さて、これはなんでエラーになるんや?WebSocketとかで使うんではなかったか?(うろ覚え)

というところから、なんとなく気になったのであれこれ調べてみたメモです。

エラーと環境

RangeError: init["status"] must be in the range of 200 to 599, inclusive.

というエラーが`throw`されてた。

確認できた環境としては、

  • Node.js(v18以降)
    • というより`undici`といったほうが正しそう
  • Chrome
  • Firefox
  • Safari

どれも同様なエラーになって、おそらくバージョンは関係なさそう。

ちなみに、このOSSのテストでは`isomorphic-fetch`が使用されていて、`isomorphic-fetch`(が内部的に依存している`node-fetch`)の実装では、`new Response(null, { status: 101 })`はエラーにならない。

https://github.com/node-fetch/node-fetch/blob/7b86e946b02dfdd28f4f8fca3d73a022cbb5ca1e/src/response.js#L27

ただ、このポリフィルは`global.fetch`が存在する場合には何もしないので、バージョン18以降のNode.jsを使っていると、本体の`undici`にある`Response`で処理されて、そこでエラーになってた模様。

https://github.com/nodejs/undici/blob/472c40e4f6fb3c7a9e489605057debd81b75acdb/lib/fetch/response.js#L440-L446

そもそもSpecでは

`Response`オブジェクトといえば`fetch`の同期ということで、Fetch Standardを見てみた。

すると、どうやら仕様としては「エラーになって然るべし」らしいことがわかった。

To initialize a response, given a Response object response, ResponseInit init, and an optional body with type body, run these steps:
1. If init["status"] is not in the range 200 to 599, inclusive, then throw a RangeError.
https://fetch.spec.whatwg.org/#initialize-a-response

`throw`するってめっちゃ書いてある!

そういうわけで、モダンブラウザと`undici`は仕様に準拠した実装になってると言えそう。

`isomorphic-fetch`でも、仕様に合わせようっていうIssue/PRが立ってるようだった。

When initializing a response it should throw if status is not in the range 200 to 599 · Issue #1685 · node-fetch/node-fetch · GitHub

というわけで、エラーになるのが仕様通りで、仕様からするとテストコードにバグがあるという話だった。

〜終〜

と言いたいとこやけど・・。

エラーにならない環境もある

これが違和感の正体で、ここまで調べようと思った動機であり。

この`new Response(null, { status: 101 })`、Cloudflare Workersでは動くんですよね・・・。
なんならDocsにものってるし。

Using WebSockets · Cloudflare Workers docs

ということはもしや?って思って、エッジWorkerたちの実装を調べてみると、軒並みエラーにならなかった・・・!

どれも`101`だけは例外的に許可するって実装になってた。

WinterCGのSpecでは

どうやらサーバーサイドJSをやっていくために、このWGではFetch Standardをforkしてるらしく。

もしかそこでは許可されるように変更された?って思ったけど、今の時点では別にそうなってなかった。

https://fetch.spec.wintercg.org/#initialize-a-response

これからなるかもしれないし、ならないかもしれない。

まとめ

というわけで、`new Response(null, { status: 101 })`は、

  • 環境によって、エラーになったりならなかったりする
    • 仕様が実装に必ずしも反映されるとは限らない
    • まぁWebの常って感じではあるけど
  • `101`なレスポンスを返したいサーバーサイドの実装としては、困るかもしれない
    • ただ、Node.jsでWebSocketサーバーを`fetch.Response`を使って実装したい人なんかいない気がする
    • 既存の枯れた実装はいっぱいあるし、`node:net`なり使えばいいし
  • エッジ系ランタイムでは、仕様に明記されてないものの、動作する

そもそもFetch StandardはHTTPクライアントの出自であり、サーバー用途で使われる日がくるとは!って感じかも?知らんけど。