一、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 }
開啟效果如下:
最後,我們通過浏覽器簡單的測試一下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請求(帶參數)
92 /// </summary>
93 /// <param name="parameters">parameters例如:?name=LiLei</param>
94 /// <returns></returns>
95 public string HttpRequest(string parameters)
96 {
97 var request = (HttpWebRequest)WebRequest.Create(EndPoint + parameters);
98
99 request.Method = Method.ToString();
100 request.ContentLength = 0;
101 request.ContentType = ContentType;
102
103 if (!string.IsNullOrEmpty(PostData) && Method == EnumHttpVerb.POST)
104 {
105 var bytes = Encoding.UTF8.GetBytes(PostData);
106 request.ContentLength = bytes.Length;
107
108 using (var writeStream = request.GetRequestStream())
109 {
110 writeStream.Write(bytes, 0, bytes.Length);
111 }
112 }
113
114 using (var response = (HttpWebResponse)request.GetResponse())
115 {
116 var responseValue = string.Empty;
117
118 if (response.StatusCode != HttpStatusCode.OK)
119 {
120 var message = string.Format("請求資料失敗. 傳回的 HTTP 狀态碼:{0}", response.StatusCode);
121 throw new ApplicationException(message);
122 }
123
124 using (var responseStream = response.GetResponseStream())
125 {
126 if (responseStream != null)
127 using (var reader = new StreamReader(responseStream))
128 {
129 responseValue = reader.ReadToEnd();
130 }
131 }
132 return responseValue;
133 }
134 }
135 #endregion
136 }
137 }
接下來我們驗證上面resful 服務端當中的兩個執行個體,代碼如下:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6 using Newtonsoft.Json;
7
8 namespace RestFulClient
9 {
10 class Program
11 {
12 static void Main(string[] args)
13 {
14 Console.Title = "Restful用戶端Demo測試";
15
16 RestClient client = new RestClient();
17 client.EndPoint = @"http://127.0.0.1:7788/";
18
19 client.Method = EnumHttpVerb.GET;
20 string resultGet = client.HttpRequest("PersonInfoQuery/王二麻子");
21 Console.WriteLine("GET方式擷取結果:" + resultGet);
22
23 client.Method = EnumHttpVerb.POST;
24 Info info = new Info();
25 info.ID = 1;
26 info.Name = "張三";
27 client.PostData = JsonConvert.SerializeObject(info);//JSon序列化我們用到第三方Newtonsoft.Json.dll
28 var resultPost = client.HttpRequest("PersonInfoQuery/Info");
29 Console.WriteLine("POST方式擷取結果:" + resultPost);
30 Console.Read();
31 }
32 }
33
34 [Serializable]
35 public class Info
36 {
37 public int ID { get; set; }
38 public string Name { get; set; }
39 }
40 }
結果如圖所示:
至此、一個簡單的Restful 服務端和Restful 用戶端的就這樣實作了,下篇文章講解如果設計一個高效可擴充的Restful完整幫助類,這樣我們就可以跨平台、跨語言通路其他平台提供的Restful Service了。
文章代碼已上傳Github--->下載下傳位址
PS:如有疑問,請留言,未經允許,不得私自轉載,轉載請注明出處:http://www.cnblogs.com/xuliangxing/p/8735552.html
轉載于:https://www.cnblogs.com/xuliangxing/p/8735552.html