本文轉載自文章:我所理解的RESTful Web API [設計篇]。html
其餘參考文章有:RESTful 介紹 ppt,Restful的理解,Restful 優缺點,理解RESTful架構,RESTful API 設計指南,HTTP報文web
Web服務已經成爲了異質系統之間的互聯與集成的主要手段,在過去一段不短的時間裏,Web服務幾乎清一水地採用SOAP來構建。構建REST風格的Web服務是最近兩三年風行的潮流,因此不少人覺得REST是一個事物。而事實倒是:REST自其誕生之日起到如今(2014年)已經有14年了,它爲何叫這麼一個「奇怪」的名字呢?算法
目錄
1、爲何叫這個「奇怪」的名字?數據庫2、採用URI標識資源 segmentfault
3、採用URI標識資源 後端
4、使用「連接」關聯相關的資源 api
5、使用統一的接口 跨域
6、使用標準的HTTP方法。瀏覽器
7、支持多種資源表示方式 緩存
8、無狀態性
9、我的總結(本身加的)
2000年,Roy Thomas Fielding博士在他那篇著名的博士論文《Architectural Styles and the Design of Network-based Software Architectures》中提出了幾種軟件應用的架構風格,
REST做爲其中的一種架構風格在這篇論文的第5章中進行了歸納性的介紹。我我的建議本書的讀者都能讀讀這篇論文,原文和中文譯文均可以從網絡上找到。
REST是「REpresentational State Transfer」的縮寫,能夠翻譯成「表現狀態轉換」,可是在絕大多數場合中咱們只說REST或者RESTful。
爲何會起這麼一個奇怪的名字呢?咱們能夠從上述這篇論文中找到答案。Fielding在論文中將REST定位爲「分佈式超媒體應用(Distributed Hypermedia System)」的架構風格,它在文中提到一個名爲「HATEOAS(Hypermedia as the engine of application state)」的概念。
咱們利用一個面向最終用戶的Web應用來對這個概念進行簡單闡述:
這裏所謂的應用狀態(Application State)表示Web應用的客戶端的狀態,簡單起見能夠理解爲會話狀態。
資源在瀏覽器中以超媒體的形式呈現,經過點擊超媒體中的連接能夠獲取其它相關的資源或者對當前資源進行相應的處理,
獲取的資源或者針對資源處理的響應一樣以超媒體的形式再次呈如今瀏覽器上。因而可知,超媒體成爲了驅動客戶端會話狀態的轉換的引擎。
藉助於超媒體這種特殊的資源呈現方式,應用狀態的轉換體現爲瀏覽器中呈現資源的轉換。
若是將超媒體進一步抽象成通常意義上的資源呈現(Representation )方式,那麼應用狀態變成了可被呈現的狀態(REpresentational State)。應用狀態之間的轉換就成了可被呈現的狀態裝換(REpresentational State Transfer),這就是REST。
REST在我看來是一種很籠統的概念,它表明一種架構風格。對於多個Web應用採用的架構,咱們只能說其中某一個比其它的更具備REST風格,而不能簡單粗暴地說:「它採用了REST架構而其它的沒有」。
爲了將REST真正地落地,Lenoard Rechardson & Sam Ruby在《RESTful Web Services》一書中提出了一種名爲「面向資源的架構(ROA: Resource Oriented Architecture)」。
該書中介紹了一些採用ROA架構的Web服務應該具有的基本特徵,它們能夠指導咱們如何構架具體的RESTful Web API。
SOAP Web API採用RPC風格,它採用面向功能的架構,因此咱們在設計SOAP Web API的時候首相考慮的是應高提供怎樣的功能(或者操做)。
RESTful Web API採用面向資源的架構,因此在設計之初首先須要考慮的是有哪些資源可供操做。
資源是一個很寬泛的概念,任何寄宿於Web可供操做的「事物」都可視爲資源。
資源能夠體現爲通過持久化處理保存到磁盤上的某個文件或者數據庫中某個表的某條記錄,也能夠是Web應用接受到請求後採用某種算法計算得出的結果。
資源能夠體現爲一個具體的物理對象,它也能夠是一個抽象的流程。
一個資源必須具備一個或者多個標識,既然咱們設計的Web API,那麼很天然地應該採用URI來做爲資源的標識。
做爲資源標識的URI最好具備「可讀性」,由於具備可讀性的URI更容易被使用,使用者一看就知道被標識的是何種資源,好比以下一些URI就具備很好的可讀性。
除了必要的標誌性和可選的可讀性以外,標識資源的URI應該具備「可尋址性(Addressability)」。也就是說,URI不只僅指明瞭被標識資源所在的位置,並且經過這個URI能夠直接獲取目標資源。經過前面的介紹 咱們知道URI具備URL和URN兩種主要的表現形式,只要前者具備可尋址性,因此咱們最好採用一個URL做爲資源的標識。
URI除了能夠標識某個獨立的資源外(好比「http://www.artech.com/employees/c001」),還能夠標識一組資源的集合或者資源的容器(好比「http://www.artech.com/orders/2013/q4」)。
固然,一組同類資源的集合或者存放一組同類資源的容器自己也能夠視爲另外一種類型的複合型(Composite)資源,因此「URI老是標識某個資源」這種說法是沒有問題的。
在絕大多數狀況下,資源並不會孤立地存在,必然與其它資源具備某種關聯。
既然咱們推薦資源採用具備可尋址性的URL來標識,那麼咱們就能夠利用它來將相關的資源關聯起來。
好比咱們採用XML來表示一部電影的信息,那麼咱們採用以下的形式利用URL將相關的資源(導演、領銜主演、主演、編劇以及海報)關聯在一塊兒。
實際上這能夠視爲一份超文本/超媒體文檔。當用戶獲得這樣一份文檔的時候,能夠利用自身的內容得到某部影片基本的信息,還能夠利用相關的「連接」獲得其它相關內容的詳細信息。
1: <movie> 2: <name>魔鬼代言人</name> 3: <genre>劇情|懸疑|驚悚</genre> 4: <directors> 5: <add ref="http://www.artech.com/directors/taylor-hackford">泰勒.海克福德</add> 6: </directors> 7: <starring> 8: <add ref = "http://www.artech.com/actors/al-pacino">阿爾.帕西諾</add> 9: <add ref = "http://www.artech.com/actors/keanu-reeves ">基諾.李維斯</add> 10: </starring> 11: <supportingActors> 12: <add ref = "http://www.artech.com/actors/charlize-theron ">查理茲.塞隆</add> 13: <add ref = "http://www.artech.com/actors/jeffrey-jones ">傑弗瑞.瓊斯</add> 14: <add ref = "http://www.artech.com/actors/connie-nielsen">康尼.尼爾森</add> 15: </supportingActors> 16: <scriptWriters> 17: <add ref = "http://www.artech.com/scriptwriters/jonathan-lemkin">喬納森•萊姆金</add> 19: <add ref = "http://www.artech.com/scriptwriters/tony-gilroy">託尼•吉爾羅伊 </add> 20: </scriptWriters> 21: <language>英語</language> 22: <poster ref = "http://www.artech.com/images/the-devil-s-advocate"/> 23: <story>...</story> 24: </movie>
Fielding在他的論文中將REST定位爲「分佈式超媒體應用」的架構風格,而超媒體的核心就是利用「連接」相關的信息結成一個非線性的網,因此從一點也能夠看出REST和「使用連接關聯相關的資源」這個特性使吻合的。
因爲REST是面向資源的,因此一個Web API旨在實現針對單一資源的操做。
咱們在前面已經說個,針對資源的基本操做惟CRUD而已,這是使咱們能夠爲Web API定義標準接口成可能。
所謂的標準接口就是針對不一樣資源的Web API定義一致性的操做來操做它們,其接口能夠採用相似於下面的模式。
1: public class ResourceService 2: { 3: public IEnumerable<Resource>[] Get(); 4: public void Create(Resource resource); 5: public void Update(Resource resource); 6: public void Delete(string id); 7: }
可否採用統一接口是RESTful Web API和採用RPC風格的SOAP Web服務又一區別。
若是採用RPC風格的話,咱們在設計Web API的時候首先考慮的是具體哪些功能須要被提供,因此這樣的Web API是一組相關功能的集合而已。
以一個具體的場景爲例。如今咱們須要設計一個Web API來管理用於受權的角色,它只須要提供針對角色自己的CRUD的功能以及創建/解除與用戶名之間的映射關係。若是咱們將其定義成針對SOAP的Web服務,其服務接口具備相似於以下的結構。
1: public class RoleService 2: { 3: public IEnumerable<string> GetAllRoles(); 4: public void CreateRole(string roleName); 5: public void DeleteRole(string roleName); 6: 7: public void AddRolesInUser(string userName, string[] roleNames); 8: public void RemoveRolesFromUser(string userName, string[] roleNames); 9: }
以下咱們須要將其定義成一個純粹的RESTful的Web API,只有前面三個方法在針對角色的CRUD操做範疇以內,可是後面兩個方法卻能夠視爲針對「角色委派(Role Assignment)」對象的添加和刪除操做。因此這裏實際上涉及到了兩種資源,即角色和角色委派。爲了使Web API具備統一的接口,咱們須要定義以下兩個Web API。
1: public class RolesService 2: { 3: public IEnumerable<string> Get(); 4: public void Create(string roleName); 5: public void Delete(string roleName); 6: } 7: 8: public class RoleAssignmentsService 9: { 10: public void Create(RoleAssignment roleName); 11: public void Delete(RoleAssignment roleName); 12: }
因爲RESTful Web API採用了同一的接口,因此其成員體現爲針對同一資源的操做。
對於Web來講,針對資源的操做經過HTTP方法來體現。咱們應該將二者統一塊兒來,是Web API分別針對CRUD的操做只能接受具備對應HTTP方法的請求。
咱們甚至能夠直接使用HTTP方法名做爲Web API接口的方法名稱,那麼這樣的Web API接口就具備相似於以下的定義。對於ASP.NET Web API來講,因爲它提供了Action方法名稱和HTTP方法的自動映射,因此若是咱們採用這樣的命名規則,就無需再爲具體的Action方法設定針對HTTP方法的約束了。
1: public class ResourceService 2: { 3: public IEnumerable<Resource>[] Get(); 4: public void Post(Resource resource); 5: public void Put(Resource resource); 6: public void Patch (Resource resource); 7: public void Delete(string id); 8: 9: public void Head(string id); 10: public void Options(); 11: }
上面代碼片段提供的7個方法涉及到了7個經常使用的HTTP方法,接下來咱們針對資源操做的語義對它們做一個簡單的介紹。
首先GET、HEAD和OPTIONS這三個HTTP方法旨在發送請求以或者所需的信息。
對於GET,相應全部人對它已經很是熟悉了,它用於獲取所需的資源,服務器通常講對應的資源置於響應的主體部分返回給客戶端。
HEAD和OPTIONS相對少見。從資源操做的語義來說,一個針對某個目標資源發送的HEAD請求通常不是爲了獲取目標資源自己的內容,而是獲得描述目標資源的元數據信息。
服務器通常講對應資源的元數據置於響應的報頭集合返回給客戶端,這樣的響應通常不具備主體部分。
OPTIONS請求旨在發送一種「探測」請求以肯定針對某個目標地址的請求必須具備怎樣的約束(好比應該採用怎樣的HTTP方法以及自定義的請求報頭),而後根據其約束髮送真正的請求。好比針對「跨域資源」的預檢(Preflight)請求採用的HTTP方法就是OPTIONS。
至於其它4中HTTP方法(POST、PUT、PATCH和DELETE),它們旨在針對目標資源做添加、修改和刪除操做。
對於DELETE,它的語義很明確,就是刪除一個已經存在的資源。咱們着重推薦其它三個旨在完成資源的添加和修改的HTTP方法做一個簡單的介紹。
經過發送POST和PUT請求都可以添加一個新的資源,可是二者的不一樣之處在於:
對於前者,請求着通常不能肯定標識添加資源最終採用的URI,即服務端最終爲成功添加的資源指定URI;
對於後者,最終標識添加資源的URI是能夠由請求者控制的。
也正是由於這個緣由,若是發送PUT請求,咱們通常直接將標識添加資源的URI做爲請求的URI;對於POST請求來講,其URI通常是標識添加資源存放容器的URI。
好比咱們分別發送PUT和POST請求以添加一個員工,標識員工的URI由其員工ID來決定。若是員工ID由客戶端來指定,咱們能夠發送PUT請求;若是員工ID由服務端生成,咱們通常發送POST請求。具體的請求與下面提供的代碼片段相似,能夠看出它們的URI也是不同的。
1: PUT http://www.artech.com/employees/300357 HTTP/1.1 2: ... 3: 4: <employee> 5: <id>300357</id> 6: <name>張三</name> 7: <gender>男<gender> 8: <birthdate>1981-08-24</birthdate> 9: <department>3041</department> 10: </employee> 1: POST http://www.artech.com/employees HTTP/1.1 2: ... 3: 4: <employee> 5: <name>張三</name> 6: <gender>男<gender> 7: <birthdate>1981-08-24</birthdate> 8: <department>3041</department> 9: </employee>
POST和PUT請求通常將所加資源的內容置於請求的主體。可是對於PUT請求來講,若是添加資源的內容徹底能夠由其URI來提供,這樣的請求能夠不須要主體。好比咱們經過請求添加一個用於控制權限的角色,標識添加角色的URI由其角色名稱來決定,而且不須要指定除角色名稱的其它信息,那麼咱們只要發送以下一個不含主體的PUT請求便可。
1: PUT http://www.artech.com/roles/admin HTTP/1.1 2: 3: ...
除了進行資源的添加,PUT請求還能用於資源的修改。因爲請求包含提交資源的標識(能夠放在URI中,也能夠置於保存在主體部分的資源內容中),因此服務端可以定位到對應的資源予以修改。
對於POST和PUT,也存在一種一刀切的說法:POST用於添加,PUT用於修改。我我的比較承認的是:若是PUT提供的資源不存在,則作添加操做,不然作修改。
對於發送PUT請求以修改某個存在的資源,服務器通常會將提供資源將原有資源總體「覆蓋」掉。若是須要進行「局部」修改,咱們推薦請求採用PATCH方法,由於從語義上講「Patch」就是打補丁的意思。
關於HTTP請求採用的這些個方法,具備兩個基本的特性,即「安全性」和「冪等性」。對於上述7種HTTP方法,GET、HEAD和OPTIONS均被認爲是安全的方法,由於它們旨在實現對數據的獲取,並不具備「邊界效應(Side Effect[1])」。
至於其它4個HTTP方法,因爲它們會致使服務端資源的變化,因此被認爲是不安全的方法。
冪等性(Idempotent)是一個數學上的概念,在這裏表示發送一次和屢次請求引發的邊界效應是一致的。在網速不夠快的狀況下,客戶端發送一個請求後不能當即獲得響應,因爲不能肯定是否請求是否被成功提交,因此它有可能會再次發送另外一個相同的請求,冪等性決定了第二個請求是否有效。
上述3種安全的HTTP方法(GET、HEAD和OPTIONS)均是冪等方法。因爲DELETE和PATCH請求操做的是現有的某個資源,因此它們是冪等方法。對於PUT請求,只有在對應資源不存在的狀況下服務器纔會進行添加操做,不然只做修改操做,因此它也是冪等方法。至於最後一種POST,因爲它老是進行添加操做,若是服務器接收到兩次相同的POST操做,將致使兩個相同的資源被建立,因此這是一個非冪等的方法。
當咱們在設計Web API的時候,應該儘可能根據請求HTTP方法的冪等型來決定處理的邏輯。因爲PUT是一個冪等方法,因此攜帶相同資源的PUT請求不該該引發資源的狀態變化,若是咱們在資源上附加一個自增加的計數器表示被修改的次數,這實際上就破壞了冪等型。
不過就我我的的觀點來講,在有的場合下針對冪等型要求能夠不須要那麼嚴格。舉個例子,我對於咱們開發的發部分應用來講,數據表基本上都有一個名爲LastUpdatedTime的字段表示記錄最後一次被修改的時間,由於這是爲了數據安全審覈(Auditing)的須要。在這種狀況下,若是接收到一個基於數據修改的PUT請求,咱們老是會用提交數據去覆蓋現有的數據,並將當前服務端時間(客戶端時間不可靠)做爲字段LastUpdatedTime的值,這實際上也破壞了冪等性。
可能有人說咱們能夠在真正修改數據以前檢查提交的數據是否與現有數據一致,可是在涉及多個表連接的時候這個「預檢」操做會帶來性能損失,並且針對每一個字段的逐一比較也是一個很繁瑣的事情,因此咱們通常不做這樣的預檢操做。
資源和資源的表示(Representaion)是兩個不一樣的概念:
資源自己是一個抽象的概念,是看不見摸不着的,而看得見摸得着的是資源的表現。
好比一個表示一個財年銷售狀況的資源,它既能夠表示爲一個列表、一個表格或者是一個圖表。若是採用圖表,又可使用柱狀圖、K線圖和餅圖等,這一切都是針對同一個資源的不一樣表示。
咱們說「調用Web API獲取資源」,這句話實際上是不正確的,由於咱們獲取的不是資源自己,僅僅是資源的某一種表示而已。
對於Web來講,目前具備兩種主流的數據結構,XML和JSON,它們也是資源的兩種主要的呈現方式。在多語言環境下,還應該考慮描述資源採用的語言。
咱們在設計Web API的時候,應該支持不一樣的資源表示,咱們不能假定請求提供的資源必定表示成XML,也不能老是以JSON格式返回獲取的資源,正確的作法是:
根據請求攜帶的信息識別提交和但願返回的資源表示。對於請求提交的資源,咱們通常利用請求的Content-Type報頭攜帶的媒體類型來判斷其採用的表示類型。對於響應資源表示類型的識別,能夠採用以下兩種方式。
對於上述兩種資源表示識別機制,咱們不少人會喜歡後者,由於第一種不夠「智能」。實際上前者具備一個後者不具備的特性:「瀏覽器兼容型」[2]。
對於Web API開發來講,瀏覽器應該成爲一種最爲經常使用的測試工具。在不借助任何插件的狀況下,咱們利用瀏覽器訪問咱們在地址欄中輸入的URI時對生成的請求內容不能做任何干預的,若是與資源表示相關的信息(好比語言、媒體類型)被直接包含到請求的URI中,那麼全部的狀況均可以利用瀏覽器直接測試。
有人從另外一方面對「URI攜帶資源表示類型」做了這樣的質疑:因爲URI是資源的標識,那麼這致使了相同的資源具備多個標識。其實這是沒有問題的,URI是資源的惟一標識,但不是其「惟一的惟一標識「,相同的資源能夠具備多個標識。
RESTful只要維護資源的狀態,而不須要維護客戶端的狀態。對於它來講,每次請求都是全新的,它只須要針對本次請求做相應的操做(由於其是面向資源的),不須要將本次請求的相關信息記錄下來以便用於後續來自相同客戶端請求的處理。
對於上面咱們介紹的RESTful的這些個特性,它們都是要求咱們爲了知足這些特徵作點什麼,惟有這個無狀態倒是要求咱們不要作什麼,由於HTTP自己就是無狀態的。
舉個例子,一個網頁經過調用Web API分頁獲取符合查詢條件的記錄。通常狀況下,頁面導航均具備「上一頁」和「下一頁」連接用於呈現當前頁的前一頁和後一頁的記錄。那麼如今有兩種實現方式返回上下頁的記錄。
第一種貌似很「智能」,其實就是一種多此一舉的做法,由於它破壞了Web API的無狀態性。設計無狀態的Web API不只僅使Web API自身顯得簡單而精煉,
還因減除了針對客戶端的「親和度(Affinty)」使咱們能夠有效地實施負載均衡,由於只有這樣集羣中的每一臺服務器對於每一個客戶端纔是等效的。
如何理解RESTful API
是什麼
是一種基於HTTP協議的一套編寫客戶端和服務器端交互接口的規範。
特色
- 採用的是面向資源的架構方式,因此在設計之初首先要考慮的是有哪些資源可供操做。建議在URL中儘可能使用名詞
- 給用戶一個URL,根據不一樣的METHOD在後端進行不一樣的處理。
- POST 新建一個資源
- GET 獲取一個資源
- PUT 修改資源
- DELETE 刪除數據
- 利用HTTP狀態碼來反應服務器的處理結果
- 使用JSON或XML等格式來返回結果
- 使用查詢參數過濾信息,由於有時咱們並不須要所有的數據
?limit=10
- 版本話RESTful API,有些數據或程序可能有多個版本共存
https://api.example.com/version1/
https://api.example.com/version2/
[1] 大部分計算機書籍都將Side Effect翻譯成「反作用」,而咱們通常將「副(負)做用」理解爲負面的做用,其實計算機領域Side Effect表示的做用無所謂正負,因此咱們以爲仍是還原其字面的含義「邊界效用」。除此以外,對於GET、HEAD和OPTIONS請求來講,若是服務端須要對它們做日誌、緩存甚至計數操做,嚴格來講這也算是一種Side Effect,可是請求的發送者不對此負責。
[2] 這裏的「兼容」不是指支持由瀏覽器發送的請求,由於經過執行JavaScript腳本可讓做爲宿主的瀏覽器發送任何咱們但願的請求,這裏的兼容體如今儘量地支持瀏覽器訪問咱們在地址欄中輸入的URI默認發送的HTTP-GET請求。