Rest
RES(Representational state transfer):表現層狀態轉移。其實它省略了主語,「表現層」其實指的是「資源」的「表現層」,所以通俗來講就是:資源在網絡中以某種表現形式進行狀態轉移
分解開來:
- Resource:資源,即數據。比如newsfeed,friends,order等;
- Representational:某種表現形式,比如用JSON,XML,JPEG等;
- State Transfer:狀態變化。通過HTTP動詞實現
總結起來就是將數據(資源)轉換成多種格式用以使用,這樣的話就可以針對不同的需求提供不同的數據(資源)格式?非常方便
注意:REST是一種架構設計風格而不是標准,如果一個架構符合REST原則,我們就稱它為Restful架構,所謂架構風格就是凌駕於架構之上的一組約束
Restful
下面我們來理解一個具體的Restful架構——面向資源的架構(Resource-Oriented Architecture,ROA):
- 資源是由URI來指定。所謂「上網」,就是與互聯網上一系列的「資源」互動,調用它的URI。
- 對資源的操作包括CRUD(獲取、創建、修改和刪除)資源,這些操作正好對應HTTP協議提供的GET、POST、PUT和DELETE方法。
- 通過操作資源的表現形式來操作資源。具體表現形式,應該在HTTP請求的頭信息中用Accept和Content-Type字段指定。
- 資源的表現形式則是XML或者HTML,取決於讀者是機器還是人,是消費web服務的客戶軟件還是web瀏覽器。當然也可以是任何其他的格式。
Rest風格的使用
在設計web接口的時候,REST主要是用於定義接口名,接口名一般是用名詞寫,不用動詞,那怎么表達“獲取”或者“刪除”或者“更新”這樣的操作呢——用請求類型來區分。
比如,我們有一個friends接口,對於“朋友”我們有增刪改查四種操作,怎么定義REST接口?
- 增加一個朋友,uri: generalcode.cn/v1/friends 接口類型:POST
- 刪除一個朋友,uri: generalcode.cn/va/friends 接口類型:DELETE
- 修改一個朋友,uri: generalcode.cn/va/friends 接口類型:PUT
- 查找朋友,uri: generalcode.cn/va/friends 接口類型:GET
上面我們定義的四個接口就是符合REST協議的,請注意,這幾個接口都沒有動詞,只有名詞friends,都是通過Http請求的接口類型來判斷是什么業務操作。
舉個反例:generalcode.cn/va/deleteFriends 該接口用來表示刪除朋友,這就是不符合REST協議的接口。
一般接口的返回值是JSON或者XML類型的,應該JSON類型偏多
但是,REST開發將對資源的操作不僅僅局限於CRUD(創建、獲取、修改、刪除)的語義(即,將對資源的CRUD操作映射到 GET/POST/PUT/DELETE四個HTTP方法)。如果僅僅局限於這里其實收窄了REST的適用范圍。甚至還有很多僅僅使用了HTTP,而沒有使用SOAP的Web服務API,都自稱是REST風格(RESTful)的API,應該不太正確吧。。?
So,什么才是真正的REST風格,REST的創造者Fielding曾發博:REST APIs must be hypertext-driven!(REST API必須是超文本驅動的!),超文本驅動這個理念變成了一個縮寫詞HATEOAS,這個縮寫詞來自於當初Fielding博士論文中的一句話: hypermedia as the engine of application state(將超媒體作為應用狀態的引擎),其實超文本驅動(Hypertext Driven)的理念才是REST架構風格最核心的理念,也是REST風格的架構達到松耦合目標的根本原因。
Restful的四個級別
下面我們用一個不是那么規范的模型來理解restful各個級別:
Level 0 - 面向前台
我們在咖啡店向前台點了一杯拿鐵,這個過程可以用這段文字來描述
{ "addOrder": { "orderName": "latte" } }
我們通過這段文字,告訴前台,新增一筆訂單,訂單是一杯拿鐵咖啡,接着,前台給我們返回這么一串回復:
{ "orderId": "123456" }
假設我們有一張會員卡,我們想查詢一下這張會員卡的余額,這時候,要向前台發起另一個詢問:
{ "queryBalance": { "cardId": "4470313335" } }
查詢卡號為886333的卡的余額,查詢的結果返回來了:
{ "balance": "0" }
沒錢……
哈哈,沒錢,現在我們要跟前台說,這杯咖啡不要了:
{ "deleteOrder": { "orderId": "123456" } }
Level1面向資源
現在這家咖啡店越做越大,來喝咖啡的人越來越多,單靠前台顯然是不行的,店主決定進行分工,每個資源都有專人負責,我們可以直接面向資源操作。
比如還是下單,請求的內容不變,但是我們多了一條消息:
/orders { "addOrder": { "orderName": "latte" } }
多了一個斜杠和orders,這是什么意思?
這個表示我們這個請求是發給哪個資源的,訂單是一種資源,我們可以理解為是咖啡廳專門管理訂單的人,他可以幫我們處理所有有關訂單的操作,包括新增訂單、修改訂單、取消訂單等操作。
接着還是會返回訂單的編號給我們:
{ "orderId": "123456" }
下面,我們還是要查詢會員卡余額,這次請求的資源變成了cards:
/cards { "queryBalance": { "cardId": "447031335" } }
接下來是取消訂單:
/orders { "deleteOrder": { "orderId": "123456" } }
Level2 - 打上標簽
接下來,店主還想繼續優化他的咖啡廳的服務流程,他發現負責處理訂單的員工,每次都要去訂單內容里面看是新增訂單還是刪除訂單,還是其他的什么操作,十分不方便,於是規定,所有新增資源的請求,都在請求上面寫上大大的‘POST’,表示這是一筆新增資源的請求。
其他種類的請求,比如查詢類的,用‘GET’表示,刪除類的,用‘DELETE’表示,修改用PATCH表示。
來,我們再來重復上面那個過程,來一杯拿鐵:
POST /orders { "orderName": "latte" }
請求的內容簡潔多啦,不用告訴店員是addOrder,看到POST就知道是新增,返回的內容還是一樣:
{ "orderId": "123456" }
接着是查詢會員卡余額,這次也簡化了很多:
GET /cards { "cardId": "447031335" }
這個請求我們還可以進一步優化為這樣:
GET /cards/447031335
直接把要查詢的卡號寫在后面了。
Level 3 - 完美服務
忽然有一天,有個顧客抱怨說,他買了咖啡后,不知道要怎么取消訂單,咖啡廳一個店員回了一句,你不會看我們的宣傳單嗎,上面不寫着
DELETE /orders/{orderId}
顧客反問道,誰會去看那個啊,店員不服,又說到,你瞎了啊你……后面兩人吵着吵着還打了起來…
噗,真是悲劇…
有了這次教訓,店長決定,顧客下了單之后,不僅給他們返回訂單的編號,還給顧客返回所有可以對這個訂單做的操作,比如告訴用戶如何刪除訂單。現在,我們還是發出請求,請求內容和上一次一樣:
POST /orders { "orderName": "latte" }
但是這次返回時多了些內容:
{ "orderId": "123456", "link": { "rel": "cancel", "url": "/order/123456" } }
這次返回時多了一項link信息,里面包含了一個rel屬性和url屬性,rel是relationship的意思,這里的關系是cancel,url則告訴你如何執行這個cancel操作,接着你就可以這樣子來取消訂單啦:
DELETE /orders/123456
哈哈,這服務真是貼心,以后再也不用擔心店員和顧客打起來了。
Level3的Restful API,給使用者帶來了很大的便利,使用者只需要知道如何獲取資源的入口,之后的每個URI都可以通過請求獲得,無法獲得就說明無法執行那個請求。
看了易懂的例子,下面我們回到原理:
第一級:在架構中引入資源(Resource)的概念。
大多數WS-*服務和POX都只是使用一個URI作為一個服務端口,也只使用一個HTTP方法傳輸數據。這種做法相當於把HTTP這個應用層協議降級為傳輸層協議用,《REST實戰》也一再強調HTTP是一種應用協議而不是傳輸協議。再好一點就是使用多個URI,然而不同的URI只是作為不同的調用入口,與此同時只使用同一個HTTP方法傳輸數據。最常見的錯誤就是在URI中包含動詞,比如URI http://example.com/getOrder?orderId=1234
,其實「資源」表示一種實體,所以應該是名詞,動詞應該放在HTTP協議中。而與此同時URI也有可能破壞HTTP GET的安全性和幕等性,比如某個客戶端在http://example.com/updateOrder?id=1234&coffee=latte
上執行GET(而不是POST),就能創建一筆新的咖啡訂單(一個資源),按理來說GET請求不能改變服務的任何狀態。
第二級:每一個URI代表一種資源,支持HTTP動詞。
此時使用多個URI的話,需要讓不同的URI代表不同的資源(注意多個URI可能指向同一個Resource,而一個URI不能指向不同Resource。),同時使用多個HTTP方法操作這些資源,例如使用POST/GET/PUT/DELET分別進行CRUD操作。這時候HTTP頭和有效載荷都包含業務邏輯,例如HTTP方法對應CRUD操作,HTTP狀態碼對應操作結果的狀態。我們現在看到的大多數所謂RESTful API做到的也就是這個級別。《REST實戰》的譯者也談到:悟性差的人,理解到CRUD式Web服務就滿足了。而悟性好的人,可以徹底理解超文本驅動,甚至是與REST關系密切的語義網,最終達到 REST開發的最高境界。
第三級:HATEOAS,使用超媒體(hypermedia)作為應用狀態引擎。
根據Roy的嚴格規定,超媒體(hypermedia)是REST的先決條件。任何其他東西不應該自我標榜為REST。要解釋HATEOAS這個概念先要解釋什么是超媒體:我們已經知道什么是多媒體(multimedia),以及什么是超文本(hypertext)。其中超文本特有的優勢是擁有超鏈接(hyperlink)。如果我們把超鏈接引入到多媒體當中去,那就得到了超媒體,因此關鍵角色還是超鏈接。使用超媒體作為應用引擎狀態,意思是應用引擎的狀態變更由客戶端訪問不同的超媒體資源驅動。
讓我們來看個實例,這個響應內容可能略有不同:
GET https://api.example.com/profile { "name": "Steve", "picture": { "large": "https://somecdn.com/pictures/1200x1200.png", "medium": "https://somecdn.com/pictures/100x100.png", "small": "https://somecdn.com/pictures/10x10.png" } }
由於在響應中包含了鏈接地址,因此使用該API的客戶端就能夠自由選擇要下載怎樣的信息。這些鏈接告知了客戶端有哪些選擇,並且它們的地址在哪里。因此在這里我們無需同時返回三個不同版本的用戶檔案圖片,我們所做的只是告訴客戶端有三種可用的圖片尺寸可以選擇,並且告訴客戶端能夠在哪里找到這些圖片。這樣一來,客戶端就能夠根據不同的場景,做出符合自身需要的選擇。而且,如果客戶端只需要一種格式的圖片,那就無需下載全部三種版本的圖片了。這樣一來可謂一箭三雕:既減少了網絡負載,又增進了客戶端的靈活性,更增進了API的可探索性。
超媒體的核心概念就是所謂的<link>
元素,而這些相互鏈接的資源實際上描述了一個協議,即引導我們達成某個目標的一系列步驟,例如訂購一杯咖啡所需要的點單、付款、取咖啡等等。這就是超媒體的本質:經由資源之間的鏈接,我們改變整個應用的狀態,即超媒體轉換了分布式應用的狀態。需要注意的是,服務器和消費者兩者間交換的是資源狀態的表述,而不是應用的狀態,被轉移的表述中包括了反應應用狀態的鏈接。