1. 程式人生 > >HTTP Post, Put, Patch等方法的應用區別

HTTP Post, Put, Patch等方法的應用區別

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

PUT 會在位址列顯示引數資訊,不安全!

理解POST和PUT的區別,順便提下RESTfu

這兩個方法咋一看都可以更新資源,但是有本質區別的

具體定義可以百度,我這裡就不貼了,光說我自己的理解

首先解釋冪等,冪等是數學的一個用語,對於單個輸入或者無輸入的運算方法,如果每次都是同樣的結果,則稱其是冪等的

對於兩個引數,如果傳入值相等,結果也等於每個傳入值,則稱其為冪等的,如min(a,b)

POST

用於提交請求,可以更新或者建立資源,是非冪等的

舉個例子,在我們的支付系統中,一個api的功能是建立收款金額二維碼,它和金額相關,每個使用者可以有多個二維碼,如果連續呼叫則會建立新的二維碼,這個時候就用POST

PUT

用於向指定的URI傳送更新資源,是冪等的

還是那個例子,使用者的賬戶二維碼只和使用者關聯,而且是一一對應的關係,此時這個api就可以用PUT,因為每次呼叫它,都將重新整理使用者賬戶二維碼

比如一個介面用於使用者生成,接收的資料是使用者名稱、密碼等相關資訊,則用POST

RESTful建議所有的URI都是對應資源,所以建立使用者不應該理解為一個行為,在此將此介面命名為:

/user/creation

每次呼叫它都會新建一個使用者(假定使用者名稱可以重複)

而PUT方法更加關心一個具體資源對應的URI,比如更新當前使用者資訊,這裡可以用PUT

/user/me/update

這裡用me來指代當前使用者,如果是針對更多使用者適用的介面,可以考慮

/user/{uid}/update

注意多次呼叫同一介面,只要提交的資料一致,使用者資訊每次結果就會一致,即產生同樣的結果:伺服器端某個具體的資源得到了更新

當需要以更新的形式來修改某一具體資源的時候,如何判斷用PUT還是POST呢?

很簡單,如果該更新對應的URI多次呼叫的結果一致,則PUT

比如更新某個blog文章,因為該文章具有單一的具體URI,所以每次更新提交相同的內容,結果都一致

/blog/{document_id}/update

在每次更新提交相同的內容,最終的結果不一致的時候,用POST

舉個很常見的例子,一個介面的功能是將當前餘額減一個值,每次提交指定該值為100,介面如下

/amount/deduction

呼叫一次,你的餘額-100,呼叫兩次,餘額-200

這個時候就用POST

RESTful的4種層次

Representational status transfer

個人理解為:表現形式的狀態傳遞

1、只有一個介面交換xml來實現整個服務

目前我們的移動站點的服務就是類似的結構,我們有兩個URI介面/mapp/lead和/msdk/safepay

2、每一個資源對應一個具體的URI,比1好維護,但是問題依然很明顯,資源版本更新會引入時間戳維護,資源的獲取和更新修改必須對應不同的URI

目前PC主站和移動站點的靜態內容(包括html檔案)都是這種形式

3、在2的基礎上使用了http verb,每個URI可以有不同的動作,充分利用了http協議,所以自然居然http協議的完整優勢,比如快取和健壯性

HTML4.0只支援POST和GET,所以無論DELETE還是PUT操作,都用POST去模擬了

在WEB開發者看來,就是如果有資料變動,就用POST,如果沒有,就用GET

所以目前中國使用者來看,PC端實現RESTful很困難,只有移動端支援Html5的瀏覽器,才能讓前端做出嘗試

4、現在似乎更加無法實際應用,Hypemedia control,也就是RESTful的本意,合理的架構原理和以網路為基礎的設計相結合,帶來一個更加方便、功能強大的通訊架構

這就有點虛無縹緲了,不過是一個努力的方向,想想看,以後要繳水費了,開啟瀏覽器,輸入我要繳水費,就自動定位+自動下單+自動付款+自動展示結果,完成整個繳水費的過程,這是多麼方便的領悟!gwy要失業了有木有,那幫吃白飯做很簡單的事情的,生產力發展第1個要淘汰的就是阻礙生產力發展的落後生產關係……

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

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

但是這個行為似乎不怎麼好用,如果只是為了更新items/1的其中一個屬性,就需要重傳所有items/1的屬性也太浪費頻寬了,所以後來又有新的 PATCH Method 標準,可以用來做部分更新(Partial Update)。

用幾個 Ruby code 來舉例吧:

POST 新增:

# POST /items
def create
  @item = Item.new
  @item.attributes = { :name => params[:name],
                      :image => params[:image] }
  @item.save
end

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.save
end

PATCH 部分更新,此例中如果image引數沒有傳,就不會被更新:

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

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

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

有時候拘泥於”語意”這件事情不容易想清楚設計 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 的相容取代方案。

另一個 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。