原文:
http://www.ibm.com/developerworks/cn/webservices/0907_rest_soap/
REST 簡介
在開始我們的正式討論之前,讓我們簡單看一下 REST 的定義。
REST(Representational State Transfer)是 Roy Fielding
提出的一個描述互聯系統架構風格的名詞。為什么稱為 REST?Web 本質上由各種各樣的資源組成,資源由 URI
唯一標識。瀏覽器(或者任何其它類似于瀏覽器的應用程序)將展示出該資源的一種表現方式,或者一種表現狀態。如果用戶在該頁面中定向到指向其它資源的鏈
接,則將訪問該資源,并表現出它的狀態。這意味著客戶端應用程序隨著每個資源表現狀態的不同而發生狀態轉移,也即所謂 REST。
關于 REST 本身,本文就不再這里過多地討論,讀者可以參考 developerWorks 上其它介紹 REST 的文章。本文的重點在于通過 REST 與 SOAP Web 服務的對比,幫助讀者更深刻理解 REST 架構風格的特點,優勢。
應用場景介紹(在線用戶管理)
本文將借助于一個應用場景,通過基于 REST 和 SOAP Web 服務的不同實現,來對兩者進行對比。該應用場景的業務邏輯會盡量保持簡單且易于理解,以有助于把我們的重心放在 REST 和 SOAP Web 服務技術特質對比上。
需求描述
這是一個在線的用戶管理模塊,負責用戶信息的創建,修改,刪除,查詢。用戶的信息主要包括:
-
用戶名(唯一標志在系統中的用戶)
-
頭銜
-
公司
-
EMAIL
-
描述
需求用例圖如下:
圖 1. 需求用例圖
如圖 1 所示,客戶端 1(Client1)與客戶端 2(Client2)對于信息的存取具有不同的權限,客戶端 1
可以執行所有的操作,而客戶端 2 只被允許執行用戶查詢(Query User)與用戶列表查詢(Query User
List)。關于這一點,我們在對 REST Web 服務與 SOAP Web 服務安全控制對比時會具體談到。下面我們將分別向您介紹如何使用
REST 和 SOAP 架構實現 Web 服務。
使用 REST 實現 Web 服務
本部分將基于 Restlet 框架來實現該應用。Restlet 為那些要采用 REST 結構體系來構建應用程序的 Java 開發者提供了一個具體的解決方案。關于更多的 Restlet 相關內容,本文不做深入討論,請見參考資源列表。
設計
我們將采用遵循 REST 設計原則的 ROA(Resource-Oriented
Architecture,面向資源的體系架構)進行設計。ROA 是什么?簡單點說,ROA 是一種把實際問題轉換成 REST 式 Web
服務的方法,它使得 URI、HTTP 和 XML 具有跟其他 Web 應用一樣的工作方式。
在使用 ROA 進行設計時,我們需要把真實的應用需求轉化成 ROA 中的資源,基本上遵循以下的步驟:
-
分析應用需求中的數據集。
-
映射數據集到 ROA 中的資源。
-
對于每一資源,命名它的 URI。
-
為每一資源設計其 Representations。
-
用 hypermedia links 表述資源間的聯系。
接下來我們按照以上的步驟來設計本文的應用案例。
在線用戶管理所涉及的數據集就是用戶信息,如果映射到 ROA 資源,主要包括兩類資源:用戶及用戶列表。用戶資源的 URI 用 http://localhost:8182/v1/users/{username}
表示,用戶列表資源的 URI 用 http://localhost:8182/v1/users
表示。它們的 Representation 如下,它們都采用了如清單 1 和清單 2 所示的 XML 表述方式。
清單 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 信息 ( 如 :
<link>http://localhost:8182/v1/users/tester</link>
) 獲得具體的某個 USER Resource。
Restful Web 服務架構
首先給出 Web 服務使用 REST 風格實現的整體架構圖,如下圖所示:
圖 2. REST 實現架構
接下來,我們將基于該架構,使用 Restlet 給出應用的 RESTful Web 服務實現。
下面的章節中,我們將給出 REST Web 服務實現的核心代碼片段。關于完整的代碼清單,讀者可以通過資源列表下載。
客戶端實現
清單 3 給出的是客戶端的核心實現部分,其主要由四部分組成:使用 HTTP PUT 增加、修改用戶資源,使用 HTTP GET
得到某一具體用戶資源,使用 HTTP DELETE 刪除用戶資源,使用 HTTP GET 得到用戶列表資源。而這四部分也正對應了圖 2
關于架構描述的四對 HTTP 消息來回。關于 UserRestHelper 類的完整實現,請讀者參見本文所附的代碼示例。
清單 3. 客戶端實現
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 方法經由 URI 刪除用戶資源
public static void deleteFromServer(String name) {
Response response = new Client(Protocol.HTTP).delete(getUserUri(name));
……
}
//Put user resource to server.
//使用 HTTP PUT 方法經由 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 方法經由 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 方法經由 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());
}
}
}
|
服務器端實現
清單 4 給出的是服務器端對于用戶資源類(UserResourc)的實現,其核心的功能是響應有關用戶資源的 HTTP GET/PUT/DELETE 請求,而這些請求響應邏輯正對應了 UserRestHelper 類中關于用戶資源類的 HTTP 請求。
清單 4. 服務器端實現
public class UserResource extends Resource {
private User _user;
private String _userName;
public UserResource(Context context, Request request, Response response) {
//Constructor is here.
……
}
//響應 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;
}
//響應 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());
}
//響應 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 類是對用戶資源類的抽象,包括了對該資源的創建修改(put 方法),讀?。╤andleGet 方法
)和刪除(delete 方法),被創建出來的 UserResource 類實例被 Restlet 框架所托管,所有操縱資源的方法會在相應的
HTTP 請求到達后被自動回調。
另外,在服務端,還需要實現代表用戶列表資源的資源類 UserListResource,它的實現與 UserResource
類似,響應 HTTP GET 請求,讀取當前系統內的所有用戶信息,形成如清單 1 所示的用戶列表資源
Representation,然后返回該結果給客戶端。具體的實現請讀者參見本文所附的代碼示例。
使用 SOAP 實現 Web 服務
本文對于 SOAP 實現,就不再像 REST 那樣,具體到代碼級別的實現。本節將主要通過 URI,HTTP 和 XML 來宏觀上表述 SOAP Web 服務實現的技術本質,為下一節 REST Web 服務與 SOAP Web 服務的對比做鋪墊。
SOAP Web 服務架構
同樣,首先給出 SOAP 實現的整體架構圖,如下圖所示:
圖 3. SOAP 實現架構
可以看到,與 REST 架構相比,SOAP 架構圖明顯不同的是:所有的 SOAP 消息發送都使用 HTTP POST 方法,并且所有 SOAP 消息的 URI 都是一樣的,這是基于 SOAP 的 Web 服務的基本實踐特征。
獲得用戶信息列表
基于 SOAP 的客戶端創建如清單 5 所示的 SOAP XML 文檔,它通過類 RPC 方式來獲得用戶列表信息。
清單 5. getUserList SOAP 消息
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<p:getUserList xmlns:p="http://www.exmaple.com"/>
</soap:Body>
</soap:Envelope>
|
客戶端將使用 HTTP 的 POST 方法,將上述的 SOAP 消息發送至 http://localhost:8182/v1/soap/servlet/messagerouter
URI,SOAP SERVER 收到該 HTTP POST 請求,通過解碼 SOAP 消息確定需要調用 getUserList 方法完成該 WEB 服務調用,返回如下的響應:
清單 6. getUserListResponse 消息
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<p:get
UserListResponse xmlns:p="http://www.exmaple.com">
<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.xmlsoap.org/soap/envelope/">
<soap:Body>
<p:getUserByName xmlns:p="http://www.exmaple.com">
<username>tester</username>
</p:getUserByName >
</soap:Body>
</soap:Envelope>
|
同樣地,客戶端將使用 HTTP 的 POST 方法,將上述的 SOAP 消息發送至 http://localhost:8182/v1/soap/servlet/messagerouter
URI,SOAP SERVER 處理后返回的 Response 如下:
清單 8. getUserByNameResponse SOAP 消息
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<p:getUserByNameResponse xmlns:p="http://www.exmaple.com">
<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>
|
實際上,創建新的用戶,過程也比較類似,在這里,就不一一列出,因為這兩個例子對于本文在選定的點上對比 REST 與 SOAP 已經足夠了。
REST 與 SOAP 比較
本節從以下幾個方面來對比上面兩節給出 REST 實現與 SOAP 實現。
接口抽象
RESTful Web 服務使用標準的 HTTP 方法 (GET/PUT/POST/DELETE) 來抽象所有 Web
系統的服務能力,而不同的是,SOAP 應用都通過定義自己個性化的接口方法來抽象 Web 服務,這更像我們經常談到的 RPC。例如本例中的
getUserList 與 getUserByName 方法。
RESTful Web 服務使用標準的 HTTP 方法優勢,從大的方面來講:標準化的 HTTP 操作方法,結合其他的標準化技術,如
URI,HTML,XML 等,將會極大提高系統與系統之間整合的互操作能力。尤其在 Web 應用領域,RESTful Web
服務所表達的這種抽象能力更加貼近 Web 本身的工作方式,也更加自然。
同時,使用標準 HTTP 方法實現的 RRESTful Web 服務也帶來了 HTTP 方法本身的一些優勢:
HTTP 協議從本質上說是一種無狀態的協議,客戶端發出的 HTTP 請求之間可以相互隔離,不存在相互的狀態依賴。基于 HTTP 的
ROA,以非常自然的方式來實現無狀態服務請求處理邏輯。對于分布式的應用而言,任意給定的兩個服務請求 Request 1 與 Request 2,
由于它們之間并沒有相互之間的狀態依賴,就不需要對它們進行相互協作處理,其結果是:Request 1 與 Request 2
可以在任何的服務器上執行,這樣的應用很容易在服務器端支持負載平衡 (load-balance)。
-
安全操作與冪指相等特性(Safety /Idempotence)
HTTP 的 GET、HEAD 請求本質上應該是安全的調用,即:GET、HEAD
調用不會有任何的副作用,不會造成服務器端狀態的改變。對于服務器來說,客戶端對某一 URI 做 n 次的 GET、HAED
調用,其狀態與沒有做調用是一樣的,不會發生任何的改變。
HTTP 的 PUT、DELTE 調用,具有冪指相等特性 , 即:客戶端對某一 URI 做 n 次的 PUT、DELTE 調用,其效果與做一次的調用是一樣的。HTTP 的 GET、HEAD 方法也具有冪指相等特性。
HTTP 這些標準方法在原則上保證你的分布式系統具有這些特性,以幫助構建更加健壯的分布式系統。
安全控制
為了說明問題,基于上面的在線用戶管理系統,我們給定以下場景:
參考一開始我們給出的用例圖,對于客戶端 Client2,我們只希望它能以只讀的方式訪問 User 和 User List 資源,而 Client1 具有訪問所有資源的所有權限。
如何做這樣的安全控制?
通行的做法是:所有從客戶端 Client2 發出的 HTTP 請求都經過代理服務器 (Proxy
Server)。代理服務器制定安全策略:所有經過該代理的訪問 User 和 User List 資源的請求只具有讀取權限,即:允許
GET/HEAD 操作,而像具有寫權限的 PUT/DELTE 是不被允許的。
如果對于 REST,我們看看這樣的安全策略是如何部署的。如下圖所示:
圖 4. REST 與代理服務器 (Proxy Servers)
一般代理服務器的實現根據 (URI, HTTP Method) 兩元組來決定 HTTP 請求的安全合法性。
當發現類似于(http://localhost:8182/v1/users/{username},DELETE)這樣的請求時,予以拒絕。
對于 SOAP,如果我們想借助于既有的代理服務器進行安全控制,會比較尷尬,如下圖:
圖 5. SOAP 與代理服務器 (Proxy Servers)
所有的 SOAP 消息經過代理服務器,只能看到(http://localhost:8182/v1/soap/servlet/messagerouter
,
HTTP POST)這樣的信息,如果代理服務器想知道當前的 HTTP 請求具體做的是什么,必須對 SOAP
的消息體解碼,這樣的話,意味著要求第三方的代理服務器需要理解當前的 SOAP 消息語義,而這種 SOAP
應用與代理服務器之間的緊耦合關系是不合理的。
關于緩存
眾所周知,對于基于網絡的分布式應用,網絡傳輸是一個影響應用性能的重要因素。如何使用緩存來節省網絡傳輸帶來的開銷,這是每一個構建分布式網絡應用的開發人員必須考慮的問題。
HTTP 協議帶條件的 HTTP GET 請求 (Conditional GET)
被設計用來節省客戶端與服務器之間網絡傳輸帶來的開銷,這也給客戶端實現 Cache 機制 ( 包括在客戶端與服務器之間的任何代理 )
提供了可能。HTTP 協議通過 HTTP HEADER 域:If-Modified-Since/Last-
Modified,If-None-Match/ETag 實現帶條件的 GET 請求。
REST 的應用可以充分地挖掘 HTTP 協議對緩存支持的能力。當客戶端第一次發送 HTTP GET
請求給服務器獲得內容后,該內容可能被緩存服務器 (Cache Server)
緩存。當下一次客戶端請求同樣的資源時,緩存可以直接給出響應,而不需要請求遠程的服務器獲得。而這一切對客戶端來說都是透明的。
圖 6. REST 與緩存服務器 (Cache Server)
而對于 SOAP,情況又是怎樣的呢?
使用 HTTP 協議的 SOAP,由于其設計原則上并不像 REST 那樣強調與 Web 的工作方式相一致,所以,基于 SOAP 應用很難充分發揮 HTTP 本身的緩存能力。
圖 7. SOAP 與緩存服務器 (Cache Server)
兩個因素決定了基于 SOAP 應用的緩存機制要遠比 REST 復雜:
其一、所有經過緩存服務器的 SOAP 消息總是 HTTP POST,緩存服務器如果不解碼 SOAP 消息體,沒法知道該 HTTP 請求是否是想從服務器獲得數據。
其二、SOAP 消息所使用的 URI 總是指向 SOAP 的服務器,如本文例子中的 http://localhost:8182/v1/soap/servlet/messagerouter
,這并沒有表達真實的資源 URI,其結果是緩存服務器根本不知道那個資源正在被請求,更不用談進行緩存處理。
關于連接性
在一個純的 SOAP 應用中,URI 本質上除了用來指示 SOAP 服務器外,本身沒有任何意義。與 REST 的不同的是,無法通過 URI 驅動 SOAP 方法調用。例如在我們的例子中,當我們通過
getUserList SOAP 消息獲得所有的用戶列表后,仍然無法通過既有的信息得到某個具體的用戶信息。唯一的方法只有通過
WSDL 的指示,通過調用 getUserByName 獲得,getUserList 與 getUserByName 是彼此孤立的。
而對于 REST,情況是完全不同的:通過 http://localhost:8182/v1/users
URI 獲得用戶列表,然后再通過用戶列表中所提供的 LINK 屬性,例如 <link>http://localhost:8182/v1/users/tester</link>
獲得 tester 用戶的用戶信息。這樣的工作方式,非常類似于你在瀏覽器的某個頁面上點擊某個 hyperlink, 瀏覽器幫你自動定向到你想訪問的頁面,并不依賴任何第三方的信息。
總結
典型的基于 SOAP 的 Web 服務以操作為中心,每個操作接受 XML 文檔作為輸入,提供 XML
文檔作為輸出。在本質上講,它們是 RPC 風格的。而在遵循 REST 原則的 ROA 應用中,服務是以資源為中心的,對每個資源的操作都是標準化的
HTTP 方法。
本文主要集中在以上的幾個方面,對 SOAP 與 REST 進行了對比,可以看到,基于 REST 構建的系統其系統的擴展能力要強于
SOAP,這可以體現在它的統一接口抽象、代理服務器支持、緩存服務器支持等諸多方面。并且,伴隨著 Web Site as Web Services
演進的趨勢,基于 REST 設計和實現的簡單性和強擴展性,有理由相信,REST 將會成為 Web 服務的一個重要架構實踐領域。
下載
描述 | 名字 | 大小 | 下載方法 |
本文代碼示例 |
code_rest.zip |
10 KB |
HTTP |
參考資料