圖 2:0 級(jí)交互示例
假設(shè)我想預(yù)約醫(yī)生。我的預(yù)約軟件首先需要知道醫(yī)生在特定日期有哪些空閑時(shí)段,因此它會(huì)向醫(yī)院預(yù)約系統(tǒng)發(fā)出請(qǐng)求以獲取該信息。在 0 級(jí)場(chǎng)景中,醫(yī)院將在某個(gè) URI 上公開服務(wù)端點(diǎn)。然后,我將包含我的請(qǐng)求詳細(xì)信息的文檔發(fā)布到該端點(diǎn)。POST /appointmentService HTTP/1.1 [各種其他標(biāo)頭]
然后服務(wù)器將返回一個(gè)文檔,向我提供以下信息HTTP/1.1 200 OK [各種標(biāo)頭]
我在這里使用 XML 作為示例,但內(nèi)容實(shí)際上可以是任何內(nèi)容:JSON、YAML、鍵值對(duì)或任何自定義格式。
我的下一步是預(yù)約,我可以通過將文檔發(fā)布到端點(diǎn)來再次進(jìn)行預(yù)約。POST /appointmentService HTTP/1.1 [各種其他標(biāo)頭]
如果一切順利的話,我會(huì)收到回復(fù),告知我預(yù)約已經(jīng)完成。HTTP/1.1 200 OK [各種標(biāo)頭]
如果出現(xiàn)問題,比如說其他人在我之前進(jìn)入,那么我就會(huì)在回復(fù)正文中收到某種錯(cuò)誤消息。HTTP/1.1 200 OK [各種標(biāo)頭] Slot 不可用
到目前為止,這是一個(gè)簡單的 RPC 樣式系統(tǒng)。它很簡單,因?yàn)樗皇莵砘貍鬟f普通的舊 XML (POX)。如果您使用 SOAP 或 XML-RPC,它基本上是相同的機(jī)制,唯一的區(qū)別是您將 XML 消息包裝在某種信封中。
在 RESTful成熟度模型 中實(shí)現(xiàn) Rest 榮耀的第一步是引入資源。因此,現(xiàn)在我們不再將所有請(qǐng)求發(fā)送到單個(gè)服務(wù)端點(diǎn),而是開始與各個(gè)資源對(duì)話。
圖 3:第 1 級(jí)添加資源
因此,通過我們的初步查詢,我們可能會(huì)獲得特定醫(yī)生的資源。POST /doctors/mjones HTTP/1.1 [各種其他標(biāo)頭]
回復(fù)帶有相同的基本信息,但是每個(gè)插槽現(xiàn)在都是可以單獨(dú)尋址的資源。HTTP/1.1 200 OK [各種標(biāo)頭]
通過特定資源預(yù)約意味著發(fā)布到特定的時(shí)間段。POST /slots/1234 HTTP/1.1 [各種其他標(biāo)頭]
如果一切順利的話,我會(huì)收到與之前類似的答復(fù)。HTTP/1.1 200 OK [各種標(biāo)頭]
現(xiàn)在的區(qū)別是,如果有人需要對(duì)預(yù)約做任何事情,比如預(yù)約一些測(cè)試,他們首先獲取預(yù)約資源(可能具有類似的 URI), http://royalhope.nhs.uk/slots/1234/appointment
然后發(fā)布到該資源。
對(duì)于像我這樣的對(duì)象迷來說,這就像對(duì)象身份的概念。我們不是調(diào)用以太中的某個(gè)函數(shù)并傳遞參數(shù),而是調(diào)用一個(gè)特定對(duì)象上的方法,為其他信息提供參數(shù)。
我在級(jí)別 0 和 1 中的所有交互中都使用了 HTTP POST 動(dòng)詞,但有些人會(huì)改用或同時(shí)使用 GET。在這些級(jí)別上,這并沒有太大區(qū)別,它們都被用作隧道機(jī)制,允許您通過 HTTP 隧道進(jìn)行交互。RESTful成熟度模型 的級(jí)別 2 不再這樣做,而是盡可能接近 HTTP 本身的使用方式來使用 HTTP 動(dòng)詞。
圖 4:第 2 級(jí)添加了 HTTP 動(dòng)詞
對(duì)于插槽列表,這意味著我們要使用 GET。GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1 主機(jī):royalhope.nhs.uk
答復(fù)與 POST 相同HTTP/1.1 200 OK [各種標(biāo)頭]
在第 2 級(jí),對(duì)于此類請(qǐng)求,使用 GET 至關(guān)重要。HTTP 將 GET 定義為安全操作,即它不會(huì)對(duì)任何事物的狀態(tài)做出任何重大更改。這使我們能夠以任意順序安全地調(diào)用任意次數(shù)的 GET,并且每次都獲得相同的結(jié)果。這樣做的一個(gè)重要結(jié)果是,它允許請(qǐng)求路由中的任何參與者使用緩存,這是使 Web 性能良好的關(guān)鍵要素。HTTP 包括各種支持緩存的措施,通信中的所有參與者都可以使用這些措施。通過遵循 HTTP 規(guī)則,我們能夠利用該功能。
要預(yù)約,我們需要一個(gè)可以改變狀態(tài)的 HTTP 動(dòng)詞,即 POST 或 PUT。我將使用之前使用的相同 POST。POST /slots/1234 HTTP/1.1 [各種其他標(biāo)頭]
這里我想討論的是使用 POST 和 PUT 之間的權(quán)衡,也許有一天我會(huì)寫一篇關(guān)于它們的文章。但我想指出的是,有些人錯(cuò)誤地將 POST/PUT 與創(chuàng)建/更新對(duì)應(yīng)起來。它們之間的選擇與此完全不同。
即使我使用與第 1 級(jí)相同的帖子,遠(yuǎn)程服務(wù)的響應(yīng)方式也存在另一個(gè)顯著差異。如果一切順利,服務(wù)將回復(fù)響應(yīng)代碼 201,以表明世界上有新的資源。HTTP/1.1 201 創(chuàng)建 位置:slots/1234/appointment [各種標(biāo)題]
201 響應(yīng)包含一個(gè)帶有 URI 的位置屬性,客戶端可以使用該 URI 在將來獲取該資源的當(dāng)前狀態(tài)。此處的響應(yīng)還包括該資源的表示,以便現(xiàn)在為客戶端節(jié)省額外的調(diào)用。
如果出現(xiàn)問題,例如其他人預(yù)訂了會(huì)議,則還會(huì)有其他區(qū)別。HTTP/1.1 409 沖突 [各種標(biāo)頭]
此響應(yīng)的重要部分是使用 HTTP 響應(yīng)代碼來指示出現(xiàn)問題。在這種情況下,409 似乎是一個(gè)不錯(cuò)的選擇,它表示其他人已經(jīng)以不兼容的方式更新了資源。在級(jí)別 2 中,我們明確使用某種錯(cuò)誤響應(yīng),而不是使用返回代碼 200 但包含錯(cuò)誤響應(yīng)。由協(xié)議設(shè)計(jì)者決定使用什么代碼,但如果出現(xiàn)錯(cuò)誤,應(yīng)該有一個(gè)非 2xx 響應(yīng)。級(jí)別 2 引入了使用 HTTP 動(dòng)詞和 HTTP 響應(yīng)代碼。
這里出現(xiàn)了不一致的情況。REST 的倡導(dǎo)者談?wù)撌褂盟?HTTP 動(dòng)詞。他們還通過說 REST 試圖從 Web 的實(shí)際成功中學(xué)習(xí)來證明他們的方法是正確的。但萬維網(wǎng)在實(shí)踐中并不經(jīng)常使用 PUT 或 DELETE。有合理的理由更多地使用 PUT 和 DELETE,但 Web 的存在證明不是其中之一。
網(wǎng)絡(luò)存在所支持的關(guān)鍵元素是安全(例如 GET)和非安全操作之間的嚴(yán)格分離,以及使用狀態(tài)代碼來幫助傳達(dá)您遇到的錯(cuò)誤類型。
RESTful成熟度模型 的最后一個(gè)層次是HATEOAS(超媒體控制)這個(gè)難聽的縮寫詞。它解決了如何從空位列表到知道如何預(yù)約的問題。
圖 5:第 3 級(jí)添加超媒體控件
我們從在第 2 級(jí)中發(fā)送的相同的初始 GET 開始GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1 主機(jī):royalhope.nhs.uk
但回應(yīng)有新的元素HTTP/1.1 200 OK [各種標(biāo)頭]
每個(gè)時(shí)段現(xiàn)在都有一個(gè)鏈接元素,其中包含一個(gè) URI,用于告訴我們?nèi)绾晤A(yù)約。
超媒體控件的意義在于它們告訴我們下一步可以做什么,以及我們需要操縱的資源的 URI 才能做到這一點(diǎn)。我們不必知道在哪里發(fā)布預(yù)約請(qǐng)求,響應(yīng)中的超媒體控件會(huì)告訴我們?nèi)绾巫觥?/p>
POST 將再次復(fù)制 2 級(jí)POST /slots/1234 HTTP/1.1 [各種其他標(biāo)頭]
答復(fù)包含許多超媒體控件,用于控制接下來要執(zhí)行的不同操作。HTTP/1.1 201 創(chuàng)建 位置:http://royalhope.nhs.uk/slots/1234/appointment [各種標(biāo)題]
超媒體控件的一個(gè)明顯好處是,它允許服務(wù)器更改其 URI 方案而不會(huì)破壞客戶端。只要客戶端查找“addTest”鏈接 URI,服務(wù)器團(tuán)隊(duì)就可以處理除初始入口點(diǎn)之外的所有 URI。
另一個(gè)好處是它可以幫助客戶端開發(fā)人員探索協(xié)議。這些鏈接為客戶端開發(fā)人員提供了下一步可能的操作的提示。它并沒有提供所有的信息:“self”和“cancel”控件都指向同一個(gè) URI – 他們需要弄清楚一個(gè)是 GET,另一個(gè)是 DELETE。但至少它為他們提供了一個(gè)起點(diǎn),讓他們知道要考慮什么才能獲得更多信息,并在協(xié)議文檔中尋找類似的 URI。
同樣,它允許服務(wù)器團(tuán)隊(duì)通過在響應(yīng)中添加新鏈接來宣傳新功能。如果客戶端開發(fā)人員密切關(guān)注未知鏈接,這些鏈接可能會(huì)引發(fā)進(jìn)一步的探索。
對(duì)于如何表示超媒體控件,沒有絕對(duì)的標(biāo)準(zhǔn)。我在這里所做的是使用 REST 實(shí)踐團(tuán)隊(duì)的當(dāng)前建議,即遵循 ATOM(RFC 4287)。我使用一個(gè)
對(duì)元素本身的引用)是裸的,任何特定于該服務(wù)器的關(guān)系都是完全限定的 URI。ATOM 指出,眾所周知的鏈接關(guān)系的定義是鏈接關(guān)系注冊(cè)表。在我寫這些時(shí),這些僅限于 ATOM 所做的事情,ATOM 通常被視為 3 級(jí) restfulness 的領(lǐng)導(dǎo)者。元素,該元素具有一個(gè)
uri 目標(biāo) URI 的屬性和一個(gè)
rel用于描述關(guān)系類型的屬性。眾所周知的關(guān)系(例如
self