RESETful API 設計規範

RESTful 是目前最流行的 API 設計規範,用於 Web 數據接口的設計nginx

關於「能願動詞」的使用

爲了不歧義,文檔大量使用了「能願動詞」,對應的解釋以下:json

  • 必須 (MUST):絕對,嚴格遵循,請照作,無條件遵照;
  • 必定不可 (MUST NOT):禁令,嚴令禁止;
  • 應該 (SHOULD) :強烈建議這樣作,可是不強求;
  • 不應 (SHOULD NOT):強烈不建議這樣作,可是不強求;
  • 能夠 (MAY)可選 (OPTIONAL) :選擇性高一點,在這個文檔內,此詞語使用較少;
    參見:RFC 2119

域名

API 的根入口點應儘量保持足夠簡單,這裏有兩個常見的 URL 根例子:api

https://api.example.com/* 
    
    https://example.com/api/*

若是你的應用很龐大或者你預計它將會變的很龐大,那 應該 將 API 放到子域下(api.example.com)。這種作法能夠保持某些規模化上的靈活性。bash

客戶端請求

API 返回的數據格式,不該該是純文本,而應該是一個 JSON 對象,由於這樣才能返回標準的結構化數據。因此,客戶端但願服務器迴應的 HTTP 頭的Content-Type屬性要設爲application/json。服務器

GET /users/2 HTTP/1.1 
Accept: application/json
Content-Type: application/json

版本控制

全部的 API 必須保持向後兼容,你 必須 在引入新版本 API 的同時確保舊版本 API 仍然可用。因此 應該 爲其提供版本支持。restful

目前比較常見的兩種版本號形式:app

  • 在 URL 中嵌入版本編號debug

    https://api.example.com/v1/*
    https://api.example.com/v2/*
  • 將版本號放在 HTTP Header 頭中 經過媒體類型來指定版本信息設計

    Accept: application/vnd.example.com.v1+json

HTTP 動詞

HTTP 請求動詞一般就是五種方法,對應 CRUD 操做。代理

  • GET(SELECT):從服務器取出資源(一項或多項)。
  • POST(CREATE):在服務器新建一個資源。
  • PUT(UPDATE):在服務器更新資源(客戶端提供改變後的完整資源)。
  • PATCH(UPDATE):在服務器更新資源(客戶端提供改變的屬性)。
  • DELETE(DELETE):從服務器刪除資源。

針對每個端點來講,下面列出全部可行的 HTTP 動詞和端點的組合

用 URL 定位資源

請求方法 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 端點的, 應該 模仿上表的方式來定義端點。

資源過濾

若是記錄數量不少,服務器不可能都將它們返回給用戶。API 應該 提供參數,過濾返回結果。下面是一些常見的參數。
  • ?page=10:指定返回記錄的數量
  • ?per_page=10:指定返回記錄的開始位置。
  • ?page=2&per_page=100:指定第幾頁,以及每頁的記錄數。
  • ?sortby=name&order=asc:指定返回結果按照哪一個屬性排序,以及排序順序。
  • ?status=closed:指定篩選條件

全部 URL 參數 必須 是全小寫,必須 使用下劃線類型的參數形式。

分頁參數 必須 固定爲 pageper_page

常用的、複雜的查詢 應該 標籤化,下降維護成本。如

GET /trades?status=closed&sort=sortby=name&order=asc

返回碼

20x

200 OK

201 Created

對建立新資源的 POST 操做進行響應。應該帶着指向新資源地址的 Location 頭

202 Accepted

服務器接受了請求,可是還未處理,響應中應該包含相應的指示信息,告訴客戶端該去哪裏查詢關於本次請求的信息

204 No Content

對不會返回響應體的成功請求進行響應(好比 DELETE 請求)

3xx 重定向

40x 客戶端錯誤

400 Bad Request

請求異常,好比請求中的body沒法解析

401 Unauthorized

沒有進行認證或者認證非法或失效

403 Forbidden

服務器已經理解請求,可是拒絕執行它

404 Not Found

該狀態碼錶示用戶請求的資源不存在,如

  • 獲取不存在的用戶信息 (get /users/9999999)
  • 訪問不存在的端點

必須 返回該狀態碼,若該資源已永久不存在,則 應該 返回 410 響應。

405 Method Not Allowed

所請求的 HTTP 方法不容許當前認證用戶訪問

409 Gonfilct

該狀態碼錶示由於請求存在衝突沒法處理。
如經過手機號碼提供註冊功能的 API,當用戶提交的手機號已存在時,必須 返回此狀態碼。

410 Gone

表示當前請求的資源已永久不存在。當調用老版本 API 的時候頗有用

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":"不容許上傳的圖片格式"}

422 Unprocessable Entity

用來表示校驗錯誤
{
    "message": "422 Unprocessable Entity",
    "errors": {
        "name": [
            "姓名 必須爲字符串。"
        ]
    },
    "status_code": 422
}

429 Too Many Requests

該狀態碼錶示用戶請求次數超過容許範圍。如 API 設定爲 60次/分鐘,當用戶在一分鐘內請求次數超過 60 次後,都 應該 返回該狀態碼。而且也 應該 在響應首部中加上下列頭部:

X-RateLimit-Limit: 10 請求速率(由應用設定,其單位通常爲小時/分鐘等,這裏是 10次/5分鐘)
X-RateLimit-Remaining: 0 當前剩餘的請求數量
X-RateLimit-Reset: 1529839462 重置時間
Retry-After: 120 下一次訪問應該等待的時間(秒)

列子

必須 爲全部的 API 設置 Rate Limit 支持。

50x 服務器錯誤

500 Internal Server Error

服務器遇到了一個不曾預料的情況,致使了它沒法完成對請求的處理。通常來講,這個問題都會在服務器端的源代碼出現錯誤時出現。

501 Not Implemented

服務器不支持當前請求所須要的某個功能。當服務器沒法識別請求的方法,而且沒法支持其對任何資源的請求。

502 Bad Gateway

做爲網關或者代理工做的服務器嘗試執行請求時,從上游服務器接收到無效的響應。

503 Service Unavailable

因爲臨時的服務器維護或者過載,服務器當前沒法處理請求。這個情況是臨時的,而且將在一段時間之後恢復。若是可以預計延遲時間,那麼響應中能夠包含一個 Retry-After 頭用以標明這個延遲時間。若是沒有給出這個 Retry-After 信息,那麼客戶端應當以處理500響應的方式處理它。

注意:503狀態碼的存在並不意味着服務器在過載的時候必須使用它。某些服務器只不過是但願拒絕客戶端的鏈接。

504 Gateway timeout

做爲網關或者代理工做的服務器嘗試執行請求時,未能及時從上游服務器(URI標識出的服務器,例如HTTP、FTP、LDAP)或者輔助服務器(例如DNS)收到響應。

注意:某些代理服務器在DNS查詢超時時會返回400或者500錯誤

數據響應格式

錯誤格式

對於錯誤數據,默認使用以下結構:

'message' => ':message',          // 錯誤的具體描述
'errors' => ':errors',            // 參數的具體錯誤描述,422 等狀態提供
'code' => ':code',                // 業務自定義的異常碼
'status_code' => ':status_code',  // http狀態碼
'debug' => ':debug',              // debug 信息,非生產環境提供

422錯誤碼顯示

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
    "message": "422 Unprocessable Entity",
    "errors": {
        "username": [
            "姓名 必須爲字符串。"
            "姓名 必須介於 4 - 18 個字符之間"
        ],
        "phone": [
            "手機號碼 格式不正確。"
        ]
    },
    "status_code": 422
}

403錯誤碼顯示

HTTP/1.1 403 Forbidden
Content-Type: application/json

{
    "message": "您無權訪問該訂單",
    "status_code":"403"
}

429錯誤碼顯示

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.",
    "status_code":429
}

正確輸出顯示

分頁顯示

{
    "data": [
        {
            "id": 1,
            "name": "Eladio Schroeder Sr.",
            "email": "therese28@example.com",
        },
        {
            "id": 2,
            "name": "Liliana Mayert",
            "email": "evandervort@example.com",
        }
    ],
    "links":{
        "first": "http://example.com/pagination?page=1",
        "last": "http://example.com/pagination?page=1",
        "prev": null,
        "next": null
    },
    "meta":{
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "path": "http://example.com/pagination",
        "per_page": 15,
        "to": 10,
        "total": 10
    }
}
相關文章
相關標籤/搜索