重新解析 REST Service(REST Service 的最佳实践,第 1 部分)
from: https://www.ibm.com/developerworks/cn/webservices/1101_mace_restservicePart1/1101_mace_restservicePart1.html
三種流行 web 服務的架構
Web 服務是一種面向服務的架構的技術,通過標準的 Web 協議提供服務,目的是保證不同平臺的應用服務可以互操作。根據 W3C 的定義,Web 服務(Web service)應當是一個軟件系統,用以支持網絡間不同機器的互動操作。網絡服務通常是許多應用程序接口(API)所組成的,它們通過網絡,例如國際互聯網(Internet)的遠程服務器端,執行客戶所提交服務的請求。流行的或者曾經流行的 Web 服務架構有三種:SOAP RPC over HTTP, XML RPC over HTTP, 和 REST over HTTP。下面分別簡要介紹這三種架構。
SOAP?RPC?over HTTP
簡單對象訪問協議(SOAP,全寫為 Simple Object Access Protocol)是一種標準化的通訊規范,主要用于 Web 服務(web service)中。SOAP 的出現是為了簡化網頁服務器(Web Server)在從 XML 數據庫中提取數據時,無需花時間去格式化頁面,并能夠讓不同應用程序之間透過 HTTP 通訊協定,以 XML 格式互相交換彼此的數據,使其與編程語言、平臺和硬件無關。
用一個簡單的例子來說明 SOAP 使用過程,一個 SOAP 消息可以發送到一個具有 Web Service 功能的 Web 站點,例如,一個圖書價格信息的數據庫,消息的參數中標明這是一個查詢消息,此站點將返回一個 XML 格式的信息,其中包含了查詢結果。由于數據是用一種標準化的可分析的結構來傳遞的,所以可以直接被第三方站點所利用。
SOAP RPC over HTTP,在 HTTP 上傳送 SOAP 并不是說 SOAP 會覆蓋現有的 HTTP 語義,而是 HTTP 上的 SOAP 語義會自然的映射到 HTTP 語義。在使用 HTTP 作為協議綁定的場合中, RPC 請求映射到 HTTP 請求上,而 RPC 應答映射到 HTTP 應答。然而,在 RPC 上使用 SOAP 并不僅限于 HTTP 協議綁定。SOAP 協議沒有和 HTTP 有很好的結合,沒有利用 HTTP 協議里面關于 request 和 response 的豐富詞匯,而是鼓勵應用設計人員定義任意的詞匯(動詞和名詞),像 getUsers(),savePurchaseOrder(...),getBookPrice() 等。SOAP RPC Request 通過 HTTP POST 請求發送。清單 1 和清單 2 給出了 SOAP RPC over HTTP 的 request 和 response 的示例。請求和響應是封裝在 SOAP Envelope 里面,以 HTTP request 和 response 的 body 傳送。
? 清單 1. A SOAP Request over HTTP 示例
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | POST /books/bookquery HTTP/1.1 Host: www.Monson-Haefel.com Content-Type: text/xml; charset="utf-8" Content-Length: nnn SOAPAction="" <?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mh="http://www.example.com/books"> ??<soap:Body> ?????<mh:getBookPrice> ?????????<id>123456</id> ?????</mh:getBookPrice> ??</soap:Body> </soap:Envelope> |
從清單 1 可以看出,soap envelope 是定義好的格式,它描述所要調用的方法和方法所需要的參數及其參數值,在 HTTP 上,它作為 HTTP request 的 body 發送。
清單 2. A SOAP response over HTTP 示例
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | HTTP/1.1 200 OK Content-Type: text/xml; charset='utf-8' Content-Length: nnn <?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mh=" http://www.example.com/books" > ??<soap:Body> ?????<mh:getBookPriceResponse> ?????????<result>24.99</result> ?????</mh:getBookPriceResponse> ??</soap:Body> </soap:Envelope> |
從清單 2 可以看出,soap envelope 封裝了 getBookPrice 的調用結果,在 HTTP 上,它作為 HTTP response 的 body 返回回來。
XML RPC over HTTP
XML RPC over HTTP 和 SOAP RPC over HTTP 從結構上看很類似。這種遠程過程調用使用 HTTP 作為傳輸協議,XML 作為傳送信息的編碼格式。XML-RPC 的定義盡可能的保持了簡單,但同時能夠傳送、處理、返回復雜的數據結構。XML-RPC 是工作在 Internet 上的遠程過程調用協議。一個 XML-RPC 消息就是一個請求體為 XML 的 HTTP POST 請求,被調用的方法在服務器端執行并將執行結果以 XML 格式編碼后返回。清單 3 和清單 4 給出了 XML RPC over HTTP 的 request 和 response 的示例。請求和響應是封裝在一個固定的格式里面,以 HTTP request 和 response 的 body 傳送。
清單 3. A XML RPC Request over HTTP 示例
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | POST /books/bookquery HTTP/1.1 Host: www.Monson-Haefel.com Content-Type: text/xml; charset="utf-8" Content-Length: nnn Connection:keep-alive <?xml version="1.0"?> <methodCall> ??<methodName>getBookPrice</methodName> ??<params> ?????<param> ????????<value><string>123456</string></value> ????????</param> ??</params> </methodCall> |
從清單 3 可以看出,methodcall 是定義好的 XML 格式,它描述所要調用的方法和方法所需要的參數及其參數值,在 HTTP 上,它作為 HTTP request 的 body 發送。
清單 4. A XML RPC response over HTTP 示例
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | HTTP/1.1 200 OK Content-Type: text/xml; charset='utf-8' Content-Length: nnn Connection: close <?xml version="1.0"?> <methodResponse> ??<methodName>getBookPrice</methodName> ??<params> ?????<param> ????????<value><double>24.99</double></value> ????????</param> ??</params> </methodResponse> |
從清單 4 可以看出,methodResponse 封裝了 getBookPrice 的調用結果,在 HTTP 上,它作為 HTTP response 的 body 返回回來。
REST over HTTP
REST 風格的架構也并不強調和協議的綁定。HTTP 是 WWW 網上廣泛使用的并且被證明是有效的通信協議,所以現在 RESTful 服務基本也是基于 HTTP 協議的。資源是由 URI 來指定。
對資源的操作包括獲取、創建、修改和刪除資源,這些操作正好對應 HTTP 協議提供的 GET、POST、PUT 和 DELETE 方法。通過操作資源的 representation 來操作資源。資源的 representation 可以是 XML 也可以是 HTML,取決于讀者是機器還是人,是消費 web 服務的客戶軟件還是 web 瀏覽器。當然也可以是任何其他的格式。清單 5 和清單 6 給出了 REST over HTTP 的 request 和 response 的示例。
清單 5. A REST Request over HTTP 示例
| 1 2 | GET /books/123456/xml HTTP/1.1 Host: example.com |
清單 6. A REST Response over HTTP 示例
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | HTTP/1.1 200 OK Date: Fri, 10 Sept 2010 17:15:33 GMT Last-Modified: Fri, 10 Sept 2010 17:15:33 GMT ETag: "2b3f6-a4-5b572640" Accept-Ranges: updated Content-Type: text/xml; charset="utf-8" <book category="CHILDREN"> ????<title lang="en">Harry Potter</title> ????<author>J K. Rowling</author> ????<year>2005</year> ????<price>24.99</price> </book> |
從清單 5 和清單 6 種,可以清楚的看到,REST 在最大程度上充分利用了 HTTP,沒有增加額外的詞匯和約定。
通過前面的分析和比較,我們可以清楚地看到,REST 風格的 web 服務與 SOAP RPC 和 XML RPC 風格的 web 服務相比,http request 更簡單,response 的語意更清楚。而且客戶端不需要知道那么多關于應用的細節,比如 method name,method 調用的參數等等。
簡言之,目前在三種主流的 Web 服務實現方案中,因為 REST 模式的 Web 服務與復雜的 SOAP 和 XML-RPC 對比來講明顯的更加簡潔,越來越多的 web 服務開始采用 REST 風格設計和實現。例如,Amazon.com 提供接近 REST 風格的 Web 服務進行圖書查找;雅虎提供的 Web 服務也是 REST 風格的。
REST 風格的 web 服務已被廣泛的接受和使用,但是在使用的過程中,我們發現其實有很多號稱 RESTful 的 web 服務并不是 Roy 定義的 REST 服務,或者違背了其中的一些約束。像 Amazon 和 Flickr 的 web 服務。接下來,我們首先結合實際經驗,重新解讀 REST 架構風格中的核心概念,幫助讀者準確的掌握 REST 架構,最后給出一個完全符合 REST 風格架構的 web 服務定義的例子。
REST 核心概念解讀
Representational State Transfer
在理解 REST 相關的核心概念之前,我們來看看“REST”本身應該怎么理解。“Representational State Transfer”是一個不完整的句子,“Representational”代表的是什么的“表示”?“State”又指的是什么的狀態 ?“Transfer”的又是什么?如果我們要把它補全該如何呢?根據 Roy 的論文和我們的實踐,應該是“Application States Transfer among the resource ’ s representation”。這里的“State”指的是“應用”的“狀態”,這個“狀態”是用“資源的表示”來代表的,用戶可以在“狀態”之間“跳轉”。
REST 架構風格定義的約束
REST 是一種架構的風格,它是對分布式 hypermidea 系統的架構元素的抽象,提供了一些核心的概念和指導思想。這種架構風格是“客戶端 - 服務器”架構風格的一種,也就是說“客戶端”發起“請求”,“服務器”處理“請求”并返回相應的結果。REST 的貢獻是規定了一系列的方法論,在“請求”方面,規定“客戶端”怎么發起“請求”,發的是什么樣的“請求”,以什么協議發“請求”;在處理方面,規定“服務器”怎么響應“請求”,返回什么樣的“響應”,系統后續應該怎么“跳轉”等等。讓我們再回顧 Roy 定義的這些約束。
“無狀態”(Stateless)
“客戶端”和“服務器端”的交互是“無狀態”的,也就是說“請求”之間是相互隔離的。“服務器端”不保存“客戶端”的應用上下文(context),每個從“客戶端”來的“請求”都必須包括所有的必要的信息讓“服務器端”能夠完全理解“請求”并處理。“客戶端”存了很多“會話”的信息。讓我們以搜索引擎為例來看看“有狀態”和“無狀態”的區別。圖 1 和圖 2 分別給出了兩個搜索引擎的“有狀態”和“無狀態”的交互示例。
圖 1. 無狀態的搜索引擎的交互示例
圖 1 所示是無狀態的搜索引擎的請求(request)和響應(response)的實例。可以清楚地看出,每個 request 和 response 都是互相獨立的,相互之間沒有數據的依賴。每個 request 包含服務器端響應這個 request 所需要的所有信息。 以“搜索 SOAP”為例,用戶首先發了 request http://www.google.com/search?q=SOAP,并且得到了搜索結果,其中包含了 10 個最相關的搜索結果。這個交互過程就結束了,服務器端沒有保存任何和這個請求相關的信息。但是在這個返回的狀態中,服務器端把下一步的可能的狀態嵌在其中了。比如用戶如果在這些結果沒有找到自己想要的結果,他可以翻頁,翻到第二頁。第二頁是另一個狀態,這時用戶點擊了第二頁,然后客戶端又發了一個 request http://www.google.com/search?q=SOAP&start=10,這個 request 了包含了所有的上下文,也就是“按關鍵字 SOAP 搜索并且以第 10 個搜索結果開始返回”。也就是說,服務器把當前的狀態隱含中結果中返回,客戶端保存下這些隱含的狀態,而不是保存在服務器端。客戶端可以根據服務器端返回來的狀態構建下一個狀態的請求。
“無狀態”的好處是每個狀態都有一個對應的 URI,這些 URI 可以 bookmark,可以使得用戶方便的在瀏覽器中前進后退,可以在用戶希望的任何時候訪問任意多次。
圖 2. 有狀態的搜索引擎的交互示例
圖 2 是有狀態的搜索引擎的 request 和 response 的交互示例。可以看出,request 之間的時序依賴性。以“搜索 SOAP”為例,用戶在看 1~10 個搜索結果并想看 11~20 個結果,客戶端只需要發一個“start=10”的請求而是發“/search?q=SOAP&start=10”的請求,也就是客戶端不用重復前面的 request 的上下文。這使得 HTTP request 相對簡單了很多。但是他使得 HTTP protocol 變得復雜。服務器端和客戶端需要同步會話的狀態,在可靠網絡上,這是一個復雜的任務。
“無狀態”帶來了一些性能的提升。在“visibility”方面,對于監控系統而言,它不用去關心跨請求的數據對當前請求的影響,所以“visibility”有所提升。在“reliability”方面,由于“客戶端”保存了很多“會話”數據,因此減少了部分“服務器”端的故障或者網絡故障對應用的影響,因此對于用戶來說,“reliability”有所提升。最值得一提的是“scalability”,由于“服務器”端能夠獨立的響應每個 request,而不用依賴會話歷史,因此少了很多“資源”的管理和同步,使得多個服務器可以同時服務不同的用戶的請求,獲得很好的自由擴展功能。
當然,事務都有兩面性,“無狀態”帶來的不足之處包括可能的網絡性能的降低,因為“服務器”會發很多重復的數據到不同的“客戶端”,使得“客戶端”保存所有的會話信息;這也導致了另一個問題,就是“服務器”將失去對應用的一致行為的一部分控制權,而且還對“客戶端”的實現產生依賴。
“緩存”(Cacheable)
為了提高 REST 風格架構的網絡性能,Roy 加入了“緩存”的約束。“緩存”不是一個新的概念,HTTP 協議提供機制,使得“客戶端”可以“緩存”一些數據。在“服務器”返回的“響應”中,可以隱式或者顯式的指明數據的“緩存”屬性,這樣,“客戶端”可以在未來使用“緩存”下來的數據,減少“客戶端”和“服務器”端的交互次數,從而達到提高網絡性能的目的。
統一的接口 (Uniform Interface)
這是 REST 風格的架構區別于其他架構的一個最關鍵的指標,就是 REST 風格的架構對于任意應用,規定了統一的接口定義。接口定義包括四個部分:
- 1)identification of resources(標識資源);
- 2)manipulation of resources through representations(通過資源的表示來操作資源);
- 3)self-descriptive messages(自描述的信息);
- 4)hypermedia as the engine of application state(超媒體作為應用狀態的引擎)
下面分別對這些概念進行解析。
Identification of resources
第一部分“identification of resources”講了 REST 架構風格的一個最核心的概念“Resource”以及 “Resource”的“identification”。“Resource”是信息系統的一種抽象,可以是任何重要的足以把本身作為一個引用(reference)的事物。從應用的角度看,如果應用的用戶需要“創建一個到它鏈接”,獲取或者緩存它的“表示”,或者想對他做些操作,那么都可以創建一個“Resource”。一個“Resource”可以是現實世界的事物,比如一本書,一輛車,一棟房子;也可以是一個虛擬的概念,也可以是一個算法的結果。“identification”是“Resource”的全局唯一的標識,如果“Resource”沒有 identification,那么他不能稱為“Resource”。用 URI 來表示一個 Resource 的 identification。關于 URI 的最佳實踐包括 URI 是自描述的,有結構的,這樣可以使得“客戶端”可以自己創建一些有預測性的請求。幾個比較好的 Resource URI 示例:
| 1 2 3 4 | http://www.example.com/books/123456 http://www.example.com/softwares/im/db2/9.5 http://www.example.com/blog/2010/09/10/1 http://www.example.com/bugs/by-state/new |
前面解釋了什么是 Resource,怎么用 URI 來標識一個 Resource,下面來看下 Resource 和 URI 的關系,他們之間是不是一一對應的呢?讓我們來回答下面幾個問題:1)兩個 URI 能指向同一個 Resource 么?回答是肯定的。作為程序員,我們都有這樣的經歷,就是經常做一些 release,讓我們考慮這兩個 URI: http://www.example.com/releases/3.1.1 和 http://www.example.com/releases/latest,在特定的時刻,他們指向的就是同一個 resource,但是背后的概念是不一樣的,一個是指向一個特定的 release 版本號,一個是指向一個隨著時間演進的 resource,是兩個完全不同的概念。所以一個 resource 可以有一個或者多個 URI 來標識。2)一個 URI 能指向不同的 resource 么?答案是否定的。這和 Universal Resource Identifier 的原則相違背。
Manipulation of resources through representations
接著來看看“Representation”。Representation 是 Resource 的表現形式。Resource 是信息系統的抽象,但是最終必須以人們能理解的一種形式提供出來。可以是一個 XML 文檔,HTML 文檔,CSV,file 等等。一個 Resource 可以有多種 Representation。那么服務器端怎么知道用戶想要哪個 Representation 呢?通常來講有兩種方式:
1)在 resource 的 URI 里面指明,為不同的 Representation 提供不同的 URI。舉個例子,我們有個 book 的 resource,我們提供幾個不同的 URI 來表示不同的 representation。清單 7、8、9 分別是 XML、JSON、CSV 三種 representation 的 HTTP request 和 response 的示例。
清單 7. Representation 為 XML 的 request 和 response 示例
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | GET /books/123456/xml HTTP/1.1 Host: example.com Reponse: HTTP/1.1 200 OK Date: Fri, 10 Sept 2010 17:15:33 GMT Last-Modified: Fri, 10 Sept 2010 17:15:33 GMT ETag: "2b3f6-a4-5b572640" Accept-Ranges: updated Content-Type: text/xml; charset="utf-8" <book category="CHILDREN"> ????<title lang="en">Harry Potter</title> ????<author>J K. Rowling</author> ????<year>2005</year> ????<price>24.99</price> </book> |
清單 8. Representation 為 JSON 的 request 和 response 示例
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | GET /books/123456/json HTTP/1.1 ?Host: example.com ?Reponse: ?HTTP/1.1 200 OK ?Date: Fri, 10 Sept 2010 17:15:33 GMT ?Last-Modified: Fri, 10 Sept 2010 17:15:33 GMT ?ETag: "2b3f6-a4-5b572640" ?Accept-Ranges: updated ?Content-Type: text/json; charset="utf-8" ?[ "book": ?{ "title":Harry Potter, "author":J K. Rowling, "year":2005, "price":24.99 ?} ?] |
清單 8. Representation 為 CSV 的 request 和 response 示例
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | GET /books/123456/csv HTTP/1.1 Host: example.com Reponse: HTTP/1.1 200 OK Date: Fri, 10 Sept 2010 17:15:33 GMT Last-Modified: Fri, 10 Sept 2010 17:15:33 GMT ETag: "2b3f6-a4-5b572640" Accept-Ranges: updated Content-Type: text/csv; charset="utf-8" title,author,year,price Harry Potter,J K. Rowling,2005,24.99 |
2) 利用 HTTP 的內容協商(content negotiation)機制。客戶端可以利用“Accept”header 指明它希望的內容表示形式。還是拿 book 的例子來說,客戶端發的 HTTP 請求如清單 9 所示:
清單 9. 利用 content negotiation 機制請求 representation 的 request 示例
| 1 2 3 | GET /books/123456 HTTP/1.1 Host: example.com Accept: text/xml |
| 1 2 3 | GET /books/123456 HTTP/1.1 Host: example.com Accept: text/json |
| 1 2 3 | GET /books/123456 HTTP/1.1 Host: example.com Accept: text/csv |
對應的 response 和第一種方式相同,所以忽略了。
相比較這兩種方式,根據我們的經驗,第一種方式更好,URI 是一種好的實踐,用來表示 resource 的各方面的信息,包括結果,版本,representation 形式,時間等等,它帶來的好處是使客戶端的工作大大降低,它很方便的可以在不同的應用之間共享。
那么怎么對 resource 進行下一步的操作(“manipulation”)呢? REST 對 Resource 的操作定義和 HTTP method 是對應起來的。HTTP 協議定義 GET、POST、PUT、DELETE 來表示對資源的獲取、創建、更新和刪除。“Manipulation of resources through representations”約束規定了 resource 的操作是通過 representation 來完成的,所以在 resource 的 representation 里面,需要包含對 resource 的操作怎么進行。APP(ATOM Publishing Protocol)提供了一個最佳實踐,是用 <link> 的方式指明 Resource 響應的操作,下面是一個實例:
<link rel="edit" href="http://www.example.com/books/123456"/>
rel 定義了該 link 的 resource 和當前 resource 的關系,href 定義了 resource 的 HTTP endpoint。所以當用戶想更新 id 為 123456 的 book 的價錢的時候,客戶端會發一個 PUT 請求到指定的 URI,示例如清單 10 所示:
清單 10. 更新 resource 的 request 示例
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | PUT /books/123456 HTTP/1.1 Host: example.com Reponse: HTTP/1.1 200 OK Date: Fri, 10 Sept 2010 17:15:33 GMT Last-Modified: Fri, 10 Sept 2010 17:15:33 GMT <book category="CHILDREN"> ????<title lang="en">Harry Potter</title> ????<author>J K. Rowling</author> ????<year>2005</year> ????<price>31.99</price> </book> |
同樣,客戶端可以發 HTTP 請求去刪除這個 book,如清單 11 所示。
清單 11. 刪除 resource 示例
| 1 2 3 4 5 | DELETE /books/123456 HTTP/1.1 Host: example.com Reponse: HTTP/1.1 200 OK |
Self-descriptive messages
REST 接口的定義強調了自描述“Self-descriptive”性。自描述性也是為了讓客戶端充分的理解當前的狀態,下一步的狀態怎么跳轉。
前面講了 resource 的 representation,只講述了 representation 的數據的一部分。在 representation 里面,除了包含數據部分,還包括元數據,它是用來描述數據的信息,或者是一些客戶端需要知道的一些領域知識。這里以 ATOM 協議為例看看怎么在 representation 里面提供自描述的信息。清單 12 是一個 ATOM 的片段。
清單 12. 一個 ATOM 形式的 resource representation
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <?xml version="1.0" encoding="UTF-8"?> ?<feed xmlns="http://www.w3.org/2005/Atom" ?xmlns:os="http://a9.com/-/spec/opensearch/1.1/" ?xmlns:catalog="http://www.ibm.com/opensearch/1.0"> ??<id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 </id> ?<link href="http://www.example.com/books?q=harry potter&start=0" rel="self"></link> ?<link href="http://www.example.com/books?q=harry potter&start=10" rel="next"></link> ??<updated>2009-08-11T03:00:27.062Z</updated> ??<title type="text"> harry potter 相關的圖書 </title> ??<os:startIndex xmlns:os="http://a9.com/-/spec/opensearch/1.1/">0</os:startIndex> ??<os:itemsPerPage xmlns:os="http://a9.com/-/spec/opensearch/1.1/">10</os:itemsPerPage> ??<os:totalResults xmlns:os="http://a9.com/-/spec/opensearch/1.1/">10</os:totalResults> ??? ?<entry> ?<title type="text"> Harry Potter </title> ????<id>urn:id:23-27</id> ?<updated>2009-07-09T11:01:26.000Z</updated> ????<category term="harry potter"></category> ???<category term="best seller"></category> ??<category term="book"></category> ???<category term="Hyper Service"></category> ???<content type="application/xml" xmlns= ’ http://www.example.com/books ’ > ????<book category="CHILDREN"> ???? <title lang="en">Harry Potter</title> ???? <author>J K. Rowling</author> ???? <year>2005</year> ???? <price>31.99</price> ????????????</book> ????</content> ????</entry> … ?</feed> |
從上面的例子可以看出,除了 <content> 里面的內容是和 book 數據相關的信息以外,剩下的信息都是描述性的信息,通常包括翻頁信息:一共有多少頁,前后翻頁的 link 是什么;分類信息,時間信息,還有一些文本信息供用戶閱讀。
總之,服務器端盡可能的返回詳細的信息,幫助客戶端理解當前的狀態,以及發起下一個請求所需要的所有信息,以達到“服務器端無狀態”和客戶端發起“狀態跳轉”的目的。
Hypermedia as the engine of application state
“Hypermedia as the engine of application state”是“統一接口”的最后一個約束,也是最重要的一個約束,不幸的是, Roy 的 REST 論文中對這個約束的解釋特別少,在這里,我們根據我們的經驗和理解,對這個約束進行描述。這個約束其實規定的是應用系統是通過 Hypermedia 的方式在不同的狀態之間跳轉。這句話聽起來有點拗口,還是來看一個例子吧。
Flickr 提供了 REST API,以 flickr.groups.members.getList 為例來看看這個 REST API 的 定義,清單 13 是一個響應示例(sample response),可以看出“members”是一個 resource,它是一系列“member”的集合,也就是說,它指向其他的 resource。
清單 13. flickr.groups.members.getList 的響應示例
| 1 2 3 4 5 6 7 8 9 10 11 | <members page="1" pages="1" perpage="100" total="33"> <member nsid="123456@N01" username="foo" iconserver="1" iconfarm="1" membertype="2"/> <member nsid="118210@N07" username="kewlchops666" iconserver="0" iconfarm="0" membertype="4"/> <member nsid="119377@N07" username="Alpha Shanan" ?iconserver="0" iconfarm="0" membertype="2"/> <member nsid="67783977@N00" username="fakedunstanp1" iconserver="1003" iconfarm="2" membertype="3"/> ... </members> |
如果用戶還想對“Alpha Shanan”了解更多呢?這時,客戶端會構建一個 HTTP request,URI 為:
| 1 2 | http://api.flickr.com/services/rest/? method=flickr.people.getInfo?auth_key=xxxx&user_id=119377@N07 |
如果系統按照這種方式運行,問題在哪呢?客戶端和服務器端需要有很多的共享的知識和約定,客戶端需要知道獲取人員信息的 API 是 method=flickr.people.getInfo 以 user_id 作為參數。這不是 Hypermedia,同時也違背了 Roy 的關于“Hypermedia as the engine of application state”的約束,所以這個不是好的 RESTful 的實現。
Hypermedia 的實質是 hyperlink, 用 hyperlink 把這些相互依賴的 resource 聯系起來,這些 hyperlink 是由服務器端生成并且在 representation 里面返回來的,包括了當前的狀態集合和可能的下一步的狀態集合。客戶端不需要任何 domain specific 知識就能夠實現狀態的跳轉。Hypermedia 類型的 sample response 如清單 14 所示。在清單 14 中可以看到,resource 和 resource 之間的聯系通過 hyperlink 關聯起來,并且在 resource 的 representation 里面表示。
清單 14. REST 風格的 flickr.groups.members.getList 的 sample response
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <members page="1" pages="1" perpage="100" total="33"> <member href=" http://api.flickr.com/services/rest/? method=flickr.people.getInfo?auth_key=xxxx&user_id=123456@N01" ?username="foo" iconserver="1" iconfarm="1" membertype="2"/> <member href=" http://api.flickr.com/services/rest/? method=flickr.people.getInfo?auth_key=xxxx&user_id=118210@N07" ?username="kewlchops666" iconserver="0" iconfarm="0" membertype="4"/> <member href=" http://api.flickr.com/services/rest/? method=flickr.people.getInfo?auth_key=xxxx&user_id=119377@N07" ?username="Alpha Shanan" iconserver="0" iconfarm="0" membertype="2"/> <member href=" http://api.flickr.com/services/rest/? method=flickr.people.getInfo?auth_key=xxxx&user_id=67783977@N00" ?username="fakedunstanp1" iconserver="1003" iconfarm="2" membertype="3"/> ... </members> |
通過前面關于“統一接口”的解析,我們清楚地知道,REST 風格的架構使得 web 服務的“客戶端”和“服務器端”很好的分離開來。“客戶端”不需要關心數據的存儲,使得“客戶端”的可移植性(portability)提高了。“服務器端”不用關心用戶的接口和用戶的狀態,所以“服務器端”將變得更加簡單,而且方便的獲得更好的可伸縮性(scalability)。只要保持接口的定義不變 ,“客戶端”和“服務器端”可以獨立的開發和演變。
一個定義良好的 RESTful Web 服務接口
首先,用一個文檔描述 web 服務的 capabilities 以及服務的 location。如清單 15 所示。
清單 15. 用 APP 協議描述的服務 capabilities 和 location
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <?xml version="1.0" encoding="UTF-8"?> ?<service xmlns="http://www.w3.org/2007/app" ??xmlns:atom="http://www.w3.org/2005/Atom"> ?<workspace> ?<atom:title type="text"> InfoSphere MashupHub </atom:title> ?<collection href="http://localhost:8080/mashuphub/atom?collection=all"> ?<atom:title type="text"> All </atom:title> ?<accept> application/atom+xml; type=entry </accept> ?<categories> <categories> ALL </categories> </categories> ?</collection> ?<collection href="http://localhost:8080/mashuphub/atom?collection=feeds"> ?<atom:title type="text">Feeds</atom:title> ?<accept> application/atom+xml;type=entry </accept> ?<categories> <categories> feeds </categories> </categories> ?</collection> … ?</workspace> ?</service> |
這個服務文檔指明 InfoSphere MashupHub 提供了一系列的 web 服務,包括 list 所有的資源,list 所有的 feeds 等等。并且還描述了每個 web 服務的 location 可以提供的 resource representation 的類型。這種描述文檔對于 web 服務的客戶端來說非常有益。這個服務描述文檔本身也是一個 resource,可以用 HTTP URI 定位到。
其次,我們來看一下每個 web 服務是怎么定義的。
清單 16. RESTful web 服務接口的定義示例
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | 1) Resource:所有的 feeds 2) Resource URI: http://localhost:8080/mashuphub/atom?collection=feeds 3) Resource representation:??????????? <feed xmlns="http://www.w3.org/2005/Atom"> ?<id> ?http://9.186.64.113:8080/mashuphub/atom?collection=feed </id> ?<link href="http://9.186.64.113:8080/mashuphub/atom?collection=feeds" ?rel ="self"></link> ?<updated> 2010-09-11 T 15:12:24.968Z </updated> ?<title type = "text"> feeds Collection </title> ?<author> ???<name> InfoSphere MashupHub Search Feed</name> ?</author> <entry> <author> ?????<name> InfoSphere MashupHub Search Feed </name> ???</author> ???<title type="html"> MyCo Customer List </title> ???<updated> 2010-09-07 T 05:08:52.000Z </updated> ???<id> urn:id:1460 </id> <link href="http://9.186.64.113:8080/mashuphub/atom?collection=feeds&id=1460" rel="self"></link> <link href="http://9.186.64.113:8080/mashuphub/atom?collection=feeds&id=1498" rel="related" title = "MyCo Customer List with weather info" > </link> ???<category term="feed"> </category> ???<content type = "application/xml" > ?????<catalog:feed xmlns:catalog="http://www.ibm.com/xmlns/atom/opensearch/feed/1.0/"> ???????<catalog:version> 1.0 </catalog:version> ???????<catalog:name> MyCo Customer List </catalog:name> ???????<catalog:author> admin </catalog:author> ???????<catalog:permission> public </catalog:permission> ???????<catalog:description> </catalog:description> ???????<catalog:rating> 0.0 </catalog:rating> ???????<catalog:useCount> 320 </catalog:useCount> ???????<catalog:dateModified> 1283836132 </catalog:dateModified> ???????<catalog:numComments> 0 </catalog:numComments> ???????<catalog:tags> </catalog:tags> ???<catalog:categories> </catalog:categories>????????????????? ???<catalog:downloadURL>http://9.186.64.113:8080/mashuphub/client ???/plugin/generate/entryid/1460/pluginid/3 ????</catalog:downloadURL> ???????<catalog:mimetype> application/atom+xml </catalog:mimetype> ?????</catalog:feed> ???</content>???? </entry> </feed> 4) Http method: GET 5) 和別的 resource 的關系。 在 representation 中用 <link> 定義了“MyCo Customer List”和“MyCo Customer List with weather info”的關系 |
總結一下,一個設計良好的 RESTful web 服務應該包括一下幾個定義:
- 1) 服務描述文檔,如清單 15 所示;
- 2) 資源 (resource) 及其 identification;
- 3) 資源的 representation 的定義
- 4) 用 HTTP method 表示的資源的 manipulation
- 5) 在資源的 representation 里面以 Hyperlink 表述的資源和資源之間的關系
結束語
REST 是一種架構風格,汲取了 WWW 的成功經驗:無狀態,以資源為中心,充分利用 HTTP 協議和 URI 協議,提供統一的接口定義,使得它作為一種設計 Web 服務的方法而變得流行。在某種意義上,通過強調 URI 和 HTTP 等早期 Internet 標準,REST 是對大型應用程序服務器時代之前的 Web 方式的回歸。
本文根據 Roy 的論文,結合實際的例子,重新解析了 REST 中的核心概念。根據作者多年的創建和使用 RESTful web 服務的經驗,更多的是在觀念上的澄清。最后通過一個 RESTful web 服務,告訴讀者一個真正意義上的 RESTful web 服務是什么樣子。但愿本文的討論會您提供了一定意義上的思想上的指導和實踐上的指導。
總結
以上是生活随笔為你收集整理的重新解析 REST Service(REST Service 的最佳实践,第 1 部分)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RPC框架性能基本比较测试
- 下一篇: REST service 化一个数据系统