具代表性的面試主題

通用面試:HTTP 508 Loop Detected 何時成立,如何避免 WebDAV 深度遍歷循環?

通用中等
Offer.cc 編輯團隊發佈 更新

題幹

WebDAV 服務處理 Depth infinity 的 PROPFIND 時回傳 508。請解釋協定語意,設計循環偵測、回應選擇、客戶端相容與防 DoS 方案。

題幹與適用場景

一個 WebDAV 服務允許集合之間建立多個 binding。客戶端送出 Depth: infinity 的 PROPFIND 後,服務端發現遍歷回到已造訪集合,於是回傳 508。請解釋 508 的準確語意,區分它與一般重試環路,說明何時可用 208 Already Reported、如何避免資源耗盡,以及客戶端應如何處理。

這是 HTTP 狀態語意、圖遍歷、協定相容與安全邊界的綜合題。RFC 5842 將 508 定義為處理無限深度操作時遇到循環並終止整個操作;IANA 將 508 登記為 RFC 5842 狀態碼。

面試官在考察什麼

  • 是否知道 508 的上下文是 WebDAV 綁定與 Depth: infinity,不會泛化成任意重新導向錯誤。
  • 能否把資源、URI binding、存取路徑與已造訪集合建模為圖。
  • 能否區分發現重複資源後繼續回傳 208,與客戶端不理解 208 而整體失敗的 508。
  • 能否給出確定性終止、預算、觀測與客戶端回退,而不只說「加一個 visited 集合」。

先釐清哪些問題

  1. 請求方法和 Depth 值是什麼?只有需要遞迴遍歷的範圍才會觸發該語意。
  2. 服務是否實作 RFC 5842 binding 擴充並宣告 DAV 能力?客戶端是否理解 208?
  3. 圖節點按資源 ID、規範化 URI 還是 binding 路徑去重?同一資源可能有多個 URI。
  4. 回應要報告部分結果,還是協定要求整個操作原子失敗?
  5. 最大節點數、回應體大小、CPU 時間和認證範圍如何限制惡意深遍歷?

30 秒回答框架

先限定語意:508 表示伺服器處理 WebDAV Depth: infinity 操作時遇到 binding 循環,因而終止整個操作,不是通用 HTTP 重試狀態。把 binding 關係視為圖,按資源身分偵測回邊;若客戶端理解 208,可在多狀態結果中只報告一次資源並繼續;不理解 208 時,服務可用 508 明確失敗。遍歷還要有節點、深度、位元組與時間預算,記錄 cycle 位置並讓客戶端停止自動重試。

分步作答

1. 畫出資源圖而非只看 URL 字串

節點是資源或集合,邊是 binding。URI 是存取路徑,不一定是資源身分;同一資源可能擁有多個 binding。因此同時保留資源 ID、目前路徑和父路徑,用於結果去重、稽核與診斷。

2. 採用明確 DFS/BFS 狀態

維護目前遞迴棧和全域已報告集合。進入節點前檢查它是否已在目前棧中;命中就是回邊。若只是另一條路徑再次到達同一資源,可按協定能力決定回傳已報告標記,而非無限展開。

text
visit(node, path):
  if node in activePath: return CYCLE
  if node in reported: return ALREADY_REPORTED
  budget.consume(node)
  activePath.add(node)
  report(node)
  for child in children(node): visit(child, path + child)
  activePath.remove(node)

activePath 識別真正回路,reported 防止多 binding 重複輸出;不能只用一個集合,否則可能把合法共享資源誤判成循環。

3. 根據客戶端能力選擇 208 或 508

若客戶端宣告理解 binding 擴充及 208,服務可以在多狀態回應中讓首次出現的資源正常回傳,後續重複 binding 標為 Already Reported,並省略其後代。若客戶端不理解 208,RFC 5842 的相容路徑允許因無限深度循環讓整次操作以 508 失敗。回應不能偽裝成成功的 200。

4. 設計確定性預算與安全邊界

即使沒有循環,深層或寬集合也可能耗盡 CPU、記憶體和回應體。設定最大節點數、活動路徑長度、總位元組、牆鐘時間與並發限制;超預算時記錄原因並回傳明確業務錯誤或協定允許的失敗,不要繼續重試。對跨租戶 binding 做權限檢查,避免把不可見節點洩露到多狀態回應。

5. 處理寫操作與並發變化

BIND、REBIND、UNBIND 會改變圖。讀操作開始時固定一致性視圖或版本,避免遍歷中途拓撲變化讓偵測失效。寫操作在建立可能成環的 binding 前可執行可達性檢查,或要求明確允許 cycle;檢查與提交要在同一交易邊界內。

6. 讓客戶端停止錯誤重試

508 表示請求操作失敗,客戶端不應像 503 一樣盲目按指數退避重試。客戶端應讀取回應體和日誌關聯 ID,改用有限深度、修復 binding 或請求伺服器提供能力資訊。若 API 代理把 508 映射成統一錯誤,也必須保留原始狀態和可診斷欄位。

高品質示範答案

我會把 binding 建模成有向圖,並區分資源身分與存取 URI。處理 Depth: infinity 時,用目前遞迴棧偵測回邊,用已報告集合抑制同一資源經多個 binding 重複輸出;兩者不能混為一個集合。遍歷前設定節點、深度、回應位元組與時間預算,並在一致性視圖中執行。

若客戶端宣告理解 RFC 5842 的 208,服務可在 207 多狀態回應中首次報告資源,後續重複項標記 Already Reported,避免展開後代。對不理解 208 的客戶端,遇到循環時按 RFC 5842 以 508 終止整個無限深度操作。508 不是通用重試錯誤,客戶端應停止自動重試並修復圖或降低深度。所有路徑還要做權限檢查、指標和關聯日誌,防止循環與超深遍歷成為 DoS。

常見失分點

  • 把 508 解釋成反向代理重試次數耗盡或一般 URL 重新導向循環。
  • 只按字串 URI 去重,忽略多個 binding 指向同一資源。
  • 只設定全域 visited,誤把共享資源當回邊,或無法區分 208 與 508。
  • 沒有節點、位元組、時間與權限預算,任意接受 Depth: infinity
  • 對 508 自動重試,導致同一拓撲持續消耗資源。
  • 忘記客戶端能力協商、207 多狀態回應和寫操作競態。

追問與參考回答

508 與 208 的邊界是什麼?

208 用於客戶端理解 binding 擴充時報告已出現過的資源,允許操作繼續回傳其餘結果;508 表示遇到循環後整個無限深度操作被終止,常用於不理解 208 的相容路徑。

為什麼不能只用 URI 作為 visited key?

多個 URI 可能 binding 到同一資源,單看 URI 會重複遍歷;反過來,路徑上下文仍有診斷價值,所以應同時保存資源身分與路徑。

如何避免檢查與寫入之間的 TOCTOU?

在同一交易或版本化快照中做可達性檢查與 binding 提交;若跨節點無法原子化,就用版本條件失敗並重新檢查。

客戶端收到 508 是否應該重試?

預設不應盲重試。它表示目前拓撲下操作失敗,應限制深度、修復循環或取得能力資訊;只有拓撲明確改變後才重新發起。

如何驗證循環偵測沒有誤報?

建立無環 DAG、共享資源、多 binding 環和深度超限四組夾具,分別檢查結果數量、狀態碼、遍歷預算、稽核日誌與回應體上限。

508 是否適用於任意微服務呼叫環?

不能直接泛化。508 的標準語意來自 WebDAV RFC 5842;其他服務環路應使用自身錯誤契約,必要時保留 5xx 但不能聲稱符合 508 的 WebDAV 語意。

公開來源

同類題目