兩次:哪些會重復(fù)執(zhí)行,哪些不會)
授權(quán)與合規(guī)聲明本文全部操作對象均為自建隔離靶場本機(jī)容器或隔離虛擬機(jī)涉及安全測試的環(huán)節(jié)必須以取得合法授權(quán)為前提。未經(jīng)授權(quán)的滲透測試違反《中華人民共和國網(wǎng)絡(luò)安全法》與《刑法》相關(guān)條款須承擔(dān)相應(yīng)法律責(zé)任。本文只講環(huán)境配置、版本對照與靶場隔離不含任何攻擊步驟、利用載荷與繞過手法請勿將文中環(huán)境指向任何非自有系統(tǒng)。一、先把三個詞分清safe、冪等、可緩存1.1 抖一下之后兩種完全相反的現(xiàn)場線上常見的一幕客戶端把一個寫請求發(fā)出去等到超時也沒等到響應(yīng)中間網(wǎng)絡(luò)抖了一下??蛻舳死锬嵌巫詣又匕l(fā)邏輯按設(shè)定把同一個請求又發(fā)了一遍。隨后兩種結(jié)果都可能出現(xiàn)。第一種列表里出現(xiàn)兩條內(nèi)容一樣、時間只差幾百毫秒的記錄立刻有人說這是重復(fù)提交得加鎖。第二種列表里只有一條于是有人得出另一個結(jié)論這個接口是冪等的POST 也能放心重發(fā)。兩個結(jié)論都下得太快。你看到一條還是兩條取決于服務(wù)端怎么處理這次請求不取決于方法名。方法名給的是「客戶端可以怎么假設(shè)」不是「服務(wù)端一定怎么做」。這句話是本篇的地基。1.2 三個詞問的不是同一件事safe、冪等、可緩存這三個詞經(jīng)常被當(dāng)成一組近義詞混著用。它們回答的問題完全不同詞它在問什么答錯之后會出什么事誰最在意safe這個方法的語義是不是本質(zhì)上只讀客戶端會在用戶沒同意時發(fā)起帶副作用的請求瀏覽器、爬蟲、預(yù)取邏輯冪等同一請求發(fā)多次服務(wù)端被請求的效果是否和發(fā)一次相同斷連之后不敢重發(fā)或者不該重發(fā)時重發(fā)了客戶端重試、網(wǎng)關(guān)、SDK可緩存這個響應(yīng)能否被中間層存下來復(fù)用緩存把不該復(fù)用的響應(yīng)拿去復(fù)用了代理、CDN、瀏覽器緩存三者有包含關(guān)系但不是同一條線。safe 的方法一定冪等——這是 IDEM-02 的直接含義PUT、DELETE 和所有 safe 方法都在冪等集合里反過來冪等的方法不一定 safePUT 和 DELETE 就是例子。可緩存和另外兩個基本正交它關(guān)心響應(yīng)能不能被復(fù)用不關(guān)心請求重復(fù)執(zhí)行會怎樣。1.3 六個方法擺成一張矩陣先把結(jié)論攤開后面幾章逐格解釋方法safe冪等默認(rèn)能否用緩存響應(yīng)代替一句話GET是是能取回表示語義只讀HEAD是是能與 GET 同但不帶正文OPTIONS是是一般不能問這個資源支持什么TRACE是是一般不能回顯收到的請求診斷用PUT否是不能用報文里的表示替換目標(biāo)狀態(tài)DELETE否是不能解除 URI 映射與當(dāng)前功能的關(guān)聯(lián)POST否否默認(rèn)不能讓目標(biāo)資源按自己的語義處理PATCH否否請求可以做到不能按補(bǔ)丁部分修改第三列要說明一句POST 那一格有本文唯一的逐字依據(jù)就是 IDEM-07 里那句「a POST request cannot be satisfied by a cached POST response because POST is potentially unsafe」其余幾格按 RFC 9110 緩存一節(jié)的通行表述給出定位本文不逐字引。PATCH 那格的括號原因在 3.3 節(jié)。本章可以帶走的一句safe 說的是「能不能不問就發(fā)」冪等說的是「斷了能不能再發(fā)」可緩存說的是「響應(yīng)能不能被別人拿去用」三個問題三套答案。二、冪等說的是「效果」不是「響應(yīng)」2.1 定義句要一個詞一個詞讀RFC 9110 給冪等的定義只有一句但每個成分都在限定范圍「A request method is considered “idempotent” if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request.」拆開看intended effect不是 actual effect規(guī)范約束的是請求打算達(dá)成的效果不是這次執(zhí)行實(shí)際發(fā)生了什么on the server效果落在服務(wù)端multiple identical requests前提是請求相同同一個方法配不同報文本來就不叫同一個請求the same as the effect for a single such request比的是多次和一次不是兩次和三次。2.2 日志、修訂歷史、別的副作用都不在射程內(nèi)緊接著的一句把邊界劃得更死「the idempotent property only applies to what has been requested by the user; a server is free to log each request separately, retain a revision control history, or implement other non-idempotent side effects for each idempotent request.」這句話給了服務(wù)端很大的自由每來一次請求就記一條日志、留一個修訂版本、做一個別的非冪等副作用都允許。前提只有一個——這些都不是用戶請求的那件事?;氐介_頭那個場面判斷就清楚了。列表里多了一條業(yè)務(wù)記錄是被請求的效果重復(fù)了不冪等日志多兩行、審計多兩條、修訂歷史多一個版本這些是服務(wù)端自己的記錄與冪等無關(guān)。計數(shù)器加了兩則要看它是不是用戶請求的那件事用戶點(diǎn)的是「點(diǎn)贊」加兩次就不冪等計數(shù)器只是運(yùn)維側(cè)的訪問統(tǒng)計它屬于附帶記錄。這一層只能由接口設(shè)計者自己判斷。2.3 三個層次分開看層次冪等管不管典型例子判斷依據(jù)被請求的效果資源狀態(tài)管記錄被創(chuàng)建了幾條、余額被扣了幾次多次請求后的狀態(tài)與一次是否相同響應(yīng)本身不管第一次 201、第二次 200帶不帶 ETag定義句里沒有響應(yīng)這個詞服務(wù)端附帶記錄不管訪問日志、審計流水、修訂歷史IDEM-03 明確說服務(wù)端可以自由做IDEM-04 里還有半句正好印證第二行「even if the original request succeeded, though the response might differ」——原請求可能已經(jīng)成功了盡管響應(yīng)可能不同。本章可以帶走的一句冪等約束的是「用戶請求的那件事在服務(wù)端留下的狀態(tài)」不是響應(yīng)長什么樣也不是服務(wù)端順手記了幾行日志。三、逐方法過一遍3.1 safe 的四個GET、HEAD、OPTIONS、TRACE「Request methods are considered “safe” if their defined semantics are essentially read-only」——注意 essentially 這個詞它說的是「本質(zhì)上是只讀的」不是「絕對沒有任何寫入」。一個 GET 讓服務(wù)端寫了行訪問日志它仍然 safe刷了個緩存條目通常也還是 safe。判據(jù)仍是第二章那條這些寫入是不是用戶請求的那件事。四個里 GET、HEAD 日常用OPTIONS 問資源支持什么TRACE 回顯請求用于診斷。四個都 safe按 IDEM-02 那句話也都在冪等集合里。所以「GET 斷了能不能重發(fā)」答案是能。冪等集合比 safe 集合大多出來的正是 PUT 和 DELETE。3.2 PUT 與 DELETE冪等但都不是「沒有后果」「The PUT method requests that the state of the target resource be created or replaced with the state defined by the representation enclosed in the request message content.」兩個動詞created、replaced。狀態(tài)由報文里那份表示決定不是在現(xiàn)有狀態(tài)上疊一層。這就是 PUT 冪等的機(jī)制來源同一份報文發(fā)一次和發(fā)五次最終狀態(tài)一樣。所以判斷一個 PUT 接口是不是真冪等不看它標(biāo)了什么方法看它內(nèi)部做了什么。服務(wù)端要是把 PUT 實(shí)現(xiàn)成「把報文追加到列表末尾」它已經(jīng)不是在實(shí)現(xiàn) PUT 的語義冪等性也不成立。DELETE 更容易被誤解「requests that the origin server remove the association between the target resource and its current functionality. In effect, this method is similar to the “rm” command in UNIX: it expresses a deletion operation on the URI mapping of the origin server rather than an expectation that the previously associated information be deleted.」第一句說的是解除 URI 映射與當(dāng)前功能之間的關(guān)聯(lián)第二句補(bǔ)了關(guān)鍵的一筆它表達(dá)的是對 URI 映射的刪除操作不是「之前關(guān)聯(lián)的信息一定會被抹掉」。規(guī)范拿 rm 打比方——rm 一個文件刪掉的是目錄里那條名字到 inode 的關(guān)聯(lián)。這就解釋了 DELETE 為什么冪等第一次刪完「目標(biāo)資源已經(jīng)不在那」成立第二次再刪這個狀態(tài)還是成立。響應(yīng)可能從 204 變成 404但那是響應(yīng)層面的差異按第二章的判據(jù)不算數(shù)。3.3 POST 與 PATCH一個不在集合里一個要分兩句讀POST 不在冪等集合里規(guī)范在講緩存時順帶把原因說清了「a POST request cannot be satisfied by a cached POST response because POST is potentially unsafe」potentially unsafe「可能不安全」。這個措辭不是「POST 一定寫數(shù)據(jù)」而是「規(guī)范不承諾 POST 的效果可以重復(fù)」。一個 POST 接口碰巧什么也沒寫它仍然不在冪等集合里因?yàn)橐?guī)范給的是方法的默認(rèn)假設(shè)。PATCH 有兩句話必須一起讀「PATCH is neither safe nor idempotent as defined by [RFC2616], Section 9.1.」「A PATCH request can be issued in such a way as to be idempotent」只讀第一句會得出 PATCH 不能重試只讀第二句會得出 PATCH 可以重試。兩句都對說的是兩件不同的事方法層面不保證具體請求可以做到。差別在補(bǔ)丁的內(nèi)容不在方法名上。同樣是 PATCH「把這個字段設(shè)成這個值」發(fā)幾次結(jié)果一樣可以做到冪等「把這個計數(shù)加一」發(fā)幾次加幾次不冪等。所以很多團(tuán)隊(duì)會在文檔里給 PATCH 單獨(dú)寫一句「本接口冪等」或「不冪等」而不是讓調(diào)用方猜——文檔給的是這一個接口的實(shí)際情況那才是重試邏輯該看的。下面是 PUT 一次往返的報文形狀。本文沒有在本機(jī)發(fā)出任何真實(shí)請求這些是按規(guī)范形狀寫出來的示意不是抓包結(jié)果也不指向任何線上系統(tǒng)。??代碼待驗(yàn)證本機(jī)未發(fā)出請求僅示意報文形狀PUT /notes/42 HTTP/1.1 Host: notes.example.internal Content-Type: text/plain; charsetutf-8 Content-Length: 11 hello world HTTP/1.1 204 No Content這個往返只能看出一件事請求體帶的是目標(biāo)狀態(tài)響應(yīng)體是空的。服務(wù)端到底替換了還是追加了報文里看不出來。本章可以帶走的一句方法名給的是默認(rèn)假設(shè)接口的真實(shí)冪等性由服務(wù)端的實(shí)現(xiàn)決定PATCH 尤其要兩句一起讀因?yàn)樗选阜椒ú槐WC」和「請求可以做到」寫在了同一段里。四、為什么協(xié)議要在意這件事4.1 斷連之后的那一次自動重發(fā)冪等不是為了好看才寫進(jìn)規(guī)范的它解決的是一個很具體的工程問題「the request can be repeated automatically if a communication failure occurs before the client is able to read the server’s response」注意時間點(diǎn)before the client is able to read the server’s response。不是「請求失敗之前」是「客戶端讀到響應(yīng)之前」。這個限定描述的正是那種「不知道服務(wù)端做沒做」的狀態(tài)。規(guī)范接著舉了 PUT 的例子PUT 發(fā)出去之后連接斷了客戶端可以換一條連接重發(fā)「even if the original request succeeded, though the response might differ」。重發(fā)可能撞上一個已經(jīng)成功的操作但因?yàn)橛袃绲冗@個前提撞上去不會多出一次效果。連接怎么復(fù)用、持久連接什么時候斷不在本篇范圍。本篇只關(guān)心重發(fā)在語義上允不允許。4.2 非冪等的紀(jì)律SHOULD NOT 后面還有兩個出口「A client SHOULD NOT automatically retry a request with a non-idempotent method unless it has some means to know that the request semantics are actually idempotent, regardless of the method, or some means to detect that the original request was never applied.」結(jié)構(gòu)值得拆主句是默認(rèn)不許自動重試出口一是「有辦法知道這個請求的語義實(shí)際上是冪等的」注意后面那個 regardless of the method不管它標(biāo)的是什么方法出口二是「有辦法檢測到原請求從未被應(yīng)用」。出口一是本篇最容易被忽略的一句方法名是默認(rèn)值不是天花板。一個 POST 接口如果被設(shè)計成冪等的客戶端在有依據(jù)的情況下重發(fā)是合規(guī)的。但依據(jù)必須是能說清的東西——文檔寫了、服務(wù)端有去重、或者調(diào)用方能查到操作狀態(tài)。出口二更實(shí)用能檢測到原請求從未被應(yīng)用就可以放心重發(fā)。比如請求根本沒發(fā)出去或者服務(wù)端能明確回答「這個操作我這兒沒有」。4.3 「結(jié)果未知」是最難的一格客戶端的處境有三種不是兩種成功拿到完整響應(yīng)服務(wù)端說做完了明確失敗拿到完整響應(yīng)服務(wù)端說沒做結(jié)果未知請求發(fā)出去了響應(yīng)沒回來或者回來了但不完整。第三種最容易被當(dāng)成第二種?!赋瑫r就當(dāng)失敗走重試」這種寫法對冪等的方法合理對非冪等的方法就是把「可能已經(jīng)做了」當(dāng)成「肯定沒做」。??代碼待驗(yàn)證本機(jī)未發(fā)出請求以下為時刻示意不是抓包t0 客戶端發(fā)出請求 t1 服務(wù)端收到開始處理 t2 服務(wù)端處理完成效果已經(jīng)落在狀態(tài)里 t3 響應(yīng)離開服務(wù)端 t4 客戶端讀到完整響應(yīng) 斷點(diǎn)落在 t0~t1 之間請求沒到效果沒發(fā)生 → 結(jié)果確定沒做 斷點(diǎn)落在 t1~t4 之間效果可能已發(fā)生 → 結(jié)果未知 斷點(diǎn)落在 t4 之后客戶端已讀到 → 結(jié)果確定做了從 t1 到 t4 這一整段客戶端看到的都是「沒拿到響應(yīng)」但服務(wù)端的狀態(tài)可能已經(jīng)變了。重試決策的難點(diǎn)全在這一段里。本章可以帶走的一句冪等不是給服務(wù)端省事的屬性它是給客戶端一個「結(jié)果未知時可以重發(fā)」的許可許可的邊界由方法語義劃定不能靠「發(fā)一次試試」去試探。五、重試決策的三層判據(jù)5.1 第一層方法在不在冪等集合里第一層最省事也最容易被跳過。拿到一個接口第一個問題不是「它會不會失敗」而是「它標(biāo)的是什么方法、默認(rèn)在不在冪等集合里」。在集合內(nèi)GET、HEAD、OPTIONS、TRACE、PUT、DELETE默認(rèn)允許自動重發(fā)但要過第二層和第三層不在集合里POST以及沒有特別設(shè)計過的 PATCH默認(rèn)不允許除非能滿足 4.2 節(jié)那兩個出口之一。5.2 第二層失敗落在哪一段判據(jù)按斷點(diǎn)位置分四檔斷點(diǎn)位置客戶端看到什么服務(wù)端可能處于什么狀態(tài)冪等方法非冪等方法建連之前就失敗請求一個字節(jié)都沒發(fā)出去沒收到可以重發(fā)可以重發(fā)屬「原請求從未被應(yīng)用」請求已發(fā)出響應(yīng)頭未收全只知道自己發(fā)過可能已執(zhí)行可以重發(fā)默認(rèn)不重發(fā)響應(yīng)頭已收全正文中斷狀態(tài)碼已經(jīng)讀到已執(zhí)行結(jié)果已知可以重發(fā)按狀態(tài)碼判斷響應(yīng)完整收到結(jié)果明確已執(zhí)行按狀態(tài)碼按狀態(tài)碼這張表是本文的歸納條文依據(jù)只有兩處IDEM-04 那句「讀到響應(yīng)之前」和 IDEM-05 那句「檢測到原請求從未被應(yīng)用」。其余格子是工程判斷用時按自己系統(tǒng)改。5.3 第三層響應(yīng)碼說的是「失敗」還是「不知道」4xx請求被理解了但被拒絕。重發(fā)通常還是一樣的結(jié)果重試沒有意義少數(shù)與時間有關(guān)的除外。408、429明確要求「過一會兒再來」是少數(shù)幾個「重試被邀請」的狀態(tài)碼。5xx服務(wù)端自認(rèn)沒做成。但 5xx 不等于「沒產(chǎn)生效果」——寫操作可能已經(jīng)落庫然后在返回路徑上出了問題。對非冪等的方法5xx 仍屬結(jié)果不確定。連接中斷、超時沒有狀態(tài)碼結(jié)果未知。Retry-After就掛在這一層上。它的值可以是 HTTP-date也可以是秒數(shù)規(guī)范里delay-seconds 1*DIGIT是一個非負(fù)十進(jìn)制整數(shù)。配在 503 上表示服務(wù)預(yù)計要不可用多久配在 3xx 上表示兩次請求之間至少要等多久。??代碼待驗(yàn)證本機(jī)未發(fā)出請求僅示意響應(yīng)形狀HTTP/1.1 503 Service Unavailable Retry-After: 120 Content-Length: 0同一個頭也可以寫成日期形式形如Sun, 25 Oct 2026 06:30:00 GMT。兩種都合法只認(rèn)秒數(shù)的代碼遇上日期形式會當(dāng)成沒有這個頭。把三層串起來判讀順序是這樣??代碼待驗(yàn)證本機(jī)未執(zhí)行僅記錄判讀順序printf%s\n第 1 步確認(rèn)方法——它在不在冪等集合里printf%s\n第 2 步確認(rèn)斷點(diǎn)——請求發(fā)出去了沒有響應(yīng)收到了多少printf%s\n第 3 步確認(rèn)狀態(tài)碼——是明確失敗還是結(jié)果未知printf%s\n第 4 步三步都能給出答案才重發(fā)任何一步含糊就先不重發(fā)本章可以帶走的一句重試不是「再發(fā)一次」是「在三層判據(jù)都能給出答案時再發(fā)一次」任何一層含糊就先不發(fā)。完整版對照資料六個方法在 safe、冪等、可緩存三列上的完整對照以及每個結(jié)論對應(yīng)的 RFC 段落位置都整理在這一份里。放在資料包里掃碼即可獲取六、冪等鍵是怎么回事6.1 先把身份說清草案不是標(biāo)準(zhǔn)有一個名字經(jīng)常出現(xiàn)在接口文檔里Idempotency-Key本文按待驗(yàn)證項(xiàng)處理。截至 2026-10-25業(yè)界確實(shí)有這類草案約定——IETF 有一份關(guān)于這個請求頭的草案文檔draft-ietf-httpapi-idempotency-key-header但它不是已發(fā)布的標(biāo)準(zhǔn)。這個區(qū)別在落地時要守住它不是 RFC不能當(dāng)規(guī)范條文引草案會改字段名、位置、服務(wù)端回應(yīng)方式不同版本之間可能不一樣。本文只寫到「業(yè)內(nèi)有這類草案約定」這一層不展開字段的具體語義。6.2 服務(wù)端自己兜的三種做法草案管不到的地方得靠服務(wù)端自己。常見的有三種都不依賴任何特定標(biāo)準(zhǔn)。第一種唯一約束。把「這是哪一次操作」落成一個標(biāo)識給標(biāo)識建唯一索引。第二次帶著同一個標(biāo)識來插入沖突服務(wù)端就知道這件事做過了把第一次的結(jié)果返回去。判斷點(diǎn)在最底層不易被上層繞過。第二種狀態(tài)機(jī)。先把操作落成一條「待處理」記錄再推進(jìn)到「已完成」。重復(fù)請求進(jìn)來時先查沒有就新建并執(zhí)行有且未完成就等或者返回當(dāng)前狀態(tài)有且已完成就直接返回結(jié)果。好處是能區(qū)分「正在做」和「做完了」。第三種冪等重寫。不改基礎(chǔ)設(shè)施改接口語義把「加一」改成「設(shè)成某個值」和 PUT 是同一個思路。代價是有些場景改不了比如「每次調(diào)用都要新增一條流水」本質(zhì)上就是要多次效果。做法能兜住什么兜不住什么唯一約束同一個標(biāo)識的重復(fù)插入同一標(biāo)識下內(nèi)容不同的沖突狀態(tài)機(jī)重復(fù)執(zhí)行以及「正在執(zhí)行中」的并發(fā)狀態(tài)推進(jìn)那一步本身失敗的情況冪等重寫語義層面的重復(fù)天生要求「每次都有新效果」的需求6.3 三個邊界窗口、作用域、清理窗口。去重記錄留多久留一天第二天帶同一個標(biāo)識來就是新操作留一年存儲成本和沖突概率都要重新算。窗口多長等于定義了「多長時間內(nèi)的重試算同一次」。作用域。標(biāo)識按客戶端算、按賬號算、還是按接口算范圍劃小了換個入口進(jìn)來的重試就兜不住范圍劃大了兩個不相干的操作可能撞在一起被誤判成重復(fù)。清理。失敗的那一次要不要留記錄失敗就刪掉重試進(jìn)來會當(dāng)成新操作重新執(zhí)行——對冪等的方法沒問題對非冪等的方法等于沒兜。留著又得能區(qū)分「失敗但可以重試」和「失敗且不該重試」。??代碼待驗(yàn)證示意非真實(shí)實(shí)現(xiàn)收到請求帶著一個操作標(biāo)識 ├─ 標(biāo)識不存在 → 新建記錄待處理→ 執(zhí)行 → 標(biāo)記完成 → 返回結(jié)果 ├─ 標(biāo)識存在未完成 → 不重復(fù)執(zhí)行返回當(dāng)前狀態(tài)或讓調(diào)用方稍后再來 ├─ 標(biāo)識存在已完成 → 不重復(fù)執(zhí)行返回第一次的結(jié)果 └─ 標(biāo)識存在內(nèi)容不同 → 沖突顯式報錯不能當(dāng)成功本章可以帶走的一句冪等鍵是「讓服務(wù)端認(rèn)出這是同一次操作」的記號不是「讓請求自動變冪等」的魔法認(rèn)不出來的那部分仍然要靠接口自己的語義來兜。七、給接口寫一份「重試友好度」自查7.1 七個問題給一個接口做重試友好度評估問七個問題就夠了順序不要換這個接口標(biāo)的是什么方法默認(rèn)在不在冪等集合里如果不在服務(wù)端做了什么讓它實(shí)際上冪等這對應(yīng) IDEM-05 的第一個出口??蛻舳擞袥]有辦法檢測「原請求從未被應(yīng)用」這對應(yīng)第二個出口。失敗落在哪一段能不能區(qū)分「沒發(fā)出去」和「發(fā)出去了沒回音」5xx 的時候服務(wù)端能不能保證「要么全做要么全不做」有沒有Retry-After給的是秒數(shù)還是 HTTP-date去重窗口多長、作用域多大、失敗記錄怎么清理7.2 三個「以為冪等其實(shí)不冪等」的寫法寫法一方法名對實(shí)現(xiàn)不對。標(biāo)了 PUT內(nèi)部卻把報文追加到列表里。這種接口在文檔上看著完全合規(guī)實(shí)際每重發(fā)一次就多一條。寫法二唯一約束建了沖突時返回 500。服務(wù)端確實(shí)擋住了重復(fù)客戶端拿到的卻是一次失敗分不清「已經(jīng)做過了」和「真的做失敗了」。前者該當(dāng)成功后者該排查。沖突時返回的應(yīng)該是能表達(dá)「這件事已經(jīng)完成」的結(jié)果。寫法三去重鍵用請求體的哈希而請求體里帶了時間戳。每次生成的內(nèi)容都不一樣哈希自然不一樣去重永遠(yuǎn)命不中。這種問題在聯(lián)調(diào)階段很難發(fā)現(xiàn)因?yàn)檎A鞒滔麓_實(shí)只有一次請求。三個寫法的共同點(diǎn)是冪等性是在實(shí)現(xiàn)里成立的不是在校驗(yàn)里成立的。方法名和文檔都只是聲明能不能扛住重發(fā)要看服務(wù)端真的做了什么。7.3 落地順序先判方法把接口按「默認(rèn)可重發(fā)」「默認(rèn)不可重發(fā)」分兩堆。再補(bǔ)服務(wù)端兜底不可重發(fā)的那堆里值得補(bǔ)的補(bǔ)唯一約束或狀態(tài)機(jī)不值得補(bǔ)的在文檔里寫清楚讓調(diào)用方別猜。最后才打開客戶端的自動重試。順序反了等于拿真實(shí)流量做實(shí)驗(yàn)。把七個問題收成一張表自查問題合格的樣子不合格的信號方法在冪等集合里嗎方法與語義一致標(biāo)了 PUT實(shí)現(xiàn)是追加不在集合里有兜底嗎唯一約束或狀態(tài)機(jī)沖突時能表達(dá)「已完成」沖突時返回籠統(tǒng)失敗能檢測「從未被應(yīng)用」嗎能查到操作是否存在只有「成功」和「失敗」兩種回答失敗階段能區(qū)分嗎能區(qū)分沒發(fā)出、發(fā)出沒回音、收到不完整一律按超時處理5xx 時的原子性要么全做要么全不做部分完成且沒有標(biāo)記有沒有 Retry-After有且形式明確秒數(shù)或日期讓調(diào)用方自己猜間隔去重窗口與作用域有明確數(shù)值和范圍說不清留多久、按什么算本章可以帶走的一句重試友好度不是文檔里的一句「本接口冪等」是方法、實(shí)現(xiàn)、狀態(tài)碼、去重窗口四樣?xùn)|西對得上對不上的地方就是將來重復(fù)數(shù)據(jù)長出來的地方。完整版對照資料這份自查表連同判斷依據(jù)的原文段落做成了可以逐項(xiàng)打勾的版本。放在資料包里掃碼即可獲取附表 A本文引用事實(shí)與官方出處對照表序號事實(shí)出處核驗(yàn)日期本文位置1冪等定義多次相同請求對服務(wù)端的 intended effect 與一次相同RFC 9110 §9.2.2IDEM-012026-10-25第二章 2.12PUT、DELETE 與 safe 方法屬于冪等集合RFC 9110 §9.2.2IDEM-022026-10-25第一章 1.23冪等只約束被請求的效果服務(wù)端可自由記日志、留修訂歷史RFC 9110 §9.2.2IDEM-032026-10-25第二章 2.24通信失敗發(fā)生在讀到響應(yīng)之前時請求可被自動重發(fā)RFC 9110 §9.2.2IDEM-042026-10-25第四章 4.15重發(fā)可能撞上已成功的原請求「though the response might differ」RFC 9110 §9.2.2IDEM-042026-10-25第二章 2.36非冪等方法默認(rèn)不得自動重試及其兩個例外RFC 9110 §9.2.2IDEM-052026-10-25第四章 4.27safe 的定義GET、HEAD、OPTIONS、TRACE 為 safeRFC 9110 §9.2.1IDEM-062026-10-25第三章 3.18冪等集合比 safe 集合大PUT、DELETE 冪等但不 safeRFC 9110 §9.2.2IDEM-062026-10-25第一章 1.29POST 是 potentially unsafe不能被緩存的 POST 響應(yīng)滿足RFC 9110 §9.3.3IDEM-072026-10-25第三章 3.310PUT 的語義把目標(biāo)資源狀態(tài)創(chuàng)建或替換為報文表示定義的狀態(tài)RFC 9110 §9.3.4IDEM-082026-10-25第三章 3.211DELETE 的語義解除 URI 映射與當(dāng)前功能的關(guān)聯(lián)類比 rmRFC 9110 §9.3.5IDEM-092026-10-25第三章 3.212PATCH 既不 safe 也不冪等RFC 5789 §2IDEM-102026-10-25第三章 3.313「A PATCH request can be issued in such a way as to be idempotent」RFC 5789 §2IDEM-102026-10-25第三章 3.314Retry-After的值可以是 HTTP-date 或秒數(shù)RFC 9110 §10.2.3IDEM-112026-10-25第五章 5.315delay-seconds 1*DIGIT為非負(fù)十進(jìn)制整數(shù)RFC 9110 §10.2.3IDEM-112026-10-25第五章 5.316Retry-After配 503 表示服務(wù)預(yù)計不可用多久RFC 9110 §10.2.3IDEM-112026-10-25第五章 5.317Retry-After配 3xx 表示兩次請求之間至少等多久RFC 9110 §10.2.3IDEM-112026-10-25第五章 5.318重試判據(jù)三層方法 / 失敗階段 / 響應(yīng)碼為本文歸納本文歸納依據(jù) IDEM-122026-10-25第五章 5.1 至 5.319失敗階段與能否重發(fā)的對應(yīng)表為工程歸納非規(guī)范原文本文歸納IDEM-04、IDEM-052026-10-25第五章 5.220待驗(yàn)證Idempotency-Key是 IETF 草案不是已發(fā)布標(biāo)準(zhǔn)draft-ietf-httpapi-idempotency-key-headerIDEM-132026-10-25第六章 6.121本文未在本機(jī)發(fā)出任何 HTTP 請求報文塊均為示意本批紀(jì)律2026-10-25第三章 3.322不指向任何真實(shí)業(yè)務(wù)接口本批紀(jì)律列明不寫的內(nèi)容全文未涉及本批紀(jì)律IDEM-142026-10-25全文23連接復(fù)用與持久連接不在本篇范圍本批紀(jì)律2026-10-25第四章 4.1附表 B術(shù)語速查表術(shù)語一句話解釋safe方法的定義語義本質(zhì)上只讀GET、HEAD、OPTIONS、TRACE 屬此類冪等同一請求發(fā)多次服務(wù)端被請求的效果與發(fā)一次相同不比響應(yīng)intended effect請求打算達(dá)成的效果冪等定義里用的就是這個詞結(jié)果未知請求已發(fā)出、響應(yīng)未完整收到服務(wù)端可能已執(zhí)行冪等鍵讓服務(wù)端認(rèn)出「這是同一次操作」的標(biāo)記相關(guān)草案尚未成為標(biāo)準(zhǔn)冪等重寫把「加一」這類非冪等操作改寫成「設(shè)成某個值」Retry-After告訴調(diào)用方多久之后再來值可以是秒數(shù)或 HTTP-datepotentially unsafe規(guī)范對 POST 的定性意思是效果不承諾可重復(fù)去重窗口服務(wù)端保留操作記錄的時長超出窗口的重試算新操作三層判據(jù)方法、失敗階段、響應(yīng)碼三者都能給出答案才允許重發(fā)寫在最后這篇用到的資料寫這篇文章時我把 RFC 9110 里講方法語義的那幾段和 RFC 5789 里 PATCH 那一節(jié)對著讀了很多遍最費(fèi)勁的是把「響應(yīng)不一樣」和「效果不一樣」這兩件事徹底分開順手也整理了幾份配套的東西靶場環(huán)境對照表DVWA、upload-labs 在 Windows / macOS / Linux 三平臺的可行性與推薦路徑Web 安全學(xué)習(xí)路線圖從基礎(chǔ)打牢到安全管理四個階段各學(xué)什么常用靶場清單每個靶場練什么、適合哪個階段資料是我自己整理的放在下面這個碼上掃碼即可獲取添加時備注「靶場」優(yōu)先通過。拿到之后建議先看環(huán)境對照表那一份再回頭把自己項(xiàng)目里那幾個帶自動重試的接口按第五章的三層判據(jù)過一遍。