該倉庫整理了目前比較流行的 RESTful api 設計規范,為了方便討論規范帶來的問題及爭議,現把該文檔托管於 Github,歡迎大家補充!!
Table of Contents
- RESTful API 設計規范
- 關於「能願動詞」的使用
- Protocol
- API Root URL
- Endpoints
- HTTP 動詞
- Filtering
- Authentication
-
- 200 ok
- 201 Created
- 202 Accepted
- 204 No Content
- 3xx 重定向
- 400 Bad Request
- 401 Unauthorized
- 403 Forbidden
- 404 Not Found
- 405 Method Not Allowd
- 406 Not Acceptable
- 408 Request Timeout
- 409 Gonfilct
- 410 Gone
- 413 Request Entity Too Large
- 414 Request-URI Too Long
- 415 Unsupported Media Type
- 429 Too Many Request
- 500 Internal Server Error
- 503 Service Unavailable
- 版權聲明
- 建議參考
- LICENSE
關於「能願動詞」的使用
為了避免歧義,文檔大量使用了「能願動詞」,對應的解釋如下:
必須 (MUST):絕對,嚴格遵循,請照做,無條件遵守;一定不可 (MUST NOT):禁令,嚴令禁止;應該 (SHOULD):強烈建議這樣做,但是不強求;不該 (SHOULD NOT):強烈不建議這樣做,但是不強求;可以 (MAY)和可選 (OPTIONAL):選擇性高一點,在這個文檔內,此詞語使用較少;
參見: RFC 2119
Protocol
客戶端在通過 API 與后端服務通信的過程中,應該 使用 HTTPS 協議。
API Root URL
API 的根入口點應盡可能保持足夠簡單,這里有兩個常見的 URL 根例子:
- api.example.com/*
- example.com/api/*
如果你的應用很龐大或者你預計它將會變的很龐大,那應該將API放到子域下(api.example.com)。這種做法可以保持某些規模化上的靈活性。
Versioning
所有的 API 必須保持向后兼容,你 必須 在引入新版本 API 的同時確保舊版本 API 仍然可用。所以 應該 為其提供版本支持。
目前比較常見的兩種版本號形式:
在 URL 中嵌入版本編號
api.example.com/v1/*
這種做法是版本號直觀、易於調試;另一種做法是,將版本號放在 HTTP Header 頭中:
通過媒體類型來指定版本信息
Accept: application/vnd.example.com.v1+json
其中 vnd 表示 Standards Tree 標准樹類型,有三個不同的樹: x,prs 和 vnd。你使用的標准樹需要取決於你開發的項目
- 未注冊的樹(
x)主要表示本地和私有環境 - 私有樹(
prs)主要表示沒有商業發布的項目 - 供應商樹(
vnd)主要表示公開發布的項目
后面幾個參數依次為應用名稱(一般為應用域名)、版本號、期望的返回格式。
至於具體把版本號放在什么地方,這個問題一直存在很大的爭議,但由於我們大多數時間都在使用 Laravel 開發,應該 使用 dingo/api 來快速構建應用,它采用第二種方式來管理 API 版本,並且已集成了標准的 HTTP Response。
Endpoints
端點就是指向特定資源或資源集合的 URL。在端點的設計中,你 必須 遵守下列約定:
- URL 的命名
必須全部小寫 - URL 中資源(
resource)的命名必須是名詞,並且必須是復數形式 必須優先使用Restful類型的 URL- URL
必須是易讀的 - URL
一定不可暴露服務器架構
至於 URL 是否必須使用連字符(-) 或下划線(_),不做硬性規定,但必須根據團隊情況統一一種風格。
來看一個反例
- https://api.example.com/getUs...
- https://api.example.com/getusers
- https://api.example.com/sv/u
- https://api.example.com/cgi-b...
再來看一個正列
- https://api.example.com/zoos
- https://api.example.com/animals
- https://api.example.com/zoos/{zoo}/animals
- https://api.example.com/anima...
- https://api.example.com/emplo...
HTTP 動詞
對於資源的具體操作類型,由 HTTP 動詞表示。常用的 HTTP 動詞有下面五個(括號里是對應的 SQL 命令)。
- GET(SELECT):從服務器取出資源(一項或多項)。
- POST(CREATE):在服務器新建一個資源。
- PUT(UPDATE):在服務器更新資源(客戶端提供改變后的完整資源)。
- PATCH(UPDATE):在服務器更新資源(客戶端提供改變的屬性)。
- DELETE(DELETE):從服務器刪除資源。
其中
1 刪除資源 必須 用 DELETE 方法
2 創建新的資源 必須 使用 POST 方法
3 更新資源 應該 使用 PUT 方法
4 獲取資源信息 必須 使用 GET 方法
針對每一個端點來說,下面列出所有可行的 HTTP 動詞和端點的組合
| 請求方法 | URL | 描述 |
|---|---|---|
| GET | /zoos | 列出所有的動物園(ID和名稱,不要太詳細) |
| POST | /zoos | 新增一個新的動物園 |
| GET | /zoos/{zoo} | 獲取指定動物園詳情 |
| PUT | /zoos/{zoo} | 更新指定動物園(整個對象) |
| PATCH | /zoos/{zoo} | 更新動物園(部分對象) |
| DELETE | /zoos/{zoo} | 刪除指定動物園 |
| GET | /zoos/{zoo}/animals | 檢索指定動物園下的動物列表(ID和名稱,不要太詳細) |
| GET | /animals | 列出所有動物(ID和名稱)。 |
| POST | /animals | 新增新的動物 |
| GET | /animals/{animal} | 獲取指定的動物詳情 |
| PUT | /animals/{animal} | 更新指定的動物(整個對象) |
| PATCH | /animals/{animal} | 更新指定的動物(部分對象) |
| GET | /animal_types | 獲取所有動物類型(ID和名稱,不要太詳細) |
| GET | /animal_types/{type} | 獲取指定的動物類型詳情 |
| GET | /employees | 檢索整個雇員列表 |
| GET | /employees/{employee} | 檢索指定特定的員工 |
| GET | /zoos/{zoo}/employees | 檢索在這個動物園工作的雇員的名單(身份證和姓名) |
| POST | /employees | 新增指定新員工 |
| POST | /zoos/{zoo}/employees | 在特定的動物園雇佣一名員工 |
| DELETE | /zoos/{zoo}/employees/{employee} | 從某個動物園解雇一名員工 |
超出Restful端點的,應該模仿上表的方式來定義端點。
Filtering
如果記錄數量很多,服務器不可能都將它們返回給用戶。API
應該 提供參數,過濾返回結果。下面是一些常見的參數。
- ?limit=10:指定返回記錄的數量
- ?offset=10:指定返回記錄的開始位置。
- ?page=2&per_page=100:指定第幾頁,以及每頁的記錄數。
- ?sortby=name&order=asc:指定返回結果按照哪個屬性排序,以及排序順序。
- ?animal_type_id=1:指定篩選條件
所有 URL 參數 必須 是全小寫,必須 使用下划線類型的參數形式。
分頁參數必須固定為page、per_page
經常使用的、復雜的查詢 應該 標簽化,降低維護成本。如
GET /trades?status=closed&sort=sortby=name&order=asc
# 可為其定制快捷方式 GET /trades/recently_closed
Authentication
應該 使用 OAuth2.0 的方式為 API 調用者提供登錄認證。必須 先通過登錄接口獲取 Access Token 后再通過該 token 調用需要身份認證的 API。
Oauth 的端點設計示列
- RFC 6749 /token
- Twitter /oauth2/token
- Fackbook /oauth/access_token
- Google /o/oauth2/token
- Github /login/oauth/access_token
- Instagram /oauth/authorize
客戶端在獲得 access token 的同時 必須 在響應中包含一個名為 expires_in 的數據,它表示當前獲得的 token 會在多少 秒 后失效。
{
"access_token": "token....", "token_type": "Bearer", "expires_in": 3600 }
客戶端在請求需要認證的 API 時,必須 在請求頭 Authorization 中帶上 access_token。
Authorization: Bearer token...
當超過指定的秒數后,access token 就會過期,再次用過期/或無效的 token 訪問時,服務端 應該 返回 invalid_token 的錯誤或 401 錯誤碼。
HTTP/1.1 401 Unauthorized Content-Type: application/json Cache-Control: no-store Pragma: no-cache { "error": "invalid_token" }
Laravel 開發中,應該使用 JWT 來為管理你的 Token,並且一定不可在api中間件中開啟請求session。
Response
所有的 API 響應,必須 遵守 HTTP 設計規范,必須 選擇合適的 HTTP 狀態碼。一定不可 所有接口都返回狀態碼為 200 的 HTTP 響應,如:
HTTP/1.1 200 ok Content-Type: application/json Server: example.com { "code": 0, "msg": "success", "data": { "username": "username" } }
或
HTTP/1.1 200 ok Content-Type: application/json Server: example.com { "code": -1, "msg": "該活動不存在", }
下表列舉了常見的 HTTP 狀態碼
| 狀態碼 | 描述 |
|---|---|
| 1xx | 代表請求已被接受,需要繼續處理 |
| 2xx | 請求已成功,請求所希望的響應頭或數據體將隨此響應返回 |
| 3xx | 重定向 |
| 4xx | 客戶端原因引起的錯誤 |
| 5xx | 服務端原因引起的錯誤 |
只有來自客戶端的請求被正確的處理后才能返回2xx的響應,所以當 API 返回2xx類型的狀態碼時,前端必須認定該請求已處理成功。
必須強調的是,所有 API 一定不可 返回 1xx 類型的狀態碼。當 API 發生錯誤時,必須 返回出錯時的詳細信息。目前常見返回錯誤信息的方法有兩種:
1、將錯誤詳細放入 HTTP 響應首部;
X-MYNAME-ERROR-CODE: 4001 X-MYNAME-ERROR-MESSAGE: Bad authentication token X-MYNAME-ERROR-INFO: http://docs.example.com/api/v1/authentication
2、直接放入響應實體中;
HTTP/1.1 401 Unauthorized Server: nginx/1.11.9 Content-Type: application/json Transfer-Encoding: chunked Cache-Control: no-cache, private Date: Sun, 24 Jun 2018 10:02:59 GMT Connection: keep-alive {"error_code":40100,"message":"Unauthorized"}
考慮到易讀性和客戶端的易處理性,我們 必須 把錯誤信息直接放到響應實體中,並且錯誤格式 應該 滿足如下格式:
{
"message": "您查找的資源不存在", "error_code": 404001 }
其中錯誤碼(error_code)必須 和 HTTP 狀態碼對應,也方便錯誤碼歸類,如:
HTTP/1.1 429 Too Many Requests Server: nginx/1.11.9 Content-Type: application/json Transfer-Encoding: chunked Cache-Control: no-cache, private Date: Sun, 24 Jun 2018 10:15:52 GMT Connection: keep-alive {"error_code":429001,"message":"你操作太頻繁了"}
HTTP/1.1 403 Forbidden Server: nginx/1.11.9 Content-Type: application/json Transfer-Encoding: chunked Cache-Control: no-cache, private Date: Sun, 24 Jun 2018 10:19:27 GMT Connection: keep-alive {"error_code":403002,"message":"用戶已禁用"}
應該 在返回的錯誤信息中,同時包含面向開發者和面向用戶的提示信息,前者可方便開發人員調試,后者可直接展示給終端用戶查看如:
{
"message": "直接展示給終端用戶的錯誤信息", "error_code": "業務錯誤碼", "error": "供開發者查看的錯誤信息", "debug": [ "錯誤堆棧,必須開啟 debug 才存在" ] }
下面詳細列舉了各種情況 API 的返回說明。
200 ok
200 狀態碼是最常見的 HTTP 狀態碼,在所有 成功 的 GET 請求中,必須 返回此狀態碼。HTTP 響應實體部分 必須 直接就是數據,不要做多余的包裝。
錯誤示例:
HTTP/1.1 200 ok Content-Type: application/json Server: example.com { "user": { "id":1, "nickname":"fwest", "username": "example" } }
正確示例:
1、獲取單個資源詳情
{
"id": 1, "username": "godruoyi", "age": 88, }
2、獲取資源集合
[
{
"id": 1, "username": "godruoyi", "age": 88, }, { "id": 2, "username": "foo", "age": 88, } ]
3、額外的媒體信息
{
"data": [ { "id": 1, "avatar": "https://lorempixel.com/640/480/?32556", "nickname": "fwest", "last_logined_time": "2018-05-29 04:56:43", "has_registed": true }, { "id": 2, "avatar": "https://lorempixel.com/640/480/?86144", "nickname": "zschowalter", "last_logined_time": "2018-06-16 15:18:34", "has_registed": true } ], "meta": { "pagination": { "total": 101, "count": 2, "per_page": 2, "current_page": 1, "total_pages": 51, "links": { "next": "http://api.example.com?page=2" } } } }
其中,分頁和其他額外的媒體信息,必須放到
meta 字段中。
201 Created
當服務器創建數據成功時,應該 返回此狀態碼。常見的應用場景是使用 POST 提交用戶信息,如:
- 添加了新用戶
- 上傳了圖片
- 創建了新活動
等,都可以返回 201 狀態碼。需要注意的是,你可以選擇在用戶創建成功后返回新用戶的數據
HTTP/1.1 201 Created Server: nginx/1.11.9 Content-Type: application/json Transfer-Encoding: chunked Date: Sun, 24 Jun 2018 09:13:40 GMT Connection: keep-alive { "id": 1, "avatar": "https:\/\/lorempixel.com\/640\/480\/?32556", "nickname": "fwest", "last_logined_time": "2018-05-29 04:56:43", "created_at": "2018-06-16 17:55:55", "updated_at": "2018-06-16 17:55:55" }
也可以返回一個響應實體為空的 HTTP Response 如:
HTTP/1.1 201 Created Server: nginx/1.11.9 Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Date: Sun, 24 Jun 2018 09:12:20 GMT Connection: keep-alive
這里我們
應該 采用第二種方式,因為大多數情況下,客戶端只需要知道該請求操作成功與否,並不需要返回新資源的信息。
202 Accepted
該狀態碼表示服務器已經接受到了來自客戶端的請求,但還未開始處理。常用短信發送、郵件通知、模板消息推送等這類很耗時需要隊列支持的場景中;
返回該狀態碼時,響應實體
必須 為空。
HTTP/1.1 202 Accepted
Server: nginx/1.11.9
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 24 Jun 2018 09:25:15 GMT
Connection: keep-alive
204 No Content
該狀態碼表示響應實體不包含任何數據,其中:
- 在使用
DELETE方法刪除資源 成功 時,必須返回該狀態碼 - 使用
PUT、PATCH方法更新數據 成功 時,也應該返回此狀態碼
HTTP/1.1 204 No Content Server: nginx/1.11.9 Date: Sun, 24 Jun 2018 09:29:12 GMT Connection: keep-alive
3xx 重定向
所有 API 不該 返回 3xx 類型的狀態碼。因為 3xx 類型的響應格式一般為下列格式:
HTTP/1.1 302 Found
Server: nginx/1.11.9
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Cache-Control: no-cache, private
Date: Sun, 24 Jun 2018 09:41:50 GMT
Location: https://example.com
Connection: keep-alive
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="refresh" content="0;url=https://example.com" /> <title>Redirecting to https://example.com</title> </head> <body> Redirecting to <a href="https://example.com">https://example.com</a>. </body> </html>
所有 API 一定不可 返回純 HTML 結構的響應;若一定要使用重定向功能,可以 返回一個響應實體為空的 3xx 響應,並在響應頭中加上 Location 字段:
HTTP/1.1 302 Found Server: nginx/1.11.9 Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Date: Sun, 24 Jun 2018 09:52:50 GMT Location: https://godruoyi.com Connection: keep-alive
400 Bad Request
由於明顯的客戶端錯誤(例如,請求語法格式錯誤、無效的請求、無效的簽名等),服務器 應該 放棄該請求。
當服務器無法從其他 4xx 類型的狀態碼中找出合適的來表示錯誤類型時,都
必須 返回該狀態碼。
HTTP/1.1 400 Bad Request Server: nginx/1.11.9 Content-Type: application/json Transfer-Encoding: chunked Cache-Control: no-cache, private Date: Sun, 24 Jun 2018 13:22:36 GMT Connection: keep-alive {"error_code":40000,"message":"無效的簽名"}
401 Unauthorized
該狀態碼表示當前請求需要身份認證,以下情況都 必須 返回該狀態碼。
- 未認證用戶訪問需要認證的 API
- access_token 無效/過期
客戶端在收到401響應后,都應該提示用戶進行下一步的登錄操作。
HTTP/1.1 401 Unauthorized Server: nginx/1.11.9 Content-Type: application/json Transfer-Encoding: chunked WWW-Authenticate: JWTAuth Cache-Control: no-cache, private Date: Sun, 24 Jun 2018 13:17:02 GMT Connection: keep-alive {"message":"Token Signature could not be verified.","error_code": "40100"}
403 Forbidden
該狀態碼可以簡單的理解為沒有權限訪問該請求,服務器收到請求但拒絕提供服務。
如當普通用戶請求操作管理員用戶時,必須 返回該狀態碼。
HTTP/1.1 403 Forbidden Server: nginx/1.11.9 Content-Type: application/json Transfer-Encoding: chunked Cache-Control: no-cache, private Date: Sun, 24 Jun 2018 13:05:34 GMT Connection: keep-alive {"error_code":40301,"message":"權限不足"}
404 Not Found
該狀態碼表示用戶請求的資源不存在,如
- 獲取不存在的用戶信息 (get /users/9999999)
- 訪問不存在的端點
都 必須 返回該狀態碼,若該資源已永久不存在,則 應該 返回 410 響應。
405 Method Not Allowed
當客戶端使用的 HTTP 請求方法不被服務器允許時,必須 返回該狀態碼。
如客戶端調用了
POST 方法來訪問只支持 GET 方法的 API
該響應 必須 返回一個 Allow 頭信息用以表示出當前資源能夠接受的請求方法的列表。
HTTP/1.1 405 Method Not Allowed Server: nginx/1.11.9 Content-Type: application/json Transfer-Encoding: chunked Allow: GET, HEAD Cache-Control: no-cache, private Date: Sun, 24 Jun 2018 12:30:57 GMT Connection: keep-alive {"message":"405 Method Not Allowed","error_code": 40500}
406 Not Acceptable
API 在不支持客戶端指定的數據格式時,應該返回此狀態碼。如支持 JSON 和 XML 輸出的 API 被指定返回 YAML 格式的數據時。
Http 協議一般通過請求首部的 Accept 來指定數據格式
408 Request Timeout
客戶端請求超時時 必須 返回該狀態碼,需要注意的時,該狀態碼表示 客戶端請求超時,在涉及第三方 API 調用超時時,一定不可 返回該狀態碼。
409 Confilct
該狀態碼表示因為請求存在沖突無法處理。如通過手機號碼提供注冊功能的 API,當用戶提交的手機號已存在時,必須 返回此狀態碼。
HTTP/1.1 409 Conflict Server: nginx/1.11.9 Content-Type: application/json Transfer-Encoding: chunked Cache-Control: no-cache, private Date: Sun, 24 Jun 2018 12:19:04 GMT Connection: keep-alive {"error_code":40900,"message":"手機號已存在"}
410 Gone
和 404 類似,該狀態碼也表示請求的資源不存在,只是 410 狀態碼進一步表示所請求的資源已不存在,並且未來也不會存在。在收到 410 狀態碼后,客戶端 應該 停止再次請求該資源。
413 Request Entity Too Large
該狀態碼表示服務器拒絕處理當前請求,因為該請求提交的實體數據大小超過了服務器願意或者能夠處理的范圍。
此種情況下,服務器可以關閉連接以免客戶端繼續發送此請求。
如果這個狀況是臨時的,服務器 應該 返回一個 Retry-After 的響應頭,以告知客戶端可以在多少時間以后重新嘗試。
414 Request-URI Too Long
該狀態碼表示請求的 URI 長度超過了服務器能夠解釋的長度,因此服務器拒絕對該請求提供服務。
415 Unsupported Media Type
通常表示服務器不支持客戶端請求首部 Content-Type 指定的數據格式。如在只接受 JSON 格式的 API 中放入 XML 類型的數據並向服務器發送,都 應該 返回該狀態碼。
該狀態碼也可用於如:只允許上傳圖片格式的文件,但是客戶端提交媒體文件非法或不是圖片類型,這時 應該 返回該狀態碼:
HTTP/1.1 415 Unsupported Media Type Server: nginx/1.11.9 Content-Type: application/json Transfer-Encoding: chunked Cache-Control: no-cache, private Date: Sun, 24 Jun 2018 12:09:40 GMT Connection: keep-alive {"error_code":41500,"message":"不允許上傳的圖片格式"}
429 Too Many Requests
該狀態碼表示用戶請求次數超過允許范圍。如 API 設定為 60次/分鍾,當用戶在一分鍾內請求次數超過 60 次后,都 應該 返回該狀態碼。並且也 應該 在響應首部中加上下列頭部:
X-RateLimit-Limit: 10 請求速率(由應用設定,其單位一般為小時/分鍾等,這里是 10次/5分鍾)
X-RateLimit-Remaining: 0 當前剩余的請求數量
X-RateLimit-Reset: 1529839462 重置時間
Retry-After: 120 下一次訪問應該等待的時間(秒)
列子
HTTP/1.1 429 Too Many Requests Server: nginx/1.11.9 Content-Type: application/json Transfer-Encoding: chunked X-RateLimit-Limit: 10 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1529839462 Retry-After: 290 Cache-Control: no-cache, private Date: Sun, 24 Jun 2018 11:19:32 GMT Connection: keep-alive {"message":"You have exceeded your rate limit.","error_code":42900}
必須 為所有的 API 設置 Rate Limit 支持。
500 Internal Server Error
該狀態碼 必須 在服務器出錯時拋出,對於所有的 500 錯誤,都 應該 提供完整的錯誤信息支持,也方便跟蹤調試。
503 Service Unavailable
該狀態碼表示服務器暫時處理不可用狀態,當服務器需要維護或第三方 API 請求超時/不可達時,都 應該 返回該狀態碼,其中若是主動關閉 API 服務,應該 在返回的響應首部加上 Retry-After 頭部,表示多少秒后可以再次訪問。
HTTP/1.1 503 Service Unavailable Server: nginx/1.11.9 Content-Type: application/json Transfer-Encoding: chunked Cache-Control: no-cache, private Date: Sun, 24 Jun 2018 10:56:20 GMT Retry-After: 60 Connection: keep-alive {"error_code":50300,"message":"服務維護中"}
