HTTP 方法 — 安全與冪等這兩個約定
依「想做什麼」區分使用 GET・POST・PUT・DELETE・HEAD,並用安全(不改變伺服器狀態)與冪等(做幾次結果都相同)這 2 個約定畫出界線,讓它能用於重試的判斷。
是不是「反正全部用 POST」在開發?
表單也好、取得也好、刪除也好,因為能動就全部用 POST 做掉。或者反過來,送出按鈕被再點一次,訂單就被重複登錄了兩筆,卻找不到原因 — 方法的使用區分,正因為只求「能動」時再含糊也能過關,所以總是在出事之後才顯出它的重要。
如上一章所讀,位於請求行開頭的方法,是往返書信的動詞。HTTP 的方法有很多個,但一開始只要能區分使用這 5 個就足夠了。
例如 PUT /users/42/profile 是「把 42 號使用者的個人資料變成這份內容」,POST /orders 是「處理這份訂購內容並建立新訂單」。目的地已定的置換用 PUT,會新生出東西的或處理的委託用 POST,這是基本的使用區分。
練習 2-1 — 區分使用 5 個動詞
從「想做什麼」出發,練習選出合適的方法。
Q1. 在網路商店 https://shop.example.com,要送出訂購表單填寫的內容並建立一筆新訂單時,最合適的方法是哪一個?
「請建立新的東西・請處理這份內容」這種請託是 POST。GET 有取得專用、不改變伺服器狀態的約定,不會用於以內文送出訂單資料這類用途。HEAD 是不含本文的確認,DELETE 是刪除的請託,兩者都不可能成為訂單的建立。
Q2. 對於一個巨大的影片檔,不想下載、只想知道大小或更新日期時,最合適的方法是哪一個?
HEAD 是「給我和 GET 相同的回答,但去掉內文」的請託。伺服器只回傳標頭(Content-Length、Last-Modified 等),因此可以不搬運巨大的本文就確認中繼資料。GET 也能取得資訊,但連本文都會被傳輸,相對於目的浪費太大。
Q3. PUT 與 POST 差異的說明,最合適的是哪一個?
PUT 是指定了目的地 URL 的「覆寫儲存」,同一個請求送 2 次,最終狀態和送 1 次相同。POST 是「處理的委託」,送 2 次就會產生 2 筆訂單,效果可能隨每次執行而累積。這個差異就是下一組練習要處理的「冪等」界線,與加密、資料格式、HTTP 版本無關。
2 個約定 — 安全與冪等
比起背下 5 個動詞,更重要的是繫在方法身上的2 個約定。這 2 個詞在規格書與錯誤調查中都頻繁出現。
在這裡動手確認一下。用 PUT /users/42/profile 把資料置換成「姓名 = 小村」的請求送 3 次,最終狀態仍是「姓名 = 小村」— 和送 1 次相同。DELETE /orders/7 送 2 次,「7 號訂單已消失」這個最終狀態也相同(第 2 次也許會回 404,但冪等性看的是伺服器端的最終狀態)。另一方面,POST /orders 送 3 次就會產生 3 筆訂單 — POST 不是冪等的。
整理彼此的關係:安全就必定冪等(既然什麼都不改變,做幾次當然都相同)。反過來不成立,PUT 與 DELETE 是「會改變,但做幾次都相同」— 冪等但不安全,落在中間的帶狀區域。
把 5 個方法沿著安全・冪等 2 個軸排列的地圖。GET/HEAD 遵守兩個約定,PUT/DELETE 只有冪等,POST 兩個約定都不做。這張地圖就是「可以重試嗎」的判斷表。
在實務上的效果 — 更新的重試,先確認冪等性
這 2 個約定在實務上最有效的場面,是通訊失敗時重送(重試)的判斷。
送出請求後逾時,其實存在 2 種可能:「請求沒有送達伺服器」,還是「送達了、處理也完成了,只有回覆遺失了」— 從用戶端無法區分。這時冪等性就派上用場。
正因如此,瀏覽器在 POST 的結果頁面按重新整理時,才會特地確認「要重新送出表單嗎?」(這個場面會在第 7 章的案例中再次登場)。此外,內建自動重試的程式庫多半預設只重送 GET,依據也是這個約定。「更新的重試,先確認那個方法是否冪等」 — 請默唸到手能記住為止。
練習 2-2 — 畫出安全與冪等的界線
精確說出 2 個約定的差別,並連結到實務上的「可以重試嗎」。
Q4. 方法是「安全(safe)」的,指的是什麼樣的約定?
安全 = 「只讀取,不改變伺服器狀態」的約定,GET 與 HEAD 屬於此類。這不是加密(那是 TLS 的工作)、速度或無錯誤的保證。正因為有這個約定,搜尋引擎的爬蟲才能放心地用 GET 追蹤連結 — 用 GET 就會刪除商品的做法,是違背了這個約定的設計。
Q5. 「冪等(idempotent)但不安全」的方法組合是哪一個?
PUT(置換成相同內容)與 DELETE(刪除同一個東西)會改變伺服器狀態所以不安全,但執行 2 次的最終狀態與 1 次相同,所以是冪等的。GET 與 HEAD 是安全的(不改變),既然不改變當然也是冪等的。POST 是既不安全也不冪等的代表。用「安全 ⊂ 冪等」這個包含關係記憶會比較好整理。
Q6. 送出更新類的 API 請求後,沒有收到回應而逾時了。在再送一次相同請求之前,最應該確認的是哪一個?
逾時可怕的地方在於,可能是「請求已送達伺服器、處理也完成了,只有回覆遺失了」。冪等的 PUT/DELETE 即使已被處理,重送的結果也不會改變,可以放心重送。POST 重送則可能讓訂單或付款變成兩筆,要先確認是否已處理,或在防止重複執行的機制之上重送。「更新的重試,先確認冪等性」— 這是本章對實務最有效的一句話。
本章帶回家的重點
- 方法是往返書信的動詞。取得用 GET,不含本文的確認用 HEAD,處理委託・新增建立用 POST,指定目的地的置換用 PUT,刪除用 DELETE
- 安全 = 不改變伺服器的狀態(GET/HEAD)。冪等 = 做幾次最終狀態都相同(GET/HEAD/PUT/DELETE)。安全就必定冪等
- POST 既不安全也不冪等。逾時後的無條件重試有雙重處理的危險
- 更新的重試,先確認冪等性 — 這一句話就是在實務上的效果
請託的讀法已經上手,下一章是往返書信的另一側 — 來自伺服器的回覆。從百位數開始,依序解讀 3 位數的狀態碼。