一、RestFul簡介
REST(Representational State Transfer 通常被翻譯為“表述性狀態傳輸”或者“表述性狀態轉移”)是RoyFielding提出的一個描述互聯系統架構風格的名詞。為什么稱為REST?Web本質上由各種各樣的資源組成,資源由URI 唯一標識。瀏覽器(或者任何其它類似於瀏覽器的應用程序)將展示出該資源的一種表現方式,或者一種表現狀態。如果用戶在該頁面中定向到指向其它資源的鏈接,則將訪問該資源,並表現出它的狀態。這意味着客戶端應用程序隨着每個資源表現狀態的不同而發生狀態轉移,也即所謂REST。
簡單地來說REST它是一種使用URL來定位資源,使用HTTP請求描述操作的Web服務規范。REST主要包括以下幾方面:
(1) REST是一組架構約束條件和原則,而滿足這些約束條件和原則的應用程序就是RESTful。
(2)REST的目標是構建可擴展的Web Service,它是一種更簡單的SOAP(Simple Object Access Protocol)協議以及以WSDL為基礎的WebService的替代。
(3)REST采用的是HTTP協議並通過HTTP中的GET、POST、PUT、DELETE等動詞收發數據。
(4) REST希望通過HTTP來完成對數據的元操作,即傳統的CRUD(Create、Read、Update、Delete)分別對應GET、POST、PUT、DELETE,這樣就統一了數據操作的接口,實現在不同平台上提供一套相同的服務。
(5) REST是一種面向服務的、分布式的API設計風格。
RESTful API的開發和使用,無非是客戶端向服務器發請求(request),以及服務器對客戶端請求的響應(response)。所以RESTful架構風格具有統一接口的特點,即:使用不同的http方法表達不同的行為:
- GET(SELECT):從服務器取出資源(一項或多項)
- POST(CREATE):在服務器新建一個資源
- PUT(UPDATE):在服務器更新資源(客戶端提供完整資源數據)
- PATCH(UPDATE):在服務器更新資源(客戶端提供需要修改的資源數據)
- DELETE(DELETE):從服務器刪除資源
二、REST的約束條件和原則
REST本質上是Web服務的一種規范,一種思想。它主要包括以下特性:
1、資源(Resources)
在REST中資源是整個架構或者說整個網絡處理的核心,那么什么又是資源呢?在我們傳統的觀念中,資源是指服務器上的一個文件,而在REST里資源則是指一個URL。URL即統一資源定位,而我們都知道通過URL可以訪問互聯網上的資源,所以在REST里這種對資源的指向性更加強烈,並且在這里資源的范疇會被無限放大而並非局限在文件本身,例如以下實例:
1 http://api.cnblogs.com/info/source 表示獲取某人的成績 2 http://api.cnblogs.com/info/friends 表示獲取某人的好友列表 3 http://api.cnblogs.com/info/profile 表示獲取某人的詳細信息
由此我們注意到REST在形式上更加趨向API設計,而我們獲取的資源則通過一定的形式進行統一而規范化的表達,因此REST實現了讓不同的平台共享一套API這樣的願望,這是一件非常美好的事情,這個世界上的技術陣營舉不勝數,而它們為了各自的利益建立一套封閉、臃腫的體系框架,很多時候當我們不需要這樣的“全家桶”並且希望“跨平台”的時候,REST將會是一個不錯的選擇。
2、表現形式(Representational)
在REST中表現形式作為我們對資源請求的一個結果的呈現,通過對HTTP協議的學習我們已經知道,服務器會給客戶端返回什么形式的信息,這一點取決於服務器響應報文中相關頭部字段,而對REST來講,它通常會采用XML或者JSON來告訴請求者請求的結果,因為JSON相比XML所含的冗余信息較少,所以目前更加傾向於或者說流行使用JSON作為請求結果的表現形式。
3、狀態變化(State Transfer)
雖然我們一再強調HTTP協議是無狀態,這主要體現在HTTP請求與請求、HTTP響應與響應的上下文無關性上。在REST中,我們所說狀態變化更多是指HTTP中的GET、POST、PUT、DELETE等動詞實現。具體來講,看下面的簡單示例:
1 GET http://url/info 表示獲取全部的info 2 POST http://url/info 表示創建一個新的info 3 GET http://url/info/{id} 表示獲取一個指定id的info 4 PUT http://url/info/{id} 表示更新一個指定id的info 5 DELETE http://url/info/{id} 表示刪除一個指定id的info
除此之外,我們注意到REST基於HTTP協議,所以HTTP協議中的狀態碼對它來講同樣適用,例如最常用的200表示成功、500表示服務器內部錯誤、404表示無法找到請求資源、400表示請求錯誤等等。
三、如何構建RestFul風格的API
如何構建REST風格的API?我們可以通過以下實例說明
- URLRoot采用下面這樣的結構:
1 http://example.com/api/v1/ 2 http://api.example.com/v1/
- API版本可以放在URL或者HTTP的Header里
- URL使用名詞而非動詞:
1 http://example.com/api/v1/getProducts 這是一個糟糕的設計 2 GET http://example.com/api/v1/products 這是一個優雅的設計
- 保證方法時安全的不會對資源狀態有所改變。例如:
GET http://example.com/api/v1/deleteProduct?id=1 這是一個危險的信號
- 資源的地址推薦使用嵌套結構
GET http://example.com/api/v1/friends/123456789/profile
- 使用正確的HTTP狀態碼表示訪問狀態
- 返回含義明確的結果(一般推薦JSON)
四、Restful 服務端
下面主要講解如何用C#實現一個Rest 風格的web服務供外部調用,主要包括以下4點:
- 定義service的契約
- 定義URL Routing
- 實現service
- 為服務編寫宿主程序
1、定義service的契約和URL Routing
先定義服務契約,這里我介紹最常見的兩種方式,分別采用GET和POST方式訪問,使用VS2015創建一個新的控制台工程,命名為RestFulService。如圖所示:
並為該工程添加引用System.ServiceModel 和System.ServiceModel.Web
創建一個接口類文件,命名為IPersonInfoQuery。為了讓.Net FrameWork識別這是一個service接口,我們需要給接口添加上ServiceContract特性,並且給接口定義的方法添加OperationContract特性。這里我介紹最常見的兩種方式,分別采用GET和POST方式訪問。我們可以看到,與普通WCF服務契約不同的是,需要額外用WebGet或者WebInvoke指定REST訪問的方式。另外還要指定消息包裝樣式和消息格式,默認的消息請求和響應格式為XML,若選擇JSON需要顯式聲明。 UriTemplate用來將方法映射到具體的Uri上,但如果不指定映射,將映射到默認的Uri。比如采用Get訪問的GetUser方法,默認映射是:/GetSource?Name={Name}
我們定義兩種方法,1、GetScore方法:通過GET請求傳入name,返回對應的成績;2、GetInfo方法:通過POST請求,傳入Info對象,查找對應的User並返回給客戶端,代碼如下
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.ServiceModel; 5 using System.ServiceModel.Web; 6 using System.Text; 7 using System.Threading.Tasks; 8 9 namespace RestFulService 10 { 11 /// <summary> 12 /// 簡單定義兩種方法,1、GetScore方法:通過GET請求傳入name,返回對應的成績;2、GetInfo方法:通過POST請求,傳入Info對象,查找對應的User並返回給客戶端 13 /// </summary> 14 [ServiceContract(Name = "PersonInfoQueryServices")] 15 public interface IPersonInfoQuery 16 { 17 /// <summary> 18 /// 說明:GET請求 19 /// WebGet默認請求是GET方式 20 /// UriTemplate(URL Routing)的參數名name必須要方法的參數名必須一致(不區分大小寫) 21 /// RequestFormat規定客戶端必須是什么數據格式請求的(JSon或者XML),不設置默認為XML 22 /// ResponseFormat規定服務端返回給客戶端是以是什么數據格返回的(JSon或者XML) 23 /// </summary> 24 /// <param name="name"></param> 25 /// <returns></returns> 26 [OperationContract] 27 [WebGet(UriTemplate = "PersonInfoQuery/{name}", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] 28 User GetScore(string name); 29 30 /// <summary> 31 /// 說明:POS請求 32 /// WebInvoke請求方式有POST、PUT、DELETE等,所以需要明確指定Method是哪種請求的,這里我們設置POST請求。 33 /// 注意:POST情況下,UriTemplate(URL Routing)一般是沒有參數(和上面GET的UriTemplate不一樣,因為POST參數都通過消息體傳送) 34 /// RequestFormat規定客戶端必須是什么數據格式請求的(JSon或者XML),不設置默認為XML 35 /// ResponseFormat規定服務端返回給客戶端是以是什么數據格返回的(JSon或者XML) 36 /// </summary> 37 /// <param name="info"></param> 38 /// <returns></returns> 39 [OperationContract] 40 [WebInvoke(Method = "POST", UriTemplate = "PersonInfoQuery/Info", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] 41 User GetInfo(Info info); 42 } 43 }
定義對象User
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Runtime.Serialization; 7 8 namespace RestFulService 9 { 10 [DataContract] 11 public class User 12 { 13 [DataMember] 14 public int ID { get; set; } 15 16 [DataMember] 17 public string Name { get; set; } 18 19 [DataMember] 20 public int Age { get; set; } 21 22 [DataMember] 23 public int Score { get; set; } 24 } 25 }
定義對象Info
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.Serialization; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 namespace RestFulService 9 { 10 [DataContract] 11 public class Info 12 { 13 [DataMember] 14 public int ID { get; set; } 15 16 [DataMember] 17 public string Name { get; set; } 18 } 19 }
說明:
UriTemplate就是我們之前提到的URL Routing(可以單獨創建一個Routing進行管理)
WebGet默認請求是GET方式。
WebInvoke請求方式有POST、PUT、DELETE等,所以需要明確指定Method是哪種請求的。
UriTemplate(URL Routing)的參數名name必須要方法的參數名必須一致(不區分大小寫)
POST情況下,UriTemplate(URL Routing)一般是沒有參數(和GET的UriTemplate不一樣,因為POST參數都通過消息體傳送)
RequestFormat規定客戶端必須是什么數據格式請求的(JSon或者XML),不設置默認為XML
ResponseFormat規定服務端返回給客戶端是以是什么數據格返回的(JSon或者XML)
2、實現service
上面我們定義了接口,那么我們需要創建一個服務去實現這個接口的方法,我們創建一個類名為PersonInfoQueryServices,我們需要設置一些ServiceBehavior特征屬性。代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.ServiceModel; 5 using System.ServiceModel.Activation; 6 using System.Text; 7 using System.Threading.Tasks; 8 9 namespace RestFulService 10 { 11 [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single, IncludeExceptionDetailInFaults = true)] 12 [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] 13 public class PersonInfoQueryServices : IPersonInfoQuery 14 { 15 private List<User> UserList = new List<User>(); 16 /// <summary> 17 /// 生成一些測試數據 18 /// </summary> 19 public PersonInfoQueryServices() 20 { 21 UserList.Add(new User() { ID = 1, Name = "張三", Age = 18, Score = 98 }); 22 UserList.Add(new User() { ID = 2, Name = "李四", Age = 20, Score = 80 }); 23 UserList.Add(new User() { ID = 3, Name = "王二麻子", Age = 25, Score = 59 }); 24 } 25 /// <summary> 26 /// 實現GetScore方法,返回某人的成績 27 /// </summary> 28 /// <param name="name"></param> 29 /// <returns></returns> 30 public User GetScore(string name) 31 { 32 return UserList.FirstOrDefault(n => n.Name == name); 33 } 34 /// <summary> 35 /// 實現GetInfo方法,返回某人的User信息 36 /// </summary> 37 /// <param name="info"></param> 38 /// <returns></returns> 39 public User GetInfo(Info info) 40 { 41 return UserList.FirstOrDefault(n => n.ID == info.ID && n.Name == info.Name); 42 } 43 44 } 45 }
3、服務編寫宿主程序
上面我們定義了Service接口,並實現了Service方法,現在我們需要將編寫宿主程序,以便能夠使客戶端通過GET或者POST方法進行請求。代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.ServiceModel; 5 using System.ServiceModel.Description; 6 using System.ServiceModel.Web; 7 using System.Text; 8 using System.Threading.Tasks; 9 10 namespace RestFulService 11 { 12 class Program 13 { 14 static void Main(string[] args) 15 { 16 try 17 { 18 PersonInfoQueryServices service = new PersonInfoQueryServices(); 19 Uri baseAddress = new Uri("http://127.0.0.1:7788/"); 20 using (ServiceHost _serviceHost = new ServiceHost(service, baseAddress))//或者:WebServiceHost _serviceHost = new WebServiceHost(typeof(PersonInfoQueryServices), baseAddress); 21 { 22 //如果不設置MaxBufferSize,當傳輸的數據特別大的時候,很容易出現“提示:413 Request Entity Too Large”錯誤信息,最大設置為20M 23 WebHttpBinding binding = new WebHttpBinding 24 { 25 TransferMode = TransferMode.Buffered, 26 MaxBufferSize = 2147483647, 27 MaxReceivedMessageSize = 2147483647, 28 MaxBufferPoolSize = 2147483647, 29 ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max, 30 Security = { Mode = WebHttpSecurityMode.None } 31 }; 32 _serviceHost.AddServiceEndpoint(typeof(IPersonInfoQuery), binding, baseAddress); 33 _serviceHost.Opened += delegate 34 { 35 Console.WriteLine("Web服務已開啟..."); 36 }; 37 _serviceHost.Open(); 38 Console.WriteLine("輸入任意鍵關閉程序!"); 39 Console.ReadKey(); 40 _serviceHost.Close(); 41 } 42 } 43 catch (Exception) 44 { 45 Console.WriteLine("Web服務開啟失敗:{0}\r\n{1}", ex.Message, ex.StackTrace); 46 } 47 } 48 } 49
如果上面的ServiceHost服務啟動了,但是提示服務卻找不到相對應的接口,可以使用下面服務方式(簡單明了~)
1 //使用新的方式 2 WebServiceHost _serviceHost = new WebServiceHost(service, baseAddress); 3 _serviceHost.Open(); 4 Console.WriteLine("Web服務已開啟..."); 5 Console.WriteLine("輸入任意鍵關閉程序!"); 6 Console.ReadKey(); 7 _serviceHost.Close();
開啟效果如下:
最后,我們通過瀏覽器簡單的測試一下GET請求的效果是怎樣的,如圖所示:
PS:更多Restful的C#規范,可以參考微軟的文檔--->WebGet和WebInvoke傳送門 API傳送門
五、Restful 客戶端
上面我們已經簡單的成功實現了Restful Service,下面我們簡單的講解一下,如何實現Restful 客戶端來校驗上面的Restful 服務器的正確性。PS:如何定義高效便捷的Restful Client幫助類,我們將在下篇文章進行講解,本文就先介紹一種簡單有效的Restful Client做一個Demo測試。
我們定義一個Restful 客戶端的幫助類RestClient,用於和Restful 服務端交互,如圖所示:
、
幫助類代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 using System.Net; 6 using System.Text; 7 using System.Threading.Tasks; 8 9 namespace RestFulClient 10 { 11 /// <summary> 12 /// 請求類型 13 /// </summary> 14 public enum EnumHttpVerb 15 { 16 GET, 17 POST, 18 PUT, 19 DELETE 20 } 21 22 public class RestClient 23 { 24 #region 屬性 25 /// <summary> 26 /// 端點路徑 27 /// </summary> 28 public string EndPoint { get; set; } 29 30 /// <summary> 31 /// 請求方式 32 /// </summary> 33 public EnumHttpVerb Method { get; set; } 34 35 /// <summary> 36 /// 文本類型(1、application/json 2、txt/html) 37 /// </summary> 38 public string ContentType { get; set; } 39 40 /// <summary> 41 /// 請求的數據(一般為JSon格式) 42 /// </summary> 43 public string PostData { get; set; } 44 #endregion 45 46 #region 初始化 47 public RestClient() 48 { 49 EndPoint = ""; 50 Method = EnumHttpVerb.GET; 51 ContentType = "application/json"; 52 PostData = ""; 53 } 54 55 public RestClient(string endpoint) 56 { 57 EndPoint = endpoint; 58 Method = EnumHttpVerb.GET; 59 ContentType = "application/json"; 60 PostData = ""; 61 } 62 63 public RestClient(string endpoint, EnumHttpVerb method) 64 { 65 EndPoint = endpoint; 66 Method = method; 67 ContentType = "application/json"; 68 PostData = ""; 69 } 70 71 public RestClient(string endpoint, EnumHttpVerb method, string postData) 72 { 73 EndPoint = endpoint; 74 Method = method; 75 ContentType = "application/json"; 76 PostData = postData; 77 } 78 #endregion 79 80 #region 方法 81 /// <summary> 82 /// http請求(不帶參數請求) 83 /// </summary> 84 /// <returns></returns> 85 public string HttpRequest() 86 { 87 return HttpRequest(""); 88 } 89 90 /// <summary> 91 /// http請求(帶參數)