重新認識HTTP請求方法


iThome 網站首載:重新認識HTTP請求方法

傳統Web應用程式中,HTML的form標籤提供的method值只有GETPOST,伺服端程式通常只需取出表單發送的請求參數,像是request.getParameter("id");一些書籍或文件,更直接教導多數情況下,GETPOST會作相同的事,這造就了許多網頁或程式設計師,忽略了HTTP協定中規範的其他請求方法,也有不少人認為選用GETPOST並不重要,模糊地具有「POST可以傳送更多資料」、「GET比較不安全」等不完全正確的印象。隨著網路應用程式的多元化,我們應該重新認清HTTP請求方法中,有哪些重要的概念。

基本的GETPOST

就HTTP/1.1對GET的規範來說,是從指定的URI「取得」想要的資訊,指定的URI包括了查詢(Query)部份,例如GET /?id=0093。瀏覽器會將指定的URI顯示在網址列上,雖然標準中沒有限制URI長度,然而各家瀏覽器對網址列的長度限制不一,伺服器對URI長度也有限制,基本上由於Internet Explorer對網址列長度限制為2083個字元,因而一般不建議使用超過此長度的URI。由於瀏覽器書籤功能是針對網址列,因此想讓使用者可以針對查詢結果設定書籤的話,傳統上是使用GET;然而反過來說,由於請求查詢的字串會顯示在網址列,因而像密碼之類的敏感資料,並不適合使用GET,這或許是過去對「GET比較不安全」的不正確印象來源。

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

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

安全與等冪方法

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

需注意的是,安全與否並不是指方法對伺服端是否產生副作用(Side effect),而是指對使用者來說該動作是否安全,GET也有可能在伺服端產生副作用,像是在伺服端產生日誌(Logging)或統計使用量。對於副作用的進一步規範是在方法的等冪特性,GETHEADPUTDELETE是等冪方法,也就是單一請求產生的副作用,與同樣請求進行多次的副作用必須是相同的,舉例來說,若使用DELETE的副作用就是某筆資料被刪除,相同請求再執行多次的結果就是該筆資料不存在,而不是造成更多的資料被刪除。OPTIONSTRACE本身就不該有副作用,所以他們也是等冪方法。

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

應用於REST架構

現在不少Web服務或框架都走向支援REST風格的架構,REST全名REpresentational State Transfer,可譯為表徵狀態轉移,為Roy Fielding於2000年在他的博士論文 《Architectural Styles and the Design of Network-based Software Architectures》中提及。REST架構由客戶端/伺服端組成,兩者間通訊機制是無狀態的(Stateless);客戶端對伺服端請求資源,伺服端回應為資源的表徵(Representation),或稱為表現方式,也就是說,資源在REST中是可定址的(Addressed)概念,為獨一無二的識別名稱(Nouns),可能用檔案、文件、格式等內容型態(Content type)來表現,代表資源目前或可能的狀態(State)。客戶端獲取的表徵狀態,可能包括下次狀態轉移的連結,請求動詞(Verbs)必須能表現出如何處理請求。

「名稱」、「動詞」、「內容型態」組成許多人討論REST時會提到REST Triangle,而這些概念,與HTTP規範不謀而合。URI就是處於名稱角色用以定義資源,HTTP具有GETPOSTPUTDELETE等具語義之動詞,並可以使用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下不存在資源時,也會新建請求中附上的資源。等冪性是在選用POSTPUT時考量的要素之一,另一個重要的考量要性,在HTTP/1.1中也有規範,也就是請求時指定的URI之作用。POST中請求的URI,是要求其背後資源必須處理附加的實體,而不是代表處理後實體的URI;然而PUT時請求的URI,就代表請求中附加實體的URI,無論是更新或是新增實體。

重新認識的必要性

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

重新認識的必要性之一則是安全考量,例如若GET方法使用時,違反了HTTP/1.1中規範的安全方法特性,就容易遭受跨站偽站請求(Cross-Site Request Forgery, CSRF)之類的攻擊;實際上POST方法也不是安全方法,根據實際需求考慮安全、等冪以及HTTP/1.1規範中各方法的特性,並採取對應的安全措施,才是比道聽塗說而來的錯誤印象更實際的作法。近年來,由於智慧行動裝置迅速普及,越來越多的Web網站必須提供了API服務,而不再只是產生瀏覽器顯示用的Web網頁,在設計API為主的Web服務時,對於HTTP請求方法的正確認識也就更形重要。