基於一些不錯的RESTful開發組件,能夠快速的開發出不錯的RESTful API,但若是不瞭解開發規範的、健壯的RESTful API的基本面,即使優秀的RESTful開發組件擺在面前,也沒法很好的理解和使用。下文Gevin結合本身的實踐經驗,整理了從零開始開發RESTful API的核心要點,完善的RESTful開發組件基本都會包含所有或大部分要點,對於支持不夠到位的要點,咱們也能夠本身寫代碼實現。javascript
RESTful API的開發和使用,無非是客戶端向服務器發請求(request),以及服務器對客戶端請求的響應(response)。本真RESTful架構風格具備統一接口的特色,即,使用不一樣的http方法表達不一樣的行爲:html
客戶端會基於GET
方法向服務器發送獲取數據的請求,基於PUT
或PATCH
方法向服務器發送更新數據的請求等,服務端在設計API時,也要按照相應規範來處理對應的請求,這點如今應該已經成爲全部RESTful API的開發者的共識了,並且各web框架的request類和response類都很強大,具備合理的默認設置和靈活的定製性,Gevin在這裏僅準備強調一下響應這些request時,經常使用的Response要包含的數據和狀態碼(status code),不完善的內容,歡迎你們補充:前端
GET
, PUT
和PATCH
請求成功時,要返回對應的數據,及狀態碼200
,即SUCCESSPOST
建立數據成功時,要返回建立的數據,及狀態碼201
,即CREATEDDELETE
刪除數據成功時,不返回數據,狀態碼要返回204
,即NO CONTENTGET
不到數據時,狀態碼要返回404
,即NOT FOUND400
,即BAD REQUEST401
,即NOT AUTHORIZED403
,即FORBIDDEN最後,關於Request 和 Response,不要忽略了http header中的Content-Type。以json爲例,若是API要求客戶端發送request時要傳入json數據,則服務器端僅作好json數據的獲取和解析便可,但若是服務端支持多種類型數據的傳入,如同時支持json和form-data,則要根據客戶端發送請求時header中的Content-Type,對不一樣類型是數據分別實現獲取和解析;若是API響應客戶端請求後,須要返回json數據,須要在header中添加Content-Type=application/json
。java
Serialization 和 Deserialization即序列化和反序列化。RESTful API以規範統一的格式做爲數據的載體,經常使用的格式爲json
或xml
,以json格式爲例,當客戶端向服務器發請求時,或者服務器相應客戶端的請求,向客戶端返回數據時,都是傳輸json格式的文本,而在服務器內部,數據處理時基本不用json格式的字符串,而是native類型的數據,最典型的如類的實例,即對象(object),json僅爲服務器和客戶端通訊時,在網絡上傳輸的數據的格式,服務器和客戶端內部,均存在將json轉爲native類型數據和將native類型數據轉爲json的需求,其中,將native類型數據轉爲json即爲序列化
,將json轉爲native類型數據即爲反序列化
。雖然某些開發語言,如Python
,其原生數據類型list
和dict
能輕易實現序列化和反序列化,但對於複雜的API,內部實現時總會以對象做爲數據的載體,所以,確保序列化和反序列化方法的實現,是開發RESTful API最重要的一步準備工做python
題外話,序列化和反序列化的便捷,造就了RESTful API跨平臺的特色,使得REST取代RPC成爲Web Service的主流git
序列化和反序列化是RESTful API開發中的一項硬需求,因此幾乎每一種經常使用的開發語言都會有一個或多個優秀的開源庫,來實現序列化和反序列化,所以,咱們在開發RESTful API時,不必製造重複的輪子,選一個好用的庫便可,如python中的marshmallow,若是基於Django開發,Django REST Framework中的serializer
便可。github
Validation即數據校驗,是開發健壯RESTful API中另外一個重要的一環。仍以json爲例,當客戶端向服務器發出post
, put
或patch
請求時,一般會同時給服務器發送json格式的相關數據,服務器在作數據處理以前,先作數據校驗,是最合理和安全的先後端交互。若是客戶端發送的數據不正確或不合理,服務器端通過校驗後直接向客戶端返回400錯誤及相應的數據錯誤信息便可。常見的數據校驗包括:web
以上三種類型的校驗,數據邏輯校驗最爲複雜,一般涉及到多個字段的配合,或者要結合用戶和權限作相應的校驗。Validation雖然是RESTful API 編寫中的一個可選項,但它對API的安全、服務器的開銷和交互的友好性而言,都具備重要意義,所以,Gevin建議,開發一套完善的RESTful API時,Validation的實現必不可少。正則表達式
Authentication指用戶認證,Permission指權限機制,這兩點是使RESTful API 強大、靈活和安全的基本保障。django
經常使用的認證機制是Basic Auth
和OAuth
,RESTful API 開發中,除非API很是簡單,且沒有潛在的安全性問題,不然,認證機制是必須實現的,並應用到API中去。Basic Auth
很是簡單,不少框架都集成了Basic Auth
的實現,本身寫一個也能很快搞定,OAuth
目前已經成爲企業級服務的標配,其相關的開源實現方案很是豐富(更多)。
我在《RESTful 架構風格概述》中,對認證機制
作了更加詳細的描述,有興趣的同窗不妨閱讀相關章節。
權限機制是對API請求更近一步的限制,只有經過認證的用戶符合權限要求,才能訪問API。權限機制的具體實現一般依賴於系統的業務邏輯和應用場景,generally speaking,經常使用的權限機制主要包含全局型
的和對象型
的,全局型的權限機制,主要指經過爲用戶賦予權限,或者爲用戶賦予角色或劃分到用戶組,而後爲角色或用戶組賦予權限的方式來實現權限控制,對象型的權限機制,主要指權限控制的顆粒度在object上,用戶對某個具體對象的訪問、修改、刪除或其行爲,要單獨在該對象上爲用戶賦予相關權限來實現權限控制。
全局型的權限機制容易理解,實現也簡單,有不少開源庫可作備選方案,很多完善的web開發框架,也會集成相關的權限邏輯,object permission 相對難複雜一點,但也有不少典型的應用場景,如多人博客系統中,做者對本身文章的編輯權限即爲object permission,其對應的開源庫也有不少。
注: 我寫過一篇《Django權限機制的實現》,Django 開發者可作延伸閱讀。
開發一套完整的RESTful API,權限機制必須歸入考慮範圍,雖然權限機制的具體實現依賴於業務,權限機制自己,是有典型的模式存在的,須要開發者掌握基本的權限機制實現方案,以便隨時應用到API中去。
CORS即Cross-origin resource sharing,在RESTful API開發中,主要是爲js服務的,解決javascript 調用 RESTful API時的跨域問題。
因爲固有的安全機制,js的跨域請求時是沒法被服務器成功響應的。如今先後端分離日益成爲web開發主流方式的大趨勢下,後臺逐漸趨向指提供API服務,爲各客戶端提供數據及相關操做,而網站的開發所有交給前端搞定,網站和API服務不多部署在同一臺服務器上並使用相同的端口,js的跨域請求時廣泛存在的,開發RESTful API時,一般都要考慮到CORS功能的實現,以便js能正常使用API。
目前各主流web開發語言都有不少優秀的實現CORS的開源庫,咱們在開發RESTful API時,要注意CORS功能的實現,直接拿現有的輪子來用便可。
更多關於CORS的介紹,有興趣的同窗能夠查看阮一峯老師的跨域資源共享 CORS 詳解
RESTful API 是寫給開發者來消費的,其命名和結構須要有意義。所以,在設計和編寫URL時,要符合一些規範。Url rules 能夠單獨寫一篇博客來詳細闡述,本文只列出一些關鍵點。
規範的API應該包含版本信息,在RESTful API中,最簡單的包含版本的方法是將版本信息放到url中,如:
/api/v1/posts/ /api/v1/drafts/ /api/v2/posts/ /api/v2/drafts/
另外一種優雅的作法是,使用HTTP header中的accept
來傳遞版本信息,這也是GitHub API 採起的策略。
RESTful API 中的url是指向資源的,而不是描述行爲的,所以設計API時,應使用名詞而非動詞來描述語義,不然會引發混淆和語義不清。即:
# Bad APIs /api/getArticle/1/ /api/updateArticle/1/ /api/deleteArticle/1/
上面四個url都是指向同一個資源的,雖然一個資源容許多個url指向它,但不一樣的url應該表達不一樣的語義,上面的API能夠優化爲:
# Good APIs /api/Article/1/
article 資源的獲取、更新和刪除分別經過 GET
, PUT
和 DELETE
方法請求API便可。試想,若是url以動詞來描述,用PUT
方法請求 /api/deleteArticle/1/
會感受多麼不舒服。
GET
and HEAD
should always be safeRFC2616已經明確指出,GET
和HEAD
方法必須始終是安全的。例如,有這樣一個不規範的API:
# The following api is used to delete articles # [GET] /api/deleteArticle?id=1
試想,若是搜索引擎訪問了上面url會如何?
若是要獲取一個資源子集,採用 nested routing
是一個優雅的方式,如,列出全部文章中屬於Gevin編寫的文章:
# List Gevin's articles /api/authors/gevin/articles/
獲取資源子集的另外一種方式是基於filter
(見下面章節),這兩種方式都符合規範,但語義不一樣:若是語義上將資源子集看做一個獨立的資源集合,則使用 nested routing
感受更恰當,若是資源子集的獲取是出於過濾的目的,則使用filter
更恰當。
至於編寫RESTful API時到底應採用哪一種方式,則仁者見仁,智者見智,語義上說的通便可。
對於資源集合,能夠經過url參數對資源進行過濾,如:
# List Gevin's articles /api/articles?author=gevin
分頁就是一種最典型的資源過濾。
對於資源集合,分頁獲取是一種比較合理的方式。若是基於開發框架(如Django REST Framework),直接使用開發框架中的分頁機制便可,若是是本身實現分頁機制,Gevin的策略是:
返回資源集合是,包含與分頁有關的數據以下:
{
"page": 1, # 當前是第幾頁 "pages": 3, # 總共多少頁 "per_page": 10, # 每頁多少數據 "has_next": true, # 是否有下一頁數據 "has_prev": false, # 是否有前一頁數據 "total": 27 # 總共多少數據 }
當想API請求資源集合時,可選的分頁參數爲:
參數 | 含義 |
---|---|
page | 當前是第幾頁,默認爲1 |
per_page | 每頁多少條記錄,默認爲系統默認值 |
另外,系統內還設置一個per_page_max
字段,用於標記系統容許的每頁最大記錄數,當per_page
值大於 per_page_max
值時,每頁記錄條數爲 per_page_max
。
(1)Url是區分大小寫的,這點常常被忽略,即:
/Posts
/posts
上面這兩個url是不一樣的兩個url,能夠指向不一樣的資源
(2)Back forward Slash (/
)
目前比較流行的API設計方案,一般建議url以/
做爲結尾,若是API GET
請求中,url不以/
結尾,則重定向到以/
結尾的API上去(這點如今的web框架基本都支持),由於有沒有 /
,也是兩個url,即:
/posts/
/posts
這也是兩個不一樣的url,能夠對應不一樣的行爲和資源
(3)鏈接符 -
和 下劃線 _
RESTful API 應具有良好的可讀性,當url中某一個片斷(segment)由多個單詞組成時,建議使用 -
來隔斷單詞,而不是使用 _
,即:
# Good /api/featured-post/ # Bad /api/featured_post/
這主要是由於,瀏覽器中超連接顯示的默認效果是,文字並附帶下劃線,若是API以_
隔斷單詞,兩者會重疊,影響可讀性。
編寫本文的初衷,是爲了整理一套從零開始編寫規範、安全的RESTful API的基本思路。本文介紹了開發RESTful API時,要考慮的基本內容,對於相似Flask這種天生支持RESTful風格的web框架,不依賴其餘RESTful第三方庫開發RESTful 服務時,能夠從本文內容入手;很多強大的RESTful 庫,雖然其相關接口基本涵蓋了本文的所有或大部份內容,但本文的總結,相信對這些庫的理解和使用也是有幫助的。
最後,關於如何開發RESTful API,歡迎你們與我交流~