題幹與適用場景
你要在瀏覽器或邊緣執行環境處理二進位請求本文。平台新增 Request.bytes(),但請求本文只能消費一次。請說明何時使用、如何避免重複讀取、如何限制記憶體並設計回退。
MDN 說明 Request.bytes() 回傳 Promise,成功值是請求本文的 Uint8Array;讀取請求本文會消費 body。重點是 Fetch body 生命週期、記憶體邊界與錯誤處理,不是把新方法當成所有場景的預設選擇。
面試官考察點
- 能否解釋 body 已使用狀態,以及
bytes()、arrayBuffer()、json()、text()的互斥關係。 - 能否根據請求大小與處理目標選擇完整讀取或串流讀取。
- 能否在重試、日誌、簽章驗證與業務解析之間安排唯一讀取點。
- 能否設計能力偵測、限制、取消、逾時與錯誤映射。
- 能否避免把原始請求本文寫入日誌、快取或跨租戶共享物件。
需要先釐清的問題
- 請求本文大小、來源、Content-Type、是否需要簽章驗證與目標執行環境是什麼?
- 業務需要完整位元組陣列、增量雜湊、分片上傳還是解碼後結構?
- 失敗重試由客戶端、閘道還是業務服務負責?請求是否可安全重播?
- 請求本文是否包含個人資料、憑證或租戶隔離資訊?
Body 生命週期與 API 選擇
Request.bytes() 成功消費 body 後,後續再呼叫 json() 或 text() 會失敗;反過來也一樣。若同一請求需要多個消費者,應在唯一入口讀取一次,再把受控結果傳給解析、簽章與業務層。不要把 Request 物件到處傳遞,讓多個模組爭奪讀取權。
完整讀取適合受限的小請求或需要一次性校驗的協定。大請求應優先使用 request.body 的串流讀取,在讀取過程做增量雜湊與大小檢查。bytes() 回傳 Uint8Array 方便位元組處理,但不代表讀取過程沒有記憶體峰值。
記憶體、大小限制與背壓
入口先檢查 Content-Length,但不能只相信它;分塊傳輸要在讀取時累計實際位元組,超過上限立即取消。為完整讀取設定租戶、路由與全域上限,避免多個並行請求同時配置不受控陣列。串流路徑使用背壓,消費者處理不及時就暫停讀取。
需要轉發請求時,不要為記錄或重試複製多個完整陣列。若必須重播,先把經大小限制的位元組寫入受控暫存,綁定租戶、過期時間與摘要;原始請求本文不得進入普通日誌。
簽章驗證、解析與錯誤契約
需要驗證簽章時,先確定簽章涵蓋原始位元組還是正規化結構。原始位元組應在一次讀取後同時送入驗證器與解析器,避免 JSON 重新序列化改變簽章輸入。解析失敗、簽章失敗、超限、取消與逾時應映射為不同業務錯誤,不能統一回傳「格式錯誤」。
邊緣函式可能有不同 body API;用能力偵測選擇 bytes()、arrayBuffer() 或串流路徑,並在啟動時記錄執行環境能力版本。回退必須維持相同大小限制、摘要與錯誤語意,不得因舊平台而靜默放寬安全策略。
取消、逾時與重播
把 AbortSignal 傳給讀取與下游操作,客戶端斷線或超過截止時間時立即停止讀取並釋放參照。對帶副作用的請求,逾時後不能自動重播;需要冪等鍵、服務端去重與明確重試窗口。純校驗或查詢請求也要確認重播是否會洩露敏感資料。
閘道重試可能產生多個請求副本,因此簽章驗證、冪等鍵與稽核事件應關聯同一請求 ID。失敗回應應告訴呼叫方是否可以安全重試,不洩露內部讀取狀態或密鑰資訊。
安全與可觀測性
限制 Content-Type、請求大小、讀取時長與並行數;對壓縮請求考慮解壓後大小,防止壓縮炸彈。按租戶隔離快取與暫存物件,敏感位元組只在必要範圍存在。日誌記錄位元組數、耗時、取消原因、錯誤類別與請求摘要,不記錄內容。
監控 body 消費失敗率、超限率、p95 讀取時間、峰值記憶體、下游背壓與重試率。若 bytes() 在某執行環境的失敗率升高,切換到已驗證回退並保留告警;不能捕獲例外後再次讀取同一 body。
驗證清單與追問
測試空 body、單位元組、接近上限、超過上限、分塊傳輸、慢速客戶端、客戶端中斷、重複讀取、簽章不匹配、壓縮炸彈、並行請求、舊執行環境回退與下游逾時。驗證每條路徑都只消費一次 body,並檢查取消後沒有繼續讀取。
為什麼不能先 json() 再用 bytes() 驗簽?
第一次讀取已消費 body,且 JSON 解析與重新序列化可能改變空白、順序或編碼。應先讀取原始位元組,再用同一位元組輸入驗簽與解析。
什麼時候 bytes() 比串流讀取合適?
請求很小、記憶體上限明確、協定需要完整位元組校驗時可用。大檔案、持續上傳或高並行場景應採用串流讀取與增量處理。
如何證明回退沒有降低安全性?
對每個執行環境強制覆蓋能力路徑,比較大小限制、摘要、簽章、錯誤碼、取消與稽核事件;回退不能改變安全策略或重試邊界。