Node.js と ブラウザでのfetchの違いについて
ブラウザにはSOPを元にしたセキュリティ機構があるが、それらはNode.jsには不要なのでちょっと違いがありそうというところを深堀りしたい
古川さんのツイートでみたやつ
nodejs/undici の README にはいくつかサポートしていない観点が記載されている
denoもドキュメントに記載されている
まず Deno からまとめると以下の通り
- cookie jar がない → レスポンスに含まれる set-cookie ヘッダは無視される
- SOPの概念がない → SOPに関連するスペックをサポートしない
- fetchのredirectオプションにmanualを渡した時のレスポンスは、
opaqueredirect
レスポンスではなくbasic
レスポンスになる
cookie jar がないことについて
cookie jar がないということは、リクエストでcookieを保存しないということ。cookie の用途といえば、ブラウザから fetch するときにユーザーを判別してコンテンツを出し分けたりするところだと思うので、確かに deno 環境からの fetch には不要そう。そもそも set-cookie に指定されるデータはブラウザに保存される前提だったので、納得感ある。
SOPの概念がないことについて
SOPは、ブラウザからのリソース取得のときに同一オリジンからしか原則許可しないルールのこと。なので、SOPの概念がないのは理解できる。Denoで言及されているスペックは以下の通り。
- Section 3.1. 'Origin' header
- Section 3.2. CORS protocol
- Section 3.5. CORB
- Section 3.6. 'Cross-Origin-Resource-Policy' header
fetchのredirectオプションの詳細について
ブラウザでは、fetchのredirectオプションにmanualを渡した時のレスポンスは、opaqueredirect
レスポンスになる。MDNによると、ヘッダーにもボディにも何も情報が含まれない status 0 を返すレスポンスらしい。
manual の redirect なので次のリダイレクト先のリンクの情報は必要になるが、opaqueredirect
レスポンスにはそれらの情報は含まない。そもそも、fetchのredirectオプションのmanualってなんのためにある...?
同じことを質問している人がいた
service worker で使われる感じはわかったけど、どういう感じで使われるのかは全くわからなかった... これは聞いておこう
PS. この答えは Fetch のSpecにあった
Retrieves an opaque-redirect filtered response when a request is met with a redirect, to allow a service worker to replay the redirect offline. The response is otherwise indistinguishable from a network error, to not violate atomic HTTP redirect handling.
service worker がオフラインでもリダイレクトできるように、リクエストがリダイレクトに遭遇したときにopaque-redirect filtered response を取得する。このレスポンスは、atomic HTTP redirect handling に反しないように定義されたレスポンスである。
Opaque Response とは?
azu さんの資料にまとまっている
- fetch で mode: "no-cors" の場合のレスポンス
- typeがopaqueとなり、statusが0、bodyがnullとフィルターされる
- same-originを無視して取得したデータをそのまま見れるのはまずい
- エラーレスポンスと区別がつかないようなデータ
- キャッシュとしては使える (内部のみ)
- これができないとService Worker は SOP を超えたキャッシュができなくなってしまう...
まとめ
SOPを超えたリダイレクトのキャッシュをSWがしたいがために、fetchのredirectオプションのmanualやOpaque Responseなどはあると理解した。SWはブラウザの話かつ Opaque にしている理由も XSS を防ぐためなので、無視して良さそうな感じはする。
atomic HTTP redirect handling
アトミックなHTTPリダイレクト処理とは、リダイレクトに関するレスポンス (status が 300系?) をAPIに公開しないこと。XSS攻撃を受ける可能性がある。opaqueredirect レスポンスが何も情報を持たないのは、この仕様によるもの。
XSS自体は、確かにブラウザでのリクエストに関する脆弱性なので、対処しなくてよさそうなのはなんとなくわかる。
最後にNodeの方も見ておく
Expect
Expect のヘッダーフィールドをサポートしない。すぐに リクエスト body は送られる。100 Continue
のレスポンスは無視される。
Expect ヘッダーは、これから Content-Length
で指定されているサイズでbody をPOSTしても問題ないかをサーバー側に問い合わせるために使われる。100 Continue
が帰ってきたら、ブラウザはbodyをPOSTする。
ネットワーク帯域の節約のために使われる技術らしいので、確かにサーバー側でサポートしないのはわかる感じがする。
Pipelining
- pipelining factor が 1 より大きい時のみ pipeline を使う
- 基本は stream を使うってことなのかな
- connection が必ず確立されているということを前提にリクエストを送信する
- pipeline 化されてなかったときの fallback には対応しない
- ブラウザでは connection を確認するはず
- 接続が失敗するとすぐにパイプラインを再開しようとするが、失敗したリクエストについて retry せずエラーにする
- ブラウザだと対応するリクエストがすべて取得できるようにretryする
- 失敗したリクエストも再度取得しようと試みる
- ブラウザだと対応するリクエストがすべて取得できるようにretryする
- リクエスト失敗後は、対応するコールバック/プロミス/ストリームをエラーにする
- ブラウザでは、対応するレスポンスがすべて取得できるようにリトライする
undici は 基本的にはコネクションが確立されていることを前提としてリクエストを送る。またコネクションが失敗した時の retry 機構を持つが、一度失敗したリクエストはエラーを返す。
GC
ブラウザの Fetch は接続リソースの解放をブラウザのGCに任せているが、undici の場合はGCに任せていない。なので、メモリなどを適切に管理する必要がある場合もあるかもしれない。Node.jsのGCに任せない理由としては、NodeのGCが積極的なものではなく、非決定的なものであるため。
Node.js では、CookieStore API の実装が始まっているらしい
Cookie をParseして、リクエストにセットする方法が欲しいという感じ。
ユースケースとしては、認証のあるページのスクレイピングなどが挙げられる。Cookieをセットできれば node.js からでもfetchできるが、現状は cookie を扱えないので jsdom or puppeteer などの heavy なライブラリを使うしかないとのこと。
a node.js environment isn’t good for simulating a browser, maybe you want to use something like jsdom or puppeteer
It’s a common use case to want to request data from an API that requires authentication, where that authentication check takes the form of checking for a cookie. It’s not only done when you’re trying to simulate a browser.