Github 同步發表連接html
筆者在上一篇文章中提過,任何一種非「強制性」約束同時也沒有「標杆」工具支持的開發風格或協議,最後都會在不一樣的程序員手中獲得不一樣的詮釋,微服務是如此,DDD 是如此,筆者把它稱爲技術思想上的「康威定律」。不出意外的,REST 一樣難逃此劫。光是在學習和收集資料的過程當中,筆者就已經見過不下十多篇此類理解,甚至於在 url 中使用短劃線或下劃線鏈接單詞也是衆口難調。git
儘管這只是小事。程序員
微軟也發佈過關於如何設計 REST API 的開發指南,可是不幸的是,REST 的創始人 Roy Fielding 認爲微軟的 REST API 規範與 REST 沒有多大關係。github
「即便是我最糟糕的 REST 描述也比微軟的 API 指南提供的總結或參考要好不少。」後端
那什麼纔是正確的 REST 描述呢,或者說,REST 是什麼。本文的創做動機即是希冀於解決這樣一個問題。api
本文假設讀者已經具有基本的 REST 和 Web 知識,哪怕大家如今認爲 HTTP API 就是 REST API 也可。瀏覽器
REST 英文全稱爲 Representational State Transfer,又名「表述性狀態移交」,是由 Roy Fielding 在《架構風格與基於網絡的軟件架構設計》一文中提出的一種架構風格(Architectural Style)。而在這篇 REST 聖經問世以前,R.F 博士就已經參與了 HTTP 1.0 協議規範的開發工做(1996年),而且負責了 HTTP 1.1 協議規範的制定(1997年)。緩存
一種架構風格由一組準確命名的,相互協做的架構約束組成。當咱們在談論 REST 本質的時候,咱們談論的實際上是架構約束。安全
REST 用以指導基於網絡的分佈式超媒體系統的設計和實現,Web(即萬維網)就是一種典型的分佈式超媒體系統。能夠肯定的是,在制定 HTTP 協議的過程當中,R.F 博士就已經以 REST 架構風格做爲指導原則來完成相關工做。論文中提到了如下內容:ruby
「在過去的6年中,咱們使用 REST 架構風格來指導現代 Web 架構的設計和開發。這個工做是與我所創做的 HTTP 和 URI 兩個互聯網規範共同完成的,這兩個規範定義了在 Web 上進行交互的全部組件所使用的通用接口。」
「自從1994年起,REST 架構風格就被用來指導現代 Web 架構的設計與開發。」
「開發 REST 的動機是爲 Web 的運轉方式建立一種架構模型,使之成爲 Web 協議標準的指導框架。」
「REST 的初版開發於1994年10月至1995年8月之間,起初,在我編寫 HTTP/1.0 規範和最初的 HTTP/1.1 建議時,將它用來做爲表達各類 Web 概念的一種方法……」
Web 架構規範主要包括 HTTP, URI 和 HTML 等。
因此咱們也不難理解爲何 REST 與 Web 和 HTTP 可以結合得如此緊密。儘管直到2000年,這隻「雞」纔在下完雞蛋後,出如今了世人面前。
不管是否願意認可,REST 一開始就是爲 Web 而服務的,能夠這麼說的是,REST 是現代 Web 的架構風格,Web 也是 REST 最典型和最成功的案例。包括在 R.F 博士的論文中,他也是在解決現代 Web 需求(沒法控制的可伸縮性和獨立部署)的過程當中而逐步推導出 REST。前文已經提到一種架構風格是由一組準確命名的,相互協做的架構約束組成。而所謂架構約束,即是這個推導過程當中最重要的產物。甚至高於 REST 自己。
早先的 Web 與 REST 所描述的模型有着大量出入,然而正是在對應的 HTTP 和 URI 規範出爐後,纔有了所謂「現代 Web」的說法。筆者更願意把「現代 Web」的定義期限定爲1996年後。
設計與實現上的關注點分離。
在客戶端沒有發起請求時,服務器並不知道它的存在。一樣的,服務器無須維護當前請求以外的客戶端狀態,從而改善服務器的可伸縮性。Session 和 Cookie 都是「須要」被拋棄的。
若是有些應用狀態重要到服務器須要去關心,那它應該成爲一個資源。
對於客戶端而言,使用緩存則是維護狀態和提高性能的更好作法。
使 REST 架構風格區別於其餘基於網絡的架構風格的核心特徵是,它強調組件之間具備一個統一的接口。實現與他們所提供的服務是解耦的,這促進了獨立的可進化性。同時這也引伸出了其餘的約束:資源識別;經過表述來操做資源;自描述信息;超媒體做爲應用狀態引擎(即 HATEOAS)。下文會專門說明。
「分層系統」約束在「客戶 - 服務端」約束的基礎上增長了代理組件和網關組件。儘管筆者認爲代理和網關都不是重點,「分層系統」約束更注重的是「在客戶端和服務端之間添加一個組件應該是一個透明操做」,組件只能「看到」與其交互的相鄰層(是否是想到了迪米特法則),使用層級來封裝服務,同時可以支持負載均衡和諸如安全性檢查的功能。
這是六大約束中惟一的可選約束。REST 容許客戶端經過下載並執行腳本或其餘形式的代碼,對客戶端的功能進行擴展,從而提升客戶端的靈活性和性能。通俗點說,HTML 中的 <script>
標籤就是一種按需代碼,儘管它可能會致使一些例如跨站腳本攻擊這樣的問題。
R.F 博士在論文中針對六大約束中的「統一接口」作了額外的約束分解和說明,但遺憾的是並無以列表的方式展現出來。但在接下來的內容中你可能就會發現,這幾項多是目前大部分開發者踐行 REST 原則時所遵循的所有標準。
REST 對於信息的核心抽象是資源,任何可以被命名的信息均可以稱爲是資源,只要你的想象力容許。資源一詞一般和「可尋址性」綁定,一個或多個 URI 標識一個資源。若是資源的 URI 發生了變化,服務器應該使用超媒體引導客戶端訪問新的 URI 或提示對應信息。
當客戶端對一個資源發起一個請求時,服務器會以一種有效的方式提供一個採集了資源信息的文檔做爲迴應。這就是表述——一種以機器可讀的方式對資源當前狀態的說明。客戶端和服務器之間也能夠繼續傳遞表述,從而對資源執行某種操做。客戶端歷來不會直接看到資源,能看到的都是資源的表述。能夠這麼說的是,服務器發送的表述用於描述資源當前的狀態,客戶端發送的表述用於描述客戶端但願資源擁有的狀態,這就是表述性狀態轉移/移交。
一個表述由一個「字節序列」和描述這些字節的「表述元數據」構成,且不與服務器端代碼綁定,這意味着當服務器端的資源實現和業務操做代碼發生變化時,能夠選擇不更改資源的呈現方式。
值得注意的是,通常人一般會將表述認爲成資源的「值」,這雖然能夠理解,可是當你請求一個天氣服務時,千萬不要認爲表述必定即是溫度等肯定的值信息,由於它仍然多是某次響應中的錯誤提示。一個表述的具體含義取決於消息中的控制數據。
「控制數據定義了在組件之間移交的消息的用途,例如被請求的動做或相應的含義。它也可用於提供請求的參數,或覆蓋某些鏈接元素的默認行爲。例如,可使用(包含在請求或響應消息中的)控制數據來修改緩存的行爲。」
「表述的數據格式稱爲媒體類型(media type)。發送者可以將一個表述包含在一個消息中,發送給接收者。接收者收到消息以後,根據消息中的控制數據和媒體類型的性質,來對該消息進行處理。」
表述在現代 Web 中的實例包括 HTML,Json,XML,圖片等。
一個 (HTTP)消息體包含了全部足以讓接收者理解它的必要信息,在現代 Web 中,自描述的消息由一些標準的HTTP方法、可定製的HTTP頭信息、可定製的HTTP響應代碼組成。擴展開來,它一般有如下三方面的含義。
該約束即是大名鼎鼎的「HATEOAS」(Hypertext/Hypermedia As The Engine Of Application State),但實際上 R.F 博士在論文中並無對它作過詳細的介紹。在目前的共識中(諷刺的是在大多數時候它並無被應用到設計所謂 REST APIs 中去),HATEOAS 意味着客戶端應該使用超文原本做爲你在接收到當前的表述後,再進行下一步尋址的方式。更進一步的,客戶端須要經過解析超文本理解服務器提供了哪些資源,而不是在客戶端事先定義或約定俗成。
「客戶端依賴的是超文本的狀態遷移語義,而不該該對因而否存在某個URI或URI的某種特殊構造方式做出假設。一切都有可能變化,只有超媒體的狀態遷移語義可以長期保持穩定。」 —— 《理解本真的REST架構風格》
最終結果即是客戶端能夠自動化地適應服務器端的變化,服務器也容許在不破壞全部客戶端的狀況下更改它底層的實現。一樣的,咱們能夠列出幾點說明。
看起來,上述四點內容說的可能是集中式 Web 應用的狀況,在現在多用 Web APIs 進行先後端分離開發的 Web 應用中,HATEOAS 又該作如何理解呢?現在有這麼一項技術可讓超文本繼續充當驅動應用狀態更新流動的引擎,那就是 Web Links,RFC 5988 定義了 HTTP 的這項擴展。
在 Github REST API v3 中,咱們能夠在不少 apis (如列表翻頁)的響應體中看到 Link Header,對應引導的 Uri 一樣有相關標準,即 Uri Templates(RFC 6570)
Link: <https://api.github.com/user/repos?page=3&per_page=100>; rel="next", <https://api.github.com/user/repos?page=50&per_page=100>; rel="last"
至此,想必你也大體清晰了 HATEOAS 的含義。
若是能夠的話,你能夠將「應用狀態」理解爲客戶端對資源操做後的展現結果。好比「主頁」,「博客」,「關於我」,「成功提交」等操做界面。它和「資源狀態」有抽象概念上的區分。
你能夠放棄對 Hypertext 和 Hypermedia 之間區別的思考,筆者也認爲它們在你理解 REST 時並不該該區分。
Richardson Maturity Model 是一個能夠嘗試的甜品,特別是當你在設計本身的 REST APIs 時。和 REST 類似,你也能夠把該模型稱爲指導原則。
筆者無心去解釋這幾個層級,由於這些層級和「統一接口」的擴展約束都是間接的映射關係。並且正如上文一直在強調的,REST 不依賴於任何單一的通訊/傳輸/移交協議,因此模型中的 HTTP 指示就有些回味無窮了。
「它是標準嗎?」
「固然不是,它只是目前設計 REST APIs 時的一種潛規則。」
對於理解概念性文章的總結老是特別艱難,看起來內容挺豐富,同時結合了 R.F 博士論文及其譯者李琨教授相關文章,其實攤開了目錄整篇文章也就只涉及到了起源—>約束->模型這樣幾個方面,實際上 REST 也確實只是一組約束而已。最後,僅用筆者認爲的 R.F 博士論文中相當重要的兩段話做爲結束。
「所以,REST的模型應用是一個引擎,它經過檢查和選擇當前的表述集合中的狀態遷移選項,從一個狀態遷移到下一個狀態。絕不奇怪,這與一個超媒體瀏覽器的用戶接口徹底匹配。然而,REST風格並不假設全部應用都是瀏覽器。事實上,通用的鏈接器接口對服務器隱藏了應用的細節,所以各類形式的用戶代理都是等價的,不管是爲一個索引服務執行信息獲取任務的自動化機器人,仍是查找匹配特定查詢標準的數據的私人代理,或者是忙於巡視破損的引用或被修改的內容的維護爬蟲。」
「這個名稱「表述性狀態轉移」是有意喚起人們對於一個良好設計的Web應用如何運轉的印象:一個由網頁組成的網絡(一個虛擬狀態機),用戶經過選擇連接(狀態遷移)在應用中前進,致使下一個頁面(表明應用的下一個狀態)被轉移給用戶,而且呈現給他們,以便他們來使用。」