本文是為 大渝網
API
開發規範擬定的一個beta
版,文章大量參考了目前比較常見的RESTful API
設計。
為了更好的討論規範帶來的爭議及問題,現已把該文件整理並開源到 github,關於大家補充及提問。
關於「能願動詞」的使用
為了避免歧義,文件大量使用了「能願動詞」,對應的解釋如下:
必須 (MUST)
:絕對,嚴格遵循,請照做,無條件遵守;一定不可 (MUST NOT)
:禁令,嚴令禁止;應該 (SHOULD)
:強烈建議這樣做,但是不強求;不該 (SHOULD NOT)
:強烈不建議這樣做,但是不強求;可以 (MAY)
和可選 (OPTIONAL)
:選擇性高一點,在這個文件內,此詞語使用較少;
參見:RFC 2119
協議
在通過 API
於後端服務通訊的過程中,應該
使用 HTTPS
協議。
API Root URL
API
的根入口點應儘可能保持足夠簡單,這裡有兩個常見的 URL
根例子:
- api.example.com/*
- example.com/api/*
如果你的應用很龐大或者你預計它將會變的很龐大,那
應該
將API
放到子域下。這種做法可以保持某些規模化上的靈活性。
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/getUserInfo?userid=1
- https://api.example.com/getusers
- https://api.example.com/sv/u
- https://api.example.com/cgi-bin/users/get_user.php?userid=1
再來看一個正列
- https://api.example.com/zoos
- https://api.example.com/animals
- https://api.example.com/zoos/{zoo}/animals
- https://api.example.com/animal_types
- https://api.example.com/employees
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} | 從某個動物園解僱一名員工 |
Filtering
如果記錄數量很多,伺服器不可能都將它們返回給使用者。API
應該
提供引數,過濾返回結果。下面是一些常見的引數。
- ?limit=10:指定返回記錄的數量
- ?offset=10:指定返回記錄的開始位置。
- ?page=2&per_page=100:指定第幾頁,以及每頁的記錄數。
- ?sortby=name&order=asc:指定返回結果按照哪個屬性排序,以及排序順序。
- ?animal_type_id=1:指定篩選條件
所有 URL
引數 必須
是全小寫,必須
使用下劃線型別的引數形式。
經常使用的、複雜的查詢 應該
標籤化,降低維護成本。如
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)
- 訪問不存在的端點
都 必須
返回該狀態碼,若該資源已永久不存在,則 應該
返回 401
響應。
405 Method Not Allowd
當客戶端使用的 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 Gonfilct
該狀態碼錶示因為請求存在衝突無法處理。如通過手機號碼提供註冊功能的 API
,當使用者提交的手機號已存在時,必須
返回此狀態碼。
HTTP/1.1 409 Gonfilct
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 Request
該狀態碼錶示使用者請求次數超過允許範圍。如 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":"服務維護中"}
其他 HTTP
狀態碼請參考 HTTP 狀態碼- 維基百科。
版權宣告
文章釋出於 二楞徐的閒談雜魚,自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證)