前記:一開始原本是想用自己的語言寫一篇關(guān)于RESTful架構(gòu)的文章祠饺,然而無奈于自己也是剛剛接觸哩牍,怕理解不到位有誤導(dǎo)讀者之嫌玲昧,故重新將這篇文章定位為了本人的RESRful學(xué)習(xí)路徑疗垛。若有錯誤罢洲,還請大家指正乱灵。
什么是RESTful架構(gòu)塑崖?
參考文獻(xiàn):理解 RESTful 架構(gòu) - 阮一峰
注:以下內(nèi)容都引用自該文獻(xiàn)
REST這個詞,是Roy Thomas Fielding在他2000年的博士論文中提出的痛倚。Fielding是一個非常重要的人规婆,他是HTTP協(xié)議(1.0版和1.1版)的主要設(shè)計(jì)者、Apache服務(wù)器軟件的作者之一蝉稳、Apache基金會的第一任主席抒蚜。所以,他的這篇論文一經(jīng)發(fā)表耘戚,就引起了關(guān)注嗡髓,并且立即對互聯(lián)網(wǎng)開發(fā)產(chǎn)生了深遠(yuǎn)的影響。
Fielding將他對互聯(lián)網(wǎng)軟件的架構(gòu)原則收津,定名為REST饿这,即Representational State Transfer的縮寫浊伙。我對這個詞組的翻譯是"表現(xiàn)層狀態(tài)轉(zhuǎn)化"。
如果一個架構(gòu)符合REST原則长捧,就稱它為RESTful架構(gòu)嚣鄙。
要理解RESTful架構(gòu),最好的方法就是去理解Representational State Transfer這個詞組到底是什么意思串结,它的每一個詞代表了什么涵義哑子。如果你把這個名稱搞懂了,也就不難體會REST是一種什么樣的設(shè)計(jì)奉芦。
REST的名稱"表現(xiàn)層狀態(tài)轉(zhuǎn)化"中赵抢,省略了主語。"表現(xiàn)層"其實(shí)指的是"資源"(Resources)的"表現(xiàn)層"声功。
所謂"資源"烦却,就是網(wǎng)絡(luò)上的一個實(shí)體,或者說是網(wǎng)絡(luò)上的一個具體信息先巴。它可以是一段文本其爵、一張圖片、一首歌曲伸蚯、一種服務(wù)摩渺,總之就是一個具體的實(shí)在。你可以用一個URI(統(tǒng)一資源定位符)指向它剂邮,每種資源對應(yīng)一個特定的URI摇幻。要獲取這個資源,訪問它的URI就可以挥萌,因此URI就成了每一個資源的地址或獨(dú)一無二的識別符绰姻。
"資源"是一種信息實(shí)體,它可以有多種外在表現(xiàn)形式引瀑。我們把"資源"具體呈現(xiàn)出來的形式狂芋,叫做它的"表現(xiàn)層"(Representation)。
比如憨栽,文本可以用txt格式表現(xiàn)帜矾,也可以用HTML格式、XML格式屑柔、JSON格式表現(xiàn)屡萤,甚至可以采用二進(jìn)制格式;圖片可以用JPG格式表現(xiàn)锯蛀,也可以用PNG格式表現(xiàn)灭衷。
訪問一個網(wǎng)站,就代表了客戶端和服務(wù)器的一個互動過程旁涤。在這個過程中翔曲,勢必涉及到數(shù)據(jù)和狀態(tài)的變化迫像。
互聯(lián)網(wǎng)通信協(xié)議HTTP協(xié)議,是一個無狀態(tài)協(xié)議瞳遍。這意味著闻妓,所有的狀態(tài)都保存在服務(wù)器端。因此掠械,如果客戶端想要操作服務(wù)器由缆,必須通過某種手段,讓服務(wù)器端發(fā)生"狀態(tài)轉(zhuǎn)化"(State Transfer)猾蒂。而這種轉(zhuǎn)化是建立在表現(xiàn)層之上的均唉,所以就是"表現(xiàn)層狀態(tài)轉(zhuǎn)化"。
客戶端用到的手段肚菠,只能是HTTP協(xié)議舔箭。具體來說,就是HTTP協(xié)議里面蚊逢,四個表示操作方式的動詞:GET层扶、POST、PUT烙荷、DELETE镜会。它們分別對應(yīng)四種基本操作:GET用來獲取資源,POST用來新建資源(也可以用于更新資源)终抽,PUT用來更新資源戳表,DELETE用來刪除資源。
優(yōu)秀的RESTful架構(gòu)需要具備哪些特質(zhì)昼伴?
參考文獻(xiàn):Principles of good RESTful API Design - (譯:好 RESTful API 的設(shè)計(jì)原則 - moonz-wu)
注:以下內(nèi)容都引用自該文獻(xiàn)
這里有一些非常重要的術(shù)語扒袖,我將在本文里面一直用到它們:
資源:一個對象的單獨(dú)實(shí)例,如一只動物
集合:一群同種對象亩码,如動物
HTTP:跨網(wǎng)絡(luò)的通信協(xié)議
客戶端:可以創(chuàng)建HTTP請求的客戶端應(yīng)用程序
第三方開發(fā)者:這個開發(fā)者不屬于你的項(xiàng)目但是有想使用你的數(shù)據(jù)
服務(wù)器:一個HTTP服務(wù)器或者應(yīng)用程序,客戶端可以跨網(wǎng)絡(luò)訪問它
端點(diǎn):這個API在服務(wù)器上的URL用于表達(dá)一個資源或者一個集合
冪等:無邊際效應(yīng)野瘦,多次操作得到相同的結(jié)果
URL段:在URL里面已斜杠分隔的內(nèi)容
一描沟、四個半非常重要的HTTP動詞
這里至少有四個半非常重要的HTTP動詞需要你知道。我之所以說“半個”的意思是PATCH這個動詞非常類似于PUT鞭光,并且它們倆也常常被開發(fā)者綁定到同一個API上吏廉。
GET (選擇):從服務(wù)器上獲取一個具體的資源或者一個資源列表。
POST (創(chuàng)建): 在服務(wù)器上創(chuàng)建一個新的資源惰许。
PUT (更新):以整體的方式更新服務(wù)器上的一個資源席覆。
PATCH (更新):只更新服務(wù)器上一個資源的一個屬性。
DELETE (刪除):刪除服務(wù)器上的一個資源汹买。
還有兩個不常用的HTTP動詞:
HEAD : 獲取一個資源的元數(shù)據(jù)佩伤,如數(shù)據(jù)的哈希值或最后的更新時間聊倔。
OPTIONS:獲取客戶端能對資源做什么操作的信息。
一個好的RESTful API只允許第三方調(diào)用者使用這四個半HTTP動詞進(jìn)行數(shù)據(jù)交互生巡,并且在URL段里面不出現(xiàn)任何其他的動詞耙蔑。
二、版本信息
無論你正在構(gòu)建什么孤荣,無論你在入手前做了多少計(jì)劃甸陌,你核心的應(yīng)用總會發(fā)生變化,數(shù)據(jù)關(guān)系也會變化盐股,資源上的屬性也會被增加或刪除钱豁。只要你的項(xiàng)目還活著,并且有大量的用戶在用疯汁,這種情況總是會發(fā)生牲尺。
請謹(jǐn)記一點(diǎn),API是服務(wù)器與客戶端之間的一個公共契約涛目。如果你對服務(wù)器上的API做了一個更改秸谢,并且這些更改無法向后兼容,那么你就打破了這個契約霹肝,客戶端又會要求你重新支持它估蹄。為了避免這樣的事情,你既要確保應(yīng)用程序逐步的演變沫换,又要讓客戶端滿意臭蚁。那么你必須在引入新版本API的同時保持舊版本API仍然可用。
注:如果你只是簡單的增加一個新的特性到API上讯赏,如資源上的一個新屬性或者增加一個新的端點(diǎn)垮兑,你不需要增加API的版本。因?yàn)檫@些并不會造成向后兼容性的問題漱挎,你只需要修改文檔即可系枪。
隨著時間的推移,你可能聲明不再支持某些舊版本的API磕谅。申明不支持一個特性并不意味著關(guān)閉或者破壞它私爷。而是告訴客戶端舊版本的API將在某個特定的時間被刪除,并且建議他們使用新版本的API膊夹。
一個好的RESTful API會在URL中包含版本信息衬浑。另一種比較常見的方案是在請求頭里面保持版本信息。但是跟很多不同的第三方開發(fā)者一起工作后放刨,我可以很明確的告訴你工秩,在請求頭里面包含版本信息遠(yuǎn)沒有放在URL里面來的容易。
三、簡潔的API根入口點(diǎn)
無論你信不信助币,API的根地址很重要浪听。當(dāng)一個開發(fā)者接手了一個舊項(xiàng)目(如進(jìn)行代碼考古時)。而這個項(xiàng)目正在使用你的API奠支,同時開發(fā)者還想構(gòu)建一個新的特性馋辈,但他們完全不知道你的服務(wù)。幸運(yùn)的是他們知道客戶端對外調(diào)用的那些URL列表倍谜。讓你的API根入口點(diǎn)保持盡可能的簡單是很重要的迈螟,因?yàn)殚_發(fā)者很可能一看到那些冗長而又復(fù)雜的URL就轉(zhuǎn)身而走。
這里有兩個常見的URL根例子:
https://example.org/api/v1/*
https://api.example.com/v1/*
如果你的應(yīng)用很龐大或者你預(yù)期它將會變的很龐大尔崔,那么將API放到子域下通常是一個好選擇答毫。這種做法可以保持某些規(guī)模化上的靈活性季春。
但如果你覺得你的API不會變的很龐大洗搂,或是你只是想讓應(yīng)用安裝更簡單些(如你想用相同的框架來支持站點(diǎn)和API),將你的API放到根域名下也是可以的载弄。
讓API根擁有一些內(nèi)容通常也是個好主意耘拇。Github的API根就是一個典型的例子。從個人角度來說我是一個通過根URL發(fā)布信息的粉絲宇攻,這對很多人來說是有用的惫叛,例如如何獲取API相關(guān)的開發(fā)文檔。
同樣也請注意HTTPS前綴逞刷,一個好的RESTful API總是基于HTTPS來發(fā)布的嘉涌。
四、減少無謂的限制
當(dāng)客戶端創(chuàng)建了一個請求來獲取一個對象列表時夸浅,很重要一點(diǎn)就是你要返回給他們一個符合查詢條件的所有對象的列表仑最。這個列表可能會很大。但你不能隨意給返回?cái)?shù)據(jù)的數(shù)量做限制帆喇。因?yàn)檫@些無謂的限制會導(dǎo)致第三方開發(fā)者不知道發(fā)生了什么警医。如果他們請求一個確切的集合并且要遍歷結(jié)果,然而他們發(fā)現(xiàn)只拿到了100條數(shù)據(jù)坯钦。接下來他們就不得不去查找這個限制條件的出處法严。到底是ORM的bug導(dǎo)致的,還是因?yàn)榫W(wǎng)絡(luò)截?cái)嗔舜髷?shù)據(jù)包葫笼?
盡可能減少那些會影響到第三方開發(fā)者的無謂限制
這點(diǎn)很重要,但你可以讓客戶端自己對結(jié)果做一些具體的過濾或限制拗馒。這么做最重要的一個原因是可以最小化網(wǎng)絡(luò)傳輸路星,并讓客戶端盡可能快的得到查詢結(jié)果。其次是客戶端可能比較懶,如果這時服務(wù)器能對結(jié)果做一些過濾或分頁洋丐,對大家都是好事呈昔。另外一個不那么重要的原因是(從客戶端角度來說),對服務(wù)器來說響應(yīng)請求的負(fù)載越少越好友绝。
四堤尾、符合標(biāo)準(zhǔn)的狀態(tài)碼
對于一個RESTful API來說很重要的一點(diǎn)就是要使用HTTP的狀態(tài)碼,因?yàn)樗鼈兪荋TTP的標(biāo)準(zhǔn)迁客。很多的網(wǎng)絡(luò)設(shè)備都可以識別這些狀態(tài)碼郭宝,例如負(fù)載均衡器可能會通過配置來避免發(fā)送請求到一臺web服務(wù)器,如果這臺服務(wù)器已經(jīng)發(fā)送了很多的50x錯誤回來掷漱。
1xx范圍的狀態(tài)碼是保留給底層HTTP功能使用的粘室,并且估計(jì)在你的職業(yè)生涯里面也用不著手動發(fā)送這樣一個狀態(tài)碼出來。
2xx范圍的狀態(tài)碼是保留給成功消息使用的卜范,你盡可能的確保服務(wù)器總發(fā)送這些狀態(tài)碼給用戶衔统。
3xx范圍的狀態(tài)碼是保留給重定向用的。大多數(shù)的API不會太常使用這類狀態(tài)碼海雪,但是在新的超媒體樣式的API中會使用更多一些锦爵。
4xx范圍的狀態(tài)碼是保留給客戶端錯誤用的。例如奥裸,客戶端提供了一些錯誤的數(shù)據(jù)或請求了不存在的內(nèi)容险掀。這些請求應(yīng)該是冪等的,不會改變?nèi)魏畏?wù)器的狀態(tài)刺彩。
5xx范圍的狀態(tài)碼是保留給服務(wù)器端錯誤用的迷郑。這些錯誤常常是從底層的函數(shù)拋出來的,并且開發(fā)人員也通常沒法處理创倔。發(fā)送這類狀態(tài)碼的目的是確蔽撕Γ客戶端能得到一些響應(yīng)。收到5xx響應(yīng)后畦攘,客戶端沒辦法知道服務(wù)器端的狀態(tài)霸妹,所以這類狀態(tài)碼是要盡可能的避免。
五知押、完善的API文檔
老實(shí)說叹螟,即使你不能百分之百的遵循指南中的條款,你的API也不是那么糟糕台盯。但是罢绽,如果你不為API準(zhǔn)備文檔的話,沒有人會知道怎么使用它静盅,那它真的會成為一個糟糕的API良价。
讓你的文檔對那些未經(jīng)認(rèn)證的開發(fā)者也可用
不要使用文檔自動化生成器,即便你用了,你也要保證自己審閱過并讓它具有更好的版式明垢。
不要截?cái)嗍纠姓埱笈c響應(yīng)的內(nèi)容蚣常,要展示完整的東西。并在文檔中使用高亮語法痊银。
文檔化每一個端點(diǎn)所預(yù)期的響應(yīng)代碼和可能的錯誤消息抵蚊,和在什么情況下會產(chǎn)生這些的錯誤消息
如果你有富余的時間,那就創(chuàng)建一個控制臺來讓開發(fā)者可以立即體驗(yàn)一下API的功能溯革。創(chuàng)建一個控制臺并沒有想象中那么難贞绳,并且開發(fā)者們(內(nèi)部或者第三方)也會因此而擁戴你。
另外確保你的文檔能夠被打印鬓照。CSS是個強(qiáng)大的工具可以幫助到你熔酷。而且在打印的時候也不用太擔(dān)心邊側(cè)欄的問題。即便沒有人會打印到紙上豺裆,你也會驚奇的發(fā)現(xiàn)很多開發(fā)者愿意轉(zhuǎn)化成PDF格式進(jìn)行離線閱讀拒秘。
六、提供主流數(shù)據(jù)格式
目前臭猜,大多數(shù)“精彩”的API都為RESTful接口提供JSON數(shù)據(jù)躺酒。諸如Facebook,Twitter蔑歌,Github等等你所知的羹应。XML曾經(jīng)也火過一把(通常在一個大企業(yè)級環(huán)境下)。這要感謝SOAP次屠,不過它已經(jīng)掛了园匹,并且我們也沒看到太多的API把HTML作為結(jié)果返回給客戶端(除非你在構(gòu)建一個爬蟲程序)。
只要你返回給他們有效的數(shù)據(jù)格式劫灶,開發(fā)者就可以使用流行的語言和框架進(jìn)行解析裸违。如果你正在構(gòu)建一個通用的響應(yīng)對象,通過使用一個不同的序列化器本昏,你也可以很容易的提供之前所提到的那些數(shù)據(jù)格式(不包括SOAP)供汛。而你所要做的就是把使用方式放在響應(yīng)數(shù)據(jù)的接收頭里面。
有些API的創(chuàng)建者會推薦把.json, .xml, .html等文件的擴(kuò)展名放在URL里面來指示返回內(nèi)容類型涌穆,但我個人并不習(xí)慣這么做怔昨。我依然喜歡通過接收頭來指示返回內(nèi)容類型(這也是HTTP標(biāo)準(zhǔn)的一部分),并且我覺得這么做也比較適當(dāng)一些宿稀。