技術熱點:RESTful API 最佳實踐
在參考了GitHub API設計和大量博客文章后總結了一下RESTful API的設計,分享如下。想要更好的理解RESTful API首先需要理解如下概念:
REST:REST(Representational State Transfer)這個詞,是Roy Thomas Fielding在他2000年的博士論文中提出的,翻譯成中文大意為表現層狀態傳輸。由于他是HTTP協議(1.0版和1.1版)的主要設計者、Apache服務器軟件的作者之一、Apache基金會的***任主席,所以REST原則迅速流行起來。當一個軟件架構符合REST原則,我們稱之為RESTful架構。說了這么多,我們為什么要使用RESTful架構?使用RESTful架構有什么好處?因為按照RESTful架構可以充分的利用HTTP協議帶給我們的各種功能,算是對HTTP協議使用的***實踐,還有一點就是可以使軟件架構設計更加清晰,可維護性更好,但是并不是所有情況都需要完全遵守REST原則,畢竟實際情況遠遠比REST原則所定義的更加復雜,下面會詳細介紹。
冪等性:冪等性(Idempotence)本身是一個數學概念,在HTTP/1.1規范中冪等性的定義是:
Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.
翻譯過來大意就是如果方法調用一次和多次產生額外的效果是相同的,它就具有冪等性。
例子:在HTTP中使用GET方法通常用于從服務器獲取資源,無論調用多少次產生的額外效果都是從服務器獲取資源,所以GET具有冪等性;而POST方法通常用于提交數據在服務器上創建一個資源,由于最終創建的結果每次都是不同的,所以POST不具有冪等性;但是PUT方法卻是冪等的,因為每次調用產生的效果都是對資源進行更新。
安全方法:安全方法是指不修改資源的 HTTP 方法。譬如,當使用 GET 或者 HEAD 作為資源 URL,都必須不去改變資源。然而,這并不全準確。意思是:它不改變資源的 表示形式。對于安全方法,它仍然可能改變服務器上的內容或資源,但這必須不導致不同的表現形式。
有關HTTP常用方法冪等性和安全性如下:
RESTful API設計規則:
1. URI
- 應該將API部署在專用域名之下:https://api.example.com
- 不用大寫
- 用中杠-不用下杠_;
- 參數列表要encode;
- URI中不應該出現動詞,動詞應該使用HTTP方法表示,但是如果無法表示,也可使用動詞,例如:search沒有對應的HTTP方法,可以在路徑中使用search,更加直觀;
- URI中的名詞表示資源集合,使用復數形式;
- 雖然/在URI中表達層級,但是避免為了追求REST導致層級過深,適當使用參數表示。
GET /comments/tid/tid=1&page=1
2. Request:通過標準HTTP方法對資源CRUD
- GET:查詢資源
GET /comments //獲取所有評論 GET /comments/tid/1 //獲取文章tid為1的所有評論
- POST:創建資源
POST /comments/tid/1 //為tid為1的文章創建評論
- PUT:更新資源
PUT /comments/cid/like/1 //為cid為1的評論點贊
- DELETE:刪除資源
DELETE /comments/cid/1 //刪除cid為1的評論
3. Response
- 采用JSON,不要使用XML
- 默認情況下JSON外層不需要嵌套大括號,API需要支持JSONP跨域訪問或者客戶端無法訪問HTTP Header才需要加上嵌套大括號
- 默認情況下不要過濾API輸出中的空格,并且要支持gzip
4. API版本控制
- 在URI中存放:GET /v1/comments;
- 客戶端在Accept Header中存放:Accept: application/vnd.github.v3+json,服務器自定義Header返回當前版本信息:X-GitHub-Media-Type: github.v3; format=json(GitHub在用);
- 以上兩種方法根據情況選擇,Github用的方式是REST中所要求的方式;
- 測試API和正式API要進行區分,方式通過如上兩種方式實現。
5. 速度限制
為了避免請求泛濫,給API設置速度限制很重要。為此 RFC 6585 引入了HTTP狀態碼429(too many requests)。加入速度設置之后,應該提示用戶,至于如何提示標準上沒有說明,不過流行的方法是使用HTTP的返回頭。
下面是幾個必須的返回頭(依照twitter的命名規則):
- X-Rate-Limit-Limit :當前時間段允許的并發請求數
- X-Rate-Limit-Remaining:當前時間段保留的請求數。
- X-Rate-Limit-Reset:當前時間段剩余秒數
為什么使用當前時間段剩余秒數而不是時間戳?
時間戳保存的信息很多,但是也包含了很多不必要的信息,用戶只需要知道還剩幾秒就可以再發請求了這樣也避免了clock skew問題。
6.緩存
HTTP提供了自帶的緩存框架。你需要做的是在返回的時候加入一些返回頭信息,在接受輸入的時候加入輸入驗證。基本兩種方法:
- ETag:當生成請求的時候,在HTTP頭里面加入ETag,其中包含請求的校驗和和哈希值,這個值和在輸入變化的時候也應該變化。如果輸入的HTTP請求包含IF-NONE-MATCH頭以及一個ETag值,那么API應該返回304 not modified狀態碼,而不是常規的輸出結果。
- Last-Modified:和etag一樣,只是多了一個時間戳。返回頭里的Last-Modified:包含了 RFC 1123 時間戳,它和IF-MODIFIED-SINCE一致。HTTP規范里面有三種date格式,服務器應該都能處理。
7.覆蓋HTTP方法
一些HTTP客戶端只支持GET和POST請求。為了能夠加強這些客戶端的訪問能力,API需要能夠覆蓋HTTP方法。盡管這里沒有任何強制的標準,但流行的做法是API會接收一個請求頭X-HTTP-Method-Override,它的值可以是PUT、PATCH或者DELETE三者之一。
注意,用來覆蓋HTTP方法的header只能在POST請求中被接受。GET請求永遠不能修改服務器上的數據。
8.過濾信息
如果記錄數量很多,服務器不可能都將它們返回給用戶。API應該提供參數,過濾返回結果。
下面是一些常見的參數:
?limit=10:指定返回記錄的數量
?offset=10:指定返回記錄的開始位置。
?page=2&per_page=100:指定第幾頁,以及每頁的記錄數。
?sortby=name&order=asc:指定返回結果按照哪個屬性排序,以及排序順序。
?animal_type_id=1:指定篩選條件
就像HTML的出錯頁面向訪問者展示了有用的錯誤消息一樣,API也應該用之前熟悉易讀的格式來提供有用的錯誤消息。錯誤的表現形式應該跟其他資源保持一致,只是用一些自己的字段。
API應該一直返回合理的HTTP狀態碼。API錯誤一般情況下分成兩類:代表客戶端錯誤的400系列狀態碼和代表服務端錯誤的500系列狀態碼。API至少把所有400系列錯誤統一用易讀的JSON格式來展示。如果可能(比如,如果負載均衡和反向代理能夠創建自定義錯誤內容的話),500系列的狀態碼也這么弄。
JSON錯誤內容應該為開發者提供一些東西 – 有用的錯誤消息,唯一的錯誤碼(通過它可以在文檔中找到更多錯誤細節),可能的話提供錯誤細節描述。用JSON格式來輸出錯誤看起來這樣:
{
"code" : 1234,
"message" : "Something bad happened : (",
"description" : "More details about the error here"
}
對于PUT、PATCH和POST的請求進行的校驗錯誤需要嵌套多個字段。***做法是用固定的錯誤碼來表示校驗失敗,然后在額外的errors字段中提供錯誤的細節,像這樣:
{
"code" : 1024,
"message" : "Validation Failed",
"errors" : [
{
"code" : 5432,
"field" : "first_name",
"message" : "First name cannot have fancy characters"
},
{
"code" : 5622,
"field" : "password",
"message" : "Password cannot be blank"
}
]
}
10.HTTP狀態碼
HTTP定義了很多有意義的狀態碼,你可以在你的API中使用。這些狀態碼可以幫助API消費者用來路由它們獲取到的響應內容。整理了一個你肯定會用到的狀態碼列表:
- 200 OK – 對成功的GET、PUT、PATCH或DELETE操作進行響應。也可以被用在不創建新資源的POST操作上
- 201 Created – 對創建新資源的POST操作進行響應。應該帶著指向新資源地址的Location header)
- 204 No Content – 對不會返回響應體的成功請求進行響應(比如DELETE請求)
- 304 Not Modified – HTTP緩存header生效的時候用
- 400 Bad Request – 請求異常,比如請求中的body無法解析
- 401 Unauthorized – 沒有進行認證或者認證非法。當API通過瀏覽器訪問的時候,可以用來彈出一個認證對話框
- 403 Forbidden – 當認證成功,但是認證過的用戶沒有訪問資源的權限
- 404 Not Found – 當一個不存在的資源被請求
- 405 Method Not Allowed – 所請求的HTTP方法不允許當前認證用戶訪問
- 410 Gone – 表示當前請求的資源不再可用。當調用老版本API的時候很有用
- 415 Unsupported Media Type – 如果請求中的內容類型是錯誤的
- 422 Unprocessable Entity – 用來表示校驗錯誤
- 429 Too Many Requests – 由于請求頻次達到上限而被拒絕訪問
11.認證
RESTful API應該是無狀態。這意味著對請求的認證不應該基于cookie或者session。相反,每個請求應該帶有一些認證憑證。
如果一直使用SSL,認證憑證可以簡單的使用隨機生成的access token,把其做為HTTP Basic Auth中user name字段的值傳給API。這么做的好處是可以通過瀏覽器訪問 – 如果瀏覽器從服務器收到401 Unauthorized狀態碼,它將會彈出一個對話框讓人輸出認證憑證。
當然,這種基于token來進行基本認證的方法只能當用戶從API管理后臺拷貝了一個token到自己的代碼中才行。如果搞不到token,只能使用OAuth 2來把安全token傳遞給第三方。OAuth 2使用Bearer token,并且也是基于SSL來保證傳輸安全。
支持JSONP的API可能需要第三種方法來實現認證,因為JSONP的請求沒法發送HTTP Basic Auth憑證或者Bearer token。這種情況下,可以使用一個額外的查詢參數access_token。注意:使用查詢參數來傳遞token存在一個固有的安全隱患,因為大多數web服務器會在服務器日志中保存查詢參數。
不管怎么樣,以上三種方法是用來在API之間傳輸token的方法。實際傳輸的token可以是一樣的。
12.使用SSL
一定要使用SSL。沒有例外。如今,你的web API可以從任何有互聯網的地方(像圖書館,咖啡館,機場等等)被訪問到。這些地方并不都是安全的。很多地方根本沒有對網絡連接進行加密,如果認證憑證被劫持的話,這樣訪問者很容易被竊聽或者被冒充。
一直使用SSL的另一個優勢是,加密的連接簡化了用戶認證的工作 – 你可以使用簡單的access token,而不需要對每個API請求進行簽名。
需要注意的一件事是以非SSL的形式訪問API的URL。不要把請求跳轉到它們的SSL版本上。直接拋出一個嚴重錯誤!
13.Hypermedia API
RESTful API***做到Hypermedia,即返回結果中提供鏈接,連向其他API方法,使得用戶不查文檔,也知道下一步應該做什么。
比如,當用戶向http://api.example.com的根目錄發出請求,會得到這樣一個文檔。
{"link": {
"rel": "collection https://www.example.com/comments",
"href": "https://api.example.com/comments",
"title": "List of comments",
"type": "application/vnd.yourformat+json"
}}
上面代碼表示,文檔中有一個link屬性,用戶讀取這個屬性就知道下一步該調用什么API了。rel表示這個API與當前網址的關系(collection關系,并給出該collection的網址),href表示API的路徑,title表示API的標題,type表示返回類型。
Hypermedia API的設計被稱為HATEOAS。
在進行分頁查詢時可以返回下一頁的URI,如果沒有說明服務器已經取到***一條數據了,客戶端可以減少不必要的請求以及URI的構造,建議在分頁的情況下使用。
這就是我總結出來的RESTful API的***設計實踐,文章如果有紕漏,歡迎指出。