RESTful詳解(非原創)


文章大綱

一、什么是RESTful
二、為什么要使用RESTful
三、RESTful實戰
四、項目源碼下載
五、參考文章

 

一、什么是RESTful

1. RESTful概念

  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  消息體類型報頭 

2. REST 系統的特征

(1)客戶-服務器(Client-Server),提供服務的服務器和使用服務的客戶需要被隔離對待。
(2)無 狀態(Stateless),來自客戶的每一個請求必須包含服務器處理該請求所需的所有信息。換句話說,服務器端不能存儲來自某個客戶的某個請求中的信息,並在該客戶的其他請求中使用。
(3)可緩存(Cachable),服務器必須讓客戶知道請求是否可以被緩存。(Ross:更詳細解釋請參考 理解本真的REST架構風格 以及 StackOverflow 的這個問題 中對緩存的解釋。)
(4)分層系統(Layered System),服務器和客戶之間的通信必須被這樣標准化:允許服務器和客戶之間的中間層(Ross:代理,網關等)可以代替服務器對客戶的請求進行回應,而且這些對客戶來說不需要特別支持。
(5)統一接口(Uniform Interface),客戶和服務器之間通信的方法必須是統一化的。(Ross:GET,POST,PUT.DELETE, etc)
(6)支持按需代碼(Code-On-Demand,可選),服務器可以提供一些代碼或者腳本(Ross:Javascrpt,flash,etc)並在客戶的運行環境中執行。這條准則是這些准則中唯一不必必須滿足的一條。(Ross:比如客戶可以在客戶端下載腳本生成密碼訪問服務器。)
其中重點對(2)中內容進行說明:
GET(SELECT):從服務器取出資源(一項或多項)。
POST(CREATE):在服務器新建一個資源。
PUT(UPDATE):在服務器更新資源(客戶端提供完整資源數據)。
PATCH(UPDATE):在服務器更新資源(客戶端提供需要修改的資源數據)。
DELETE(DELETE):從服務器刪除資源。

二、為什么要使用RESTful

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的起源,只要能夠把問題解決掉,總會有人用他認為最方便的方式去解決問題,比如說汽車門把手壞掉了直接系根繩子當把手,emmmm這樣確實很棒啊。
  
常見使用錯誤三:無序

  返回的結果往往是很隨意,各種錯誤信息本來就是用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混亂的問題,你反而很快能弄懂了~

三、RESTful實戰

1. 新建Spring Boot項目

 
 
 
 

創建后項目結構如下:

 

2. pom.xml文件添加依賴

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.wxc</groupId> <artifactId>restful-test</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.0.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!--Spring Boot 項目默認的編譯版本是 1.6,如果我們想使用其他的編譯版本我們就需要在 pom.xml 文件中定義一個變量--> <java.version>1.8</java.version> </properties> <dependencies> <!-- 加入web開發的支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <!--添加模板引擎(freemarker)依賴包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> </dependencies> <build> <plugins> <!-- maven的編譯插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <!-- 沒有該配置,devtools 不生效 --> <fork>true</fork> <addResources>true</addResources> </configuration> </plugin> </plugins> </build> </project> 

3. 編寫測試類

com.wxc.test.controller包下創建UserController.java

package com.wxc.test.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @RequestMapping(value = "/user", method = RequestMethod.POST) public String addUser( String user) { System.out.println("開始新增..."); return "開始新增..."+"傳過來參數為:"+user; } @RequestMapping(value = "/user", method = RequestMethod.PUT) public boolean updateUser( String user) { System.out.println("開始更新..."); return true; } @RequestMapping(value = "/user", method = RequestMethod.DELETE) public boolean delete(@RequestParam(value = "userName", required = true) int userId) { System.out.println("開始刪除..."); return true; } @RequestMapping(value = "/user", method = RequestMethod.GET) public String findByUserName(@RequestParam(value = "userName", required = true) String userName) { System.out.println("開始查詢..."); return "開始查詢..."+"傳過來的參數為:"+userName; } @RequestMapping(value = "/userAll", method = RequestMethod.GET) public String findByUserAge() { System.out.println("開始查詢所有數據..."); return "開始查詢所有數據..."; } } 

4. 創建項目啟動類

com.wxc.test包下創建Application.java

package com.wxc.test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { //入口運行類 SpringApplication.run(Application.class, args); } } 

創建后項目結構如下:

 

5. 運行項目並訪問

 
 
 
 

可以看到,接口訪問方式必須和指定的一致,比如POST無法通過GET進行請求

四、項目源碼下載

鏈接:https://pan.baidu.com/s/1QYlIadtgxUZ7g0QyTwZlaA
提取碼:rg60

五、參考文章

    1. https://blog.csdn.net/qq_21383435/article/details/80032375


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM