RESTful Service API 設計最佳工程實踐和常見問題解決方案

前面兩篇內容(RESTful Web Service 架構剖析HTTP Methods 和 RESTful Service API 設計)介紹了 RESTful Service 的基礎概念和理論知識,本篇內容打算總結 RESTful Service APIs 設計最佳工程實踐和常見問題的解決方案,側重點是幫助讀者更加有效的解決實際工程問題和如何快速設計一套優(yōu)秀易用的 APIs。

為了有個討論的標準和設計目標,我們先來定義下一套優(yōu)秀的的 RESTful APIs 應該是什么樣子:

  • 盡可能的遵守有關 WEB 規(guī)范和常見約定;
  • 調用接口簡單明了,可讀性強,沒有歧義;
  • 不同 API 風格保持一致,調用規(guī)則,傳入?yún)?shù)和返回數(shù)據(jù)有統(tǒng)一的標準;
  • 能夠為客戶端提供簡單靈活的數(shù)據(jù)訪問方式;
  • 有一定的容錯性和防止非法參數(shù)功能;
  • 高效,安全可靠,容易擴展。

[長文預警]閱讀本文可能需要半個小時以上,如果你沒有時間從頭讀到尾,可以先收藏或看看目錄,遇到你關注的問題再詳細看起。

  1. API 命名應該采用約定俗成的方式,保持簡潔明了;
  2. 考慮到系統(tǒng)迭代和兼容性需求,API 中應該引入版本規(guī)則;
  3. 優(yōu)雅的設計條件過濾,排序,搜索等傳入?yún)?shù)形式;
  4. 合理設計返回數(shù)據(jù)的形式,格式和考慮啟用壓縮(gzip);
  5. 根據(jù)不同的 API 操作,設置合適的 HTTP 狀態(tài)碼和必要的出錯信息;
  6. 使用 token 機制設計鑒權和驗證系統(tǒng)(Authorization and Authentication[1])
  7. 如何實現(xiàn)數(shù)據(jù)的分頁返回;
  8. 如何處理有關聯(lián)資源的返回數(shù)據(jù);
  9. 考慮啟用 HTTP 緩存機制;
  10. 限制 API 調用頻次(Rate limiting);
  11. 盡可能的使用 HTTPS,涉及用戶驗證的 API 一定要強制啟用 HTTPS。

<br />

1.API 命名應該采用約定俗成的方式,保持簡潔明了

API 應該采用簡單明了和約定俗成的命名方式

簡單明了意味著能消除歧義,更少出錯和能夠減少不必要的文檔記錄。

所有 API 應該使用 REST 架構約定形式命名。REST架構的思想是將 API 請求對象看成一個個資源,實現(xiàn)者使用相應的 HTTP 的動詞(GET, POST, PUT, PATCH, DELETE)來訪問和操作這些資源。這些具體動詞的意義和使用方法可以參見本系列的前一篇文章
為了使 API 看上去簡單明了,可讀性強,我們一般使用名詞,而不是動詞來命名這些資源。比如下面這些都是糟糕的設計:

  • /getAllUser[2]
  • /setUserComments
  • /DeleteUserForId

之所以糟糕,不僅僅是它們顯得拖沓冗長,最重要的是,使用這樣的風格和名字沒有固定的形式,不同的開發(fā)者往往需要閱讀你的文檔才能開始使用,也沒有充分利用HTTP Method,何況使用自己的動詞可能會產(chǎn)生和HTTP Method沖突的情況。使用 REST 風格的優(yōu)秀設計應該像下面這些:

- GET /users  獲取所有用戶
- GET /users/1234  獲取ID為1234的用戶
- POST /users  創(chuàng)建一個新用戶
- PUT /users/1234  更新ID為1234的用戶
- PATCH /users/1234  更新ID為1234的用戶的部分內容
- DELETE /users/1234  刪除ID為1234的用戶

這些API所有的操作只有一個節(jié)點 /users,顯得簡潔明了,如果熟悉 HTTP Method 的開發(fā)者,一眼看上去就能猜到應該如何使用。

您可能已經(jīng)注意到了,以上 API 中資源命名都使用了復數(shù)的形式。這是一個約定,它可以省去設計時考慮數(shù)據(jù)具體細節(jié)的麻煩(數(shù)據(jù)是復數(shù)還是單數(shù)?)?,F(xiàn)在大很多常見的系統(tǒng)都使用了復數(shù)形式。比如Twitter 的 REST APIs 和 Facebook 的 Graph API 基本都是復數(shù)形式。

然而實際系統(tǒng)一般都不可能只有單一資源,資源和資源之間有各種關系是很正常的情況,那么如何設計存在關聯(lián)資源(數(shù)據(jù))的API呢?

如果要設計一個資源擁有另外一個資源的情況的API,例如,設計一個包含用戶(users)和用戶的評論(comments)的 API 可以采用這樣的形式:

- GET /users/1234/comments  獲取用戶ID為1234的所有評論
- GET /users/1234/comments/1 獲取用戶ID為1234的評論ID為1的單個評論
- DELETE /users/1234/messages/1  刪除用戶評論ID為1,屬于用戶1234的單個評論

當然,如果一個資源并不依附其它資源而可以獨立存在,是沒有必要這樣設計的,完全可以使用和 users 一樣的形式提供,如果要查詢其中的關系,可以使用其它資源作為 ID 的形式來過濾。例如 /comments?user_id=1234。關于這點詳細內容可以參見下面的第三條“優(yōu)雅的設計條件過濾,排序,搜索和限制返回數(shù)據(jù)的參數(shù)形式”。

上述設計原則都是使用 HTTP Method,會不會有超出 HTTP Method 表達語義的 API 呢?答案是肯定的。實際工程實踐中往往會遇到并不是對一個資源簡單的 CRUD 的場景,設計此類 API 有這些手法可供參考:

  1. 將這些操作變成一個資源的屬性,比如 disable 一個 user,可以在 user 里面加一個 disabled 的屬性,可以設計一個 API 使用 PATCH /users/1234 將 disabled 設置成 true 即可。

  2. 將這個操作看成某個資源的附屬資源(就像上面例子中的 comments 一樣)來設計,比如GitHub的Star a gist API ,就是這樣的,它把star操作放在這個資源的后面,看上去好像是一個附屬資源:
    - PUT /gists/:id/star
    - DELETE /gists/:id/star

  3. 在不得不使用其它例外形式設計 API 時,盡量用文檔寫清楚輸入輸出和返回值等其他必要信息,避免讓習慣了使用資源名的調用者感到困惑。
    例如,如果要設計一個 API 用來根據(jù)輸入關鍵詞返回搜索結果,搜索結果可能有 user,可能有 comments,或者二者都有,這種情況下,我們很難按照約定的資源形式設計API。我們可以使用 GET /search 這樣的形式設計 API,但是最好給出文檔說明,說明輸入和輸出細節(jié)。

<br />

2.考慮到系統(tǒng)迭代和兼容性,需要在 API 中引入版本規(guī)則

考慮到系統(tǒng)迭代和兼容性,需要在 API 中引入版本規(guī)則

現(xiàn)代系統(tǒng)的迭代速度一般都很快,設計優(yōu)良的 API 版本規(guī)則可以給持續(xù)集成和系統(tǒng)升級帶來便利,降低因系統(tǒng)迭代引發(fā)的問題。在升級到新版 API 到同時,可以選擇依然支持舊版本 API 一段時間,這樣可以給其它客戶端和子系統(tǒng)一個緩沖時間,讓其有充分的時間升級和適配新版本的API。

關于設置API的版本信息,常見的有兩種方法,一種是將版本號放在 http header 內,另一種是直接放在 URL 中。而放在 URL 中是最常見的做法,比如:
- GET https://api.twitter.com/1.1/friends
- GET "https://graph.facebook.com/v2.8/me

其中1.1 和v2.8就是API的版本號,這種做法的好處是簡單易讀,不容易混淆。
為了簡單起見,可以省略最新的 API 版本號,假設v3.0是最新版本,調用下面的API應該返回相同的結果:

- /api/users/1234
- /api/v3.0/users/1234
- /v3/users/1234

如果一個 API 的版本過期了,任何把該請求重定向到最新版本上。比如 user API v1 版本過期了,當有調用/api/v1.0/users/1234的時候,應該被重定向(http 30x)到最新的 /api/v2.0/users/1234 上。

<br />

3.優(yōu)雅的設計條件過濾,排序,搜索等傳入?yún)?shù)形式

優(yōu)雅的設計條件過濾,排序,搜索等傳入?yún)?shù)形式

RESTful API 經(jīng)常有對返回數(shù)據(jù)過濾和排序的要求,這些輸入?yún)?shù)推薦采用 HTTP Query Parameter 的方式實現(xiàn)。

  • 比如你要設計一個API,返回所有已經(jīng)登錄的用戶,可以這樣做:
    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 專門用于搜索。這樣特別適用你的后端在使用了ElasticSearch 或者其它如 Lucene,Solr之類的搜索引擎架構,因為從 API 中傳遞過來的 Query Parameter 可以直接設置成這些搜索框架的輸入條件。這種情況的API可以這樣設計:
    GET /users?q=key&&sort=-create_at,login_at&diabled=false

  • 對于一些常用的條件搜索和過濾,可以考慮映射到一個新的API(相當于快捷方式)比如設計一個用于返回最近登錄用戶的API:
    GET /users/recently_login
    這種設計可以簡化客戶端的調用,否則調用者每次都要根據(jù)時間合成 Query Parameter,增加了客戶端使用復雜度。

  • 查詢數(shù)據(jù)的部分內容
    有些時候資源屬性很多(比如 user 包含 name, address, email, phone...),不同的客戶端需要的內容不盡相同(有的客戶端可能只需要name, address),如果一股腦的全部返回,尤其在數(shù)據(jù)量比較大情況下會對帶寬帶來不必要的浪費。我們可以采用這樣的形式來過濾數(shù)據(jù)的屬性:
    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

您可以已經(jīng)注意到了上述API中都使用了下劃線(user_name)的形式來命名這些參數(shù)。作為程序員你一定會爭論是使用劃線(user_name)還是使用駝峰(userName)的形式呢?

這個問題一直沒有一個明確的答案。一般要求所有 API 保持風格一致即可。從個人接觸的一些常見系統(tǒng) API 來看,使用下劃線的方式居多。值得提到的是有項研究表明,使用下劃線分割的形式比使用駝峰的形式更容易閱讀(容易20%),如果從可讀性方面來說應該使用下劃線的方式來分隔是個不錯的選擇。

<br />

4.合理設計返回數(shù)據(jù)的形式,格式和考慮啟用壓縮(gzip)

返回數(shù)據(jù)的形式,格式和啟考慮用壓縮(gzip)

GET 操作的返回數(shù)據(jù)是顯而易見,這里不做過多討論。對于更新和創(chuàng)建操作(PUT POST PATCH),API 在執(zhí)行相關的操作之后要把更新后的數(shù)據(jù)也做為返回值的一部分返回給調用者,這樣可以避免調用者再次調用 GET API 來獲取更新,而浪費一次 HTTP 請求。特別是對于 POST 操作的 API,因為該 API 會創(chuàng)建數(shù)據(jù),該數(shù)據(jù)被創(chuàng)建后的唯一性 ID 往往由服務端生成,如果不返回新創(chuàng)建的 ID,客戶端就不能基于這個數(shù)據(jù)做進一步操作。這個部分理論基礎可以參見RESTful Web Service 架構剖析 - 6.2 Resource Identifiers。

舉個例子來說明這個情況:
假如有個系統(tǒng)提供一個 API 用于上傳一張圖,這張圖上傳之后你可以調用另外一個 API 修改這個圖片的描述。如果調用上傳 API 后,返回數(shù)據(jù)中沒有返回這張圖的唯一性 ID,你就無法接著調用其它 API 引用到這個圖的資源,從而無法進行修改描述的操作,除非之前額外再次調用查詢操作拉取到這張圖唯一性 ID。

通常,POST 操作成功以后,我們一般也把新創(chuàng)建的資源的 URL 放在 HTTP header 的 location 字段中,方便客戶的拉取。例如上上樹圖片上傳的 API 返回的 header 中可以包含 location: http://api.domain.name/photos/1234

對于返回數(shù)據(jù),另一個值得一提的優(yōu)化是使用gzip,這雖然和 API 設計本身無關,只是服務器配置上的問題,之所以特別提出,是因為 RESTful API 一般都是返回文本數(shù)據(jù),啟用 gzip 通??梢?strong>節(jié)省60%-80%以上的帶寬(這個數(shù)據(jù)很好證明,隨便使用幾個個 json 文件 gzip下就可以看出來,我測試幾個 json 文件一般300K左右都能被壓縮成50K左右),尤其是在返回的數(shù)據(jù)比較大情況下,壓縮比更高。不過啟用gzip 不可避免會增加 CPU 的負擔,實際工程項目中需要權衡考量。

至于到底用什么用的格式來返回數(shù)據(jù)?XML?JSON?純文本?但從統(tǒng)計數(shù)據(jù)來看 JSON 格式目前是使用做多的 REST API 的輸入輸出格式。

有些系統(tǒng)設計采用 application/x-www-form-urlencoded 形式作為輸入內容(以key=value&key=value...的形式 POST 去服務端,其中value使用urlencode)。這樣設計優(yōu)點是由于 value 內容是純文本,可用自由的定義成各種其它系統(tǒng)方便解析的格式,使得服務端在解析 model 的上獲取更大的自由度。此外 JAVA 的一個流行框架 Spring MVC controller 中可以直接使用@RequestParam用一個函數(shù)參數(shù)自動對應上 form post 過來的數(shù)據(jù),省去了解析body中 JSON 的麻煩。

個人更傾向使用 JSON, 因為現(xiàn)在幾乎主流的平臺和語言都對 JSON 有著穩(wěn)定高效的支持,各種簡單易用的解析和生成 JOSN 的框架層出不窮,所以建議對于輸入輸出統(tǒng)一使用 JSON 格式(其中輸入是指 POST, PUT & PATCH API 中放在 http body 中的輸入?yún)?shù))。

<br />

5.根據(jù)不同的 API 操作,設置合適的 HTTP 狀態(tài)碼和必要的出錯信息

根據(jù)不同的 API 操作,設置合適的 HTTP 狀態(tài)碼和必要的出錯信息

使用合理的狀態(tài)碼有助于提高客戶端的易用性,因為這些 HTTP 狀態(tài)代碼本身就有一定的含義,如能在 API 返回信息中合理的利用,可以減少額外的文檔描述,讓API返回結果“不言自明”。

http status code 的常用應用場景如下:

  • 200 OK 用于返回 GET, PUT, PATCH 或 DELETE 的操作。有使用也用來返回沒有創(chuàng)建數(shù)據(jù)的 POST 操作;
  • ** 201 Created** 用來返回 POST 操作并且成功創(chuàng)建了數(shù)據(jù)的情況。新創(chuàng)建的數(shù)據(jù)資源的鏈接應該放在location中返回,具體參見這里
  • 204 No Content 用來返回一次成功的請求,但是該請求返回的 body 為空的情況,如 DELETE 請求;
  • 304 Not Modified 表示緩存沒有失效,和上次的請求相比,沒有新的內容;
  • 400 Bad Request 用于返回 API 參數(shù)不正確的情況,比如傳入的 JSON 格式錯誤無法解析等;
  • 401 Unauthorized 用于表示請求等 API 缺少身份驗證信息;
  • 403 Forbidden 用于表示該資源不允許特定用戶訪問;
  • 404 Not Found 請求一個不存在的資源;
  • 429 Too Many Requests 請求過于頻繁,可以用在客戶端調用過于頻繁的情況。

對于需要提供額外說明的錯誤類型,可以在 HTTP Body 中詳細描述,便于調用者排查原因。

{
 "error": {
  "message":"Message describing the error",
  "type":"OAuthException",
  "code":190,
  "error_subcode":460,
  "error_user_title":"A title",
  "error_user_msg":"A message",
  "fbtrace_id":"EJplcsCHuLu" 
  }
}

錯誤信息要容易解析,比如上面的錯誤信息中,返回的 JSON 數(shù)據(jù)下有個 error 屬性,客戶端只要判斷屬性是否存在即可判斷是否有詳細的錯誤信息。
如果你的API比較復雜,最好能有文檔按照 error code 分門別類記錄這些 error 產(chǎn)生的原因以及如何應對。

<br />

6.使用 token 機制設計鑒權和驗證系統(tǒng)(Authorization and Authentication[1]

使用 token 機制設計鑒權和驗證系統(tǒng)(Authorization and Authentication

這個話題可以討論的內容有很多,這里主要從實用的角度來給出一些解決方案和解決問題的思路。
由于 RESTful API 的無狀態(tài)的特性,所以我們不能依賴請求前后的上下文來做鑒權和用戶驗證,那到底該如何區(qū)分調用者是誰從而確定它有沒有相應的權限調用某個API?

我們先來看個例子,這個例子來自騰訊云微視頻MVS API
你在成功申請騰訊云的微視頻服務之后會給你分配 Appid、Secret ID 之類的信息。客戶端在調用上傳和刪除視頻之類的 API 時, 需要把一個 token 放在 API 請求的 http header 的 Authorization 字段中。其中 token 是按照某種規(guī)則拼接 Appid、Secret ID 生成的。這樣服務端在收到這個調用請求時就可以區(qū)分這個 API 是哪個用戶調用的,該用戶是否有相應的權限(其中 Appid 相當于用戶名,Secret ID 相當于密碼,應當妥善保存)。

這種設計思想很簡單,原理就是:針對特定用戶生成一個 token,之后每次API的調用請求都帶上這個 token。為了防止 token 泄露引發(fā)的安全問題,還應該考慮 token 什么時候失效,什么時候需要重新生成。說到這里,可能會有人會問,為什么不實施OAuth 2?答案是適用場景不同,部署 OAuth2 也會將問題復雜化。OAuth 2 適合需要把某一資源暴露給第三方應用的情況,比如新浪微博提供 OAuth 2 驗證,如果你使用新浪微博登錄豆瓣,在你的同意下(你在微博的登錄界面輸入用戶名密碼,并且確認),微博最終會給豆瓣一個具有實效性的 token,豆瓣憑借這個 token 來讀取你的昵稱和頭像信息。想想,如果不使用token,豆瓣只有知道你的用戶名密碼才能讀取昵稱和頭像信息,這也是OAuth 2 要解決的一個問題。

在實際工程實踐中,常見的場景就是用戶系統(tǒng)。那么到底如何設計一個 API 能夠針對不同的用戶做出鑒權和驗證?結合 OAuth2,參考上面騰訊云微視頻MVS API的例子,這里給出一個實用的解決方案:

  1. 用戶使用戶名密碼或者第三方登錄,最終請求一個我們設計的登錄 API(這個 API 接受用戶名密碼,或第三方登錄驗證結果);
  2. 服務端認證成功以后,生成一個 token,并將這個 token 和用戶信息關聯(lián)在一起,同時返回這個 token 給調用客戶端;
  3. 客戶端記錄并保存下這個 token;
  4. 下次客戶端發(fā)起和用戶相關請求 API 都要在 http header 中帶上這個 token;
  5. 服務端通過這個 token 去區(qū)分用戶是誰,判斷這個用戶是否已經(jīng)登錄和有什么樣的權限;
  6. 服務端也要考慮 token 的失效時間;
  7. 客戶端在發(fā)現(xiàn) token 失效的時候重新請求新的 token

具體步驟和實現(xiàn)如下圖:


使用Token驗證用戶

細心的讀者可能要問:為什么要多一個步驟使用 token 呢?為什么不直接把用戶名和密碼放在 http header 中直接做授權和驗證?原因是調用 API 一般會被頻繁調用,這樣用戶名和密碼頻繁在網(wǎng)絡上傳輸,增加了泄漏的危險。如果使用token,即使泄漏了也不會暴露用戶的密碼,何況 token 也被經(jīng)常被設計成有時間限制的,超時以后當前 token 就會失效,需要客戶端重新做驗證獲得新的 token,暴露之后的影響很快就會過去。

其實獲取 token,用 token 做授權和驗證和 OAuth 2 如出一轍,手法完全相同的,只是 OAuth 2 有更復雜的標準步驟去換取這個token,并且這個 token 的用途不同。OAuth 2 的 token 用來授權給第三方使用,我們自己設計的系統(tǒng) token 僅限在自己系統(tǒng)本身 API 使用。

<br />

7.如何實現(xiàn)數(shù)據(jù)的分頁返回

如何實現(xiàn)數(shù)據(jù)的分頁返回

通常情況下一次API調用不可能返回該資源的所有數(shù)據(jù),因為,一來多數(shù)情況下一個資源包含的數(shù)據(jù)太多,二來客戶端也沒有必要一次使用所有數(shù)據(jù),因為用戶短時間上根本就看不了那么多內容,完全可以在需要的時候加載更多。

RESTful API 一般有兩種形式的設計,一種是使用類似 Facebook Graph API 的方法,它把分頁信息和數(shù)據(jù)一起返回,調用者只需要再次請求 next 中 URL 就可以獲取下一頁的數(shù)據(jù),這種方式優(yōu)點是靈活和直觀,可以隨意添加和分頁相關的其他屬性,例如總記錄數(shù),總頁數(shù)等等。其中cursors用來解決“流”的問題(由于數(shù)據(jù)是動態(tài)增加的,基于舊數(shù)據(jù)的頁數(shù)和頁碼會失效,于是引入 cursors 來標記數(shù)據(jù)位置,關于這個問題,Twitter 在介紹其timeline API時有圖文并貌的詳細描述)。

// Facebook Graph API paging 
{
  "data":[{...},{...}],
  "paging": {
    "cursors":{ "after":"MTAxNTExOTQ1MjAwNzI5NDE=", "before":"NDMyNzQyODI3OTQw" }, 
    "previous":"https://graph.facebook.com/me/albums?limit=25&before=NDMyNzQyODI3OTQw", 
    "next":"https://graph.facebook.com/me/albums?limit=25&after=MTAxNTExOTQ1MjAwNzI5NDE="
  }
}

另一種符合WEB標準的做法是使用 link header,簡單來說就是在 http header 使用 link字段,提供一個和超鏈接一樣目的 URL 地址,來實現(xiàn)不同資源之間的轉跳。如GitHub的Api文檔是這樣規(guī)定分頁信息的

Link: <https://api.github.com/user/repos?page=3&per_page=100>; rel="next",
  <https://api.github.com/user/repos?page=50&per_page=100>; rel="last"

這種做法缺點是不太直觀,如果使用 Postman 之類工具調試的時候,需要手動找到header中的內容復制出來才能發(fā)出下一頁的請求。而第一種實現(xiàn),直接點擊這個鏈接即可。但第二種實現(xiàn)的優(yōu)點是不會干擾數(shù)據(jù),返回內容都是數(shù)據(jù)本身,無需在數(shù)據(jù)上嵌入額外的屬性來說明分頁信息,簡單干凈。至于如何選擇,完全要看個人偏好和具體使用場景了。

<br />

8.如何處理有關聯(lián)資源的返回數(shù)據(jù)

如何處理返回數(shù)據(jù)中的關聯(lián)資源

考慮這么一個情況:有一個 API,輸入一個指定用戶 id,返回一個該用戶所有評論信息。最終要在 UI 上顯示的,除了該用戶評論的具體文本內容以外,還有用戶名,頭像,個人簡介之類和該用戶相關的詳細信息。該API的返回值應該如何設計?

對客戶端來說,最直觀和容易處理的返回形式如下:

{
  data: [
    {user_id: "1234", avatar: "a.jpg", nick_name:"Jeffrey", comment:"RESTful Service API"}, 
    {user_id: "1234", avatar: "a.jpg", nick_name:"Jeffrey", comment:"J:"}
    ...
  ]
}

你肯定一眼就能看出問題,是的,返回數(shù)據(jù)中 avatar 和 name 是每條數(shù)據(jù)都是重復的,所以你也可以這樣設計返回數(shù)據(jù):
先返回該用戶的所有評論 /comments?user=1234

{
  data: [
    {user_id: "1234", comment:"RESTful Service API"}, 
    {user_id: "1234", comment:"J:"},
    ...
  ]
}

再通過請求該用戶 API 的相關內容 /users/1234:
{user_id: "1234", avatar: "a.jpg", nickName:"Jeffrey"...}

這種情況下其實可以將依賴資源嵌入返回對象中,避免了客戶端需要再一次發(fā)起請求來獲取這個 user 的詳細信息:
/comments?user=1234 直接返回類似這樣的信息即可:

{
  data: [
   {comment:"RESTful Service API"},
   {comment:"J:"},
   ...
 ],

 comment_user: {user_id: "1234", avatar: "a.jpg", nickName:"Jeffrey"...}
}

<br />

9.考慮啟用 HTTP 緩存機制

考慮啟用 HTTP 緩存機制

HTTP協(xié)議本身支持兩種緩存機制: ETagLast-Modified。由于這部分內容更多屬于服務器配置范疇,這里只做簡單介紹:

  1. ETag:HTTP 請求中在 header 中包含一個內容的 hash,如果返回結果沒有變化,該請求會直接返回304 Not Modified,而不是所有數(shù)據(jù)內容本身
  2. Last-Modified: 和 Etag 工作原理差不多,只是使用時間戳作為內容是否過期的標志。

需要自己配制 WEB Server 的同學可以自行搜尋相關內容,如果你使用Nginx,可以參考這里: A Guide to Caching with NGINX and NGINX Plus。

<br />

10.限制 API 調用頻次(Rate limiting)

限制 API 調用頻次

出于防止惡意訪問和服務器性能壓力考慮,限制 API 訪問頻次是非常有必要的,尤其對于大型系統(tǒng)而言。如果一個客戶端請求 API 的頻率太快,根據(jù)HTTP協(xié)議,可以返回429 Too Many Requests。

如果要為客戶端提供更加詳細的調用頻次和訪問次數(shù)之類的信息,除了提供文檔說明以外,還可以在 http header 用自定義字段的形式提供,比如 Twitter API 是這樣做的:
X-Rate-Limit-Limit: 該請求的調用上限
X-Rate-Limit-Remaining: 15分鐘內還可以調用多少次
X-Rate-Limit-Reset: 還有多少秒之后訪問限制會被重置

我們可以根據(jù)具體需求在 http header 中使用類似的形式,提供對API調用頻率和訪問限制的相關信息。當然,文檔記錄也是一個不錯的選擇,前提是你能保持文檔和代碼同步更新。

<br />

11.盡可能的使用 HTTPS,涉及用戶驗證的 API 一定要強制啟用 HTTPS

盡可能的使用 HTTPS,涉及用戶驗證的 API 一定要強制啟用 HTTPS

在閱讀第六章“使用 token 機制設計鑒權和驗證”時,可能已經(jīng)有讀者感到 RESTful API 如果通過 HTTP 明文傳遞會有很大的安全問題。如果用于鑒權的 app id 和 Secret,甚至是用戶名密碼通過明文傳遞,那么它們很容易被截獲和保存,完全沒有安全性可言。

所以凡是涉及任何和用戶特定信息相關內容 API 都要通過 HTTPS 暴露給調用者。事實上,你的 AP I應該全部使用 HTTPS。HTTPS 現(xiàn)在已經(jīng)是各種網(wǎng)絡服務的標配(比如 Xcode 默認不允許請求不安全的 HTTP 信息)

順便提下,如果你的WEB Server 是 Nginx,在部署了 HTTPS 的情況下,下面兩個選項務必仔細設置,因為這個兩個簡單的設置可以很大程度上避免一些安全問題:

  1. ssl_prefer_server_ciphers: 表示服務端加密算法優(yōu)先于客戶端加密算法,主要是防止降級攻擊 (downgrade attack)。
  2. Strict-Transport-Security(HSTS):告訴瀏覽器這個域名在指定的時間(max-age)內應該強制使用 HTTPS 訪問。

<br />

小結

以上內容,是對工作中遇到的一些關于 RESTful API 設計問題的總結和實踐。其中也總結和歸納很多現(xiàn)有系統(tǒng)的 API 設計原則和大量前人工作成果。這里主要做了一個編輯和整理(也會繼續(xù)整理和更新相關內容),希望能給一起前進的人提供一個參考,設計優(yōu)秀系統(tǒng) API,造福廣大程序員用戶。

參考文檔


  1. Authentication: 驗證你是誰,比如你輸入用戶名和密碼的過程就是Authentication。Authorization:驗證你是否有相應的權限的過程,比如確定你是否有權限訪問某個文件的過程就是Authorization。 ? ?

  2. 為了行文簡潔,此類API都省略前面的域名和前綴部分比如 /getAllUser 實際上應該是類似 https://api.thedomain.com/v1/getAllUser,下同。 ?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容