Web 服務編程,REST 與 SOAP

爲何選擇 RESThtml

李 三紅, 高級軟件工程師, IBM

簡介: REST 架構風格是一種全新的針對 Web 應用的開發風格,與 RPC 風格的 Web 服務(關於 RPC 風格的 Web 服務描述,請參見 Wikipeida )同樣,是企業信息化的一個重要架構實踐領域。本文從一個簡單的應用場景出發,使用 REST 和 SOAP 兩種不一樣的架構風格實現,經過對 REST 與 SOAP Web 服務具體對比,旨在幫助讀者更深入理解 REST 架構風格。java

發佈日期: 2009 年 7 月 28 日 
級別: 中級 
訪問狀況 5359 次瀏覽 
建議: 1 (查看或添加評論)web

1 star 2 stars 3 stars 4 stars 5 stars  平均分 (共 5 個評分 )

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. 需求用例圖
REST 

如圖 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 實現架構
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 方法),讀取(handleGet 方法 )和刪除(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 

能夠看到,與 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/messagerouterURI,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 方法自己的一些優點:

  • 無狀態性(Stateless)

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)
REST 

通常代理服務器的實現根據 (URI, HTTP Method) 兩元組來決定 HTTP 請求的安全合法性。

當發現相似於(http://localhost:8182/v1/users/{username},DELETE)這樣的請求時,予以拒絕。

對於 SOAP,若是咱們想借助於既有的代理服務器進行安全控制,會比較尷尬,以下圖:


圖 5. SOAP 與代理服務器 (Proxy Servers)
REST 

全部的 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)
REST 

而對於 SOAP,狀況又是怎樣的呢?

使用 HTTP 協議的 SOAP,因爲其設計原則上並不像 REST 那樣強調與 Web 的工做方式相一致,因此,基於 SOAP 應用很難充分發揮 HTTP 自己的緩存能力。


圖 7. SOAP 與緩存服務器 (Cache Server)
REST 

兩個因素決定了基於 SOAP 應用的緩存機制要遠比 REST 複雜:

其1、全部通過緩存服務器的 SOAP 消息老是 HTTP POST,緩存服務器若是不解碼 SOAP 消息體,無法知道該 HTTP 請求是不是想從服務器得到數據。

其2、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

關於下載方法的信息


參考資料

關於做者

李三紅,任職於 IBM CDL,負責 Lotus Notes 產品研發。在這以前,做者一直從事網絡應用開發相關工做。做者感興趣的技術領域包括:分佈式對象計算,網絡應用,OSGi,協做計算,Java 安全等方面。

相關文章
相關標籤/搜索