1. 簡介
目前 "互聯網軟件"從用客戶端/服務端模式,建立在分布式體系上,通過互聯網通訊,具有高延時、高開發等特點。但是軟件開發和網絡是兩個不同的領域,交集很少。要使得兩個融合,就要考慮如何在互聯網環境使用軟件。
REST 描述了一個架構樣式的網絡系統,2000 年 Roy Fielding 在他的博士論文中首次提出了 REST,同時他也是HTTP協議(1.0版和1.1版)的主要設計者、Apache服務器軟件的作者之一、Apache基金會的第一任主席。
目前主流的三種 Web 服務交互方案:
- REST:表現層狀態轉化
- SOAP(Simple Object Access protocol):簡單對象訪問協議
- XML-RPC
要想理解什么是 REST(Representational State Transfe
) 就要理解這里幾個詞是什么意思。
資源(Resources)
REST的名稱"表現層狀態轉化"中,省略了主語。"表現層"其實指的是"資源"(Resources)的"表現層"。
網絡中所謂資源即實體表現的一種,如:文本、圖片、音頻,視頻等。通過調用特定的 URI 來獲取相應資源,因此 URI 就成了每一個資源的地址或獨一無二的識別符。
表現層(Representation)
"資源"是一種信息實體,它可以有多種外在表現形式。我們把"資源"具體呈現出來的形式,叫做它的"表現層"(Representation)。
如:文本可以以 txt、json、xml
等格式表現,甚至是二進制格式,圖片也有 jpg、png
等格式。
URI 只代表資源的實體,不代表它的形式。嚴格地說,有些網址最后的.html
后綴名是不必要的,因為這個后綴名表示格式,屬于"表現層"范疇,而URI應該只代表"資源"的位置。它的具體表現形式,應該在HTTP請求的頭信息中用 Accept 和 Content-Type
字段指定,這兩個字段才是對"表現層"的描述。
狀態轉化(State Transfer)
眾所周知互聯網通訊協議 HTTP 協議是無狀態的,所有狀態都保存在服務器上。因此如果客戶端要想操作(獲取、刪除、更新操作數據),就必須讓服務器狀態發生改變(State Transfer)而這種狀態轉化建立在表現層上,所有稱為 表現層狀態轉化。
客戶端通過 HTTP 協議可以與服務端進行通信,HTTP 協議中表示操作的四個動作是:
- GET:獲取資源
- POST:新建資源
- PUT:更新資源
- DELETE:刪除資源
客戶端用到的手段,只能是HTTP協議。具體來說,就是HTTP協議里面,四個表示操作方式的動詞:GET、POST、PUT、DELETE。它們分別對應四種基本操作:GET用來獲取資源,POST用來新建資源(也可以用于更新資源),PUT用來更新資源,DELETE用來刪除資源。
RESTful架構典型的設計誤區
1、URI 中包含動詞:資源是一種實體,應該用名詞,動詞應放在 HTTP 協議中。
# 錯誤,show 為動詞
/posts/show/1# 如果要獲取可用 get
/posts/1
/get/ # 表示 show
若某些動作用 HTTP 動詞表示不了,可用將動作看做一種資源,如:網上付款 URI 設計:
# 錯誤
POST /accounts/1/transfer/500/to/2# 正確
POST /transaction HTTP/1.1
Host: 127.0.0.1from=1&to=2&amount=500.00
2、在 URI 中加入版本號
# 錯誤
http://www.example.com/app/1.0/foo
http://www.example.com/app/1.1/foo
不同的版本,可以理解成同一種資源的不同表現形式,所以應該采用同一個U RI。版本號可以在HTTP請求頭信息的Accept字段中進行區分(參見 Versioning REST Services):
# 正確
Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.1
然而實際開發中,大多開發人員會將版本號放在 URI 中,因為方便。
總結
什么是 RESTful 架構:
- RESTful 是一種在網絡上能夠開發軟件的體系架構
- 互聯網之間通訊是資源的一種交互
- 每個 URI 代表一種資源,通過特定 URI 可以訪問相應資源
- 資源有不同的表現形式
- HTTP 協議無狀態
- 客戶端獲取服務器資源,需要改變其狀態
- 客戶端通過四個HTTP動詞,對服務器端資源進行操作,實現"表現層狀態轉化"
2. RESTful API 設計指南
2.1 協議
API與用戶的通信協議,總是使用HTTPs協議。
2.2 域名
1、應該盡量將 API 部署在專用域名之下,(這中情況會存在跨域問題)
https://api.example.com
2、如果確定 API 很簡單,不會有進一步擴展,可以考慮放在主域名下。
https://example.org/api/
2.3 版本(Versioning)
應該將 API 的版本號放入 URL。
https://api.example.com/v1/
另一種做法是,將版本號放在HTTP頭信息中,但不如放入URL方便和直觀。Github采用這種做法。
2.4 路徑(Endpoint)
路徑又稱"終點"(endpoint),表示 API 的具體網址。
在 RESTful 架構中,每個網址代表一種資源(resource),所以網址中不能有動詞,只能有名詞,而且所用的名詞往往與數據庫的表格名對應。一般來說,數據庫中的表都是同種記錄的"集合"(collection),所以 API 中的名詞也應該使用復數。
舉例來說,有一個API提供動物園(zoo)的信息,還包括各種動物和雇員的信息,則它的路徑應該設計成下面這樣。
- https://api.example.com/v1/zoos
- https://api.example.com/v1/animals
- https://api.example.com/v1/employees
2.5 HTTP動詞
對于資源的具體操作類型,由 HTTP 動詞表示。常用的 HTTP 動詞有下面五個(括號里是對應的SQL命令):
- GET(SELECT):從服務器取出資源(一項或多項)。
- POST(CREATE):在服務器新建一個資源。
- PUT(UPDATE):在服務器更新資源(客戶端提供改變后的完整資源)。
- PATCH(UPDATE):在服務器更新資源(客戶端提供改變的屬性)。
- DELETE(DELETE):從服務器刪除資源。
不常用的 HTTP 動詞:
- HEAD:獲取資源的元數據。
- OPTIONS:獲取信息,關于資源的哪些屬性是客戶端可以改變的。
下面是一些例子。
- GET /zoos:列出所有動物園
- POST /zoos:新建一個動物園
- GET /zoos/ID:獲取某個指定動物園的信息
- PUT /zoos/ID:更新某個指定動物園的信息(提供該動物園的全部信息)
- PATCH /zoos/ID:更新某個指定動物園的信息(提供該動物園的部分信息)
- DELETE /zoos/ID:刪除某個動物園
- GET /zoos/ID/animals:列出某個指定動物園的所有動物
- DELETE /zoos/ID/animals/ID:刪除某個指定動物園的指定動物
2.6 過濾信息(Filtering)
如果記錄數量很多,服務器不可能都將它們返回給用戶。API 應該提供參數,過濾返回結果。
下面是一些常見的參數:
# 指定返回記錄的數量
https://api.example.com/v1/zoos?limit=10# 指定返回記錄的開始位置
https://api.example.com/v1/zoos?offset=10# 指定第幾頁,以及每頁的記錄數
https://api.example.com/v1/zoos?page=2&per_page=100# 指定返回結果按照哪個屬性排序,以及排序順序
https://api.example.com/v1/zoos?sortby=name&order=asc# 指定篩選條件
https://api.example.com/v1/zoos?animal_type_id=1
參數的設計允許存在冗余,即允許API路徑和URL參數偶爾有重復。比如,GET /zoo/ID/animals
與 GET /animals?zoo_id=ID
的含義是相同的。
2.7 狀態碼(Status Codes)
服務器向用戶返回的狀態碼和提示信息,常見的有以下一些(方括號中是該狀態碼對應的HTTP動詞)。
- 200 OK - [GET]:服務器成功返回用戶請求的數據,該操作是冪等的(Idempotent)。
- 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 - [*]:服務器發生錯誤,用戶將無法判斷發出的請求是否成功。
狀態碼的完全列表參見這里。
2.8 錯誤處理(Error handling)
如果狀態碼是4xx,就應該向用戶返回出錯信息。一般來說,返回的信息中將 error 作為鍵名,出錯信息作為鍵值即可。
{error: "Invalid API key"
}
2.9 返回結果
針對不同操作,服務器向用戶返回的結果應該符合以下規范。
- GET /collection:返回資源對象的列表(數組)
- GET /collection/resource:返回單個資源對象
- POST /collection:返回新生成的資源對象
- PUT /collection/resource:返回完整的資源對象
- PATCH /collection/resource:返回完整的資源對象
- DELETE /collection/resource:返回一個空文檔
2.10 Hypermedia API
RESTful API最好做到 Hypermedia
,即返回結果中提供鏈接,連向其他 API方法,使得用戶不查文檔,也知道下一步應該做什么。
比如,當用戶向 api.example.com
的根目錄發出請求,會得到這樣一個文檔。
{"link": {"rel": "collection https://www.example.com/zoos","href": "https://api.example.com/zoos","title": "List of zoos","type": "application/vnd.yourformat+json"
}}
上面代碼表示,文檔中有一個 link 屬性,用戶讀取這個屬性就知道下一步該調用什么API了。rel表示這個API與當前網址的關系(collection關系,并給出該collection的網址),href表示API的路徑,title表示API的標題,type表示返回類型。
Hypermedia API的設計被稱為HATEOAS。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"
}
上面代碼表示,服務器給出了提示信息,以及文檔的網址。
其他
(1)API 的身份認證應該使用OAuth 2.0框架。
(2)服務器返回的數據格式,應該盡量使用JSON,避免使用XML。