在 IBM Bluemix 云平臺(tái)上開發(fā)并部署您的下一個(gè)應(yīng)用。 REST 簡(jiǎn)介在開始我們的正式討論之前,讓我們簡(jiǎn)單看一下 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ù)的對(duì)比,幫助讀者更深刻理解 REST 架構(gòu)風(fēng)格的特點(diǎn),優(yōu)勢(shì)。 應(yīng)用場(chǎng)景介紹(在線用戶管理)本文將借助于一個(gè)應(yīng)用場(chǎng)景,通過基于 REST 和 SOAP Web 服務(wù)的不同實(shí)現(xiàn),來對(duì)兩者進(jìn)行對(duì)比。該應(yīng)用場(chǎng)景的業(yè)務(wù)邏輯會(huì)盡量保持簡(jiǎn)單且易于理解,以有助于把我們的重心放在 REST 和 SOAP Web 服務(wù)技術(shù)特質(zhì)對(duì)比上。 需求描述這是一個(gè)在線的用戶管理模塊,負(fù)責(zé)用戶信息的創(chuàng)建,修改,刪除,查詢。用戶的信息主要包括:
需求用例圖如下: 圖 1. 需求用例圖如圖 1 所示,客戶端 1(Client1)與客戶端 2(Client2)對(duì)于信息的存取具有不同的權(quán)限,客戶端 1 可以執(zhí)行所有的操作,而客戶端 2 只被允許執(zhí)行用戶查詢(Query User)與用戶列表查詢(Query User List)。關(guān)于這一點(diǎn),我們?cè)趯?duì) REST Web 服務(wù)與 SOAP Web 服務(wù)安全控制對(duì)比時(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)容,本文不做深入討論,請(qǐng)見參考資源列表。 設(shè)計(jì)我們將采用遵循 REST 設(shè)計(jì)原則的 ROA(Resource-Oriented Architecture,面向資源的體系架構(gòu))進(jìn)行設(shè)計(jì)。ROA 是什么?簡(jiǎn)單點(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>tester@cn.ibm.com</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 得到用戶列表資源。而這四部分也正對(duì)應(yīng)了圖 2 關(guān)于架構(gòu)描述的四對(duì) HTTP 消息來回。關(guān)于 UserRestHelper 類的完整實(shí)現(xiàn),請(qǐng)讀者參見本文所附的代碼示例。 清單 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ù)器端對(duì)于用戶資源類(UserResourc)的實(shí)現(xiàn),其核心的功能是響應(yīng)有關(guān)用戶資源的 HTTP GET/PUT/DELETE 請(qǐng)求,而這些請(qǐng)求響應(yīng)邏輯正對(duì)應(yīng)了 UserRestHelper 類中關(guān)于用戶資源類的 HTTP 請(qǐng)求。 清單 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 請(qǐng)求邏輯 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 請(qǐng)求邏輯。 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 請(qǐng)求邏輯。 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 類是對(duì)用戶資源類的抽象,包括了對(duì)該資源的創(chuàng)建修改(put 方法),讀?。╤andleGet 方法 )和刪除(delete 方法),被創(chuàng)建出來的 UserResource 類實(shí)例被 Restlet 框架所托管,所有操縱資源的方法會(huì)在相應(yīng)的 HTTP 請(qǐng)求到達(dá)后被自動(dòng)回調(diào)。 另外,在服務(wù)端,還需要實(shí)現(xiàn)代表用戶列表資源的資源類 UserListResource,它的實(shí)現(xiàn)與 UserResource 類似,響應(yīng) HTTP GET 請(qǐng)求,讀取當(dāng)前系統(tǒng)內(nèi)的所有用戶信息,形成如清單 1 所示的用戶列表資源 Representation,然后返回該結(jié)果給客戶端。具體的實(shí)現(xiàn)請(qǐng)讀者參見本文所附的代碼示例。 使用 SOAP 實(shí)現(xiàn) Web 服務(wù)本文對(duì)于 SOAP 實(shí)現(xiàn),就不再像 REST 那樣,具體到代碼級(jí)別的實(shí)現(xiàn)。本節(jié)將主要通過 URI,HTTP 和 XML 來宏觀上表述 SOAP Web 服務(wù)實(shí)現(xiàn)的技術(shù)本質(zhì),為下一節(jié) REST Web 服務(wù)與 SOAP Web 服務(wù)的對(duì)比做鋪墊。 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>tester@cn.ibm.com</email> <description>testing!</description> </p:getUserByNameResponse> </soap:Body> </soap:Envelope> 實(shí)際上,創(chuàng)建新的用戶,過程也比較類似,在這里,就不一一列出,因?yàn)檫@兩個(gè)例子對(duì)于本文在選定的點(diǎn)上對(duì)比 REST 與 SOAP 已經(jīng)足夠了。 REST 與 SOAP 比較本節(jié)從以下幾個(gè)方面來對(duì)比上面兩節(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)勢(shì),從大的方面來講:標(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)勢(shì):
HTTP 協(xié)議從本質(zhì)上說是一種無狀態(tài)的協(xié)議,客戶端發(fā)出的 HTTP 請(qǐng)求之間可以相互隔離,不存在相互的狀態(tài)依賴?;?HTTP 的 ROA,以非常自然的方式來實(shí)現(xiàn)無狀態(tài)服務(wù)請(qǐng)求處理邏輯。對(duì)于分布式的應(yīng)用而言,任意給定的兩個(gè)服務(wù)請(qǐng)求 Request 1 與 Request 2, 由于它們之間并沒有相互之間的狀態(tài)依賴,就不需要對(duì)它們進(jìn)行相互協(xié)作處理,其結(jié)果是:Request 1 與 Request 2 可以在任何的服務(wù)器上執(zhí)行,這樣的應(yīng)用很容易在服務(wù)器端支持負(fù)載平衡 (load-balance)。
HTTP 的 GET、HEAD 請(qǐng)求本質(zhì)上應(yīng)該是安全的調(diào)用,即:GET、HEAD 調(diào)用不會(huì)有任何的副作用,不會(huì)造成服務(wù)器端狀態(tài)的改變。對(duì)于服務(wù)器來說,客戶端對(duì)某一 URI 做 n 次的 GET、HAED 調(diào)用,其狀態(tài)與沒有做調(diào)用是一樣的,不會(huì)發(fā)生任何的改變。 HTTP 的 PUT、DELTE 調(diào)用,具有冪指相等特性 , 即:客戶端對(duì)某一 URI 做 n 次的 PUT、DELTE 調(diào)用,其效果與做一次的調(diào)用是一樣的。HTTP 的 GET、HEAD 方法也具有冪指相等特性。 HTTP 這些標(biāo)準(zhǔn)方法在原則上保證你的分布式系統(tǒng)具有這些特性,以幫助構(gòu)建更加健壯的分布式系統(tǒng)。 安全控制為了說明問題,基于上面的在線用戶管理系統(tǒng),我們給定以下場(chǎng)景: 參考一開始我們給出的用例圖,對(duì)于客戶端 Client2,我們只希望它能以只讀的方式訪問 User 和 User List 資源,而 Client1 具有訪問所有資源的所有權(quán)限。 如何做這樣的安全控制? 通行的做法是:所有從客戶端 Client2 發(fā)出的 HTTP 請(qǐng)求都經(jīng)過代理服務(wù)器 (Proxy Server)。代理服務(wù)器制定安全策略:所有經(jīng)過該代理的訪問 User 和 User List 資源的請(qǐng)求只具有讀取權(quán)限,即:允許 GET/HEAD 操作,而像具有寫權(quán)限的 PUT/DELTE 是不被允許的。 如果對(duì)于 REST,我們看看這樣的安全策略是如何部署的。如下圖所示: 圖 4. REST 與代理服務(wù)器 (Proxy Servers)一般代理服務(wù)器的實(shí)現(xiàn)根據(jù) (URI, HTTP Method) 兩元組來決定 HTTP 請(qǐng)求的安全合法性。 當(dāng)發(fā)現(xiàn)類似于(http://localhost:8182/v1/users/{username},DELETE)這樣的請(qǐng)求時(shí),予以拒絕。 對(duì)于 SOAP,如果我們想借助于既有的代理服務(wù)器進(jìn)行安全控制,會(huì)比較尷尬,如下圖: 圖 5. SOAP 與代理服務(wù)器 (Proxy Servers)所有的 SOAP 消息經(jīng)過代理服務(wù)器,只能看到( 關(guān)于緩存眾所周知,對(duì)于基于網(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 請(qǐng)求 (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 請(qǐng)求。 REST 的應(yīng)用可以充分地挖掘 HTTP 協(xié)議對(duì)緩存支持的能力。當(dāng)客戶端第一次發(fā)送 HTTP GET 請(qǐng)求給服務(wù)器獲得內(nèi)容后,該內(nèi)容可能被緩存服務(wù)器 (Cache Server) 緩存。當(dāng)下一次客戶端請(qǐng)求同樣的資源時(shí),緩存可以直接給出響應(yīng),而不需要請(qǐng)求遠(yuǎn)程的服務(wù)器獲得。而這一切對(duì)客戶端來說都是透明的。 圖 6. REST 與緩存服務(wù)器 (Cache Server)而對(duì)于 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 請(qǐng)求是否是想從服務(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 是彼此孤立的。 而對(duì)于 REST,情況是完全不同的:通過 總結(jié)典型的基于 SOAP 的 Web 服務(wù)以操作為中心,每個(gè)操作接受 XML 文檔作為輸入,提供 XML 文檔作為輸出。在本質(zhì)上講,它們是 RPC 風(fēng)格的。而在遵循 REST 原則的 ROA 應(yīng)用中,服務(wù)是以資源為中心的,對(duì)每個(gè)資源的操作都是標(biāo)準(zhǔn)化的 HTTP 方法。 本文主要集中在以上的幾個(gè)方面,對(duì) SOAP 與 REST 進(jìn)行了對(duì)比,可以看到,基于 REST 構(gòu)建的系統(tǒng)其系統(tǒng)的擴(kuò)展能力要強(qiáng)于 SOAP,這可以體現(xiàn)在它的統(tǒng)一接口抽象、代理服務(wù)器支持、緩存服務(wù)器支持等諸多方面。并且,伴隨著 Web Site as Web Services 演進(jìn)的趨勢(shì),基于 REST 設(shè)計(jì)和實(shí)現(xiàn)的簡(jiǎn)單性和強(qiáng)擴(kuò)展性,有理由相信,REST 將會(huì)成為 Web 服務(wù)的一個(gè)重要架構(gòu)實(shí)踐領(lǐng)域。 下載
|
|