RESTFul API 設計規範

0. API設計知足關鍵點

  1. API應當基於 web 標準來設計
  2. API應當對開發者友好而且便於在瀏覽器地址欄中瀏覽和探索
  3. API應當是簡單、直觀和一致的,使它用起來方便和溫馨
  4. API應當是高效的,同時要維持和其餘需求之間的平衡

1. API命名

1.1 根地址

好的RESTful API要基於HTTPS來發布 API規模不大時,在域名後面增長 api 目錄,如:https://www.trawe.cn/api/ API規模很大時,使用以api開頭的二級域名,如:https://api.trawe.cn/git

1.2 版本問題

  1. 新版本儘可能對舊版本做兼容github

  2. 版本信息放在URL中web

https://api.trawe.cn/v1.2/users/123
  1. 協議報文中增長version字段
{
    version: "1.0",
    .... ....
}
  1. HTTP Header中增長版本信息
使用已的HTTPHeader:Accept Header:Accept: application/json+v1.2
自定義 Header:             X-Api-Version: 1.2

1.3 端點設計原則

1) 命名算法

  1. CRUD操做一概使用名詞,不使用動詞
  2. url一概使用小寫字母
  3. url命名方式不使用 camel方式,採用 - 鏈接兩個單詞,如:app-setups,而不是appSetups
  4. 請求參數命名方式使用 下劃線 「_」 鏈接兩個單詞(Javascript規範),如:user_name,而不是userName或user-name
  5. 在url中不要出現 get / add / delete / put / modify / update 等動詞,使用HTTP Method來替代

2) 不管對於單個資源仍是集合,名詞都使用複數形式,這樣便於風格的統一數據庫

GET  /tickets      # 獲取 tickets 列表
GET  /tickets/12     # 獲取一個單獨的 ticket
POST /tickets        # 建立一個新的 ticket
PUT  /tickets/12         # 更新 ticket #12
PATCH /tickets/12     # 部分更新 ticket #12
DELETE /tickets/12   #  刪除 ticket #12
GET /tickets/12/messages     #  獲取ticket #12下的消息列表
GET /tickets/12/messages/5     #  獲取ticket #12下的編號爲5的消息
POST /tickets/12/messages     #  爲ticket #12建立一個新消息
PUT /tickets/12/messages/5     #  更新ticket #12下的編號爲5的消息
PATCH /tickets/12/messages/5     #  部分更新ticket #12下的編號爲5的消息
DELETE /tickets/12/messages/5    #  刪除ticket #12下的編號爲5的消息

3) 對於非CRUD的操做 有很非CRUD服務,能夠把這些服務當作資源,計算的結果是資源的presentation,按服務屬性選擇合適的HTTP方法。json

1. 從新構造這個Action,使得它像一個資源的操做。
    這種方法在Action不包含參數的狀況下能夠奏效。例如一個有效的action能夠映射成布爾類型field,而且能夠經過PATCH更新資源。
2. 利用RESTful原則像處理子資源同樣處理它。
    例如:Github的API讓你經過PUT /gists/:id/star 來 star a gist ,而經過DELETE /gists/:id/star來進行 unstar 。
3. 有時候你實在是沒有辦法將Action映射到任何有意義的RESTful結構。
    例如:多資源搜索沒辦法真正地映射到任何一個資源接入點。這種狀況,/search 將很是有意義,雖然它不是一個名詞,可是這樣作沒有問題,只須要從API消費者的角度作正確的事,並確保所作的一切都用文檔清晰記錄下來了便可。

4) 查詢過濾 過濾: 對每個字段使用一個惟一查詢參數,就能夠實現過濾。 例如: 當經過 /tickets 終端來請求一個票據列表時,咱們須要增長一些限定來查詢那些在售的票。可使用 GET /tickets?state=open 這樣的請求來實現。這裏「state」是一個實現了過濾功能的查詢參數。api

對於經常使用的查詢,有如下兩種處理:跨域

  1. 能夠單獨將查詢包裝爲一個獨立的API 如:GET /trades?status=closed&sort=created,desc 能夠包裝GET /trades/recently-closed
  2. 查詢結果標籤化 將常用的、複雜的查詢標籤化,下降維護成本。如:GET /trades?status=closed&sort=created,desc 能夠標籤化爲 GET /trades#recently-closed

排序: 跟過濾相似,使用排序參數字段來描述排序的規則。 爲適應複雜排序需求,讓排序參數採起逗號分隔的字段列表的形式,每個字段前均可能有一個負號來表示按降序排序。 例如:瀏覽器

排序字段前面的 +表示升序   -表示降序  默認爲升序
GET /tickets?sort=-priority  # 獲取票據列表,按優先級字段降序排序
GET /tickets?sort=-priority,created_at  # 獲取票據列表,按「priority」字段降序排序。在一個特定的優先級內,較早的票排在前面。

5) 減小層級深度 /在url中表達層級,用於按實體關聯關係進行對象導航,通常根據id導航。 過深的導航容易致使url膨脹,不易維護,如 GET /zoos/1/areas/3/animals/4,儘可能使用查詢參數代替路徑中的實體導航,如GET /animals?zoo=1&area=3緩存

6) 限制返回哪些字段 使用一個字段查詢參數,它包含一個 用逗號隔開的字段列表。例如,下列請求得到的信息將剛剛足夠展現一個在售票的有序列表: GET /tickets?fields=id,subject,customer_name,updated_at&state=open&sort=-updated_at

7) 關於返回結果 一般狀況下只返回JSON格式結果。 返回結果支持gzip壓縮:進行gzip壓縮的數據可節省50%以上的帶寬 對於須要返回不一樣格式資源的狀況:

  1. 在HTTP Header中指定格式 如:Accept:application/xml;q=0.6,application/atom+xml;q=1.0
  2. URL後綴增長擴展名: 如:/users/1.xml

8) 分頁 經常使用的分頁字段以下:

limit=10:指定返回記錄的數量
offset=10:指定返回記錄的開始位置。
page=2&per_page=100:指定第幾頁,以及每頁的記錄數。

9) 緩存 ETag: 當產生一個請求時, 包含一個HTTP 頭,ETag會在裏面置入一個和表達內容對應的哈希值或校驗值。這個值應當跟隨表達內容的變化而變化。如今,若是一個入站HTTP請求包含了一個If-None-Match頭和一個匹配的ETag值,API應當返回一個304未修改狀態碼,而不是返回請求的資源。 Last-Modified: 基本上像ETag那樣工做,不一樣的是它使用時間戳。在響應頭中,Last-Modified包含了一個RFC 1123格式的時間戳,它使用If-Modified-Since來進行驗證。注意,HTTP規範已經有了 3 種不一樣的可接受的日期格式 ,服務器應當準備好接收其中的任何一種。

2. HTTP方法

經常使用:

GET (選擇):從服務器上獲取一個具體的資源或者一個資源列表。
POST (建立): 在服務器上建立一個新的資源。
PUT (更新):以總體的方式更新服務器上的一個資源。
PATCH (更新):只更新服務器上一個資源的一個屬性。
DELETE (刪除):刪除服務器上的一個資源。

不經常使用:

HEAD : 獲取一個資源的元數據,如數據的哈希值或最後的更新時間。
OPTIONS:獲取客戶端能對資源作什麼操做的信息。

3. 數據報文

  1. 統一報文格式
請求使用JSON格式時:
{
    method: 'testMethod',       // 請求方法名稱
    version: '1.0',                     // 接口版本
    token: '0X十六進制',         //  Token
    sign_type: 'MD5',              //  簽名算法
    sign: '0X十六進制',            //  簽名
    timestamp: '12345678'    // 請求的時間戳
    
}

響應:
{
    code: 0,                        // 返回碼。 -1:失敗  0:成功  其它:具體業務代碼
    message: '處理成功',  // 返回碼描述信息
    sign: '0X十六進制',      // 簽名
    ...                                   // 業務數據
}

響應數據不做多餘包裝,以下爲錯誤示例:

{
    code: 0,
    data: {userName: "用戶名"} // 這裏面的data沒有業務含意,僅僅是爲了包裝,因此應該去掉
}

須要進行包裝的狀況:

  1. 使用JSONP進行跨域請求
  2. 當客戶端沒有能力處理HTTP頭信息時
  1. 對於日期類型的字段處理
  1. 一種方式爲:轉爲必定格式的字符串,如 yyyy-MM-dd HH:mm:ss.SSS
  2. 另外一種方式爲:轉爲長整型數字時間戳

4. 安全

  1. 使用Https傳輸數據
  2. 使用Token(如JWT)來標識用戶狀態並設置失效時間
  3. Token失效後客戶端自動從新登陸獲取新的Token
  4. 發送請求時對參數按ASCII排序計算簽名(Hash算法或對稱加密算法,能夠全部接口統一密鑰,也能夠一個接口一個密鑰),接收到請求後先驗證簽名
  5. 每一個端(Android、iOS、微信服務號、Web網站)生成一個AppKey
  6. 不設置密碼,登陸時使用手機+驗證碼方式登陸

5. 文檔

  1. 文檔須提供從請求到響應整個循環的示例;
  2. 請求應該是可粘貼的例子,要麼是能夠貼到瀏覽器的連接,要麼是能夠貼到終端裏的curl示例 ;
  3. 一旦發佈一個公開的API,必須承諾 在沒有通知的前提下,不會更改API的功能
  4. 對於外部可見API的更新,文檔必須包含任何將廢棄的API的時間表和詳情;

6. HTTP狀態代碼

HTTP定義了一套能夠從API返回的有意義的狀態代碼。 這些代碼可以用來幫助API使用者對不一樣的響應作出相應處理。

200 OK (成功)  -  對一次成功的GET, PUT, PATCH 或 DELETE的響應。也可以用於一次未產生建立活動的POST
201 Created (已建立)  -  對一次致使建立活動的POST的響應。 同時結合使用一個位置頭信息指向新資源的位置
204 No Content (沒有內容) - 對一次沒有返回主體信息(像一次DELETE請求)的請求的響應
304 Not Modified (未修改) - 當使用HTTP緩存頭信息時使用304
400 Bad Request (錯誤的請求) - 請求是畸形的, 好比沒法解析請求體
401 Unauthorized (未受權) - 當沒有提供或提供了無效認證細節時。若是從瀏覽器使用API,也能夠用來觸發彈出一次認證請求
403 Forbidden (禁止訪問) - 當認證成功可是認證用戶無權訪問該資源時
404 Not Found (未找到) - 當一個不存在的資源被請求時
405 Method Not Allowed (方法被禁止) - 當一個對認證用戶禁止的HTTP方法被請求時
410 Gone (已刪除) - 表示資源在終端再也不可用。當訪問老版本API時,做爲一個通用響應頗有用
415 Unsupported Media Type (不支持的媒體類型) - 若是請求中包含了不正確的內容類型
422 Unprocessable Entity (沒法處理的實體) - 出現驗證錯誤時使用
429 Too Many Requests (請求過多) - 當請求因爲訪問速率限制而被拒絕時

7. 錯誤處理

原則

  1. 不要發生了錯誤但給2xx響應,客戶端可能會緩存成功的http請求;
  2. 正確設置http狀態碼,不要自定義;
  3. Response body 提供 1) 錯誤的代碼(日誌/問題追查);2) 錯誤的描述文本(展現給用戶)。

API 可能拋出兩類異常:業務異常和非業務異常。 業務異常由本身的業務代碼拋出,表示一個用例的前置條件不知足、業務規則衝突等,好比參數校驗不經過、權限校驗失敗。 非業務類異常表示不在預期內的問題,一般由類庫、框架拋出,或因爲本身的代碼邏輯錯誤致使,好比數據庫鏈接失敗、空指針異常、除0錯誤等等。

業務類異常必須提供2種信息:

  1. 若是拋出該類異常,HTTP 響應狀態碼應該設成什麼;
  2. 異常的文本描述;

在Controller層使用統一的異常攔截器:

  1. 設置 HTTP 響應狀態碼:對業務類異常,用它指定的 HTTP code;對非業務類異常,統一500;
  2. Response Body 的錯誤碼:異常類名
  3. Response Body 的錯誤描述:
  4. 對業務類異常,用它指定的錯誤文本;
  5. 對非業務類異常,線上能夠統一文案如「服務器端錯誤,請稍後再試」;
  6. 開發或測試環境中用異常的 stacktrace,服務器端提供該行爲的開關。

8. 超媒體API

RESTful API最好作到Hypermedia,即返回結果中提供連接,連向其餘API方法,使得用戶不查文檔,也知道下一步應該作什麼。 好比,當用戶向api.example.com的根目錄發出請求,會獲得這樣一個文檔。

{"link": {
  "rel":   "collection https://www.example.com/zoos",
  "href":  "https://api.example.com/zoos",
  "title": "List of zoos",
  "type":  "application/vnd.yourformat+json"
}}

上面代碼表示,文檔中有一個link屬性,用戶讀取這個屬性就知道下一步該調用什麼API了。rel表示這個API與當前網址的關係(collection關係,並給出該collection的網址),href表示API的路徑,title表示API的標題,type表示返回類型。 Hypermedia API的設計被稱爲HATEOAS。Github的API就是這種設計,訪問api.github.com會獲得一個全部可用API的網址列表。

{
  "current_user_url": "https://api.github.com/user",
  "authorizations_url": "https://api.github.com/authorizations",
  // ...
}

從上面能夠看到,若是想獲取當前用戶的信息,應該去訪問api.github.com/user,而後就獲得了下面結果。

{
  "message": "Requires authentication",
  "documentation_url": "https://developer.github.com/v3"
}

上面代碼表示,服務器給出了提示信息,以及文檔的網址。

相關文章
相關標籤/搜索