Rest API 開發 學習筆記

benbenxiongyuan發表於2014-01-23

 

概述

REST 從資源的角度來觀察整個網路,分佈在各處的資源由URI確定,而客戶端的應用通過URI來獲取資源的表示方式。獲得這些表徵致使這些應用程式轉變了其狀態。隨著不斷獲取資源的表示方式,客戶端應用不斷地在轉變著其狀態,所謂表述性狀態轉移(Representational State Transfer)。

這一觀點不是憑空臆造的,而是通過觀察當前Web網際網路的運作方式而抽象出來的。Roy Fielding 認為,

“設計良好的網路應用表現為一系列的網頁,這些網頁可以看作的虛擬的狀態機,使用者選擇這些連結導致下一網頁傳輸到使用者端展現給使用的人,而這正代表了狀態的轉變。”

REST是設計風格而不是標準。REST通常基於使用HTTP,URI,和XML以及HTML這些現有的廣泛流行的協議和標準。

  • 資源是由URI來指定。
  • 對資源的操作包括獲取、建立、修改和刪除資源,這些操作正好對應HTTP協議提供的GET、POST、PUT和DELETE方法。
  • 通過操作資源的表現形式來操作資源。
  • 資源的表現形式則是XML或者HTML,取決於讀者是機器還是人,是消費web服務的客戶軟體還是web瀏覽器。當然也可以是任何其他的格式。
REST的要求
  • 客戶端和伺服器結構
  • 連線協議具有無狀態性
  • 能夠利用Cache機制增進效能
  • 層次化的系統
  • 隨需程式碼 - Javascript (可選)

 

RESTful Web 服務

RESTful Web 服務(也稱為 RESTful Web API)是一個使用HTTP並遵循REST原則的Web服務。它從以下三個方面資源進行定義:URI,比如:http://example.com/resources/。

§ Web服務接受與返回的網際網路媒體型別,比如:JSON,XML ,YAML 等。

§ Web服務在該資源上所支援的一系列請求方法(比如:POST,GET,PUT或DELETE)。

該表列出了在實現RESTful Web 服務時HTTP請求方法的典型用途。

HTTP 請求方法在RESTful Web 服務中的典型應用

資源

GET

PUT

POST

DELETE

一組資源的URI,比如http://example.com/resources/

列出 URI,以及該資源組中每個資源的詳細資訊(後者可選)。

使用給定的一組資源替換當前整組資源。

在本組資源中建立/追加一個新的資源。 該操作往往返回新資源的URL。

刪除 整組資源。

單個資源的URI,比如http://example.com/resources/142

獲取 指定的資源的詳細資訊,格式可以自選一個合適的網路媒體型別(比如:XML、JSON等)

替換/建立 指定的資源。並將其追加到相應的資源組中。

把指定的資源當做一個資源組,並在其下建立/追加一個新的元素,使其隸屬於當前資源。

刪除 指定的元素。

PUT 和 DELETE 方法是冪等方法。GET方法是安全方法 (不會對伺服器端有修改,因此也是冪等的)。

不像基於SOAP的Web服務,RESTful Web服務並沒有的“正式”標準。 這是因為REST是一種架構,而SOAP只是一個協議。雖然REST不是一個標準,但在實現RESTful Web服務時可以使用其他各種標準(比如HTTP,URL,XML,PNG等)。

REST的優點

  • 可以利用快取Cache來提高響應速度
  • 通訊本身的無狀態性可以讓不同的伺服器的處理一系列請求中的不同請求,提高伺服器的擴充套件性
  • 瀏覽器即可作為客戶端,簡化軟體需求
  • 相對於其他疊加在HTTP協議之上的機制,REST的軟體依賴性更小
  • 不需要額外的資源發現機制
  • 在軟體技術演進中的長期的相容性更好

 

Rest 開發

首先先定義介面IRestHandler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/// <summary>
/// The IRestHandler is an interface which provides Delete,Get,Post and Put methods.
/// </summary>
public interface IRestHandler : ICloneable
{
    /// <summary>
    /// Delete method for RestHandler
    /// </summary>
    /// <param name="processor">The rest processor.</param>
    /// <param name="authenticated">if set to <c>true</c> [authenticated].</param>
    /// <returns>The http response</returns>
    RestHandlerResponse Delete(IRestProcessor processor, bool authenticated);
 
    /// <summary>
    /// Get method for RestHandler
    /// </summary>
    /// <param name="processor">The rest processor.</param>
    /// <param name="authenticated">if set to <c>true</c> [authenticated].</param>
    /// <returns>The http response</returns>
    RestHandlerResponse Get(IRestProcessor processor, bool authenticated);
 
    /// <summary>
    /// Post method for RestHandler
    /// </summary>
    /// <param name="processor">The rest processor.</param>
    /// <param name="authenticated">if set to <c>true</c> [authenticated].</param>
    /// <returns>The http response</returns>
    RestHandlerResponse Post(IRestProcessor processor, bool authenticated);
 
    /// <summary>
    /// Put method for RestHandler
    /// </summary>
    /// <param name="processor">The rest processor.</param>
    /// <param name="authenticated">if set to <c>true</c> [authenticated].</param>
    /// <returns>The http response</returns>
    RestHandlerResponse Put(IRestProcessor processor, bool authenticated);
}

我們要定義一個HttpListener,先定義一個介面IRestListener:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/// <summary>
/// Listen an ip point and accept connection.
/// </summary>
public interface IRestListener : IDisposable
{
    /// <summary>
    /// Gets or sets the max allowed connections to this listener.
    /// </summary>
    int MaxConnections { get; set; }
 
    /// <summary>
    /// Gets or sets desktop rest manager.
    /// </summary>
    DesktopRestManager DesktopRestManager { get; set; }
 
    /// <summary>
    /// Gets a value that indicate if it is listening.
    /// </summary>
    bool IsRunning { get; }
 
    /// <summary>
    /// Gets or sets the server address information.
    /// </summary>
    IPEndPoint ServerAddress { get; set; }
 
    string Protocol { get; set; }
 
    /// <summary>
    /// Start a listener.
    /// </summary>
    /// <returns>The ip end point to listen.</returns>
    bool Start(TcpListener listener, IPEndPoint address);
 
    /// <summary>
    /// Stop the listener.
    /// </summary>
    /// <returns>True if successfully, else false.</returns>
    bool Stop();
}

 

 

接下來實現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
public class HttpListener : IRestListener
    {
        public HttpListener(DesktopRestManager drm)
        {
            this.desktopRestManager = drm;
            this.isRunning = false;          
            MaxConnections = 50;
            this.serverAddress = new IPEndPoint(new IPAddress(new byte[] { 127, 0, 0, 1 }), 10000);
            this.Protocol = "Http";          
        }
 
        #region IRestServer Members
 
        //public event ServerStatusChangedHandler ServerStatusChanged;
 
        public DesktopRestManager DesktopRestManager
        {
            get { return this.desktopRestManager; }
            set { this.desktopRestManager = value; }
        }
 
        public bool IsRunning
        {
            get { return this.isRunning; }
        }
 
        public IPEndPoint ServerAddress
        {
            get { return this.serverAddress; }
            set { this.serverAddress = value; }
        }
 
        public int MaxConnections
        {
            get;
            set;
        }
 
        public string Protocol
        {
            get;
            set;
        }
 
 
        public bool Start(TcpListener tcpListener, IPEndPoint address)
        {
            this.ServerAddress = address;
            this.listener = tcpListener;
            this.isRunning = true;
            Thread th = new Thread(new ThreadStart(this.Listening));
            th.Start();
            return true;
        }
 
        public bool Stop()
        {
            bool success = true; ;
            if (this.isRunning == true)
            {
                try
                {
                    this.isRunning = false;
                    if (listener != null)
                    {
                        listener.Stop();
                    }
                }
                catch (SocketException socketEx)
                {
                    _traceLog.InfoFormat("Stop http rest server: {0}", socketEx.Message);
                    success = false;
                }
                 
            }
            return success;
        }
 
        #endregion
 
        #region IDisposable Members
 
        public void Dispose()
        {
            this.Stop();
        }
 
        #endregion
 
        #region Private Methods
        private void Listening()
        {
            while (this.isRunning)
            {
                TcpClient tcpClient = null;
                try
                {
                    tcpClient = listener.AcceptTcpClient();
                    HttpConnection connection = new HttpConnection(tcpClient);
                    RestProcessor rh = new RestProcessor(this.desktopRestManager);
                    Thread processThread = new Thread(new ParameterizedThreadStart(req => connection.SendResponse(rh.HandleRequest(req as RestHandlerRequest))));
                    processThread.Name = "RestManager_Http_ProcessRequest";
                    processThread.Start(connection.GetRequest());
                }
                catch (SocketException socketEx)
                {
                    if (this.isRunning)
                    {
                        _traceLog.InfoFormat("Socket exception: {0}", socketEx.Message);
                    }
                    else
                    {
                        _traceLog.Info("The use stop the http listener.");
                    }
                    if (tcpClient != null && tcpClient.Connected)
                    {
                        tcpClient.Close();
                    }
                }
                catch (System.ArgumentNullException ex)
                {
                    _traceLog.ErrorFormat("Error occured: {0}", ex.Message);
                }
                catch (System.OutOfMemoryException ex)
                {
                    _traceLog.ErrorFormat("Error occured: {0}", ex.Message);
                }
                catch (System.Threading.ThreadStateException ex)
                {
                    _traceLog.ErrorFormat("Error occured: {0}", ex.Message);
                }
                catch (System.InvalidOperationException ex)
                {
                    _traceLog.ErrorFormat("Error occured: {0}", ex.Message);
                }
                catch (ApplicationException ex)
                {
                    _traceLog.ErrorFormat("Error occured: {0}", ex.Message);
                }
            }
            this.Stop();
        }
 
        #endregion
 
        #region Private Members
        private DesktopRestManager desktopRestManager;
        private bool isRunning;
        private IPEndPoint serverAddress;
        private TcpListener listener;
        private static LogManager _traceLog = new LogManager("RestManager-HttpListener");
        #endregion
    }

 

 

接下來處理HandleRequest:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/// <summary>
        /// Handles an http request for an Api call.
        /// </summary>
        public RestHandlerResponse HandleRequest(RestHandlerRequest rhr)
        {
            RestHandlerResponse res;
            // 50 Requests in maximum
            if (!this.restProcessorSemaphore.WaitOne(0))
            {
                 
               res = new RestHandlerResponse(503);
            }
 
            else
            {
                try
                {
                    // There is no need decode the url here, since the address will be decoded when it is parsed.
                    //rhr.Address = System.Web.HttpUtility.UrlDecode(rhr.Address);
 
                    res = this.process(rhr);
 
                }
                catch (RestManagerException ex)
                {
                    traceLog.ErrorFormat("Error happened while processing request\n{1}.\nException info:\n{0}  ",ex.Message);
                    res = new RestHandlerResponse(500);
                }
                try
                {
                    this.restProcessorSemaphore.Release();
                }
                catch (System.Threading.SemaphoreFullException)
                {
                    traceLog.ErrorFormat("Error happened while processing Semaphore.Release");
                }
                catch (System.IO.IOException)
                {
                    traceLog.ErrorFormat("Error happened while processing Semaphore.Release");
                }
                catch (System.UnauthorizedAccessException)
                {
                    traceLog.ErrorFormat("Error happened while processing Semaphore.Release");
                }
            }
 
            return res;
        }

 

 

接下來我們寫傳送請求程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private JObject MakeRequest(string url)
{
    var subsequentRequest = WebRequest.Create(url) as HttpWebRequest;
    subsequentRequest.Timeout = 30000;
    subsequentRequest.Headers.Add("Authorization", "OAuth " + TestToken);
    subsequentRequest.Headers.Add("App-User", TestUserName);
 
    WebResponse subsequentResponse;
 
    try
    {
        subsequentResponse = subsequentRequest.GetResponse();
        Stream stream = subsequentResponse.GetResponseStream();
        StreamReader sr = new StreamReader(stream);
        string output = sr.ReadToEnd();
        JObject jsonStr = JObject.Parse(output);
        return jsonStr;
 
    }
    catch (WebException ex)
    {
        if (ex.Response != null)
        {
            HttpWebResponse errorResponse = (HttpWebResponse)ex.Response;
            StreamReader reader = new StreamReader(errorResponse.GetResponseStream());
            string output = reader.ReadToEnd();
            JObject jsonStr = JObject.Parse(output);
            return jsonStr;
        }
        else
        {
            return null;
        }
    }
}

 

 

涉及專案的原因,程式碼只能提供這麼多了,僅供參考

Json的返回結果格式如下,:

1
[{"CreatedDate":"//Date(1299687080328+0800)//","Detail":"Do Something 1","Title":"Task1"},{"CreatedDate":"//Date(1299687080328+0800)//","Detail":"Do Something 5","Title":"Task5"}]

 

REST vs SOAP
成熟度:
SOAP雖然發展到現在已經脫離了初衷,但是對於異構環境服務釋出和呼叫,以及廠商的支援都已經達到了較為成熟的情況。不同平臺,開發語言之間通過SOAP來互動的web service都能夠較好的互通(在部分複雜和特殊的引數和返回物件解析上,協議沒有作很細緻的規定,導致還是需要作部分修正)

REST國外很多大網站都發布了自己的開發API,很多都提供了SOAP和REST兩種Web Service,根據調查部分網站的REST風格的使用情況要高於SOAP。但是由於REST只是一種基於Http協議實現資源操作的思想,因此各個網站的REST實現都自有一套,在後面會講訴各個大網站的REST API的風格。也正是因為這種各自實現的情況,在效能和可用性上會大大高於SOAP釋出的web service,但統一通用方面遠遠不及SOAP。由於這些大網站的SP往往專注於此網站的API開發,因此通用性要求不高。

由於沒有類似於SOAP的權威性協議作為規範,REST實現的各種協議僅僅只能算是私有協議,當然需要遵循REST的思想,但是這樣細節方面有太多沒有約束的地方。REST日後的發展所走向規範也會直接影響到這部分的設計是否能夠有很好的生命力。

總的來說SOAP在成熟度上優於REST。

效率和易用性:
       SOAP協議對於訊息體和訊息頭都有定義,同時訊息頭的可擴充套件性為各種網際網路的標準提供了擴充套件的基礎,WS-*系列就是較為成功的規範。但是也由於SOAP由於各種需求不斷擴充其本身協議的內容,導致在SOAP處理方面的效能有所下降。同時在易用性方面以及學習成本上也有所增加。

       REST被人們的重視,其實很大一方面也是因為其高效以及簡潔易用的特性。這種高效一方面源於其面向資源介面設計以及操作抽象簡化了開發者的不良設計,同時也最大限度的利用了Http最初的應用協議設計理念。同時,在我看來REST還有一個很吸引開發者的就是能夠很好的融合當前Web2.0的很多前端技術來提高開發效率。例如很多大型網站開放的REST風格的API都會有多種返回形式,除了傳統的xml作為資料承載,還有(JSON,RSS,ATOM)等形式,這對很多網站前端開發人員來說就能夠很好的mashup各種資源資訊。

       因此在效率和易用性上來說,REST更勝一籌。

安全性:
       這點其實可以放入到成熟度中,不過在當前的網際網路應用和平臺開發設計過程中,安全已經被提到了很高的高度,特別是作為外部介面給第三方呼叫,安全性可能會高過業務邏輯本身。

       SOAP在安全方面是通過使用XML-Security和XML-Signature兩個規範組成了WS-Security來實現安全控制的,當前已經得到了各個廠商的支援,.net ,php ,java 都已經對其有了很好的支援(雖然在一些細節上還是有不相容的問題,但是互通基本上是可以的)。

       REST沒有任何規範對於安全方面作說明,同時現在開放REST風格API的網站主要分成兩種,一種是自定義了安全資訊封裝在訊息中(其實這和SOAP沒有什麼區別),另外一種就是靠硬體SSL來保障,但是這隻能夠保證點到點的安全,如果是需要多點傳輸的話SSL就無能為力了。安全這塊其實也是一個很大的問題,今年在BEA峰會上看到有演示採用SAML2實現的網站間SSO,其實是直接採用了XML-Security和XML-Signature,效率看起來也不是很高。未來REST規範化和通用化過程中的安全是否也會採用這兩種規範,是未知的,但是加入的越多,REST失去它高效性的優勢越多。

應用設計與改造:
       我們的系統要麼就是已經有了那些需要被髮布出去的服務,要麼就是剛剛設計好的服務,但是開發人員的傳統設計思想讓REST的形式被接受還需要一點時間。同時在資源型資料服務介面設計上來說按照REST的思想來設計相對來說要容易一些,而對於一些複雜的服務介面來說,可能強要去按照REST的風格來設計會有些牽強。這一點其實可以看看各大網站的介面就可以知道,很多網站還要傳入function的名稱作為引數,這就明顯已經違背了REST本身的設計思路。

       而SOAP本身就是面向RPC來設計的,開發人員十分容易接受,所以不存在什麼適應的過程。

總的來說,其實還是一個老觀念,適合的才是最好的
       技術沒有好壞,只有是不是合適,一種好的技術和思想被誤用了,那麼就會得到反效果。REST和SOAP各自都有自己的優點,同時如果在一些場景下如果去改造REST,其實就會走向SOAP(例如安全)。

       REST對於資源型服務介面來說很合適,同時特別適合對於效率要求很高,但是對於安全要求不高的場景。而SOAP的成熟性可以給需要提供給多開發語言的,對於安全性要求較高的介面設計帶來便利。所以我覺得純粹說什麼設計模式將會佔據主導地位沒有什麼意義,關鍵還是看應用場景。

       同時很重要一點就是不要扭曲了REST現在很多網站都跟風去開發REST風格的介面,其實都是在學其形,不知其心,最後弄得不倫不類,效能上不去,安全又保證不了,徒有一個看似象摸象樣的皮囊。

 

參考文獻:

http://blog.csdn.net/terryzero/article/details/6295855

http://zh.wikipedia.org/wiki/REST

 

 

轉自:http://www.cnblogs.com/springyangwc/archive/2012/01/18/2325784.html

相關文章