早年寫網頁時,資深工程師會笑新進工程師連GET跟POST都分不清楚,但這麼多年來,大家都真的清楚嗎?
近年來由於各種RESTful API的發展,跟各種工具的普及,在單一URI下,透過識別Http Request Method來判斷客戶端的目的上,越來越趨於統一。另一方面也另人感嘆,從1999年就更新的HTTP/1.1協定中就考慮到的各種應用情境,直到多年後,才逐漸地了解他在應用面的價值。
這兩天遇到兩個小問題:
1. GET是否容許用 Body 來帶參數呢?是否如我們的印象,GET就是不能有Request Body的呢?
2. 如果用來操作資料的話,應該如何區分PUT跟POST的差異呢? PUT代表新增,而POST代表更新嗎?還是相反呢?能否讓POST同時具備新增與更新的能力呢?
1. GET是否容許 Body 來帶參數呢?
對於這兩個問題,第一步我們先來看 RFC-2616 HTTP/1.1 的協定中的描述。根據規格書的描述,Request Body的存在於否,原則上是根據 Content-Length 的定義來決定。但同時也提到,特定的情境下,Request不應該具有Entity(應該就是指Body內的資料吧),而這個特定情況,並不是GET,而是當 Request Method 為 TRACE 的時候。
因此從規格上來看,GET Request完全可以包含Body,而且根據協定伺服器並不會略過內容,仍舊會處理,因此賜福端程式仍然可以取得 Body 的數值作為參數。
那這是不是代表我們原來的認知, GET 不應該帶Body,是錯誤的呢?
嚴格來說,一半一半,進一步檢討 GET Method 的設計目標,當客戶端對伺服器發出 GET 請求特定資源時,會預期相同 URI 的回傳結果,是一致的,但假如我們讓參數放在 Request Body 而非 URI 上,則可能導致因為 Body 參數的不同,相同的 URI 回傳的資訊是不同的。
這會造成甚麼問題呢?
一般而言網路上各種服務都會支持通訊協定的設計原則,因此當協定認定相同 URI 取回的資源相同時,代表會有其他機制在這個基礎上進行最佳化的調適,其中一種情況就是 Proxy Server 提供的服務,該服務因為預期 URI 相同則結果相同,因此對於相同 URI,會以快取的資訊來供應給需求端,但此時 Request Body 就很可能被忽略了,結果可能導致當資料通過 Proxy 時,回傳的結果跟客戶端預期不相符的情況。而這問題雖然可以透過改變 Header 的定義,來避免快取,但也就相對地得不到原本 Proxy 優化的好處。
同樣的問題,可以想見在使用CDN服務,或者透過一些壓縮來優化傳輸效率的機制上,都會有一定程度的影響。
因此,並不是 GET 不能有 BODY,而是不該讓 GET 的 Response 受到 BODY 的影響,伺服端應該盡量確保相同的 URI 下,每次請求時回傳的內容是相同的,而當客戶端有不同的需求時,應該反映在 URI 上。這樣一來可以提供客戶端不同的資源,同時相近的資源又可以受惠於各種優化機制,讓頻寬的運用更有效率。
HTTP/1.1 Spec
https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3
2. 應該如何區分PUT跟POST的差異呢?
回到通訊協定的設計理念,PUT 跟 POST 跟資料庫的操作行為並沒有直接關係,而是針對網路資源的存取做出區別。PUT 的規劃是針對一個完整的實體(Entity),你可以把他想成一個檔案,PUT 的 Body 通常帶著一份完整的實體給伺服器,而假設伺服器在相同 URI 下存在另一個實體,則 PUT 帶來的,應該視為是伺服端實體的新版,因此應該用新版來取代舊版。而當指定的 URI 下沒有東西時,則將 PUT 提供的實體放到指定的 URI 下,下次再有人來要,就能拿到這次提供的實體。
因此,PUT有建立新資料的特性,也有更新資料的特性,但他是考慮整個實體,整體去置換掉舊的版本,而不是針對舊版本的部分內容做變更。
而 POST 的目標則是在對應的 URI 上的實體是存在的前提下,利用 POST 帶來的資訊去更新伺服端版本當中的某個片段,因此 POST 並不一定包括實體的完整資訊,而且當伺服端找不到對應的對象時,也不會建立新的版本。
不過有趣的是,POST在某些情況下,也會進行類似新增資料的動作,他的行為完全取決於 URI 的規劃。
當 URI 的規劃是對應到單一資料,例如 /article?id=001 or /article/1 的情況,則 POST 僅更新內容,當 article.id = 1 這樣的資料不存在時,則建議回應錯誤。
但如果 URI 是對應到一個集合體,例如 /articles 的情況,則 POST 更新的是該集合下的某個個體,因此可以是新增一個 article 到 articles 集合中。
當然這只是規畫面的思維,並沒有限定一定要照這個理念去設計,目前也還沒看到明確的資料,說明按照這個方式規劃在協定層有甚麼好處,或者不按照這個原則會有甚麼問題。因此,就結論而言,並不是一定要同時包含PUT跟POST的處理,他們各自是否要具備新增,或者變更資料的能力,也沒有協定層的限制。
PUT vs POST in REST
http://stackoverflow.com/questions/630453/put-vs-post-in-rest
留言列表