前言
HTTP Method的歷史:
- HTTP 0.9 這個版本只有
GET
方法 - HTTP 1.0 這個版本有
GET
HEAD
POST
這三個方法 - HTTP 1.1 這個版本是當前版本,包含
GET
HEAD
POST
OPTIONS
PUT
DELETE
TRACE
CONNECT
這8個方法
我們先看看HTTP 1.1 規(guī)范的中文翻譯
方法定義(Method Definitions)
HTTP/1.1常用方法的定義如下。雖然方法可以被展開,但新加的方法不能認為能分享與擴展的客戶端和服務(wù)器同樣的語義。
Hst請求頭域(見13.23節(jié))必須能在所有的HTTP/1.1請求里出現(xiàn)。
9.1 安全和等冪(Idempotent)方法
9.1.1安全方法(Safe Methods)
實現(xiàn)者應(yīng)當知道軟件是代表用戶在互聯(lián)網(wǎng)上進行交互,并且應(yīng)該小心地允許用戶知道任何它們可能采取的動作(action),這些動作可能給他們自己或他人帶來無法預(yù)料的結(jié)果。
特別的,GET和HEAD方法僅僅應(yīng)該獲取資源而不是執(zhí)行動作(action)。這些方法應(yīng)該被考慮是“安全”的。可以讓用戶代理用其他的方法,如:POST,PUT,DELETE,這樣用戶代理就能知道這些方法可能會執(zhí)行不安全的動作。
自然的,保證當服務(wù)器由于執(zhí)行GET請求而不能產(chǎn)生副作用是不可能的;實際上,一些動態(tài)的資源會考慮這個特性。用戶并沒有請求這些副作用,因此不需要對這些副作用負責。
9.1.2等冪方法(Idempotent Mehtods)
方法可以有等冪的性質(zhì)因為(除了出錯或終止問題)N>0個相同請求的副作用同單個請求的副作用的效果是一樣(譯注:等冪就是值不變性,相同的請求得到相同的響應(yīng)結(jié)果,不會出現(xiàn)相同的請求出現(xiàn)不同的響應(yīng)結(jié)果)。方法GET,HEAD,PUT,DELETE都有這種性質(zhì)。同樣,方法OPTIONS和TRACE不應(yīng)該有副作用,因此具有內(nèi)在的等冪性。然而,有可能幾個請求的序列是不等冪的,即使在那樣的序列中所有方法都是等冪的。(如果整個序列整體的執(zhí)行的結(jié)果總是相同的,并且此結(jié)果不會因為序列的整體,部分的再次執(zhí)行而改變,那么此序列是等冪的。)例如,一個序列是非等冪的如果它的結(jié)果依賴于一個值,此值在以后相同的序列里會改變。
根據(jù)定義,一個序列如果沒有副作用,那么此序列是等冪的(假設(shè)在資源集上沒有并行的操作)。
9.2 OPTIONS(選項)
OPTIONS方法表明請求想得到請求/響應(yīng)鏈上關(guān)于此請求里的URI(Request-URI)指定資源的通信選項信息。此方法允許客戶端去判定請求資源的選項和/或需求,或者服務(wù)器的能力,而不需要利用一個資源動作(譯注:使用POST,PUT,DELETE方法)或一個資源獲取(譯注:用GET方法)方法。
這種方法的響應(yīng)是不能緩存的.。
如果OPTIONS請求消息里包括一個實體主體(當請求消息里出現(xiàn)Content-Length或者Transfer-Encoding頭域時),那么媒體類型必須通過Content-Type頭域指明。雖然此規(guī)范沒有定義如何使用此實體主體,將來的HTTP擴展可能會利用OPTIONS請求的消息主體去得到服務(wù)器得更多信息。一個服務(wù)器如果不支持OPTION請求的消息主體,它會遺棄此請求消息主體。
如果請求URI是一個星號(''),,OPTIONS請求將會應(yīng)用于服務(wù)器的所有資源而不是特定資源。因為服務(wù)器的通信選項通常依賴于資源,所以””請求只能在“ping”或者“no-op”方法時才有用;它干不了任何事情除了允許客戶端測試服務(wù)器的能力。例如:它能被用來測試代理是否遵循HTTP/1.1。
如果請求URI不是一個星號('*'),,OPTIONS請求只能應(yīng)用于請求URI指定資源的選項。
200響應(yīng)應(yīng)該包含任何指明選項性質(zhì)的頭域,這些選項性質(zhì)由服務(wù)器實現(xiàn)并且只適合那個請求的資源(例如,Allow頭域),但也可能包一些擴展的在此規(guī)范里沒有定義的頭域。如果有響應(yīng)主體的話也應(yīng)該包含一些通信選項的信息。這個響應(yīng)主體的格式并沒有在此規(guī)范里定義,但是可能會在以后的HTTP里定義。內(nèi)容協(xié)商可能被用于選擇合適的響應(yīng)格式。如果沒有響應(yīng)主體包含,響應(yīng)就應(yīng)該包含一個值為“0”的Content-Length頭域。
Max-Forwards請求頭域可能會被用于針對請求鏈中特定的代理。當代理接收到一個OPTIONS請求,且此請求的URI為absoluteURI,并且此請求是可以被轉(zhuǎn)發(fā)的,那么代理必須要檢測Max-Forwards頭域。如果Max-Forwards頭域的值為“0”,那么此代理不能轉(zhuǎn)發(fā)此消息;而是,代理應(yīng)該以它自己的通信選項響應(yīng)。如果Max-Forwards頭域是比0大的整數(shù)值,那么代理必須遞減此值當它轉(zhuǎn)發(fā)此請求時。如果沒有Max-Forwards頭域出現(xiàn)在請求里,那么代理轉(zhuǎn)發(fā)此請求時不能包含Max-Forwards頭域。
9.3 GET
GET方法意思是獲取被請求URI(Request-URI)指定的信息(以實體的格式)。如果請求URI涉及到一個數(shù)據(jù)生成過程,那么這個生成的數(shù)據(jù)應(yīng)該被作為實體在響應(yīng)中返回,但這并不是過程的資源文本,除非資源文本恰好是過程的輸出(譯注:URI指示的資源是動態(tài)生成的)。
如果請求消息包含 If-Modified-Since,,If-Unmodified-Since,If-Match,,If-None-Match,或者 If-Range頭域,,GET的語義將變成“條件(conditionall) GET”。一個條件GET方法會請求滿足條件頭域的實體。條件GET方法的目的是為了減少不必要的網(wǎng)絡(luò)使用,這通過利用緩存的實體的更新,從而不用多次請求或傳輸客戶已經(jīng)擁有的數(shù)據(jù)。.
如果請求方法包含一個Range頭域,那么GET方法就變成“部分Get”方法。一個部分GET會請求實體的一部分,這在14.35節(jié)里描述了。 部分GET方法的目的是為了減少不必要的網(wǎng)絡(luò)使用,這通過允許獲取部分實體,從而不需要傳輸客戶端已經(jīng)擁有的數(shù)據(jù)。
GET請求的響應(yīng)是可緩存的(cacheable)如果此響應(yīng)滿足第13節(jié)HTTP緩存的要求。
看15.1.3節(jié)關(guān)于GET請求用于表單時安全考慮。
9.4 HEAD
HEAD方法和GET方法一致,除了服務(wù)器不能在響應(yīng)里返回消息主體。HEAD請求響應(yīng)里HTTP頭域里的元信息應(yīng)該和GET請求響應(yīng)里的元信息一致。此方法被用來獲取請求實體的元信息而不需要傳輸實體主體(entity-body)。此方法經(jīng)常被用來測試超文本鏈接的有效性,可訪問性,和最近的改變。.
HEAD請求的響應(yīng)是可緩存的,因為響應(yīng)里的信息可能被用于更新以前的那個資源的緩存實體.。如果出現(xiàn)一個新的域值指明了緩存實體和當前源服務(wù)器上實體的不同(可能因為Content-Length,Content-MD5,ETag或Last-Modified值的改變),那么緩存(cache)必須認為此緩存項是過時的(stale)。
9.5 POST
POST 方法被用于請求源服務(wù)器接受請求中的實體作為請求資源的一個新的從屬物。POST被設(shè)計涵蓋下面的功能。
-已存在的資源的注釋;
-發(fā)布消息給一個布告板,新聞組,郵件列表,或者相似的文章組。
-提供一個數(shù)據(jù)塊,如提交一個表單給一個數(shù)據(jù)處理過程。
-通過追加操作來擴展數(shù)據(jù)庫。
POST方法的實際功能是由服務(wù)器決定的,并且經(jīng)常依賴于請求URI(Request-URI)。POST提交的實體是請求URI的從屬物,就好像一個文件從屬于一個目錄,一篇新聞文章從屬于一個新聞組,或者一條記錄從屬于一個數(shù)據(jù)庫。
POST方法執(zhí)行的動作可能不會對請求URI所指的資源起作用。在這種情況下,200(成功)或者204(沒有內(nèi)容)將是適合的響應(yīng)狀態(tài),這依賴于響應(yīng)是否包含一個描述結(jié)果的實體。
如果資源被源服務(wù)器創(chuàng)建,響應(yīng)應(yīng)該是201(Created)并且包含一個實體,此實體描述了請求的狀態(tài)并且此實體引用了一個新資源和一個Location頭域(見14.30節(jié))。
POST方法的響應(yīng)是可緩存的。除非響應(yīng)里有Cache-Control或者Expires頭域指示其響應(yīng)不可緩存。然而,303(見其他)響應(yīng)能被利用去指導用戶代理(agent)去獲得可緩存的響應(yīng)。
POST 請求必須遵循8.2節(jié)里指明的消息傳輸需求。
參見15.1.3節(jié)關(guān)于安全性的考慮.
9.6 PUT
PUT方法請求服務(wù)器去把請求里的實體存儲在請求URI(Request-URI)標識下。如果請求URI(Request-URI)指定的的資源已經(jīng)在源服務(wù)器上存在,那么此請求里的實體應(yīng)該被當作是源服務(wù)器此URI所指定資源實體的修改版本。如果請求URI(Request-URI)指定的資源不存在,并且此URI被用戶代理(user agent,譯注:用戶代理可認為是客戶瀏覽器)定義為一個新資源,那么源服務(wù)器就應(yīng)該根據(jù)請求里的實體創(chuàng)建一個此URI所標識下的資源。如果一個新的資源被創(chuàng)建了,源服務(wù)器必須能向用戶代理(user agent) 發(fā)送201(已創(chuàng)建)響應(yīng)。如果已存在的資源被改變了,那么源服務(wù)器應(yīng)該發(fā)送200(Ok)或者204(無內(nèi)容)響應(yīng)。如果資源不能根據(jù)請求URI創(chuàng)建或者改變,一個合適的錯誤響應(yīng)應(yīng)該給出以反應(yīng)問題的性質(zhì)。實體的接收者不能忽略任何它不理解的Content-*(如:Content-Range)頭域,并且必須返回501(沒有被實現(xiàn))響應(yīng)。
如果請求穿過一個緩存(cache),并且此請求URI(Request-URI)指示了一個或多個當前緩存的實體,那么這些實體應(yīng)該被看作是舊的。PUT方法的響應(yīng)不應(yīng)該被緩存。
POST方法和PUT方法請求最根本的區(qū)別是請求URI(Request-URI)的含義不同。POST請求里的URI指示一個能處理請求實體的資源(譯注:此資源可能是一段程序,如jsp里的servlet) 。此資源可能是一個數(shù)據(jù)接收過程,一個網(wǎng)關(guān)(gateway,譯注:網(wǎng)關(guān)和代理服務(wù)器的區(qū)別是:網(wǎng)關(guān)可以進行協(xié)議轉(zhuǎn)換,而代理服務(wù)器不能,只是起代理的作用,比如緩存服務(wù)器其實就是一個代理服務(wù)器),或者一個單獨接收注釋的實體。而PUT方法請求里有一個實體一一用戶代理知道URI意指什么,并且服務(wù)器不能把此請求應(yīng)用于其他URI指定的資源。如果服務(wù)器期望請求被應(yīng)用于一個不同的URI,那么它必須發(fā)送301(永久移動了)響應(yīng);用戶代理可以自己決定是否重定向請求。
一個獨立的資源可能會被許多不同的URI指定。如:一篇文章可能會有一個URI指定當前版本,此URI區(qū)別于其文章其他特殊版本的URI。這種情況下,一個通用URI的PUT請求可能會導致其資源的其他URI被源服務(wù)器定義。
HTTP/1.1沒有定義PUT方法對源服務(wù)器的狀態(tài)影響。
PUT請求必須遵循8.2節(jié)中的消息傳輸要求。
除非特別指出,PUT方法請求里的實體頭域應(yīng)該被用于資源的創(chuàng)建或修改。
9.7 DELETE(刪除)
DELETE方法請求源服務(wù)器刪除請求URI指定的資源。此方法可能會在源服務(wù)器上被人為的干涉(或其他方法)。客戶端不能保證此操作能被執(zhí)行,即使源服務(wù)器返回成功狀態(tài)碼。然而,服務(wù)器不應(yīng)該指明成功除非它打算刪除資源或把此資源移到一個不可訪問的位置。
如果響應(yīng)里包含描述成功的實體,響應(yīng)應(yīng)該是200(Ok);如果DELETE動作沒有通過,應(yīng)該以202(已接受)響應(yīng);如果DELETE方法請求已經(jīng)通過了,但響應(yīng)不包含實體,那么應(yīng)該以204(無內(nèi)容)響應(yīng)。
如果請求穿過緩存,并且請求URI(Request-URI)指定一個或多個緩存當前實體,那么這些緩存項應(yīng)該被認為是舊的。DELETE方法的響應(yīng)是不能被緩存的。
9.8 TRACE
TRACE方法被用于激發(fā)一個遠程的,應(yīng)用層的請求消息回路(譯注:TRACE方法讓客戶端測試到服務(wù)器的網(wǎng)絡(luò)通路,回路的意思如發(fā)送一個請返回一個響應(yīng),這就是一個請求響應(yīng)回路,)。最后的接收者或者是接收請求里Max-Forwards頭域值為0源服務(wù)器或者是代理服務(wù)器或者是網(wǎng)關(guān)。TRACE請求不能包含一個實體。
TRACE方法允許客戶端知道請求鏈的另一端接收什么,并且利用那些數(shù)據(jù)去測試或診斷。Via頭域值(見14.45)有特殊的用途,因為它可以作為請求鏈的跟蹤信息。利用Max-Forwards頭域允許客戶端限制請求鏈的長度去測試一串代理服務(wù)器是否在無限回路里轉(zhuǎn)發(fā)消息。
如果請求是有效的,響應(yīng)應(yīng)該在響應(yīng)實體主體里包含整個請求消息,并且響應(yīng)應(yīng)該包含一個Content-Type頭域值為”message/http”的頭域。TRACE方法的響應(yīng)不能不緩存。
9.9 CONNECT(連接)
HTTP1.1協(xié)議規(guī)范保留了CONNECT方法,此方法是為了能用于能動態(tài)切換到隧道的代理服務(wù)器(proxy,譯注:可以為代理,也可以是代理服務(wù)器)。
上邊的內(nèi)容對HTTP Method 說的已經(jīng)很詳細了,但冪等
這個概念可能不太容易理解。下邊我們就著重介紹下:
在HTTP/1.1規(guī)范中冪等性的定義是:
Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.
從定義上看,HTTP方法的冪等性是指一次和多次請求某一個資源應(yīng)該具有同樣的副作用。冪等性屬于語義范疇,正如編譯器只能幫助檢查語法錯誤一樣,HTTP規(guī)范也沒有辦法通過消息格式等語法手段來定義它,這可能是它不太受到重視的原因之一。但實際上,冪等性是分布式系統(tǒng)設(shè)計中十分重要的概念,而HTTP的分布式本質(zhì)也決定了它在HTTP中具有重要地位。
為什么需要冪等性呢?我們先從一個例子說起,假設(shè)有一個從賬戶取錢的遠程API(可以是HTTP的,也可以不是),我們暫時用類函數(shù)的方式記為:
bool withdraw(account_id, amount)
withdraw的語義是從account_id對應(yīng)的賬戶中扣除amount數(shù)額的錢;如果扣除成功則返回true,賬戶余額減少amount;如果扣除失敗則返回false,賬戶余額不變。值得注意的是:和本地環(huán)境相比,我們不能輕易假設(shè)分布式環(huán)境的可靠性。一種典型的情況是withdraw請求已經(jīng)被服務(wù)器端正確處理,但服務(wù)器端的返回結(jié)果由于網(wǎng)絡(luò)等原因被掉丟了,導致客戶端無法得知處理結(jié)果。如果是在網(wǎng)頁上,一些不恰當?shù)脑O(shè)計可能會使用戶認為上一次操作失敗了,然后刷新頁面,這就導致了withdraw被調(diào)用兩次,賬戶也被多扣了一次錢。如圖1所示:
<center>
</center>
這個問題的解決方案一是采用分布式事務(wù),通過引入支持分布式事務(wù)的中間件來保證withdraw功能的事務(wù)性。分布式事務(wù)的優(yōu)點是對于調(diào)用者很簡單,復雜性都交給了中間件來管理。缺點則是一方面架構(gòu)太重量級,容易被綁在特定的中間件上,不利于異構(gòu)系統(tǒng)的集成;另一方面分布式事務(wù)雖然能保證事務(wù)的ACID性質(zhì),而但卻無法提供性能和可用性的保證。
另一種更輕量級的解決方案是冪等設(shè)計。我們可以通過一些技巧把withdraw變成冪等的,比如:
int create_ticket()
bool idempotent_withdraw(ticket_id, account_id, amount)
create_ticket的語義是獲取一個服務(wù)器端生成的唯一的處理號ticket_id,它將用于標識后續(xù)的操作。idempotent_withdraw和withdraw的區(qū)別在于關(guān)聯(lián)了一個ticket_id,一個ticket_id表示的操作至多只會被處理一次,每次調(diào)用都將返回第一次調(diào)用時的處理結(jié)果。這樣,idempotent_withdraw就符合冪等性了,客戶端就可以放心地多次調(diào)用。
基于冪等性的解決方案中一個完整的取錢流程被分解成了兩個步驟:1.調(diào)用create_ticket()獲取ticket_id;2.調(diào)用idempotent_withdraw(ticket_id, account_id, amount)。雖然create_ticket不是冪等的,但在這種設(shè)計下,它對系統(tǒng)狀態(tài)的影響可以忽略,加上idempotent_withdraw是冪等的,所以任何一步由于網(wǎng)絡(luò)等原因失敗或超時,客戶端都可以重試,直到獲得結(jié)果。如圖2所示:
<center>
</center>
和分布式事務(wù)相比,冪等設(shè)計的優(yōu)勢在于它的輕量級,容易適應(yīng)異構(gòu)環(huán)境,以及性能和可用性方面。在某些性能要求比較高的應(yīng)用,冪等設(shè)計往往是唯一的選擇。
HTTP的冪等性
HTTP協(xié)議本身是一種面向資源的應(yīng)用層協(xié)議,但對HTTP協(xié)議的使用實際上存在著兩種不同的方式:一種是RESTful的,它把HTTP當成應(yīng)用層協(xié)議,比較忠實地遵守了HTTP協(xié)議的各種規(guī)定;另一種是SOA的,它并沒有完全把HTTP當成應(yīng)用層協(xié)議,而是把HTTP協(xié)議作為了傳輸層協(xié)議,然后在HTTP之上建立了自己的應(yīng)用層協(xié)議。本文所討論的HTTP冪等性主要針對RESTful風格的,不過正如上一節(jié)所看到的那樣,冪等性并不屬于特定的協(xié)議,它是分布式系統(tǒng)的一種特性;所以,不論是SOA還是RESTful的Web API設(shè)計都應(yīng)該考慮冪等性。下面將介紹HTTP GET、DELETE、PUT、POST四種主要方法的語義和冪等性。
HTTP GET方法用于獲取資源,不應(yīng)有副作用,所以是冪等的。比如:GET http://www.bank.com/account/123456,不會改變資源的狀態(tài),不論調(diào)用一次還是N次都沒有副作用。請注意,這里強調(diào)的是一次和N次具有相同的副作用,而不是每次GET的結(jié)果相同。GET http://www.news.com/latest-news這個HTTP請求可能會每次得到不同的結(jié)果,但它本身并沒有產(chǎn)生任何副作用,因而是滿足冪等性的。
HTTP DELETE方法用于刪除資源,有副作用,但它應(yīng)該滿足冪等性。比如:DELETE http://www.forum.com/article/4231,調(diào)用一次和N次對系統(tǒng)產(chǎn)生的副作用是相同的,即刪掉id為4231的帖子;因此,調(diào)用者可以多次調(diào)用或刷新頁面而不必擔心引起錯誤。
比較容易混淆的是HTTP POST和PUT。POST和PUT的區(qū)別容易被簡單地誤認為“POST表示創(chuàng)建資源,PUT表示更新資源”;而實際上,二者均可用于創(chuàng)建資源,更為本質(zhì)的差別是在冪等性方面。在HTTP規(guī)范中對POST和PUT是這樣定義的:
The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line ...... If a resource has been created on the origin server, the response SHOULD be 201 (Created) and contain an entity which describes the status of the request and refers to the new resource, and a Location header.
The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI.
POST所對應(yīng)的URI并非創(chuàng)建的資源本身,而是資源的接收者。比如:POST http://www.forum.com/articles的語義是在http://www.forum.com/articles下創(chuàng)建一篇帖子,HTTP響應(yīng)中應(yīng)包含帖子的創(chuàng)建狀態(tài)以及帖子的URI。兩次相同的POST請求會在服務(wù)器端創(chuàng)建兩份資源,它們具有不同的URI;所以,POST方法不具備冪等性。而PUT所對應(yīng)的URI是要創(chuàng)建或更新的資源本身。比如:PUT http://www.forum/articles/4231的語義是創(chuàng)建或更新ID為4231的帖子。對同一URI進行多次PUT的副作用和一次PUT是相同的;因此,PUT方法具有冪等性。
在介紹了幾種操作的語義和冪等性之后,我們來看看如何通過Web API的形式實現(xiàn)前面所提到的取款功能。很簡單,用POST /tickets來實現(xiàn)create_ticket;用PUT /accounts/account_id/ticket_id&amount=xxx來實現(xiàn)idempotent_withdraw。值得注意的是嚴格來講amount參數(shù)不應(yīng)該作為URI的一部分,真正的URI應(yīng)該是/accounts/account_id/ticket_id,而amount應(yīng)該放在請求的body中。這種模式可以應(yīng)用于很多場合,比如:論壇網(wǎng)站中防止意外的重復發(fā)帖。
上面簡單介紹了冪等性的概念,用冪等設(shè)計取代分布式事務(wù)的方法,以及HTTP主要方法的語義和冪等性特征。其實,如果要追根溯源,冪等性是數(shù)學中的一個概念,表達的是N次變換與1次變換的結(jié)果相同,有興趣的讀者可以從Wikipedia上進一步了解。