REST接口設計規範總結
簡介
Representational State Transfer 簡稱 REST 描述了一個架構樣式的網絡系統。REST 指的是一組架構約束條件和原則。滿足這些約束條件和原則的應用程序或設計就是 RESTful。
概念:
-
資源(Resources) REST是”表現層狀態轉化”,其實它省略了主語。”表現層”其實指的是”資源”的”表現層”。那麽什麽是資源呢?就是我們平常上網訪問的一張圖片、一個文檔、一個視頻等。這些資源我們通過URI來定位,也就是一個URI表示一個資源。
-
表現層(Representation)
資源是做一個具體的實體信息,他可以有多種的展現方式。而把實體展現出來就是表現層,例如一個txt文本信息,他可以輸出成html、json、xml等格式,一個圖片他可以jpg、png等方式展現,這個就是表現層的意思。
URI確定一個資源,但是如何確定它的具體表現形式呢?應該在HTTP請求的頭信息中用Accept和Content-Type字段指定,這兩個字段才是對”表現層”的描述。 -
狀態轉化(State Transfer)訪問一個網站,就代表了客戶端和服務器的一個互動過程。在這個過程中,肯定涉及到數據和狀態的變化。而HTTP協議是無狀態的,那麽這些狀態肯定保存在服務器端,所以如果客戶端想要通知服務器端改變數據和狀態的變化,肯定要通過某種方式來通知它。
URI格式規範
- URI中盡量使用連字符”-“代替下劃線”_”的使用
- URI中統一使用小寫字母
- URI中不要包含文件(腳本)的擴展名
資源的原型
- 文檔(Document)
文檔是資源的單一表現形式,可以理解為一個對象,或者數據庫中的一條記錄。在請求文檔時,
要麽返回文檔對應的數據,要麽會返回一個指向另外一個資源(文檔)的鏈接。
以下是幾個基於文檔定義的URI例子:
https://api.example.com/users/will
https://api.example.com/posts/1
https://api.example.com/posts/1/comments/1
- 集合(Collection)
集合可以理解為是資源的一個容器(目錄),我們可以向裏面添加資源(文檔)。例如:
https://api.example.com/users
https://api.example.com/posts
https://api.example.com/posts/1/comments
- 倉庫(Store)
倉庫是客戶端來管理的一個資源庫,客戶端可以向倉庫中新增資源或者刪除資源。
客戶端也可以批量獲取到某個倉庫下的所有資源。倉庫中的資源對外的訪問不會提供單獨URI的,
客戶端在創建資源時候的URI除外。例如:
PUT /users/1234/favorites/posts/1
上面的例子我們可以理解為,我們向一個id是1234的用戶的倉庫(收藏夾)中,
添加了一個id為1的post資源。通俗點兒說:就是用戶收藏了一個自己喜愛的id為1的文章。
- 控制器(Controller)
控制器資源模型,可以執行一個方法,支持參數輸入,結果返回。 是為了除了標準操作:
增刪改查(CRUD)以外的一些邏輯操作。控制器(方法)一般定義子URI中末尾,
並且不會有子資源(控制器)。例如:
向用戶重發ID為245743的消息
POST /alerts/245743/resend
發布ID為1的文章
POST /posts/1/publish
把動作轉換成資源
把動作轉換成可以執行 CRUD 操作的資源, github 就是用了這種方法。
比如“喜歡”一個 gist,就增加一個 /gists/:id/star 子資源,
然後對其進行操作:“喜歡”使用 PUT /gists/:id/star,
“取消喜歡”使用 DELETE /gists/:id/star
或者使用 POST /gists/:id/unstar
另外一個例子是 Fork,這也是一個動作,但是在 gist 下面增加 forks資源,
就能把動作變成 CRUD 兼容的:POST /gists/:id/forks 可以執行用戶 fork 的動作。
URI命名規範
- 文檔(Document)類型的資源用名詞(短語)單數命名
- 集合(Collection)類型的資源用名詞(短語)復數命名
- 倉庫(Store)類型的資源用名詞(短語)復數命名
- 控制器(Controller)類型的資源用動詞(短語)命名
- URI中有些字段可以是變量,在實際使用中可以按需替換
例如一個資源URI可以這樣定義:
https://api.example.com/posts/{postId}/comments/{commentId}
postId,commentId 是變量(數字,字符串都類型都可以)。
- CRUD的操作不要體現在URI中,HTTP協議中的操作符已經對CRUD做了映射。
CRUD是創建,讀取,更新,刪除這四個經典操作的簡稱
例如刪除的操作用REST規範執行的話,應該是這個樣子:
DELETE /users/1234
以下是幾個錯誤的示例:
GET /deleteUser?id=1234
GET /deleteUser/1234
DELETE /deleteUser/1234
POST /users/1234/delete
URI的query字段
在REST中,query字段一般作為查詢的參數補充,也可以幫助標示一個唯一的資源。但需要註意的是,
作為一個提供查詢功能的URI,無論是否有query條件,我們都應該保證結果的唯一性,
一個URI對應的返回數據是不應該被改變的(在資源沒有修改的情況下)。
HTTP中的緩存也可能緩存查詢結果。
- Query參數可以作為Collection或Store類型資源的過濾條件來使用 例如:
GET /users //返回所有用戶列表
GET /users?role=admin //返回權限為admin的用戶列表
GET /search/users?q={query}{&page,per_page,sort,order} //根據多條件查詢用戶
- Query參數可以作為Collection或Store資源列表分頁標示使用
如果是一個簡單的列表操作,可以這樣設計:
GET /users?pageSize=25&pageStartIndex=50
如果是一個復雜的列表或查詢操作的話,我們可以為資源設計一個Collection,
因為復雜查詢可能會涉及比較多的參數,建議使用Post的方式傳入,例如這樣:
POST /users/search
相關的分頁信息還可以存放到 Link 頭部,這樣客戶端可以直接得到諸如下一頁、最後一頁、上一頁
等內容的 url 地址
Status: 200 OK
Link: <https://api.github.com/resource?page=2>; rel="previous",
<https://api.github.com/resource?page=2>; rel="next",
<https://api.github.com/resource?page=5>; rel="last"
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 19
HTTP請求方法的使用
- GET方法用來獲取資源
- PUT方法可用來新增/更新Store類型的資源
- PUT方法可用來更新一個資源的全部屬性,使用時傳遞所有屬性的值,即使有的值沒有改變
- PATCH方法更新資源的部分屬性。因為 PATCH 比較新,而且規範比較復雜,所以真正實現的比較少,
一般都是用 POST 替代 - POST方法可用來創建一個資源
- POST方法可用來觸發執行一個Controller類型資源
- DELETE方法用於刪除資源
HTTP響應狀態碼的使用
- 200 (“OK”) 用於一般性的成功返回
- 200 (“OK”) 不可用於請求錯誤返回
- 201 (“Created”) 資源被創建
- 202 (“Accepted”) 用於Controller控制類資源異步處理的返回,僅表示請求已經收到。
對於耗時比較久的處理,一般用異步處理來完成 - 204 (“No Content”) 此狀態可能會出現在PUT、POST、DELETE的請求中,一般表示資源存在,
但消息體中不會返回任何資源相關的狀態或信息。 - 301 (“Moved Permanently”) 資源的URI被轉移,需要使用新的URI訪問
- 302 (“Found”) 不推薦使用,此代碼在HTTP1.1協議中被303/307替代。
我們目前對302的使用和最初HTTP1.0定義的語意是有出入的,應該只有在GET/HEAD方法下,
客戶端才能根據Location執行自動跳轉,而我們目前的客戶端基本上是不會判斷原請求方法的,
無條件的執行臨時重定向 - 303 (“See Other”) 返回一個資源地址URI的引用,但不強制要求客戶端獲取該地址的狀態(訪問該地址)
- 304 (“Not Modified”) 有一些類似於204狀態,服務器端的資源與客戶端最近訪問的資源版本一致,
並無修改,不返回資源消息體。可以用來降低服務端的壓力 - 307 (“Temporary Redirect”) 目前URI不能提供當前請求的服務,臨時性重定向到另外一個URI。
在HTTP1.1中307是用來替代早期HTTP1.0中使用不當的302 - 400 (“Bad Request”) 用於客戶端一般性錯誤返回, 在其它4xx錯誤以外的錯誤,也可以使用400,
具體錯誤信息可以放在body中 - 401 (“Unauthorized”) 在訪問一個需要驗證的資源時,驗證錯誤
- 403 (“Forbidden”) 一般用於非驗證性資源訪問被禁止,例如對於某些客戶端只開放部分API的訪問權限,
而另外一些API可能無法訪問時,可以給予403狀態 - 404 (“Not Found”) 找不到URI對應的資源
- 405 (“Method Not Allowed”) HTTP的方法不支持,例如某些只讀資源,可能不支持POST/DELETE。
但405的響應header中必須聲明該URI所支持的方法 - 406 (“Not Acceptable”) 客戶端所請求的資源數據格式類型不被支持,
例如客戶端請求數據格式為application/xml,但服務器端只支持application/json - 409 (“Conflict”) 資源狀態沖突,例如客戶端嘗試刪除一個非空的Store資源
- 412 (“Precondition Failed”) 用於有條件的操作不被滿足時
- 415 (“Unsupported Media Type”) 客戶所支持的數據類型,服務端無法滿足
- 429 (“Too Many Requests”) 客戶端在規定的時間裏發送了太多請求,在進行限流的時候會用到
- 500 (“Internal Server Error”) 服務器端的接口錯誤,此錯誤於客戶端無關
HTTP Headers
- Content-Type 標示body的數據格式
- Content-Length body 數據體的大小,客戶端可以根據此標示檢驗讀取到的數據是否完整,
也可以通過Header判斷是否需要下載可能較大的數據體 - Last-Modified 用於服務器端的響應,是一個資源最後被修改的時間戳,客戶端(緩存)可以根據
此信息判斷是否需要重新獲取該資源 - ETag 服務器端資源版本的標示,客戶端(緩存)可以根據此信息判斷是否需要重新獲取該資源,
需要註意的是,ETag如果通過服務器隨機生成,可能會存在多個主機對同一個資源產生不同ETag的問題 - Store類型的資源要支持有條件的PUT請求
假設有兩個客戶端client#1/#2都向一個Store資源提交PUT請求,服務端是無法清楚的判斷是要
insert還是要update的,所以我們要在header中加入條件標示if-Match,If-Unmodified-Since
來明確是本次調用API的意圖。例如:
client#1第一次向服務端發起一個請求 PUT /objects/2113 此時2113資源還不存在,那服務端會
認為本次請求是一個insert操作,完成後,會返回 201 (“Created”)
client#2再一次向服務端發起同一個請求 PUT /objects/2113 時,因2113資源已存在,服務端會
返回 409 (“Conflict”)
為了能讓client#2的請求成功,或者說我們要清楚的表明本次操作是一次update操作,我們必須在
header中加入一些條件標示,例如 if-Match。我們需要給出資源的ETag(if-Match:Etag),來表
明我們希望更新資源的版本,如果服務端版本一致,會返回200 (“OK”) 或者 204 (“No Content”)。
如果服務端發現指定的版本與當前資源版本不一致,會返回 412 (“Precondition Failed”)
- Location 在響應header中使用,一般為客戶端感興趣的資源URI,例如在成功創建一個資源後,我們
可以把新的資源URI放在Location中,如果是一個異步創建資源的請求,接口在響應202 (“Accepted”)
的同時可以給予客戶端一個異步狀態查詢的地址 - Cache-Control, Expires, Date 通過緩存機制提升接口響應性能,同時根據實際需要也可以禁止
客戶端對接口請求做緩存。對於REST接口來說,如果某些接口實時性要求不高的情況下,我們可以使
用max-age來指定一個小的緩存時間,這樣對客戶端和服務器端雙方都是有利的。一般來說只對GET
方法且返回200的情況下使用緩存,在某些情況下我們也可以對返回3xx或者4xx的情況下做緩存,可
以防範錯誤訪問帶來的負載。 - 我們可以自定義一些頭信息,作為客戶端和服務器間的通信使用,但不能改變HTTP方法的性質。自
定義頭盡量簡單明了,不要用body中的信息對其作補充說明。
API 地址和版本
在 url 中指定 API 的版本是個很好地做法。如果 API 變化比較大,可以把 API 設計為子域名,
比如 api.github.com/v3;也可以簡單地把版… example.com/api/v1。
另一種做法是,將版本號放在HTTP頭信息中。
限流 rate limit
如果對訪問的次數不加控制,很可能會造成 API 被濫用,甚至被 DDos 攻擊。根據使用者不同的身份對其進行限流,可以防止這些情況,減少服務器的壓力。
對用戶的請求限流之後,要有方法告訴用戶它的請求使用情況,Github API 使用的三個相關的頭部:
- X-RateLimit-Limit: 用戶每個小時允許發送請求的最大值
- X-RateLimit-Remaining:當前時間窗口剩下的可用請求數目
- X-RateLimit-Rest: 時間窗口重置的時候,到這個時間點可用的請求數量就會變成 X-RateLimit-Limit 的值
對於超過流量的請求,可以返回 429 Too many requests 狀態碼,並附帶錯誤信息。
參考文檔
- cizixs.com/2016/12/12/…
- wangwei.info/about-rest-…
- www.ruanyifeng.com/blog/2011/0…
- www.ruanyifeng.com/blog/2014/0…
- zh.wikipedia.org/wiki/REST
- developer.github.com/v3
- novoland.github.io/%E8%AE%BE%E…
REST接口設計規範總結