HTTP Verbs: 談 POST, PUT 和 PATCH 的應用

HTTP Verbs: 談 POST, PUT 和 PATCH 的應用

HTTP Verbs: 談 POST, PUT 和 PATCH 的應用

在初學REST的這幾年,我都認為這幾個 HTTP Verbs 就是對應 CRUD:html

  • POST = 新增
  • GET = 讀取
  • PUT = 更新
  • DELETE = 刪除

後來在設計 API only 的 Web service 時,經常搞不清楚到底要用 PUT 還是 POST,才發現我被 Rails 的鷹架範例誤導了(被框架框住想法了?),所謂的 PUT 其實也能夠用到新增,並且還有一個蠻新的 HTTP Verb 叫作 PATCH,像 Github API  Rails 4 都開始採用。git

PUT 比較正確的定義是 Replace (Create or Update),例如PUT /items/1的意思是替換/items/1,若是已經存在就替換,沒有就新增。PUT 必須包含items/1的全部屬性資料。github

可是這個行為一般不怎麼好用,若是隻是為了更新items/1的其中一個屬性,就須要重傳全部items/1的屬性也太浪費頻寬了,因此後來又有新的 PATCH Method 標準,能夠用來作部分更新(Partial Update)。web

用幾個 Ruby code 來舉例吧:api

POST 新增:ruby

# POST /itemsdef create  @item = Item.new  @item.attributes = { :name => params[:name],                      :image => params[:image] }  @item.saveend

PUT 替換(新增或完整更新),此例中若是image參數沒有傳,會被更新成空:框架

# PUT /items/{id}def replace   @item = Item.find_by_id(params[:id])  unless @item  # if @item.nil?      @item = Item.new      @item.id = params[:id]  end  @item.attributes = { :name => params[:name],                       :image => params[:image] }  @item.saveend

PATCH 部分更新,此例中若是image參數沒有傳,就不會被更新:less

# PATCH /items/{id}def patch   @item = Item.find(params[:id])  @item.attributes = params.slice(:name, :image)  @item.saveend

DELETE 刪除,此例中無論如何 items/1 最後都不存在了ide

# DELETE /items/{id}def destroy   @item = Item.find_by_id(params[:id])  @item.destroy if @itemend

有時候拘泥於」語意」這件事情不容易想清楚設計 REST API 時要用哪一個 HTTP 方法,因為有時候不必定是CRUD的形式。我認為 9.1 Safe and Idempotent Methods 定義中的 「Idempotent」 特性蠻實用的。idempotent 的意思是若是相同的操做再執行第二遍第三遍,結果還是一樣。根據 HTTP 的規格,GET, HEAD, PUT 和 DELETE 是 idempotent,相同的 Request 再執行一次,結果還是一樣。只有 POST 和 PATCH 不是 idempotent,POST 再執行一遍,會再新增一筆資料,PATCH 則是有不能保證 idempotent 的可能性(徵求例子)。POST 和 PATCH 都不是 idempotent 的操做,這也是為什麼 Github API 裡用 POST 當作 PATCH 的相容取代方案。wordpress

另外一個 HTTP Methods 特性是」Safe」,這比較簡單,只有 GET 和 HEAD 是 Safe 操做。Safe 特性會影響是否能夠快取(POST/PUT/PATCH/DELETE 必定都不能夠快取)。而 Idempotent 特性則是會影響能否 Retry (重試,反正結果一樣)。

SAFE? IDEMPOTENT?
GET Y Y
POST N N
PATCH N N
PUT N Y
DELETE N Y

透過 Idempotent 的特性,有時候能夠幫助你判斷該用哪一個 HTTP Methods。回到前面講 PUT 好像不太好用,例如以瀏覽器為主的 HTML 應用表單,要麻是 POST 新增資料,要麻就是用 PATCH 部分更新已經存在的資料(你不會但願用 PUT 修改個人資料的時候,每次都要重傳照片吧),所以比較少用到 PUT。這也是為什麼 Rails 4 把表單修改的 PUT 改爲 PATCH Method,透過 Rails 鷹架產生出來的 update,其實符合的是 PATCH 行為而不是 PUT。

不過還是有一些我認為蠻適合用PUT的情境,例如訂閱東西該用POST還是PUT呢?

POST /subscriptions# 還是PUT /subscriptions/{something_id}

訂閱東西只有兩個狀態,」已訂閱」或」沒有訂閱」,這個訂閱的操做再重送幾次,還是」已訂閱」,因此我認為蠻符合 PUT 的 idempotent 特性。而對應的取消訂閱 API 想當然就是

DELETE /subscriptions/{something_id}

另一個我覺得有趣又實用的 PUT 例子是,設計 API 給能夠離線 offline 使用的行動裝置(例如iPhone)。支援 offline 產生的資料,一般會使用 UUID 來產生 ID,這樣就不須要透過中央伺服器管控 ID,方便裝置之間的同步。這樣的情境下,新增資料的 REST API 其實能夠提供兩種:

POST /items # 參數帶 uuid=4937973d-e349-460a-a6ad-38625125b24a。若是不帶uuid則由server來產生uuid# 和PUT /items/4937973d-e349-460a-a6ad-38625125b24a

對行動裝置的 client 來說,用POST的問題在於離線環境的不穩定,有可能POST之後沒有收到回傳,所以行動裝置不確定有沒有同步成功,這時候要再重試(retry),可是用 POST 就爆炸了,因為 server 會再新建一筆 uuid 重複的資料。可是用 PUT 就沒有問題了,PUT 是 Idempotent 的操做,能夠重送沒有關係 (能夠再搭配 Conditional PUT 的機制,用 ETag 和 Last-Modified Headers 確保沒有覆蓋衝突)

若是是沒有 offline 需求的 client,例如第三方應用,那麼就能夠用 POST /items 這個 API,交由 server 來產生 uuid。

其餘參考資料



相關文章
相關標籤/搜索