RESTful設計方法和規范
在初步了解了 RESTful 之后,我們接到一項任務,需要為一所學校開發一套師生管理系統,客戶要求所開發的系統能在 PC 桌面通過瀏覽器使用,而且日后還想開發 IOS 和 Android 應用。了解需求之后,我們毫不猶豫選擇了前后端分離的開發模式,并且決定遵從時下最為流行的 RESTful 規范。接下來,我們就以后端開發人員的角色,一起來了解整個開發過程。
1. 域名(Domain)
根據 RESTful 規范,應該盡量使用專用的域名用于部署 API,于是我們和校方溝通,使用下方域名作為 API 訪問地址:
https://api.demo.com
但是經過溝通,發現上述域名已被占用,校方否決了我們的提議,考慮到 API 相對簡單,于是我們使用下面地址部署 API:
https://www.demo.com/api
上述地址中,https 代表協議名稱,常見的還有 http,二者區別在于前者在傳輸過程中是將信息加密后傳輸的,而后者是明文傳輸;www.demo.com 為域名,可以理解成某個機房里一臺電腦的地址,通過這個地址,就能訪問這臺電腦提供的資源;api 代表一個資源路徑,可以想象成這臺電腦中一個文件夾的路徑。
2. 版本(Versioning)
師生管理系統不是一成不變的,日后還要更新維護。為了區分不同版本,API 的 URL 中應當包含 API 版本信息:
http://www.demo.com/api/1.0/foo
http://www.demo.com/api/1.1/foo
http://www.demo.com/api/2.0/foo
除了上述方法外,API 版本信息還可放在 HTTP 請求頭中。Github 采用的就是這種做法。
因為不同的版本,可以理解成同一種資源的不同表現形式,所以應該采用同一個 URL。版本號可以在 HTTP 請求頭信息的 Accept 字段中進行區分(參見Versioning REST Services):
Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.1
Accept: vnd.example-com.foo+json; version=2.0
實際工作中,通常采用第一種方法,因為這樣的方式更加直觀,方便使用。
3. 路徑(Endpoint)
路徑即"終點"(endpoint),是訪問 API 的具體網址,通過訪問每個網址,可以獲取到相應的資源(resource)。在師生管理系統中,所謂資源,就是我們想獲取的信息,比如獲取 3 年 2 班所有學生姓名,獲取小明的年齡、成績等。
路徑須滿足以下規范:
1. 資源路徑中應當使用名詞,杜絕動詞。資源路徑中的名詞,應當與數據庫的表名相對應。
以下路徑中包含動詞,是不符合規范的例子,在實際工作中,應當避免。
/getStudents :獲取學生信息
/listTeachers :獲取老師信息
/retreiveStudentByID?Id=2020 :獲取ID為2020的學生信息
對于資源的操作,應該通過 HTTP 中的不同方法來區分處理資源的動作,資源路徑中應當只包含名詞。
GET /students :將返回所有學生信息
POST /students :將新增的學生信息存入數據庫
GET /students/4 :獲取編號為4號的學生信息
PATCH(或)PUT /students/4 :更新編號為4的學生信息
2. API 中的名詞應該使用復數。無論是子資源或者是所有資源。
例如:
獲取單個學生信息:http://www.demo.com/students/1 :獲取編號為1的學生信息
獲取所有學生信息: http://www.demo.com/students :獲取所有學生信息
4. HTTP動詞
對于資源的具體操作類型,由 HTTP 動詞表示。
常用的 HTTP 動詞有下面 4 個(括號里是對應的 SQL 命令)。
- GET(SELECT):從服務器取出資源(一項或多項)
- POST(CREATE):在服務器新建一個資源
- PUT(UPDATE):在服務器更新資源(客戶端提供改變后的完整資源)
- DELETE(DELETE):從服務器刪除資源
還有 3 個不常用的 HTTP 動詞。
- PATCH(UPDATE):在服務器更新(更新)資源(客戶端提供改變的屬性)
- HEAD:獲取資源的元數
- OPTIONS:獲取信息,關于資源的哪些屬性是客戶端可以改變的
下面是一些例子。
GET /classes:列出所有班級
POST /classes:新建一個班級(上傳文件)
GET /classes/ID:獲取某個指定班級的信息
PUT /classes/ID:更新某個指定班級的信息(提供該班級的全部信息)
PATCH /classes/ID:更新某個指定班級的信息(提供該班級的部分信息)
DELETE /classes/ID:刪除某個班級
GET /classes/ID/students:列出某個指定班級的所有學生
DELETE /classes/ID/students/ID:刪除某個指定班級的指定學生
5. 過濾信息(Filtering)
如果記錄數量很多,服務器不可能都將它們返回給用戶。API 應該提供參數,過濾返回結果。比如,我們想獲取全校師生的個人信息,如果將這些信息一股腦地全部展示在網頁上,是不明智也是不現實的。如果數據量太大,在實際開發中我們會采用分頁展示的形式。另外,如果想在一次考試后,按照成績高低展示學生信息,那么可以通過過濾信息來實現。
所謂過濾,就是在 URL 中添加一下限制參數。下面是一些常見的參數。
?limit=10:指定返回記錄的數量
?offset=10:指定返回記錄的開始位置。
?page=2&per_page=100:指定第幾頁,以及每頁的記錄數。
?sortby=score&order=asc:指定返回結果按照學生的成績(score)正序(asc)排列順序。
參數的設計允許存在冗余,即允許 API 路徑和 URL 參數允許有重復。比如,想要查詢某個班級所有學生信息,我們可以設計 GET /classes/ID/students
與 GET /students?class_id=ID
兩種地址,任何一種都可得到相同的結果。
6. 狀態碼(Status Codes)
服務器向用戶返回的狀態碼和提示信息,常見的有以下一些(方括號中是該狀態碼對應的 HTTP 動詞)。不同的狀態碼代表著不同的含義,比如以 2 開頭的狀態碼通常代表服務器成功響應,3 開頭的狀態碼代表發生了重定性(即跳轉到了別的鏈接),4 開頭的狀態碼通常表示客戶端這邊提供的信息有誤,而 5 開頭的狀態碼則表示服務器內部出現的錯誤。通過返回的狀態碼,用戶即可判斷請求成功與否,不成功問題在何處。
一些常用的狀態碼列舉如下:
- 200 OK - [GET]:服務器成功返回用戶請求的數據
- 201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數據成功。
- 202 Accepted - [*]:表示一個請求已經進入后臺排隊(異步任務)
- 204 NO CONTENT - [DELETE]:用戶刪除數據成功
- 400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發出的請求有錯誤,服務器沒有進行新建或修改數據的操作
- 401 Unauthorized - [*]:表示用戶沒有權限(令牌、用戶名、密碼錯誤)
- 403 Forbidden - [*] 表示用戶得到授權(與401錯誤相對),但是訪問是被禁止的
- 404 NOT FOUND - [*]:用戶發出的請求針對的是不存在的記錄,服務器沒有進行操作,該操作是冪等的
- 406 Not Acceptable - [GET]:用戶請求的格式不可得(比如用戶請求JSON格式,但是只有XML格式)
- 410 Gone -[GET]:用戶請求的資源被永久刪除,且不會再得到的
- 422 Unprocesable entity - [POST/PUT/PATCH]: 當創建一個對象時,發生一個驗證錯誤
- 500 INTERNAL SERVER ERROR - [*]:服務器發生錯誤,用戶將無法判斷發出的請求是否成功
7. 錯誤信息
如果狀態碼是 4xx,服務器就應該向用戶返回出錯信息。一般來說,返回的信息是鍵值對形式的數據,將 error
作為鍵名,出錯信息作為鍵值即可。比如,在一個提供查詢學生信息的 API 中,要求客戶端提供正確的 API key(可以理解為輸入了正確的用戶名和密碼)才能訪問,如果提供的 API key 不正確,此時服務器應拒絕訪問,并返回錯誤信息。這樣,客戶端就知道了為何沒能查到信息,修改成正確的 API key 即可。
{
error: "Invalid API key"
}
8. 返回結果
針對不同操作,服務器向用戶返回的結果應該符合以下規范。
- GET /collection:返回資源對象的列表(數組)
- GET /collection/resource:返回單個資源對象
- POST /collection:返回新生成的資源對象
- PUT /collection/resource:返回完整的資源對象
- PATCH /collection/resource:返回完整的資源對象
- DELETE /collection/resource:返回一個空文檔
9. 超媒體鏈接
RESTful API 最好做到 Hypermedia(即返回結果中提供鏈接,連向其他 API 方法),使得用戶不查文檔,也知道下一步應該做什么。
比如,Github 的 API 就是這種設計,訪問api.github.com會得到一個所有可用API的網址列表。
{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}
從上面可以看到,如果想獲取當前用戶的信息,應該去訪問 api.github.com/user,然后就得到了下面結果。
{
"message": "Requires authentication",
"documentation_url": "https://developer.github.com/v3"
}
上面代碼表示,服務器給出了提示信息,以及文檔的網址。
10. 數據格式
服務器返回的數據格式,應該盡量使用 JSON,避免使用 XML。什么是 JSON 呢?什么又是 XML 呢?兩種數據格式的簡單舉例如下:
# JSON
{"name":"XiaoMing",
"age":"12",
"gender":"male"}
# XML
<?xml version="1.0" encoding="UTF-8" ?>
<name>XiaoMing</name>
<age>12</age>
<gender>male</gender>
通過上面的對比可以看出,JSON 數據形式要遠比 XML 的數據形式來得簡單和易懂,所以現在的 Web 開發中 JSON 數據格式已經開始全面取代 XML 應用在實際開發中。
11. 小結
本節主要從域名、版本、路徑、HTTP動詞、過濾信息、狀態碼、錯誤信息、返回結果、超媒體鏈接、數據格式 10 個方面介紹了 RESTful 設計方法和設計規范。為了讓更多的人方便使用所設計的 API 接口,以上規范一定要遵守哦!