REST,以及RESTful的講解
1.傳統下的API介面
http是目前在網際網路上使用最多的協議,沒有之一。
可是http的創始人一直都覺得,在過去10幾年來,所有的人都在錯誤的使用Http.這句話怎麼說呢?
如果說你要刪除一個數據,以往的做法通常是 delete/{id}
如果你要更新一個數據,可能是Post資料放Body,然後方法是 update/{id}
, 或者是artichle/{id}?method=update
這種做法讓Roy Fielding很暴燥,他覺得這個世界不該這樣的,所有的人都在誤解而且在嚴重錯誤的誤解Http的設計初衷,好比是發明了火藥卻只用它來做煙花爆竹
。
那麼正確的使用方式是什麼呢?如果你要看Rest各種特性,你恐怕真的很難理解Rest,但是如果你看錯誤的使用http的人倒底兒了哪些錯,什麼是Rest就特別容易理解了
七宗罪的第一條,混亂。
一萬個人心裡有一萬個Url的命名規則,Url是統一資源定位符,重點是資源。而很多人卻把它當成了萬金油,每一個獨立的虛擬的網頁都可以隨意使用,各種操作都能夠迭加。這是混亂的來源之一。
比如:
https://localhost:8080/myweb/getUserById?id=1
https://localhost:8080/myweb/user/getById?id=1
https://localhost:8080/myweb/x/y?id=1
第二條,貪婪。
有狀態
和無狀態
全部混在一起
。特別是在購物車或者是登入的應用中,經常重新整理就丟失帶來的使用者體驗簡直棒棒噠。每一個請求並不能單獨的響應一些功能,很多的功能混雜在一起裡。這是人性貪婪的本質,也是各種Hack
第三條,無序。
返回的結果往往是很隨意,各種錯誤資訊本來就是用Http的狀態碼構成的,可是很多人還是喜歡把錯誤資訊返回在返回值中。最常見的就是Code和Message,當然對於這一點,我個人是保留疑問的,我的觀點是,Http本身的錯誤和伺服器的內部錯誤還是需要在不斷層面分開的,不能混在一起。可是在大神眼裡並非如此。
那麼怎麼解決這些問題呢?
強迫症患者的福音就是先頒規則
,第一個規則就是明確Url是什麼,該怎麼用
。就是所有的Url本質
是一種資源
。一個獨立的Url地址,就是對應一個獨一無二的資源。怎麼樣?這種感覺是不是棒棒噠?一個冰淇淋,一個老師,一間房子,在Url上對應的都是一個資源,不會有多餘的Url跟他對應,也不會表示有多個Url地址 注意,這裡點的是Url地址,並不是單獨的引數,他就是一個
/room/{room_id}
這樣的東西,舉個栗子,/room/3242 這就表示3242號房間
。這是一個清爽的世界啊,你想想,之前的Url是什麼都要,我開房,可能是/open/room/3242
我要退房可能是/exit/3242/room
,我要打理房間,可能是room/3242?method=clean.
夠了!這些亂七八糟的東西全夠了,讓世界迴歸清爽的本質,一間房,就是/room/3242 沒有別的Url地址了。
在過去的混亂世界裡,經常用的就是
Get和Post
。如果不是因為Get不支援大資料傳輸,我想連Post都不會有人使用。(想像一下Roy Fielding在憤怒的對著電腦螢幕喊,Http的Method一共有八個,你們為毛只逮著Get一隻羊的毛薅薅薅薅薅)。
而對資源最常見的操作是什麼?CRUD
,對不對,就是建立,讀,更新,刪除。再看Http的Method?是不是非常完美?其實也怪Fielding老爺子一開始命名不準確,如果剛開始就是把Get方法叫做Read
,Put方法叫做Update
,Post叫做Create這該多好
。。。
你用一個Get,大家又發現沒什麼限制沒什麼所謂,又很難理解Put和Post的差別,法無禁止即可為啊,呃,老爺子不要瞪我,我瞎說的。總之,這四種方法夠不夠你浪?你有本身找出來更多的對資源的操作來啊,我還有4個Method沒用過呢。如果這4個真的不夠了,有什麼問題,大不了我再重新更改http協議啊。其實簡單說,對於Rest理解到這裡就夠了。後續的東西,都是在這一條基礎上空想出來的,比強迫症更強迫症,當然,無狀態我是百分百支援的。以上的各種表述可能不太準確,也純屬是我的意淫和各種小道資料,並未考據,但是憑良心講,我是早就看不慣黑暗年代裡的Url命名風格了,所以當時最早接觸到Rest的時候,瞬間就找到了真愛,我靠,這不就是我一直想要的答案嗎?但是我一直想的僅僅是命名規範,從來沒有把自己的思考角度放在一個url就是一個資源,所有的操作都是對資源的更改而言的角度上啊。
所以你能理解到的程度,更多的就是在於你要弄清楚你要解決的什麼問題,如果你的問題只是理解Rest,恐怕你很理解,如果你的問題是怎麼解決Url混亂的問題,你反而很快能弄懂了~
對比
https://localhost:8080/myweb/getDogs --> GET /rest/api/dogs 獲取所有小狗狗
https://localhost:8080/myweb/addDogs --> POST /rest/api/dogs 新增一個小狗狗
https://localhost:8080/myweb/updateDogs/:dog_id --> PUT /rest/api/dogs/:dog_id 修改一個小狗狗
https://localhost:8080/myweb/deleteDogs/:dog_id --> DELETE /rest/api/dogs/:dog_id 刪除一個小狗狗
操作成功 或者 1
或者
操作失敗 或者 0
這還要我們自己去解析,還要前端和後端去協商你返回的0是啥意識啊。但是REST返回值是標準的,比如
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: xxx
{
"url" : "/api/categories/1",
"label" : "Food",
"items_url" : "/api/items?category=1",
"brands" : [
{
"label" : "友臣",
"brand_key" : "32073",
"url" : "/api/brands/32073"
}, {
"label" : "樂事",
"brand_key" : "56632",
"url" : "/api/brands/56632"
}
...
]
}
格式固定,第一行永遠是操作失敗或者成功的狀態碼,第二行是返回的型別,第三行內容的長度,第五行開始是內容。
這樣我只需寫一個程式解析返回的資訊就可以了,可以重用,但是我們上面傳統的不僅僅要協商,還有有不同的解析程式,稍微改變,就不能正常使用了。所以rest的明顯更加通用。
列子2
1、獲取文章
請求:
GET /blog/post/{postId} HTTP/1.1
響應:
HTTP/1.1 200 OK
{
"title": "foobar",
"content": "foobar",
"comments": ["", "", ""]
}
2、釋出文章
請求:
POST /blog/post HTTP/1.1
{
"title": "foobar",
"content": "foobar",
"comments": ["", "", ""]
}
響應:
HTTP/1.1 201 CREATED
規則
GET 用來獲取資源,
POST 用來新建資源(也可以用於更新資源),
PUT 用來更新資源,
DELETE 用來刪除資源
例子
DELETE http://api.qc.com/v1/friends: 刪除某人的好友 (在http parameter指定好友id)
POST http://api.qc.com/v1/friends: 新增好友UPDATE
http://api.qc.com/v1/profile: 更新個人資料
概念
REST 是面向資源的,這個概念非常重要,而資源是通過 URI 進行暴露。
URI 的設計只要負責把資源通過合理方式暴露出來就可以了。對資源的操作與它無關,操作是通過 HTTP動詞來體現,所以REST 通過 URI 暴露資源時,會強調不要在 URI 中出現動詞。
比如:左邊是錯誤的設計,而右邊是正確的
GET /rest/api/getDogs --> GET /rest/api/dogs 獲取所有小狗狗
GET /rest/api/addDogs --> POST /rest/api/dogs 新增一個小狗狗
GET /rest/api/editDogs/:dog_id --> PUT /rest/api/dogs/:dog_id 修改一個小狗狗
GET /rest/api/deleteDogs/:dog_id --> DELETE /rest/api/dogs/:dog_id 刪除一個小狗狗
REST很好地利用了HTTP本身就有的一些特徵,如HTTP動詞、HTTP狀態碼、HTTP報頭等等
REST API 是基於 HTTP的,所以你的API應該去使用 HTTP的一些標準。這樣所有的HTTP客戶端(如瀏覽器)才能夠直接理解你的API(當然還有其他好處,如利於快取等等)。REST 實際上也非常強調應該利用好 HTTP本來就有的特徵,而不是隻把 HTTP當成一個傳輸層這麼簡單了。
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: xxx
{
"url" : "/api/categories/1",
"label" : "Food",
"items_url" : "/api/items?category=1",
"brands" : [
{
"label" : "友臣",
"brand_key" : "32073",
"url" : "/api/brands/32073"
}, {
"label" : "樂事",
"brand_key" : "56632",
"url" : "/api/brands/56632"
}
...
]
}
看這個響應,包含了http裡面的狀態碼等資訊。還會有http的一些報頭。
Authorization 認證報頭
Cache-Control 快取報頭
Cnotent-Type 訊息體型別報頭
......
REST 系統的特徵
客戶-伺服器(Client-Server)
,提供服務的伺服器和使用服務的客戶需要被隔離對待。無狀態(Stateless)
,來自客戶的每一個請求必須包含伺服器處理該請求所需的所有資訊
。換句話說,伺服器端不能儲存來自某個客戶的某個請求中的資訊,並在該客戶的其他請求中使用。可快取(Cachable)
,伺服器必須讓客戶知道請求是否可以被快取。(Ross:更詳細解釋請參考 理解本真的REST架構風格 以及 StackOverflow 的這個問題 中對快取的解釋。)分層系統(Layered System)
,伺服器和客戶之間的通訊必須被這樣標準化:允許伺服器和客戶之間的中間層(Ross:代理,閘道器等)可以代替伺服器對客戶的請求進行迴應,而且這些對客戶來說不需要特別支援。統一介面(Uniform Interface)
,客戶和伺服器之間通訊的方法必須是統一化的。(Ross:GET,POST,PUT.DELETE, etc)支援按需程式碼(Code-On-Demand,可選)
,伺服器可以提供一些程式碼或者指令碼(Ross:Javascrpt,flash,etc)並在客戶的執行環境中執行。這條準則是這些準則中唯一不必必須滿足的一條。(Ross:比如客戶可以在客戶端下載指令碼生成密碼訪問伺服器。)
詳細解釋
無狀態(Stateless)
所謂無狀態的,即所有的資源,都可以通過URI定位,而且這個定位與其他資源無關,也不會因為其他資源的變化而改變。有狀態和無狀態的區別,舉個簡單的例子說明一下。如查詢員工的工資,如果查詢工資是需要登入系統,進入查詢工資的頁面,執行相關操作後,獲取工資的多少,則這種情況是有狀態的,因為查詢工資的每一步操作都依賴於前一步操作,只要前置操作不成功,後續操作就無法執行;
如果輸入一個url即可得到指定員工的工資,則這種情況是無狀態的,因為獲取工資不依賴於其他資源或狀態,且這種情況下,員工工資是一個資源,由一個url與之對應,可以通過HTTP中的GET方法得到資源,這是典型的RESTful風格。
統一介面(Uniform Interface)
RESTful架構風格規定,資料的元操作,即CRUD(create, read, update和delete,即資料的增刪查改)操作,分別對應於HTTP方法:GET用來獲取資源,POST用來新建資源(也可以用於更新資源),PUT用來更新資源,DELETE用來刪除資源,這樣就統一了資料操作的介面,僅通過HTTP方法,就可以完成對資料的所有增刪查改工作。
即:
GET(SELECT):從伺服器取出資源(一項或多項)。
POST(CREATE):在伺服器新建一個資源。
PUT(UPDATE):在伺服器更新資源(客戶端提供完整資源資料)。
PATCH(UPDATE):在伺服器更新資源(客戶端提供需要修改的資源資料)。
DELETE(DELETE):從伺服器刪除資源。
演化
優點&缺點
優點
是因為他對uri進行了限制,只用於定義資源。這樣看起來比較容易理解。尤其是對簡單的物件的增刪改查,很好理解。
缺點
是因為這種限制,導致設計uri變得複雜了。尤其是複雜的關係,操作,資源集合,硬性套用rest原則設計非常困難
。在rest基礎上的HATEOAS,返回的json裡增加了相應的關係和url。這也同樣帶來問題。好處是對簡單的關係,的確可以通過url進一步處理。但對複雜的關係和操作,HATEOAS並不能勝任描述。反而在單純的資料中增加了一堆垃圾資訊。
是什麼?
REST是一個標準,一種規範
,遵循REST風格可以使開發的介面通用,便於呼叫者理解介面的作用。