先來點理論知識,來自 http://www.cnblogs.com/simonchen/articles/2220838.html
一.什么是Rest
REST軟件架構是由Roy Thomas Fielding博士2000年在他的論文《Architectural Styles and the Design of Network- based Software Architectures》首次提出的。他提出的理論對后來的Web技術的發展產生了巨大的影響,他是許多重要Web架構標准的設計者,這些標准就是 HTTP、URI等。
- Rest的英文全稱是“Representational State Transfer”。中文翻譯為“表述性狀態轉移”。REST本身只是為分布式超媒體系統設計的一種架構風格,而不是標准。
- 那么如何理解“Representational State Transfer”這句話呢?下面我們來解釋一下:
-
- Representational :中文直譯:代表的,表像的。如果把WEB 服務器端中所有的東西(數據)都看作是資源(Resource),那么呈現在用戶面前(客戶端)的就是資源的表像(Representation)。每一個資源都有自己的唯一標識(URI)。
- State :中文直譯:狀態。首先這個狀態是客戶端的狀態,而不是服務器端的狀態(在REST 中,服務器端應該是無狀態的)。那么,把State和Representation聯系在一起(Representational State),可以理解成:每一個資源(Resource)在客戶端的表像(Representation)就是客戶端的一個狀態(State)。
- Transfer:中文直譯:轉移。當用戶通過不同的URI訪問不同的資源時,客戶端的表像(Representation)也會隨着變化,也就意味着客戶端的狀態變更(Transfer)了,連起來就是:Representational State Transfer。
- REST=老的Web規范+3個新的規范:REST實際上也是基於已有的Web規范集合產生的。傳統的Web應用大都是BS系統,這些系統共同遵循一些老的Web規范,這些規范主要包含 3條:
-
- 客戶-服務器:這種規范的提出,改善了用戶接口跨多個平台的可移植性,並且通過簡化服務器組件,改善了系統的可伸縮性。最為關鍵的是通過分離用戶接口和數據存儲這兩個關注點,使得不同用戶終端享受相同數據成為了可能。
- 無狀態性:無狀態性是在客戶-服務器約束的基礎上添加的又一層規范。他要求通信必須在本質上是無狀態的,即從客戶到服務器的每個request都 必須包含理解該request所必須的所有信息。這個規范改善了系統的可見性(無狀態性使得客戶端和服務器端不必保存對方的詳細信息,服務器只需要處理當 前request,而不必了解所有的request歷史),可靠性(無狀態性減少了服務器從局部錯誤中恢復的任務量),可伸縮性(無狀態性使得服務器端可 以很容易的釋放資源,因為服務器端不必在多個request中保存狀態)。同時,這種規范的缺點也是顯而易見得,由於不能將狀態數據保存在服務器上的共享 上下文中,因此增加了在一系列request中發送重復數據的開銷,嚴重的降低了效率。
- 緩存:為了改善無狀態性帶來的網絡的低效性,我們填加了緩存約束。緩存約束允許隱式或顯式地標記一個response中的數據,這樣就賦予了客戶 端緩存response數據的功能,這樣就可以為以后的request共用緩存的數據,部分或全部的消除一部分交互,增加了網絡的效率。但是用於客戶端緩存了信息,也就同時增加了客戶端與服務器數據不一致的可能,從而降低了可靠性。
- REST在原有的架構上增加了3個新規范:統一接口、分層系統和按需代碼:
-
- 統一接口:REST架構風格的核心特征就是強調組件之間有一個統一的接口,這表現在REST世界里,網絡上所有的事物都被抽象為資源,而REST 就是通過通用的鏈接器接口對資源進行操作。這樣設計的好處是保證系統提供的服務都是解耦的,極大的簡化了系統,從而改善了系統的交互性和可重用性。並且 REST針對Web的常見情況做了優化,使得REST接口被設計為可以高效的轉移大粒度的超媒體數據,這也就導致了REST接口對其它的架構並不是最優的。
- 分層系統:分層系統規則的加入提高了各種層次之間的獨立性,為整個系統的復雜性設置了邊界,通過封裝遺留的服務,使新的服務器免受遺留客戶端的影響,這也就提高了系統的可伸縮性。
- 按需代碼:REST允許對客戶端功能進行擴展。比如,通過下載並執行 applet或腳本形式的代碼,來擴展客戶端功能。但這在改善系統可擴展性的同時,也降低了可見性。所以它只是REST的一個可選的約束。
二.Rest的特點
由於Rest遵守的這些規范,因此Rest架構的特點也非常的明顯:
- REST是一種架構,而不是一個規范。
- REST是一種典型的Client-Server架構,但是強調瘦服務器端,服務器端只應該處理跟數據有關的操作,所有有關顯示的工作都應該放在客戶端。
- 在REST架構中,服務器是無狀態的,也就是說服務器不會保存任何與客戶端的會話狀態信息。所有的狀態信息只能放在雙方溝通的 Message(消息)中。
- REST架構是冪等的,對於相同的請求,服務器返回的結果也是相同的,因此服務器端返回的結果是可以緩存的,既可以存在客戶端也可以存在代理服務器端。
- 在REST架構中,所有的操作都是基於統一的方式進行的:
-
- 每個Resource都有一個唯一的ID。
- 通過Representation(客戶端)來處理Resource(服務器端)。也就是說,客戶端不能直接操作服務器端的Resource,只能通過對相應的Representation的操作,並發送相應的請求,最后由服務器端來處理Resource並返回結果。
- 客戶端和服務器端傳送的任何一個Message(消息),都應該是自描述的。也就是說處理這個 Message所需要的上下文環境都應該包含在這個Message當中。
- 多媒體的交互系統,客戶端和服務器端傳送的內容可以是文檔,圖片,聲音等等多媒體數據,這也是一個Resource能夠對應不同的Representation(例如文檔,圖片等)的基礎。
- 分層結構,像TCP/IP的分層結構一樣,第n層使用第n-1層提供的服務並為第n+1層提供服務。在REST中,Client- Server之間加入了Proxy層和Gateway層。在這些中間層可以加入一些業務處理以外的功能,譬如:負載均衡,安全控制等等。
- Code-On-Demand,客戶端可以訪問服務器端的Resource,但並不知道如何處理服務器端返回的結果,這個處理過程的代碼應該是從服務器端發送過來,然后在客戶端執行,也就是說客戶端的功能是根據需要動態從服務器端獲得的。一個很簡單的例子,Applet就是從服務器端下載然后在客戶端執行的。注意,這個特性是可選的(Optional),也就是說在你的REST實現當中,可以不考慮這個特性。
三.Rest的優點
既然Rest風格有這些特點,那么也就具備了許多優點:
- 緩存使用 HTTP 向 RESTful 端點申請數據時,用到的 HTTP 動詞是 GET。對於 GET 請求響應中返回的資源,可以用多種不同的方式進行緩存。Conditional GET 就是可供選擇的一種實現細節,客戶端可以向服務驗證他的數據是否為最新版本;RESTful 端點可以通過它進一步提高速度和可伸縮性。
- 擴展 REST 鼓勵每項資源包含處理特殊請求所需的所有必要狀態。滿足這一約束時,RESTful 服務更易於擴展且可以沒有狀態。
- 副作用如您使用 GET 請求資源,RESTful 服務應該沒有副作用(遺憾的是,與其他一些 REST 約束相比,這一約束更容易被打破)。
- 冪等統一接口另外兩個常用到的主要 HTTP 動詞是 PUT 和 DELETE。用戶代理想要修改資源時最常使用 PUT,DELETE 可以自我描述。要點(也就是“冪等”一詞所強調的)是您可以對特殊資源多次使用這兩個動詞,效果與首次使用一樣——至少不會有任何其他影響。構建可靠的分布式系統時(即錯誤、網絡故障或延遲可能導致多次執行代碼),這一優點可提供保障。
- 互操作性許多人將 SOAP 捧為建立客戶端-服務器程序最具互操作性的方法。但一些語言和環境至今仍沒有 SOAP 工具包。有一些雖然有工具包,但采用的是舊標准,不能保證與使用更新標准的工具包可靠溝通。對於大多數操作,REST 僅要求有 HTTP 庫(當然,XML 庫通常也很有幫助),它的互操作性肯定強過任何 RCP 技術(包括 SOAP)。
- 簡易性與其他優點相比,這一優點更主觀一些,不同的人可能有不同的感受。對我而言,使用 REST 的簡易性涉及到代表資源的 URI 和統一接口。作為一名 Web 沖浪高手,我理解在瀏覽器中輸入不同的 URI 可以得到不同的資源(有時也被稱為 URI 或 URL 黑客,但絕無惡意)。由於有多年使用 URI 的經驗,所以為資源設計 URI 對我來說得心應手。使用統一接口簡化了開發過程,因為我不必為每個需要建立的服務構建接口、約定或 API。接口(客戶端與我的服務交互的方式)由體系結構約束設置。
四.Rest的設計原則
REST架構是針對Web應用而設計的,其目的是為了降低開發的復雜性,提高系統的可伸縮性。REST提出了如下設計准則:
- 網絡上的所有事物都被抽象為資源(resource),比如圖片、音樂、視頻、文字、以及服務等等;
- 每個資源有唯一的資源標識符(resource identifier),URI定位資源;
- 通過通用的連接器接口(generic connector interface)對資源進行操作,比如使用 HTTP 標准動詞(GET、POST、PUT 和 DELETE)的統一接口完成操作;
- 對資源的各種操作不會改變資源標識符,URI不變;
- 所有的操作都是無狀態的(stateless)。
五.wcf3.5到wcf4.0 Rest的新增特性
- 增加對路由的支持
- 對緩存的支持。
- 幫助(help)頁面的改進。
- 消息錯誤處理
- 消息格式的多樣性如(XML\JSON\ATOM\TEXT\BINARY)
- 簡化操作。
甩過一遍理論,那么就趁熱實踐一番吧!
六.實踐出真知
按正常步驟新建一個WCF應用,常見的CRUD操作
[ServiceContract] public interface IExampleService { [OperationContract] string GetData(string value); [OperationContract] string AddData(string value); [OperationContract ] string UpdateData(string value); [OperationContract ] string DeleteData(string value); }
那么rest模式該是如何呢?
[ServiceContract] public interface IExampleService { [OperationContract] [WebGet(UriTemplate = "/Rest/Get/{value}", ResponseFormat = WebMessageFormat.Json)] string GetData(string value); [OperationContract] [WebInvoke(UriTemplate = "/Rest/Add/{value}", Method = "POST")] string AddData(string value); [OperationContract ] [WebInvoke(UriTemplate = "/Rest/Update/{value}", Method = "PUT")] string UpdateData(string value); [OperationContract ] [WebInvoke (UriTemplate="/Rest/Delete/{value}",Method="DELETE")] string DeleteData(string value); }
比較下就很容易看出多加了些標簽,並且也從方法的使用上可以對應出GET、POST、PUT、DELETE的使用。
wcf可以看元數據,那么rest也有對應的方式,在web.config中添加如下配置就可以查看help頁面
<services> <service name="RestWCFTest.ExampleService"> <endpoint address="" behaviorConfiguration="HelpBehavior" binding="webHttpBinding" bindingConfiguration="" contract="RestWCFTest.IExampleService" /> </service> </services> <behaviors> <endpointBehaviors> <behavior name="HelpBehavior"> <webHttp helpEnabled="true" /> </behavior> </endpointBehaviors> </behaviors>
help頁面如下
點擊方法進去可以看見調用方式
我們的接口實現
public string GetData(string value) { return string.Format("You entered: {0}", value); } public string AddData(string value) { return string.Format("You added: {0}", value); } public string UpdateData(string value) { return string.Format("You updated: {0}", value); } public string DeleteData(string value) { return string.Format("You deleted: {0}", value); }
現在我們用fiddler來模擬請求測試下
在composer選項里有模擬請求的功能,very good!我們先來調用GetData操作,根據參數獲取數據,根據設置的URI模板,“123456”為匹配
的參數,執行它!
看請求的信息
GET http://localhost/REST4/ExampleService.svc/Rest/Get/123456 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0
看響應的數據
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 21
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:16:52 GMT
"You entered: 123456"
200 OK 調用正常,content-type是json,因為我們指定的,IIS是7.5,對,我的確是部署在7.5上。。。看結果也是和預期一模一樣,so easy~
可能有同學會問,這是返回的json數據么?我也覺得不是,如果在方法標簽上修改為如下
[OperationContract]
[WebGet(UriTemplate = "/Rest/Get/{value}",BodyStyle=WebMessageBodyStyle.Wrapped, ResponseFormat = WebMessageFormat.Json)]
string GetData(string value);
多加了個修飾bodystyle,它的功能是對結果進行包裝,包裝后再看返回的結果
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 39
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 06:34:24 GMT
{"GetDataResult":"You entered: 123456"}
果然,被包裝了,它是一個json格式的數據了。
POST
請求
POST http://localhost/REST4/ExampleService.svc/Rest/Add/1234 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0
響應
HTTP/1.1 200 OK Cache-Control: private Content-Length: 92 Content-Type: application/xml; charset=utf-8 Server: Microsoft-IIS/7.5 X-AspNet-Version: 4.0.30319 X-Powered-By: ASP.NET Date: Fri, 27 Sep 2013 05:06:41 GMT <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">You added: 1234</string>
這個時候我們沒有指定返回的格式,默認為XML。
PUT
請求
PUT http://localhost/REST4/ExampleService.svc/Rest/Update/123 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0
響應
HTTP/1.1 200 OK Cache-Control: private Content-Length: 93 Content-Type: application/xml; charset=utf-8 Server: Microsoft-IIS/7.5 X-AspNet-Version: 4.0.30319 X-Powered-By: ASP.NET Date: Fri, 27 Sep 2013 05:23:04 GMT <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">You updated: 123</string>
DELETE
請求
DELETE http://localhost/REST4/ExampleService.svc/Rest/Delete/123 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0
響應
HTTP/1.1 200 OK Cache-Control: private Content-Length: 93 Content-Type: application/xml; charset=utf-8 Server: Microsoft-IIS/7.5 X-AspNet-Version: 4.0.30319 X-Powered-By: ASP.NET Date: Fri, 27 Sep 2013 05:14:56 GMT <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">You deleted: 123</string>
有同學可能DELETE請求發出去沒反應(IIS 7.5),請在web.config里加上以下節點
<system.webServer> <modules runAllManagedModulesForAllRequests="true"> <remove name="WebDAVModule" /> </modules> <handlers> <remove name="WebDAV" /> </handlers> </system.webServer>
至此一般的傳參情況就是如此了,下面列舉一些其它傳參情況
[OperationContract] [WebGet(UriTemplate = "/Rest/GetList2/", ResponseFormat = WebMessageFormat.Json)] List<ExampleData> GetDataLs2(); [OperationContract] [WebInvoke(UriTemplate = "/Rest/AddLs3", BodyStyle = WebMessageBodyStyle.Wrapped, ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, Method = "POST")] List<ExampleData> AddDataLs3(List<ExampleData> datas); [OperationContract] [WebInvoke(UriTemplate = "/Rest/AddLs4", BodyStyle = WebMessageBodyStyle.Wrapped, ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, Method = "POST")] List<ExampleData> AddDataLs4(List<ExampleData> datas1, List<ExampleData> datas2);
實體
public class ExampleData { public string Name { get; set; } public string Age { get; set; } }
接口實現
public List<ExampleData> GetDataLs2() { List<ExampleData> result = new List<ExampleData> { new ExampleData{ Name="張三", Age="20"} ,new ExampleData {Name="李四",Age="21"} ,new ExampleData {Name="王五",Age="30"} }; return result; } public List<ExampleData> AddDataLs3(List<ExampleData> datas) { return datas; } public List<ExampleData> AddDataLs4(List<ExampleData> datas1, List<ExampleData> datas2) { List<ExampleData> result = new List<ExampleData>(); result.AddRange(datas1); result.AddRange(datas2); return result; }
我們看到有獲取實體集合了,還有傳參的時候也是實體集合了
首先看看獲取集合的情況
請求
GET http://localhost/REST4/ExampleService.svc/Rest/GetList2/ HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0
響應
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 88
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:21:52 GMT
[{"Age":"20","Name":"張三"},{"Age":"21","Name":"李四"},{"Age":"30","Name":"王五"}]
嗯,返回的格式不錯。
看看怎樣做新增操作的
AddDataLs3
請求
POST http://localhost/REST4/ExampleService.svc/Rest/AddLs3 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 41
{"datas":[{"Name":"xiaohua","Age":"13"}]}
這時候我們會注意到,多了request body了,並且datas就是參數名
響應
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 52
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 06:59:55 GMT
{"AddDataLs3Result":[{"Age":"13","Name":"xiaohua"}]}
被包裝了的數據。
AddDataLs4
請求
POST http://localhost/REST4/ExampleService.svc/Rest/AddLs4 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 78
{"datas1":[{"Name":"xiaohua","Age":"13"}],"datas2":[{"Name":"li","Age":"13"}]}
響應
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 77
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 07:02:58 GMT
{"AddDataLs4Result":[{"Age":"13","Name":"xiaohua"},{"Age":"13","Name":"li"}]}
面對茫茫多的CRUD的時候,我們也許會顯得不耐煩,因為每個操作都去寫方法,真是煩躁,不妨可以試下如下的方式
[OperationContract] [WebInvoke(UriTemplate = "/Rest/*", Method = "*", ResponseFormat = WebMessageFormat.Json)] string ExecuteData();
用星號來匹配所有的請求,讓程序區識別請求到底是GET、POST、PUT還是DELETE
實現
public string ExecuteData() { var request = WebOperationContext.Current.IncomingRequest; var method = request.Method; var args = request.UriTemplateMatch.WildcardPathSegments; switch (method) { case "POST": return "POST..."; case "DELETE": return "DELETE..."; case "PUT": return "UPDATE..."; default: return "GET..."; } }
嗯,不知不覺就貼了這么多代碼了,其實我字沒寫多少,今天就到這吧,算是入門了吧。
以上例子所有源碼下載