看過不少RESTful相關的文章總結,參齊不齊,結合工做中的使用,很是有必要概括一下關於RESTful架構方式了,RESTful只是一種架構方式的約束,給出一種約定的標準,徹底嚴格遵照RESTful標準並非不少,也沒有必要。可是在實際運用中,有RESTful標準能夠參考,是十分有必要的。
實際上在工做中對api接口規範、命名規則、返回值、受權驗證等進行必定的約束,通常的項目api只要易測試、足夠安全、風格一致可讀性強、沒有歧義調用方便我以爲已經足夠了,接口是給開發人員看的,也不是給普通用戶去調用。html回想剛進公司的時候,很是慚愧,並沒仔細地瞭解接口的一些約束,給本身挖坑,給別人挖坑。第一次據說RESTful仍是剛工做那會,差很少也有兩年了,雖然項目中並無嚴格遵照RESTful規範,仍是要自我總結反思一下。
咱們必需要知道的RESTful服務最佳實踐前端
本章導讀: android
REST:Representational State Transfer(表象層狀態轉變),若是沒據說過REST,你必定覺得是rest這個單詞,剛開始我也是這樣認爲的,後來發現是這三個單詞的縮寫,即便知道了這三個單詞理解起來仍然很是晦澀難懂。如何理解RESTful架構,最好的辦法就是深入理解消化Representational State Transfer這三個單詞到底意味着什麼。git
是由美國計算機科學家Roy Fielding(百度百科沒有介紹,真是尷尬了)。Adobe首席科學家、Http協議的首要做者之1、Apache項目聯合創始人。github
這位大神牛逼的地方在於2000年那時候發表的論文,應該沒被普遍關注,那時候的網頁前端後端都是融合在一塊兒,接口服務這些東西即便存在這種概念,都沒地方發揮做用,好的東西經得起時間的考驗,就像唐詩宋詞幾千年都會有人讀,而什麼郭敬明的什麼小說我估計00後都不知道什麼玩意(我猜的)web
隨着近幾年的移動互聯網的爆發、先後端分離的思想,RESTful架構被普遍應用,如 json
Roy Fielding的原中英文論文地址以下,能夠收藏下載看看,論文通常都比較晦澀難懂。
架構風格與基於網絡的軟件架構設計.pdf
Architectural_Styles_and_the_Design_of_Network-based_Software_Architectures.pdf
前人栽樹,後人乘涼。關於RESTful服務最佳實踐 總結的一篇很是好的文章也是外國人寫的,原英文下載:RESTful Best Practices-v1 2.pdf
有園友翻譯總結後的中文:http://www.javashuo.com/article/p-vvvfqzex-p.html後端
REST之父Roy Fielding在論文中闡述REST架構的6大原則。api
1. C-S架構
數據的存儲在Server端,Client端只需使用就行。兩端完全分離的好處使client端代碼的可移植性變強,Server端的拓展性變強。兩端單獨開發,互不干擾。
2. 無狀態
http請求自己就是無狀態的,基於C-S架構,客戶端的每一次請求帶有充分的信息可以讓服務端識別。請求所需的一些信息都包含在URL的查詢參數、header、body,服務端可以根據請求的各類參數,無需保存客戶端的狀態,將響應正確返回給客戶端。無狀態的特徵大大提升的服務端的健壯性和可拓展性。
固然這總無狀態性的約束也是有缺點的,客戶端的每一次請求都必須帶上相同重複的信息肯定本身的身份和狀態(這也是必須的),形成傳輸數據的冗餘性,但這種肯定對於性能和使用來講,幾乎是忽略不計的。
3.統一的接口
這個纔是REST架構的核心,統一的接口對於RESTful服務很是重要。客戶端只須要關注實現接口就能夠,接口的可讀性增強,使用人員方便調用。
4.一致的數據格式
服務端返回的數據格式要麼是XML、要麼是Json(獲取數據),或者直接返回狀態碼,有興趣的能夠看看博客園的開放平臺的操做數據的api,post、put、patch都是返回的一個狀態碼
自我描述的信息
每項數據應該是能夠自我描述的,方便代碼去處理和解析其中的內容。好比經過HTTP返回的數據裏面有 [MIME type ]信息,咱們從MIME type裏面能夠知道數據的具體格式,是圖片,視頻仍是JSON
* 超媒體即應用狀態引擎(HATEOAS)*
客戶端經過body內容、查詢串參數、請求頭和URI(資源名稱)來傳送狀態。服務端經過body內容,響應碼和響應頭傳送狀態給客戶端。這項技術被稱爲超媒體(或超文本連接)。
除了上述內容外,HATEOS也意味着,必要的時候連接也可被包含在返回的body(或頭部)中,以提供URI來檢索對象自己或關聯對象。下文將對此進行更詳細的闡述。
如請求一條微博信息,服務端響應信息應該包含這條微博相關的其餘URL,客戶端能夠進一步利用這些URL發起請求獲取感興趣的信息,再如分頁能夠從第一頁的返回數據中獲取下一頁的URT也是基於這個原理
4.系統分層
客戶端一般沒法代表本身是直接仍是間接與端服務器進行鏈接,分層時一樣要考慮安全策略。
5.可緩存
在萬維網上,客戶端能夠緩存頁面的響應內容。所以響應都應隱式或顯式的定義爲可緩存的,若不可緩存則要避免客戶端在屢次請求後用舊數據或髒數據來響應。管理得當的緩存會部分地或徹底地除去客戶端和服務端之間的交互,進一步改善性能和延展性。
6.按需編碼、可定製代碼(可選)
服務端可選擇臨時給客戶端下發一些功能代碼讓客戶端來執行,從而定製和擴展客戶端的某些功能。好比服務端能夠返回一些 Javascript 代碼讓客戶端執行,去實現某些特定的功能。
提示:REST架構中的設計準則中,只有按需編碼爲可選項。若是某個服務違反了其餘任意一項準則,嚴格意思上不能稱之爲RESTful風格。緩存
1. 版本(Versioning)
如github開放平臺 https://developer.github.com/v3/ ,就是講版本放在url,簡潔明瞭,這個只有用了才知道,通常的項目加版本v1,v2,v3?好吧,這個加版本估計只有大公司大項目纔會去使用,說出來不怕尷尬,我真沒用過。有的會將版本號放在header裏面,可是不如url直接了當。
https://example.com/api/v1/
2. 參數命名規範
query parameter能夠採用駝峯命名法,也能夠採用下劃線命名的方式,推薦採用下劃線命名的方式,聽說後者比前者的識別度要高,多是用的人多了吧,因人而異,因團隊規範而異吧。
https://example.com/api/users/today_login 獲取今天登錄的用戶 https://example.com/api/users/today_login&sort=login_desc 獲取今天登錄的用戶、登錄時間降序排列
3. url命名規範
API 命名應該採用約定俗成的方式,保持簡潔明瞭, 在RESTful架構中,每一個url表明一種資源因此url中不能有動詞,只能有名詞,而且名詞中也應該使用複數。實現者應使用相應的Http動詞GET、POST、PUT、PATCH、DELETE、HEAD來操做這些資源便可
不規範的的url,冗餘沒有意義,形式不固定,不一樣的開發者還須要瞭解文檔才能調用
https://example.com/api/getallUsers GET 獲取全部用戶 https://example.com/api/getuser/1 GET 獲取標識爲1用戶信息 https://example.com/api/user/delete/1 GET/POST 刪除標識爲1用戶信息 https://example.com/api/updateUser/1 POST 更新標識爲1用戶信息 https://example.com/api/User/add POST 添加新的用戶
規範後的RESTful風格的url,形式固定,可讀性強,根據users名詞和http動詞就能夠操做這些資源
https://example.com/api/users GET 獲取全部用戶信息 https://example.com/api/users/1 GET 獲取標識爲1用戶信息 https://example.com/api/users/1 DELETE 刪除標識爲1用戶信息 https://example.com/api/users/1 Patch 更新標識爲1用戶部分信息,包含在body中 https://example.com/api/users POST 添加新的用戶
4. 統一返回數據格式
對於合法的請求應該統一返回數據格式,這裏演示的是json
返回成功的響應json格式
{ "code": 200, "message": "success", "data": { "userName": "123456", "age": 16, "address": "beijing" } }
返回失敗的響應json格式
{ "code": 401, "message": "error message", "data": null }
下面這個ApiResult的泛型類是在項目中用到的,拓展性強,使用方便。返回值使用統一的 ApiResult 或 ApiResult
錯誤返回 使用 ApiResultV2<OfT>.Error
進行返回; 成功返回,要求使用 ApiResultV2.Ok
進行返回
public class ApiResult: ApiResult { public new static ApiResult<T> Error(string message) { return new ApiResult<T> { Code = 1, Message = message, }; } [JsonProperty("data")] public T Data { get; set; } } public class ApiResult { public static ApiResult Error(string message) { return new ApiResult { Code = 1, Message = message, }; } public static ApiResult<T> Ok<T>(T data) { return new ApiResult<T>() { Code = 0, Message = "", Data = data }; } /// <summary> /// 0 是 正常 1 是有錯誤 /// </summary> [JsonProperty("code")] public int Code { get; set; } [JsonProperty("msg")] public string Message { get; set; } [JsonIgnore] public bool IsSuccess => Code == 0; }
5. http狀態碼
在以前開發的xamarin android博客園客戶端的時候,patch、delete、post操做時body響應裏面沒有任何信息,僅僅只有http status code。HTTP狀態碼自己就有足夠的含義,根據http status code就能夠知道刪除、添加、修改等是否成功。服務段向用戶返回這些狀態碼並非一個強制性的約束。簡單點說你能夠指定這些狀態,可是不是強制的。經常使用HTTP狀態碼對照表
HTTP狀態碼也是有規律的
1**請求未成功
2**請求成功、表示成功處理了請求的狀態代碼。
3**請求被重定向、表示要完成請求,須要進一步操做。 一般,這些狀態代碼用來重定向。
4** 請求錯誤這些狀態代碼表示請求可能出錯,妨礙了服務器的處理。
5**(服務器錯誤)這些狀態代碼表示服務器在嘗試處理請求時發生內部錯誤。 這些錯誤多是服務器自己的錯誤,而不是請求出錯。
HTTP Method | 詳細解釋 | 返回狀態碼 |
---|---|---|
GET | 獲取對象或集合 | 200成功、401沒有受權、403訪問禁止、404沒有資源、參數錯誤、406請求格式不正確、410資源被刪除、500服務器內部錯誤 |
POST | 新增一條數據 | 201建立成功、422新增數據驗證錯誤、(40一、40三、40四、40六、500) |
DELETE | 刪除一條數據 | 204刪除成功、(40一、40三、40四、40六、500) |
PATCH | 更新實體部分信息 | 201更新成功、422驗證數據錯誤(40一、40三、40四、40六、500) |
PUT | 更新實體全部信息除ID外 | 201更新成功、422(40一、40三、40四、40六、500) |
6. 合理使用query parameter
在請求數據時,客戶端常常會對數據進行過濾和分頁等要求,而這些參數推薦採用HTTP Query Parameter的方式實現
好比設計一個最近登錄的全部用戶
https://example.com/api/users?recently_login_day=3
搜索用戶,並按照註冊時間降序
https://example.com/api/users?recently_login_day=3
搜索用戶,並按照註冊時間升序、活躍度降序
https://example.com/api/users?q=key&sort=create_title_asc,liveness_desc
關於分頁,看看博客園開放平臺分頁獲取精華區博文列表 https://api.cnblogs.com/api/blogposts/@picked?pageIndex={pageIndex}&pageSize={pageSize} 返回示例: [ { 「Id」: 1, 「Title」: 「sample string 2」, 「Url」: 「sample string 3」, 「Description」: 「sample string 4」, 「Author」: 「sample string 5」, 「BlogApp」: 「sample string 6」, 「Avatar」: 「sample string 7」, 「PostDate」: 「2017-06-25T20:13:38.892135+08:00」, 「ViewCount」: 9, 「CommentCount」: 10, 「DiggCount」: 11 }, { 「Id」: 1, 「Title」: 「sample string 2」, 「Url」: 「sample string 3」, 「Description」: 「sample string 4」, 「Author」: 「sample string 5」, 「BlogApp」: 「sample string 6」, 「Avatar」: 「sample string 7」, 「PostDate」: 「2017-06-25T20:13:38.892135+08:00」, 「ViewCount」: 9, 「CommentCount」: 10, 「DiggCount」: 11 } ]
7. 多表、多參數鏈接查詢如何設計URL
這是一個比較頭痛的問題,在作單個實體的查詢比較容易和規範操做,可是在實際的API並非這麼簡單而已,這其中經常會設計到多表鏈接、多條件篩選、排序等。
好比我想查詢一個獲取在6月份的訂單中大於500元的且用戶地址是北京,用戶年齡在22歲到40歲、購買金額降序排列的訂單列表
https://example.com/api/orders?order_month=6&order_amount_greater=500&address_city=北京&sort=order_amount_desc&age_min=22&age_max=40
從這個URL上看,參數衆多、調用起來還得一個一個仔細對着,並且API自己很是不容易維護,命名看起來不是很容易,不能太長,也不能太隨意。
在.net WebAPI總咱們可使用屬性路由,屬性路由就是講路由附加到特定的控制器或操做方法上裝飾Controll及其使用[Route]屬性定義路由的方法稱爲屬性路由。
這種好處就是能夠精準地控制URL,而不是基於約定的路由,簡直就是爲這種多表查詢量身定製似的的。
從webapi 2開發,如今是RESTful API開發中最推薦的路由類型
咱們能夠在Controll中標記Route
[Route(「api/orders/{address}/{month}」)]
Action中的查詢參數就只有金額、排序、年齡。減小了查詢參數、API的可讀性和可維護行加強了。
https://example.com/api/orders/beijing/6?order_amount_greater=500&sort=order_amount_desc&age_min=22&age_max=40
這種屬性路由好比在博客園開放的API也有這方面的應用,如獲取我的博客隨筆列表
請求方式:GET 請求地址:https://api.cnblogs.com/api/blogs/{blogApp}/posts?pageIndex={pageIndex} blogApp:博客名
7.API請求受權
博客園的開放平臺API也是基於OAuth2.0,參見理解OAuth2.0,留存分析