在 IBM Bluemix 云平臺(tái)上開發(fā)并部署您的下一個(gè)應(yīng)用,。 REST 簡介在開始我們的正式討論之前,讓我們簡單看一下 REST 的定義,。 REST(Representational State Transfer)是 Roy Fielding 提出的一個(gè)描述互聯(lián)系統(tǒng)架構(gòu)風(fēng)格的名詞,。為什么稱為 REST?Web 本質(zhì)上由各種各樣的資源組成,,資源由 URI 唯一標(biāo)識(shí),。瀏覽器(或者任何其它類似于瀏覽器的應(yīng)用程序)將展示出該資源的一種表現(xiàn)方式,或者一種表現(xiàn)狀態(tài),。如果用戶在該頁面中定向到指向其它資源的鏈接,,則將訪問該資源,并表現(xiàn)出它的狀態(tài),。這意味著客戶端應(yīng)用程序隨著每個(gè)資源表現(xiàn)狀態(tài)的不同而發(fā)生狀態(tài)轉(zhuǎn)移,,也即所謂 REST。 關(guān)于 REST 本身,,本文就不再這里過多地討論,,讀者可以參考 developerWorks 上其它介紹 REST 的文章。本文的重點(diǎn)在于通過 REST 與 SOAP Web 服務(wù)的對比,,幫助讀者更深刻理解 REST 架構(gòu)風(fēng)格的特點(diǎn),,優(yōu)勢。 應(yīng)用場景介紹(在線用戶管理)本文將借助于一個(gè)應(yīng)用場景,,通過基于 REST 和 SOAP Web 服務(wù)的不同實(shí)現(xiàn),,來對兩者進(jìn)行對比,。該應(yīng)用場景的業(yè)務(wù)邏輯會(huì)盡量保持簡單且易于理解,以有助于把我們的重心放在 REST 和 SOAP Web 服務(wù)技術(shù)特質(zhì)對比上,。 需求描述這是一個(gè)在線的用戶管理模塊,負(fù)責(zé)用戶信息的創(chuàng)建,,修改,,刪除,查詢,。用戶的信息主要包括:
需求用例圖如下: 圖 1. 需求用例圖如圖 1 所示,,客戶端 1(Client1)與客戶端 2(Client2)對于信息的存取具有不同的權(quán)限,客戶端 1 可以執(zhí)行所有的操作,,而客戶端 2 只被允許執(zhí)行用戶查詢(Query User)與用戶列表查詢(Query User List),。關(guān)于這一點(diǎn),我們在對 REST Web 服務(wù)與 SOAP Web 服務(wù)安全控制對比時(shí)會(huì)具體談到,。下面我們將分別向您介紹如何使用 REST 和 SOAP 架構(gòu)實(shí)現(xiàn) Web 服務(wù),。 使用 REST 實(shí)現(xiàn) Web 服務(wù)本部分將基于 Restlet 框架來實(shí)現(xiàn)該應(yīng)用。Restlet 為那些要采用 REST 結(jié)構(gòu)體系來構(gòu)建應(yīng)用程序的 Java 開發(fā)者提供了一個(gè)具體的解決方案,。關(guān)于更多的 Restlet 相關(guān)內(nèi)容,,本文不做深入討論,請見參考資源列表,。 設(shè)計(jì)我們將采用遵循 REST 設(shè)計(jì)原則的 ROA(Resource-Oriented Architecture,,面向資源的體系架構(gòu))進(jìn)行設(shè)計(jì)。ROA 是什么,?簡單點(diǎn)說,,ROA 是一種把實(shí)際問題轉(zhuǎn)換成 REST 式 Web 服務(wù)的方法,它使得 URI,、HTTP 和 XML 具有跟其他 Web 應(yīng)用一樣的工作方式,。 在使用 ROA 進(jìn)行設(shè)計(jì)時(shí),我們需要把真實(shí)的應(yīng)用需求轉(zhuǎn)化成 ROA 中的資源,,基本上遵循以下的步驟:
接下來我們按照以上的步驟來設(shè)計(jì)本文的應(yīng)用案例,。 在線用戶管理所涉及的數(shù)據(jù)集就是用戶信息,如果映射到 ROA 資源,,主要包括兩類資源:用戶及用戶列表,。用戶資源的 URI 用 清單 1. 用戶列表資源 Representation<?xml version="1.0" encoding="UTF-8" standalone="no"?> <users> <user> <name>tester</name> <link>http://localhost:8182/v1/users/tester</link> </user> <user> <name>tester1</name> <link>http://localhost:8182/v1/users/tester1</link> </user> </users> 清單 2. 用戶資源 Representation<?xml version="1.0" encoding="UTF-8" standalone="no"?> <user> <name>tester</name> <title>software engineer</title> <company>IBM</company> <email>[email protected]</email> <description>testing!</description> </user> 客戶端通過 User List Resource 提供的 LINK 信息 ( 如 : Restful Web 服務(wù)架構(gòu)首先給出 Web 服務(wù)使用 REST 風(fēng)格實(shí)現(xiàn)的整體架構(gòu)圖,,如下圖所示: 圖 2. REST 實(shí)現(xiàn)架構(gòu)接下來,,我們將基于該架構(gòu),使用 Restlet 給出應(yīng)用的 RESTful Web 服務(wù)實(shí)現(xiàn),。 下面的章節(jié)中,,我們將給出 REST Web 服務(wù)實(shí)現(xiàn)的核心代碼片段。關(guān)于完整的代碼清單,,讀者可以通過資源列表下載,。 客戶端實(shí)現(xiàn)清單 3 給出的是客戶端的核心實(shí)現(xiàn)部分,其主要由四部分組成:使用 HTTP PUT 增加,、修改用戶資源,,使用 HTTP GET 得到某一具體用戶資源,使用 HTTP DELETE 刪除用戶資源,,使用 HTTP GET 得到用戶列表資源,。而這四部分也正對應(yīng)了圖 2 關(guān)于架構(gòu)描述的四對 HTTP 消息來回。關(guān)于 UserRestHelper 類的完整實(shí)現(xiàn),,請讀者參見本文所附的代碼示例,。 清單 3. 客戶端實(shí)現(xiàn)public class UserRestHelper { //The root URI of our ROA implementation. public static final tring APPLICATION_URI = "http://localhost:8182/v1"; //Get the URI of user resource by user name. private static String getUserUri(String name) { return APPLICATION_URI + "/users/" + name; } //Get the URI of user list resource. private static String getUsersUri() { return APPLICATION_URI + "/users"; } //Delete user resource from server by user name. //使用 HTTP DELETE 方法經(jīng)由 URI 刪除用戶資源 public static void deleteFromServer(String name) { Response response = new Client(Protocol.HTTP).delete(getUserUri(name)); …… } //Put user resource to server. //使用 HTTP PUT 方法經(jīng)由 URI 增加或者修改用戶資源 public static void putToServer(User user) { //Fill FORM using user data. Form form = new Form(); form.add("user[title]", user.getTitle()); form.add("user[company]", user.getCompany()); form.add("user[email]", user.getEmail()); form.add("user[description]", user.getDescription()); Response putResponse = new Client(Protocol.HTTP).put( getUserUri(user.getName()), form.getWebRepresentation()); …… } //Output user resource to console. public static void printUser(String name) { printUserByURI(getUserUri(name)); } //Output user list resource to console. //使用 HTTP GET 方法經(jīng)由 URI 顯示用戶列表資源 public static void printUserList() { Response getResponse = new Client(Protocol.HTTP).get(getUsersUri()); if (getResponse.getStatus().isSuccess()) { DomRepresentation result = getResponse.getEntityAsDom(); //The following code line will explore this XML document and output //each user resource to console. …… } else { System.out.println("Unexpected status:"+ getResponse.getStatus()); } } //Output user resource to console. //使用 HTTP GET 方法經(jīng)由 URI 顯示用戶資源 private static void printUserByURI(String uri) { Response getResponse = new Client(Protocol.HTTP).get(uri); if (getResponse.getStatus().isSuccess()) { DomRepresentation result = getResponse.getEntityAsDom(); //The following code line will explore this XML document and output //current user resource to console. …… } else { System.out.println("unexpected status:"+ getResponse.getStatus()); } } } 服務(wù)器端實(shí)現(xiàn)清單 4 給出的是服務(wù)器端對于用戶資源類(UserResourc)的實(shí)現(xiàn),其核心的功能是響應(yīng)有關(guān)用戶資源的 HTTP GET/PUT/DELETE 請求,,而這些請求響應(yīng)邏輯正對應(yīng)了 UserRestHelper 類中關(guān)于用戶資源類的 HTTP 請求,。 清單 4. 服務(wù)器端實(shí)現(xiàn)public class UserResource extends Resource { private User _user; private String _userName; public UserResource(Context context, Request request, Response response) { //Constructor is here. …… } //響應(yīng) HTTP DELETE 請求邏輯 public void delete() { // Remove the user from container. getContainer().remove(_userName); getResponse().setStatus(Status.SUCCESS_OK); } //This method will be called by handleGet. public Representation getRepresentation(Variant variant) { Representation result = null; if (variant.getMediaType().equals(MediaType.TEXT_XML)) { Document doc = createDocument(this._user); result = new DomRepresentation(MediaType.TEXT_XML, doc); } return result; } //響應(yīng) HTTP PUT 請求邏輯。 public void put(Representation entity) { if (getUser() == null) { //The user doesn't exist, create it setUser(new User()); getUser().setName(this._userName); getResponse().setStatus(Status.SUCCESS_CREATED); } else { getResponse().setStatus(Status.SUCCESS_NO_CONTENT); } //Parse the entity as a Web form. Form form = new Form(entity); getUser().setTitle(form.getFirstValue("user[title]")); getUser().setCompany(form.getFirstValue("user[company]")); getUser().setEmail(form.getFirstValue("user[email]")); getUser().setDescription(form.getFirstValue("user[description]")); //Put the user to the container. getApplication().getContainer().put(_userName, getUser()); } //響應(yīng) HTTP GET 請求邏輯,。 public void handleGet() { super.handleGet(); if(this._user != null ) { getResponse().setEntity(getRepresentation( new Variant(MediaType.TEXT_XML))); getResponse().setStatus(Status.SUCCESS_OK); } else { getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND); } } //build XML document for user resource. private Document createDocument(User user) { //The following code line will create XML document according to user info. …… } //The remaining methods here …… } UserResource 類是對用戶資源類的抽象,,包括了對該資源的創(chuàng)建修改(put 方法),讀?。╤andleGet 方法 )和刪除(delete 方法),,被創(chuàng)建出來的 UserResource 類實(shí)例被 Restlet 框架所托管,所有操縱資源的方法會(huì)在相應(yīng)的 HTTP 請求到達(dá)后被自動(dòng)回調(diào),。 另外,,在服務(wù)端,還需要實(shí)現(xiàn)代表用戶列表資源的資源類 UserListResource,,它的實(shí)現(xiàn)與 UserResource 類似,,響應(yīng) HTTP GET 請求,讀取當(dāng)前系統(tǒng)內(nèi)的所有用戶信息,,形成如清單 1 所示的用戶列表資源 Representation,,然后返回該結(jié)果給客戶端,。具體的實(shí)現(xiàn)請讀者參見本文所附的代碼示例。 使用 SOAP 實(shí)現(xiàn) Web 服務(wù)本文對于 SOAP 實(shí)現(xiàn),,就不再像 REST 那樣,,具體到代碼級別的實(shí)現(xiàn)。本節(jié)將主要通過 URI,HTTP 和 XML 來宏觀上表述 SOAP Web 服務(wù)實(shí)現(xiàn)的技術(shù)本質(zhì),,為下一節(jié) REST Web 服務(wù)與 SOAP Web 服務(wù)的對比做鋪墊,。 SOAP Web 服務(wù)架構(gòu)同樣,首先給出 SOAP 實(shí)現(xiàn)的整體架構(gòu)圖,,如下圖所示: 圖 3. SOAP 實(shí)現(xiàn)架構(gòu)可以看到,,與 REST 架構(gòu)相比,,SOAP 架構(gòu)圖明顯不同的是:所有的 SOAP 消息發(fā)送都使用 HTTP POST 方法,,并且所有 SOAP 消息的 URI 都是一樣的,這是基于 SOAP 的 Web 服務(wù)的基本實(shí)踐特征,。 獲得用戶信息列表基于 SOAP 的客戶端創(chuàng)建如清單 5 所示的 SOAP XML 文檔,,它通過類 RPC 方式來獲得用戶列表信息。 清單 5. getUserList SOAP 消息<?xml version="1.0" encoding="UTF-8" standalone="no"?> <soap:Envelope xmlns:soap="http://schemas./soap/envelope/"> <soap:Body> <p:getUserList xmlns:p="http://www."/> </soap:Body> </soap:Envelope> 客戶端將使用 HTTP 的 POST 方法,,將上述的 SOAP 消息發(fā)送至 清單 6. getUserListResponse 消息<?xml version="1.0" encoding="UTF-8" standalone="no"?> <soap:Envelope xmlns:soap="http://schemas./soap/envelope/"> <soap:Body> <p:get UserListResponse xmlns:p="http://www."> <Users> <username>tester<username> <username>tester1<username> ...... </Users> <p: getUserListResponse > </soap:Body> </soap:Envelope> 獲得某一具體用戶信息清單 7. getUserByName SOAP 消息<?xml version="1.0" encoding="UTF-8" standalone="no"?> <soap:Envelope xmlns:soap="http://schemas./soap/envelope/"> <soap:Body> <p:getUserByName xmlns:p="http://www."> <username>tester</username> </p:getUserByName > </soap:Body> </soap:Envelope> 同樣地,,客戶端將使用 HTTP 的 POST 方法,將上述的 SOAP 消息發(fā)送至 清單 8. getUserByNameResponse SOAP 消息<?xml version="1.0" encoding="UTF-8" standalone="no"?> <soap:Envelope xmlns:soap="http://schemas./soap/envelope/"> <soap:Body> <p:getUserByNameResponse xmlns:p="http://www."> <name>tester</name> <title>software engineer</title> <company>IBM</company> <email>[email protected]</email> <description>testing!</description> </p:getUserByNameResponse> </soap:Body> </soap:Envelope> 實(shí)際上,,創(chuàng)建新的用戶,過程也比較類似,,在這里,,就不一一列出,因?yàn)檫@兩個(gè)例子對于本文在選定的點(diǎn)上對比 REST 與 SOAP 已經(jīng)足夠了,。 REST 與 SOAP 比較本節(jié)從以下幾個(gè)方面來對比上面兩節(jié)給出 REST 實(shí)現(xiàn)與 SOAP 實(shí)現(xiàn),。 接口抽象RESTful Web 服務(wù)使用標(biāo)準(zhǔn)的 HTTP 方法 (GET/PUT/POST/DELETE) 來抽象所有 Web 系統(tǒng)的服務(wù)能力,而不同的是,,SOAP 應(yīng)用都通過定義自己個(gè)性化的接口方法來抽象 Web 服務(wù),,這更像我們經(jīng)常談到的 RPC。例如本例中的 getUserList 與 getUserByName 方法,。 RESTful Web 服務(wù)使用標(biāo)準(zhǔn)的 HTTP 方法優(yōu)勢,,從大的方面來講:標(biāo)準(zhǔn)化的 HTTP 操作方法,結(jié)合其他的標(biāo)準(zhǔn)化技術(shù),,如 URI,,HTML,,XML 等,將會(huì)極大提高系統(tǒng)與系統(tǒng)之間整合的互操作能力,。尤其在 Web 應(yīng)用領(lǐng)域,,RESTful Web 服務(wù)所表達(dá)的這種抽象能力更加貼近 Web 本身的工作方式,也更加自然,。 同時(shí),,使用標(biāo)準(zhǔn) HTTP 方法實(shí)現(xiàn)的 RRESTful Web 服務(wù)也帶來了 HTTP 方法本身的一些優(yōu)勢:
HTTP 協(xié)議從本質(zhì)上說是一種無狀態(tài)的協(xié)議,客戶端發(fā)出的 HTTP 請求之間可以相互隔離,,不存在相互的狀態(tài)依賴,。基于 HTTP 的 ROA,,以非常自然的方式來實(shí)現(xiàn)無狀態(tài)服務(wù)請求處理邏輯,。對于分布式的應(yīng)用而言,任意給定的兩個(gè)服務(wù)請求 Request 1 與 Request 2, 由于它們之間并沒有相互之間的狀態(tài)依賴,,就不需要對它們進(jìn)行相互協(xié)作處理,,其結(jié)果是:Request 1 與 Request 2 可以在任何的服務(wù)器上執(zhí)行,這樣的應(yīng)用很容易在服務(wù)器端支持負(fù)載平衡 (load-balance),。
HTTP 的 GET,、HEAD 請求本質(zhì)上應(yīng)該是安全的調(diào)用,即:GET,、HEAD 調(diào)用不會(huì)有任何的副作用,,不會(huì)造成服務(wù)器端狀態(tài)的改變。對于服務(wù)器來說,,客戶端對某一 URI 做 n 次的 GET,、HAED 調(diào)用,其狀態(tài)與沒有做調(diào)用是一樣的,,不會(huì)發(fā)生任何的改變,。 HTTP 的 PUT、DELTE 調(diào)用,,具有冪指相等特性 , 即:客戶端對某一 URI 做 n 次的 PUT,、DELTE 調(diào)用,其效果與做一次的調(diào)用是一樣的,。HTTP 的 GET,、HEAD 方法也具有冪指相等特性。 HTTP 這些標(biāo)準(zhǔn)方法在原則上保證你的分布式系統(tǒng)具有這些特性,,以幫助構(gòu)建更加健壯的分布式系統(tǒng),。 安全控制為了說明問題,基于上面的在線用戶管理系統(tǒng),,我們給定以下場景: 參考一開始我們給出的用例圖,,對于客戶端 Client2,,我們只希望它能以只讀的方式訪問 User 和 User List 資源,而 Client1 具有訪問所有資源的所有權(quán)限,。 如何做這樣的安全控制,? 通行的做法是:所有從客戶端 Client2 發(fā)出的 HTTP 請求都經(jīng)過代理服務(wù)器 (Proxy Server)。代理服務(wù)器制定安全策略:所有經(jīng)過該代理的訪問 User 和 User List 資源的請求只具有讀取權(quán)限,,即:允許 GET/HEAD 操作,,而像具有寫權(quán)限的 PUT/DELTE 是不被允許的。 如果對于 REST,,我們看看這樣的安全策略是如何部署的,。如下圖所示: 圖 4. REST 與代理服務(wù)器 (Proxy Servers)一般代理服務(wù)器的實(shí)現(xiàn)根據(jù) (URI, HTTP Method) 兩元組來決定 HTTP 請求的安全合法性。 當(dāng)發(fā)現(xiàn)類似于(http://localhost:8182/v1/users/{username},,DELETE)這樣的請求時(shí),,予以拒絕。 對于 SOAP,,如果我們想借助于既有的代理服務(wù)器進(jìn)行安全控制,,會(huì)比較尷尬,,如下圖: 圖 5. SOAP 與代理服務(wù)器 (Proxy Servers)所有的 SOAP 消息經(jīng)過代理服務(wù)器,,只能看到( 關(guān)于緩存眾所周知,對于基于網(wǎng)絡(luò)的分布式應(yīng)用,,網(wǎng)絡(luò)傳輸是一個(gè)影響應(yīng)用性能的重要因素,。如何使用緩存來節(jié)省網(wǎng)絡(luò)傳輸帶來的開銷,這是每一個(gè)構(gòu)建分布式網(wǎng)絡(luò)應(yīng)用的開發(fā)人員必須考慮的問題,。 HTTP 協(xié)議帶條件的 HTTP GET 請求 (Conditional GET) 被設(shè)計(jì)用來節(jié)省客戶端與服務(wù)器之間網(wǎng)絡(luò)傳輸帶來的開銷,,這也給客戶端實(shí)現(xiàn) Cache 機(jī)制 ( 包括在客戶端與服務(wù)器之間的任何代理 ) 提供了可能。HTTP 協(xié)議通過 HTTP HEADER 域:If-Modified-Since/Last- Modified,,If-None-Match/ETag 實(shí)現(xiàn)帶條件的 GET 請求,。 REST 的應(yīng)用可以充分地挖掘 HTTP 協(xié)議對緩存支持的能力。當(dāng)客戶端第一次發(fā)送 HTTP GET 請求給服務(wù)器獲得內(nèi)容后,,該內(nèi)容可能被緩存服務(wù)器 (Cache Server) 緩存,。當(dāng)下一次客戶端請求同樣的資源時(shí),緩存可以直接給出響應(yīng),,而不需要請求遠(yuǎn)程的服務(wù)器獲得,。而這一切對客戶端來說都是透明的,。 圖 6. REST 與緩存服務(wù)器 (Cache Server)而對于 SOAP,情況又是怎樣的呢,? 使用 HTTP 協(xié)議的 SOAP,,由于其設(shè)計(jì)原則上并不像 REST 那樣強(qiáng)調(diào)與 Web 的工作方式相一致,所以,,基于 SOAP 應(yīng)用很難充分發(fā)揮 HTTP 本身的緩存能力,。 圖 7. SOAP 與緩存服務(wù)器 (Cache Server)兩個(gè)因素決定了基于 SOAP 應(yīng)用的緩存機(jī)制要遠(yuǎn)比 REST 復(fù)雜: 其一、所有經(jīng)過緩存服務(wù)器的 SOAP 消息總是 HTTP POST,,緩存服務(wù)器如果不解碼 SOAP 消息體,,沒法知道該 HTTP 請求是否是想從服務(wù)器獲得數(shù)據(jù)。 其二,、SOAP 消息所使用的 URI 總是指向 SOAP 的服務(wù)器,,如本文例子中的 關(guān)于連接性在一個(gè)純的 SOAP 應(yīng)用中,,URI 本質(zhì)上除了用來指示 SOAP 服務(wù)器外,,本身沒有任何意義。與 REST 的不同的是,,無法通過 URI 驅(qū)動(dòng) SOAP 方法調(diào)用,。例如在我們的例子中,當(dāng)我們通過 getUserList SOAP 消息獲得所有的用戶列表后,,仍然無法通過既有的信息得到某個(gè)具體的用戶信息,。唯一的方法只有通過 WSDL 的指示,通過調(diào)用 getUserByName 獲得,,getUserList 與 getUserByName 是彼此孤立的,。 而對于 REST,情況是完全不同的:通過 總結(jié)典型的基于 SOAP 的 Web 服務(wù)以操作為中心,,每個(gè)操作接受 XML 文檔作為輸入,,提供 XML 文檔作為輸出。在本質(zhì)上講,,它們是 RPC 風(fēng)格的,。而在遵循 REST 原則的 ROA 應(yīng)用中,,服務(wù)是以資源為中心的,對每個(gè)資源的操作都是標(biāo)準(zhǔn)化的 HTTP 方法,。 本文主要集中在以上的幾個(gè)方面,,對 SOAP 與 REST 進(jìn)行了對比,可以看到,,基于 REST 構(gòu)建的系統(tǒng)其系統(tǒng)的擴(kuò)展能力要強(qiáng)于 SOAP,,這可以體現(xiàn)在它的統(tǒng)一接口抽象、代理服務(wù)器支持,、緩存服務(wù)器支持等諸多方面,。并且,伴隨著 Web Site as Web Services 演進(jìn)的趨勢,,基于 REST 設(shè)計(jì)和實(shí)現(xiàn)的簡單性和強(qiáng)擴(kuò)展性,,有理由相信,REST 將會(huì)成為 Web 服務(wù)的一個(gè)重要架構(gòu)實(shí)踐領(lǐng)域,。 下載
|
|