2008 年 12 月 22 日 代表性狀態(tài)傳輸(Representational State Transfer,REST)在 Web 領域已經(jīng)得到了廣泛的接受,是基于 SOAP 和 Web 服務描述語言(Web Services Description Language,WSDL)的 Web 服務的更為簡單的替代方法。 接口設計方面這一轉變的關鍵證據(jù)是主流 Web 2.0 服務提供者(包括 Yahoo、Google 和 Facebook)對 REST 的采用,這些提供者棄用或放棄了基于 SOAP 和 WSDL 的接口,而采用了更易于使用、面向資源的模型來公開其服務。 在本文中,Alex Rodriguez 將向您介紹 REST 的基本原理。 REST 定義了一組體系架構原則,您可以根據(jù)這些原則設計以系統(tǒng)資源為中心的 Web 服務,包括使用不同語言編寫的客戶端如何通過 HTTP 處理和傳輸資源狀態(tài)。 如果考慮使用它的 Web 服務的數(shù)量,REST 近年來已經(jīng)成為最主要的 Web 服務設計模型。 事實上,REST 對 Web 的影響非常大,由于其使用相當方便,已經(jīng)普遍地取代了基于 SOAP 和 WSDL 的接口設計。 REST 這個概念于 2000 年由 Roy Fielding 在就讀加州大學歐文分校期間在學術論文“Architectural Styles and the Design of Network-based Software Architectures”(請參見參考資料以獲取此論文的鏈接)首次提出,他的論文中對使用 Web 服務作為分布式計算平臺的一系列軟件體系結構原則進行了分析,而其中提出的 REST 概念并沒有獲得現(xiàn)在這么多關注。 多年以后的今天,REST 的主要框架已經(jīng)開始出現(xiàn),但仍然在開發(fā)中,因為它已經(jīng)被廣泛接納到各個平臺中,例如通過 JSR-311 成為了 Java? 6 不可或缺的部分。 本文認為,對于今天正在吸引如此多注意力的最純粹形式的 REST Web 服務,其具體實現(xiàn)應該遵循四個基本設計原則:
下面幾個部分將詳述這四個原則,并提供技術原理解釋,說明為什么這些原則對 REST Web 服務設計人員非常重要。
基于 REST 的 Web 服務的主要特征之一是以遵循 RFC 2616 定義的協(xié)議的方式顯式使用 HTTP 方法。例如,HTTP GET 被定義為數(shù)據(jù)產(chǎn)生方法,旨在由客戶端應用程序用于檢索資源以從 Web 服務器獲取數(shù)據(jù),或者執(zhí)行某個查詢并預期 Web 服務器將查找某一組匹配資源然后使用該資源進行響應。 REST 要求開發(fā)人員顯式地使用 HTTP 方法,并且使用方式與協(xié)議定義一致。 這個基本 REST 設計原則建立了創(chuàng)建、讀取、更新和刪除(create, read, update, and delete,CRUD)操作與 HTTP 方法之間的一對一映射。 根據(jù)此映射:
許多 Web API 中所固有的一個令人遺憾的設計缺陷在于將 HTTP 方法用于非預期用途。 例如,HTTP GET 請求中的請求 URI 通常標識一個特定的資源。 或者,請求 URI 中的查詢字符串包括一組參數(shù),這些參數(shù)定義服務器用于查找一組匹配資源的搜索條件。 至少,HTTP/1.1 RFC 是這樣描述 GET 方法的。 但是在許多情況下,不優(yōu)雅的 Web API 使用 HTTP GET 來觸發(fā)服務器上的事務性操作——例如,向數(shù)據(jù)庫添加記錄。 在這些情況下,GET 請求 URI 屬于不正確使用,或者至少不是以基于 REST 的方式使用。 如果 Web API 使用 GET 調用遠程過程,則應該類似如下: 這不是非常優(yōu)雅的設計,因為上面的 Web 方法支持通過 HTTP GET 進行狀態(tài)更改操作。 換句話說,該 HTTP GET 請求具有副作用。 如果處理成功,則該請求的結果是向基礎數(shù)據(jù)存儲區(qū)添加一個新用戶——在此例中為 Robert。 這里的問題主要在語義上。 Web 服務器旨在通過檢索與請求 URI 中的路徑(或查詢條件)匹配的資源,并在響應中返回這些資源或其表示形式,從而響應 HTTP GET 請求,而不是向數(shù)據(jù)庫添加記錄。 從該協(xié)議方法的預期用途的角度看,然后再從與 HTTP/1.1 兼容的 Web 服務器的角度看,以這種方式使用 GET 是不一致的。 除了語義之外,GET 的其他問題在于,為了觸發(fā)數(shù)據(jù)庫中的記錄的刪除、修改或添加,或者以某種方式更改服務器端狀態(tài),它請求 Web 緩存工具(爬網(wǎng)程序)和搜索引擎簡單地通過對某個鏈接進行爬網(wǎng)處理,從而意外地做出服務器端更改。 克服此常見問題的簡單方法是將請求 URI 上的參數(shù)名稱和值轉移到 XML 標記中。 這樣產(chǎn)生的標記是要創(chuàng)建的實體的 XML 表示形式,可以在 HTTP POST 的正文中進行發(fā)送,此 HTTP POST 的請求 URI 是該實體的預期父實體(請參見清單 1 和 2): 清單 1. 之前
清單 2. 之后
上述方法是基于 REST 的請求的范例: 正確使用 HTTP POST 并將有效負載包括在請求的正文中。 在接收端,可以通過將正文中包含的資源添加為請求 URI 中標識的資源的從屬資源,從而處理該請求;在此例下,應該將新資源添加為 然后客戶端應用程序可以使用新的 URI 獲取資源的表示形式,并至少邏輯地指明該資源位于 清單 3. HTTP GET 請求
以這種方式使用 GET 是顯式的,因為 GET 僅用于數(shù)據(jù)檢索。 GET 是應該沒有副作用的操作,即所謂的等冪性 屬性。 當支持通過 HTTP GET 執(zhí)行更新操作時,也需要應用類似的 Web 方法重構,如清單 4 所示。 清單 4. 通過 HTTP GET 進行更新
這更改了資源的 清單 5. HTTP PUT 請求
使用 PUT 取代原始資源可以提供更清潔的接口,這樣的接口與 REST 的原則以及與 HTTP 方法的定義一致。 清單 5 中的 PUT 請求是顯式的,因為它通過在請求 URI 中標識要更新的資源來指向該資源,并且它在 PUT 請求的正文中將資源的新表示形式從客戶端傳輸?shù)椒掌?,而不是在請?URI 上將資源屬性作為參數(shù)名稱和值的松散集合進行傳輸。 清單 5 還具有將資源從 作為一般設計原則,通過在 URI 中使用名詞而不是動詞,對于遵循有關顯式使用 HTTP 方法的 REST 指導原則是有幫助的。 在基于 REST 的 Web 服務中,協(xié)議已經(jīng)對動詞(POST、GET、PUT 和 DELETE)進行了定義。 在理想的情況下,為了保持接口的通用化,并允許客戶端明確它們調用的操作,Web 服務不應該定義更多的動詞或遠程過程,例如
REST Web 服務需要擴展以滿足日益提高的性能要求。 具有負載平衡和故障轉移功能、代理和網(wǎng)關的服務器集群通常以形成服務拓撲的方式進行組織,從而允許根據(jù)需要將請求從一個服務器路由到另一個服務器,以減少 Web 服務調用的總體響應時間。 要使用中間服務器擴大規(guī)模,REST Web 服務需要發(fā)送完整、獨立的請求;也就是說,發(fā)送的請求包括所有需要滿足的數(shù)據(jù),以便中間服務器中的組件能夠進行轉發(fā)、路由和負載平衡,而不需要在請求之間在本地保存任何狀態(tài)。 完整、獨立的請求不要求服務器在處理請求時檢索任何類型的應用程序上下文或狀態(tài)。 REST Web 服務應用程序(或客戶端)在 HTTP Header 和請求正文中包括服務器端組件生成響應所需要的所有參數(shù)、上下文和數(shù)據(jù)。 這種意義上的無狀態(tài)可以改進 Web 服務性能,并簡化服務器端組件的設計和實現(xiàn),因為服務器上沒有狀態(tài),從而消除了與外部應用程序同步會話數(shù)據(jù)的需要。 圖 1 演示了一個有狀態(tài)的服務,某個應用程序可能向其請求多頁結果集中的下一個頁面,并假設該服務跟蹤應用程序在結果集中導航時的離開位置。 在這個有狀態(tài)的設計中,該服務遞增并在某個位置存儲 圖 1. 有狀態(tài)的設計 ![]() 類似如此的有狀態(tài)的服務變得復雜化了。 在 Java Platform, Enterprise Edition (Java EE) 環(huán)境中,有狀態(tài)的服務需要大量的預先考慮,以高效地存儲會話數(shù)據(jù)和支持整個 Java EE 容器集群中的會話數(shù)據(jù)同步。 在此類環(huán)境中,存在一個 Servlet/JavaServer Pages (JSP) 和 Enterprise JavaBeans (EJB) 開發(fā)人員非常熟悉的問題,他們經(jīng)常在會話復制過程中艱難地查找引發(fā) 另一方面,無狀態(tài)的服務器端組件不那么復雜,很容易跨進行負載平衡的服務器進行設計、編寫和分布。 無狀態(tài)的服務不僅性能更好,而且還將大部分狀態(tài)維護職責轉移給客戶端應用程序。 在基于 REST 的 Web 服務中,服務器負責生成響應,并提供使客戶端能夠獨自維護應用程序狀態(tài)的接口。 例如,在針對多頁結果集的請求中,客戶端應該包括要檢索的實際頁編號,而不是簡單地要求檢索下一頁(請參見圖 2)。 圖 2. 無狀態(tài)的設計 ![]() 無狀態(tài)的 Web 服務生成的響應鏈接到結果集中的下一個頁編號,并允許客戶端完成所需的相關工作以便保留此值。 可以作為大致的分離將基于 REST 的 Web 服務設計的這個方面劃分為兩組職責,以闡明如何維護無狀態(tài)的服務: 服務器
客戶端應用程序
客戶端應用程序與服務之間的這種協(xié)作對于基于 REST 的 Web 服務中的無狀態(tài)性極為重要。 它通過節(jié)省帶寬和最小化服務器端應用程序狀態(tài)改進了性能。
從對資源尋址的客戶端應用程序的角度看,URI 決定了 REST Web 服務將具有的直觀程度,以及服務是否將以設計人員能夠預測的方式被使用。 基于 REST 的 Web 服務的第三個特征完全與 URI 相關。 REST Web 服務 URI 的直觀性應該達到很容易猜測的程度。 將 URI 看作是自身配備文檔說明的接口,開發(fā)人員只需很少(如果有的話)的解釋或參考資料即可了解它指向什么,并獲得相關的資源。 為此,URI 的結構應該簡單、可預測且易于理解。 實現(xiàn)這種級別的可用性的方法之一是定義目錄結構式的 URI。 此類 URI 具有層次結構,其根為單個路徑,從根開始分支的是公開服務的主要方面的子路徑。 根據(jù)此定義,URI 并不只是斜杠分隔的字符串,而是具有在節(jié)點上連接在一起的下級和上級分支的樹。 例如,在一個收集從 Java 到報紙的各種主題的討論線程服務中,您可能定義類似如下的結構化 URI 集合: 根 在某些情況下,指向資源的路徑尤其適合于目錄式結構。 例如,以按日期進行組織的資源為例,這種資源非常適合于使用層次結構語法。 此示例非常直觀,因為它基于規(guī)則: 第一個路徑片段是四個數(shù)字的年份,第二個路徑片斷是兩個數(shù)字的日期,第三個片段是兩個數(shù)字的月份。 這樣解釋它可能有點愚蠢,但這就是我們追求的簡單級別。 人類和計算機能夠容易地生成類似如此的結構化 URI,因為這些 URI 基于規(guī)則。 在語法的空隙中填入路徑部分就大功告成了,因為存在用于組合 URI 的明確模式: 在考慮基于 REST 的 Web 服務的 URI 結構時,需要指出的一些附加指導原則包括:
URI 還應該是靜態(tài)的,以便在資源發(fā)生更改或服務的實現(xiàn)發(fā)生更改時,鏈接保持不變。 這可以實現(xiàn)書簽功能。 URI 中編碼的資源之間的關系與在存儲資源的位置表示資源關系的方式無關也是非常重要的。
資源表示形式通常反映了在客戶端應用程序請求資源時的資源當前狀態(tài)及其屬性。 這種意義上的資源表示形式只是時間上的快照。 這可以像數(shù)據(jù)庫中的記錄表示形式一樣簡單,其中包括列名稱與 XML 標記之間的映射,XML 中的元素值包含行值。 或者,如果系統(tǒng)具有數(shù)據(jù)模型,那么根據(jù)此定義,資源表示形式是系統(tǒng)的數(shù)據(jù)模型中的對象之一的屬性快照。 這些對象就是您希望您的 REST Web 服務為客戶端提供的資源。 基于 REST 的 Web 服務設計中的最后一組約束與應用程序和服務在請求/響應有效負載或 HTTP 正文中交換的數(shù)據(jù)的格式有關。 這是真正值得將一切保持簡單、可讀和連接在一起的方面。 數(shù)據(jù)模型中的對象通常以某種方式相關,應該以在將資源傳輸?shù)娇蛻舳藨贸绦驎r表示資源的方式,反映數(shù)據(jù)模型對象(資源)之間的關系。 在討論線程服務中,連接的資源表示形式的示例可能包括根討論主題及其屬性,以及指向為該主題提供的響應的嵌入鏈接。 清單 6. 討論線程的 XML 表示形式
最后,為了賦予客戶端請求最適合它們的特定內容類型的能力,您的服務的構造應該利用內置的 HTTP Accept Header,其中該 Header 的值為 MIME 類型。 基于 REST 的服務使用的一些常見 MIME 類型如表 1 所示。 表 1. 基于 REST 的服務使用的常見 MIME 類型
這使得服務可由運行在不同平臺和設備上并采用不同語言編寫的各種各樣的客戶端所使用。 使用 MIME 類型和 HTTP Accept Header 是一種稱為內容協(xié)商 的機制,這種機制允許客戶端選擇適合于它們的數(shù)據(jù)格式,并最小化服務與使用服務的應用程序之間的數(shù)據(jù)耦合。
REST 并非始終是正確的選擇。 它作為一種設計 Web 服務的方法而變得流行,這種方法對專有中間件(例如某個應用程序服務器)的依賴比基于 SOAP 和 WSDL 的方法更少。 在某種意義上,通過強調 URI 和 HTTP 等早期 Internet 標準,REST 是對大型應用程序服務器時代之前的 Web 方式的回歸。 正如您已經(jīng)在所謂的基于 REST 的接口設計原則中研究過的一樣,XML over HTTP 是一個功能強大的接口,允許內部應用程序(例如基于 Asynchronous JavaScript + XML (Ajax) 的自定義用戶界面)輕松連接、定位和使用資源。 事實上,Ajax 與 REST 之間的完美配合已增加了當今人們對 REST 的注意力。 通過基于 REST 的 API 公開系統(tǒng)資源是一種靈活的方法,可以為不同種類的應用程序提供以標準方式格式化的數(shù)據(jù)。 它可以幫助滿足集成需求(這對于構建可在其中容易地組合 (Mashup) 數(shù)據(jù)的系統(tǒng)非常關鍵),并幫助將基于 REST 的基本服務集擴展或構建為更大的集合。 本文僅略微談到了基礎,但愿本文的討論會誘發(fā)您繼續(xù)探索該主題。 |