2013年5月2日 星期四

重新認識HTTP請求方法


重新認識HTTP請求方法

根據需求考慮安全、等冪及HTTP/1.1規範各方法的特性
傳統Web應用程式中,HTML的form標籤提供的method值只有GET與POST,伺服端程式通常只需取出表單發送的請求參數,像是request.getParameter("id");一些書籍或文件,更直接教導在多數情況下,GET與POST會作相同的事,這造就了許多網頁或程式設計師,忽略了HTTP協定中規範的其他請求方法。也有不少人認為,選用GET或POST並不重要,模糊地具有「POST可以傳送更多資料」、「GET比較不安全」等印象。

就HTTP/1.1對GET的規範來說,是從指定的URI「取得」想要的資訊,指定的URI包括了查詢部份,例如GET /?id=0093。瀏覽器會將指定的URI顯示在網址列上,各家瀏覽器對網址列的長度限制不一,伺服器對URI長度也有限制,基本上由於IE對網址列長度限制為2083個字元,因而一般不建議使用超過此長度的URI。

由於瀏覽器書籤功能是針對網址列,因此想讓使用者可以針對查詢結果設定書籤的話,傳統上是使用GET;然而反過來說,由於請求查詢的字串會顯示在網址列,因而像密碼之類的敏感資料,並不適合使用GET。

HTTP/1.1對POST的規範,是要求指定的URI「接受」請求中附上的實體(Entity),像是儲存為檔案、新增為資料庫中的一筆資料等。由於要求伺服器接受的資訊是附在請求本體(Body)而不是在URI,瀏覽器網址列不會顯示附上的資訊,也不至於會受到網址列長度限制,因而POST在過去常被用來發送檔案等大量資訊,傳統上敏感資訊也常透過POST發送,然而,POST後新增的資源不一定會有URI作為識別,基本上無法讓使用者設定書籤。

傳統上使用GET與POST,還有個容易被忽略的特性就是快取(Cache)。只要符合HTTP/1.1第13節對快取的要求,GET的回應是可以被快取的,最基本的就是指定的URI沒有變化時,許多瀏覽器會從快取中取得資料。有些應用程式會使用JavaScript等來產生隨機字串或日期,附在GET請求的URI上,或者由伺服端指定適當的Cache-Control標頭來避免GET回應被快取的問題。至於POST的回應,許多瀏覽器(但不是全部)並不會快取,但是 HTTP/1.1中規範,如果伺服端指定適當的Cache-Control或Expires標頭,仍可以對POST的回應進行快取。

安全與等冪方法 
在HTTP/1.1第9節對HTTP方法的定義中,區分了安全方法與等冪方法(Idempotent methods)。安全方法是指在實作應用程式時,使用者採取的動作必須避免有他們非預期的結果。慣例上,GET與HEAD(與GET同為取得資訊,不過僅取得回應標頭)對使用者來說,就是「取得」資訊,不應該被用來「修改」與使用者相關的資訊,像是進行轉帳之類的動作,它們是安全方法,這與傳統印象中對於GET比較不安全的看法相反。相對之下,POST、PUT與DELETE等其他方法就語義上來說,代表著對使用者來說,可能會產生不安全的操作,像是刪除使用者的資料等。

需注意的是,安全與否並非指方法對伺服端是否產生副作用,而是指對使用者來說該動作是否安全。GET也有可能在伺服端產生副作用,像是在伺服端產生日誌(Logging)或統計使用量。

對於副作用的進一步規範是在方法的等冪特性,GET、HEAD、PUT、DELETE是等冪方法,也就是單一請求產生的副作用,與同樣請求進行多次的副作用必須是相同的。

舉例來說,若DELETE的副作用就是刪除某筆資料,相同請求再執行多次的結果,就是該筆資料不存在,而不是刪除更多資料。OPTIONS與TRACE本身就不該有副作用,所以也是等冪方法。

HTTP/1.1中的方法去除掉上述的等冪方法之後,換言之,只有POST不具有等冪特性,這是使得它與PUT有所區別的特性之一。

在HTTP/1.1規範中,PUT方法要求將附加的實體儲存於指定的URI,如果指定的URI下已存在資源,則附加的實體是用來進行資源的更新,如果資源不存在,則將實體儲存下來並使用指定的URI來代表它,這亦符合等冪特性,例如用PUT來更新使用者基本資料,只要附加於請求的資訊相同,一次或多次請求的副作用都會是相同,也就是使用者資訊保持為指定的最新狀態。


應用於REST架構

現在不少Web服務或框架都走向支援REST風格的架構,REST全名REpresentational State Transfer,可譯為表徵狀態轉移,為Roy Fielding於2000年在他的博士論文中提及。

REST架構由客戶端/伺服端組成,兩者間通訊機制是無狀態的(Stateless);客戶端對伺服端請求資源,伺服端回應為資源的表徵,或稱為表現方式,也就是說,資源在REST中是可定址的(Addressed)概念,為獨一無二的識別名稱(Nouns),可能用檔案、文件、格式等內容型態(Content type)來表現,代表資源目前或可能的狀態。客戶端獲取的表徵狀態,可能包括下次狀態轉移的連結,請求動詞(Verbs)必須能表現出如何處理請求。而「名稱」、「動詞」、「內容型態」使許多人討論REST時,會提到REST Triangle,而這與HTTP規範不謀而合。

URI就是處於名稱角色用以定義資源,HTTP具有GET、POST、PUT與DELETE等具語義之動詞,並可以使用content-type標頭來定義資源表現的內容型態。REST架構基於HTTP 1.0,與HTTP1.1平行發展,但不限於HTTP。符合REST架構原則的系統,稱其為RESTful,例如Rails自1.2以來支援RESTful。

以基於HTTP的基本CRUD書籤程式來說,POST /bookmarks是用來新增一筆資料,GET /bookmarks/1用來取得ID為1的書籤,PUT /bookmarks/1用來更新ID為1的書籤資料,而DELETE /bookmarks/1用來刪除ID為1的書籤資料。Rails基本上運用了REST的概念,來讓路由設定簡化,並具有一致性。

然而,注意到以上的描述,並不是說PUT只能用於更新資源,也沒有說要新增資源只能用POST。

先前在等冪性時談過,PUT在指定的URI下不存在資源時,也會新建請求中附上的資源。等冪性是在選用POST或PUT時考量的要素之一,另一個重要的考量,在HTTP/1.1中也有規範,也就是請求時指定的URI之作用。POST中請求的URI,是要求其背後資源必須處理附加的實體,而不是代表處理後實體的URI;然而PUT時請求的URI,就代表請求中附加實體的URI,無論是更新或是新增實體。

重新認識的必要性
從Ajax概念重新興起之後,重新認識HTTP請求方法的必要性,就逐漸加重,雖然傳統表單只有GET與POST兩種選擇,然而非同步請求物件可以運用PUT、DELETE等方法,實際上Rails等應用程式框架,就是使用此方式來實現REST風格。重新認識HTTP請求方法,就能清楚地知道如何運用RESTful的框架或服務。

重新認識的必要性之一則是安全考量,例如若GET方法使用時,違反了HTTP/1.1中規範的安全方法特性,就容易遭受跨站偽站請求(Cross-Site Request Forgery, CSRF)之類的攻擊;實際上POST方法也不安全,根據實際需求考慮安全、等冪以及HTTP/1.1規範中各方法的特性,並採取對應的安全措施,才是比道聽塗說而來的錯誤印象更實際的作法。

近年來,由於智慧行動裝置迅速普及,越來越多網站須提供API服務,而不再只是產生瀏覽器顯示用的Web網頁,在設計API為主的Web服務時,對於HTTP請求方法的正確認識,也更形重要。

作者:林信良
轉載自《iThome》