REST 風格的優秀設計應該像下面這些:
如果要設計一個資源擁有另外一個資源的情況的API,例如,設計一個包含用戶(users)和用戶的評論(comments)的 API 可以采用這樣的形式:
- 將這些操作變成一個資源的屬性,比如 disable 一個 user,可以在 user 里面加一個 disabled 的屬性,可以設計一個 API 使用
PATCH /users/1234
將 disabled 設置成 true 即可。 - 將這個操作看成某個資源的附屬資源(就像上面例子中的 comments 一樣)來設計,比如GitHub的Star a gist API ,就是這樣的,它把star操作放在這個資源的后面,看上去好像是一個附屬資源:
- PUT /gists/:id/star
- DELETE /gists/:id/star
在不得不使用其它例外形式設計 API 時,盡量用文檔寫清楚輸入輸出和返回值等其他必要信息,避免讓習慣了使用資源名的調用者感到困惑
- 比如你要設計一個API,返回所有已經登錄的用戶,可以這樣做:GET /users?login=true
- 獲取所有的用戶,返回結果按照create_at降序排序可以這樣設計:GET /users?sort=-create_at
- 組合使用過濾條件和排序,GET /users?sort=-create_at,login_at&login=true 表示返回所有已登錄用戶,結果按照create_at降序, login_at升序
- 單獨為 API 設計一個 Query Parameter 專門用於搜索,從 API 中傳遞過來的 Query Parameter 可以直接設置成這些搜索框架的輸入條件GET /users?q=key&&sort=-create_at,login_at&diabled=false
- 映射到一個新的API(相當於快捷方式)比如設計一個用於返回最近登錄用戶的API:GET /users/recently_login這種設計可以簡化客戶端的調用,否則調用者每次都要根據時間合成 Query Parameter,增加了客戶端使用復雜度
- 查詢數據的部分內容GET /user?fields=id,user_name,address&diabled=false&sort=-login_at GET /facebook/v2.8/me?fields=id,name,birthday,cover,devices,email&access_token=xxX
API中都使用了下划線(user_name)的形式來命名這些參數,使用划線(user_name)還是使用駝峰(userName)的形式?下划線分割的形式比使用駝峰的形式更容易閱讀(容易20%)
合理設計返回數據的形式,格式和考慮啟用壓縮(gzip)
假如有個系統提供一個 API 用於上傳一張圖,這張圖上傳之后你可以調用另外一個 API 修改這個圖片的描述。如果調用上傳 API 后,返回數據中沒有返回這張圖的唯一性 ID,你就無法接着調用其它 API 引用到這個圖的資源,從而無法進行修改描述的操作,除非之前額外再次調用查詢操作拉取到這張圖唯一性 ID。
通常,POST 操作成功以后,我們一般也把新創建的資源的 URL 放在 HTTP header 的 location 字段中,方便客戶的拉取。例如上上樹圖片上傳的 API 返回的 header 中可以包含location: http://api.domain.name/photos/1234
RESTful API 一般都是返回文本數據,啟用 gzip 通常可以節省60%-80%以上的帶寬(這個數據很好證明,隨便使用幾個個 json 文件 gzip下就可以看出來,我測試幾個 json 文件一般300K左右都能被壓縮成50K左右),尤其是在返回的數據比較大情況下,壓縮比更高。不過啟用gzip 不可避免會增加 CPU 的負擔,實際工程項目中需要權衡考量。
至於到底用什么用的格式來返回數據?XML?JSON?純文本?但從統計數據來看 JSON 格式目前是使用做多的 REST API 的輸入輸出格式。
根據不同的 API 操作,設置合適的 HTTP 狀態碼和必要的出錯信息
- 200 OK 用於返回 GET, PUT, PATCH 或 DELETE 的操作。有使用也用來返回沒有創建數據的 POST 操作;
- ** 201 Created** 用來返回 POST 操作並且成功創建了數據的情況。新創建的數據資源的鏈接應該放在location中返回;
- 204 No Content 用來返回一次成功的請求,但是該請求返回的 body 為空的情況,如 DELETE 請求;
- 304 Not Modified 表示緩存沒有失效,和上次的請求相比,沒有新的內容;
- 400 Bad Request 用於返回 API 參數不正確的情況,比如傳入的 JSON 格式錯誤無法解析等;
- 401 Unauthorized 用於表示請求等 API 缺少身份驗證信息;
- 403 Forbidden 用於表示該資源不允許特定用戶訪問;
- 404 Not Found 請求一個不存在的資源;
- 429 Too Many Requests 請求過於頻繁,可以用在客戶端調用過於頻繁的情況。
使用 token 機制設計鑒權和驗證系統(Authorization and Authentication)
常見的場景就是用戶系統-結合 OAuth2,參考騰訊雲微視頻MVS API,這里給出一個實用的解決方案:
- 用戶使用戶名密碼或者第三方登錄,最終請求一個我們設計的登錄 API(這個 API 接受用戶名密碼,或第三方登錄驗證結果);
- 服務端認證成功以后,生成一個 token,並將這個 token 和用戶信息關聯在一起,同時返回這個 token 給調用客戶端;
- 客戶端記錄並保存下這個 token;
- 下次客戶端發起和用戶相關請求 API 都要在 http header 中帶上這個 token;
- 服務端通過這個 token 去區分用戶是誰,判斷這個用戶是否已經登錄和有什么樣的權限;
- 服務端也要考慮 token 的失效時間;
- 客戶端在發現 token 失效的時候重新請求新的 token
如何實現數據的分頁返回

另一種符合WEB標准的做法是使用 link header,簡單來說就是在 http header 使用 link字段,提供一個和超鏈接一樣目的 URL 地址,來實現不同資源之間的轉跳。如GitHub的Api文檔是這樣規定分頁信息的,這種做法缺點是不太直觀,優點是不會干擾數據,返回內容都是數據本身,無需在數據上嵌入額外的屬性來說明分頁信息,簡單干凈
如何處理有關聯資源的返回數據
對客戶端來說,最直觀和容易處理的返回形式如下:
返回數據中 avatar 和 name 是每條數據都是重復的,所以你也可以這樣設計返回數據:先返回該用戶的所有評論 /comments?user=1234
再通過請求該用戶 API 的相關內容 /users/1234:{user_id: "1234", avatar: "a.jpg", nickName:"Jeffrey"...}這種情況下其實可以將依賴資源嵌入返回對象中,避免了客戶端需要再一次發起請求來獲取這個 user 的詳細信息/comments?user=1234 直接返回類似這樣的信息即可:
考慮啟用 HTTP 緩存機制
HTTP協議本身支持兩種緩存機制: ETag 和 Last-Modified
- ETag:HTTP 請求中在 header 中包含一個內容的 hash,如果返回結果沒有變化,該請求會直接返回304 Not Modified,而不是所有數據內容本身
- Last-Modified: 和 Etag 工作原理差不多,只是使用時間戳作為內容是否過期的標志。
限制 API 調用頻次(Rate limiting)
如果一個客戶端請求 API 的頻率太快,根據HTTP協議,可以返回429 Too Many Requests。
X-Rate-Limit-Limit: 該請求的調用上限
X-Rate-Limit-Remaining: 15分鍾內還可以調用多少次
X-Rate-Limit-Reset: 還有多少秒之后訪問限制會被重置
- ssl_prefer_server_ciphers: 表示服務端加密算法優先於客戶端加密算法,主要是防止降級攻擊 (downgrade attack)。
- Strict-Transport-Security(HSTS):告訴瀏覽器這個域名在指定的時間(max-age)內應該強制使用 HTTPS 訪問。