關於REST及RESTful的概念,已有不少文章介紹,這裏整理幾篇我覺得不錯的參考:
- 維基百科的定義: REST
- 什麼是REST跟RESTful? REST理論的中文詳述,其中你可以瞭解到WCF Restful屬於RPC 樣式的 Web 服務,ASP.NET Web API屬於RESTful Web 服務。
- 深入淺出REST InfoQ的專文介紹,文中甚至有Roy T. Fielding當年REST博士論文的中文翻譯鏈接。另外值得一提的,大家可能沒聽過Roy Fielding的大名,但如果得知他是HTTP規格的主要作者及Apache HTTP Server項目的發起人之一,應該不會有人懷疑他在Web技術領域的分量。
上面的文章建議大家認真的讀一下,這裏我們簡要的介紹下REST 做入門介紹,理解整個 REST 能讓我們在 ASP.NET Web API 的路上更順暢。
REST是什麼?
REST ( REpresentational State Transfer ),State Transfer 爲 "狀態傳輸" 或 "狀態轉移 ",Representational 中文有人翻譯爲"表徵"、"具象",合起來就是 "表徵狀態傳輸" 或 "具象狀態傳輸" 或 "表述性狀態轉移",不過,一般文章或技術文件都比較不會使用翻譯後的中文來撰寫,而是直接引用 REST 或 RESTful 來代表,因爲 REST 一整個觀念,想要只用六個中文字來完整表達真有難度。
REST 一詞的出於《Architectural Styles and
the Design of Network-based Software Architectures》論文,我們先簡單從標題來看,它應該是一種架構樣式 (Architectural Styles) 與軟件架構 (Software Architectures),而且是以網絡
(Network-based) 爲基礎,重點就是:
- 架構樣式 (Architectural Styles)
- 軟件架構 (Software Architectures)
- 網絡 (Network-based) 爲基礎
REST 本身是設計風格而不是標準。REST 談論一件非常重要的事,如何正確地使用 Web標準,例如,HTTP 和 URI。想要了解 REST 最好的方式就是思索與瞭解Web 及其工作方式。如果你設計的應用程序能符合 REST 原則 (REST principles),這些符合 REST 原則的 REST 服務可稱爲 "RESTful web service" 也稱 "RESTful Web API"。"-ful" 字尾強調它們的設計完全符合 REST 論文裏的建議內容。
資源 RESOURCE
在 REST 中的資源 (Resource) 代表整個網絡上的資源。網絡上提供了各式各樣的資源,而網絡上的資源由 URI (統一資源標識符,Uniform Resource Identifier) 來提供。
回想,你如何連上我的 博客,你可能通過瀏覽器直接輸入 www.cnblogs.com/shanyou 此域名來到達首頁,也能用書籤或網絡上的鏈接,經點擊後來連上我的博客。然後,你想看這一篇名爲「REST 入門介紹」的文章,所以以你接下去點擊這文章的標題連結,接去下閱讀。我們簡易瞭解一下整個流程:
- 通過URL ( http://www.cnblogs.com/shanyou ) , Client 向 http://www.cnblogs.com/shanyou 發出請求
- www.cnblogs.com/shanyou 收到請求,迴應首頁給 Client
- Client 又點擊 REST 文章連結 (假設是 http://www.cnblogs.com/shanyou/archive/2011/06/30/2095018.html) 向http://www.cnblogs.com/shanyou發出archive/2011/06/30/2095018.html 此篇文章的請求
- www.cnblogs.com/shanyou 收到請求,響應 REST 文章內容給 Client
Client 的通過 URI 來獲取資源的具體象徵 (Representational)。Client 取得這些具體象徵使這些應用程序轉變其狀態 (以 瀏覽器而言,取得 HTML、CSS、JavaScript … 來生成界面),隨着不斷取得資源的具體象徵, Client 端不斷地改變其狀態,這樣不斷的反覆 (iterations ) 過程就是所謂的 Representational State Transfer。
使用 WEB 標準
上述是最接近日常的範例,這些行爲在 HTTP 規範中稱之爲 GET,也就是通過URL 來 GET 我想要的資源。另一常用的例子是填寫表單,例如,登入表單,我想進行登入動作,就必須先發送賬號與密碼給某一資源,此資源會驗證你所傳送的數據是否正確,再進行後續動作。我們發送信息給資源的行爲在 HTTP 規範中稱之爲 POST。
在 HTTP/1.1 RFC 2616第 5.1.1 Method 一節定義了八大類 HTTP 方法,除了我們常用的 GET 與 POST 之外,在 REST 中常用的還有 PUT 與 DELETE。此 GET, POST, PUT, DELETE 正好可以對應我們 CRUD (Create,
Read, Update, Delete) 四種數據操作。
HTTP Method 與 CURD 數據處理操作對應 |
||
HTTP方法 |
數據處理 |
說明 |
POST |
Create |
新增一個沒有id的資源 |
GET |
Read |
取得一個資源 |
PUT |
Update |
更新一個資源。或新增一個含 id 資源(如果 id 不存在) |
DELETE |
Delete |
刪除一個資源 |
RESTFUL WEB SERVICE
RESTful Web Service (又稱 RESTful Web API) 是一個使用 HTTP 並符合 REST 原則的 Web 服務。我們知道,通過 URL 可以傳送 GET 請求,在 表單指定 method="GET|POST" 來送出請求。但我們要處理 PUT 或 DELETE 的請求呢?通過 RESTful 我們可以簡單 URI 來定義資源並和 HTTP 方法配合使用。
Resource 與 HTTP 方法的對應 |
|||||
資源 |
資源說明 |
GET |
PUT |
POST |
DELETE |
http://www.cnblogs.com/Products/ |
Products是一組資源集合 |
列出 該組資源集合中每個資源的詳細信息 |
更新 當前整組資源 |
新增 或附加一個新資源。該操作傳回新資源的URL |
刪除 整組資源 |
http://www.cnblogs.com/Products/1 |
Products/1是單個資源 |
取得 指定的資源的詳細信息 |
更新 或新增指定的資源 |
新增 或附加一個新元素 |
刪除 指定的元素 |
以上表格有沒有很像我們一般在對數據庫表格的操作順序,進入一個 Table 的數據首頁 (通常是列表),此頁面會有「新增、更新、刪除、詳細」等連結,你想進行什麼操作,就點那一個連結。
在 RESTful 每個資源有自己獨立的 URI, Client 從資源集合或單個資源開始進入,不管是資源集合或單個資源,我們都能與 HTTP 方法配合使用,例如,GET 下載,PUT 更新,POST 新增,DELETE 刪除。
ASP.NET Web API 是一個框架(framework),能讓你在 .NET Framwork 之上架設 HTTP 服務 (HTTP Services)。ASP.NET Web API 是 .NET Framework 上構建 RESTful 應用程序的理想平臺。
在 Julie Lerman's 的 How I see Web API 一文中,用了一張圖來簡明說明 Web API:
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示例
以下是引用片段: 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示例
以下是引用片段: 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和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示例
以下是引用片段: 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示例
以下是引用片段: 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示例
以下是引用片段: GET /books/123456/xml HTTP/1.1 Host: example.com |
清單 6. A REST Response over HTTP示例
以下是引用片段: 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示例:
- 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示例
以下是引用片段: 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示例
以下是引用片段: 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示例
以下是引用片段: 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示例
以下是引用片段: GET /books/123456 HTTP/1.1 Host: example.com Accept: text/xml GET /books/123456 HTTP/1.1 Host: example.com Accept: text/json 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示例
以下是引用片段: 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示例
以下是引用片段: 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
以下是引用片段: <?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的響應示例
以下是引用片段: <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爲: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
以下是引用片段: <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
以下是引用片段: <?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) 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服務是什麼樣子。但願本文的討論會您提供了一定意義上的思想上的指導和實踐上的指導。