概述
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:
/// <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:
/// <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(); }
接下來實現
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:
/// <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; }
接下來我們寫發送請求代碼:
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的返回結果格式如下,:
[{"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
歡迎各位參與討論,如果覺得對你有幫助,請點擊 推薦下,萬分謝謝.
作者:spring yang
出處:http://www.cnblogs.com/springyangwc/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。