1. 程式人生 > >REST接口設計規範總結

REST接口設計規範總結

rate 分頁 檢驗 響應狀態 屬性 RR 調用 auth author

簡介

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接口設計規範總結