1. 理解 REST
REST 全稱是 Representational State Transfer,中文意思是表徵性狀態轉移。它首次出現在2000年Roy Fielding的博士論文中,Roy Fielding是HTTP規範的主要編寫者之一。值得注意的是REST並沒有一個明確的標準,而更像是一種設計的風格。如果一個架構符合REST的約束條件和原則,我們就稱它為RESTful架構。
理論上REST架構風格並不是繫結在HTTP上,只不過目前HTTP是唯一與REST相關的例項。
1.1. REST 原則
- 資源 可通過目錄結構樣式的 URIs 暴露
- 表述 可以通過 JSON 或 XML 表達的資料物件或屬性來傳遞
- 訊息 使用統一的 HTTP 方法(例如:GET、POST、PUT 和 DELETE)
無狀態 客戶端與服務端之間的互動在請求之間是無狀態的,從客戶端到服務端的每個請求都必須包含理解請求所必需的資訊
1.2. HTTP 方法
使用 HTTP 將 CRUD(create, retrieve, update, delete <建立、獲取、更新、刪除—增刪改查>)操作對映為 HTTP 請求。如果按照HTTP方法的語義來暴露資源,那麼介面將會擁有安全性和冪等性的特性,例如GET和HEAD請求都是安全的, 無論請求多少次,都不會改變伺服器狀態。而GET、HEAD、PUT和DELETE請求都是冪等的,無論對資源操作多少次, 結果總是一樣的,後面的請求並不會產生比第一次更多的影響。
1.2.1. GET
- 安全且冪等
獲取資訊
1.2.2. POST
- 不安全且不冪等
使用請求中提供的實體執行操作,可用於建立資源或更新資源
1.2.3. PUT
- 不安全但冪等
使用請求中提供的實體執行操作,可用於建立資源或更新資源
1.2.4. DELETE
- 不安全但冪等
刪除資源
POST和PUT在建立資源的區別在於,所建立的資源的名稱(URI)是否由客戶端決定。 例如為我的博文增加一個java的分類,生成的路徑就是分類名/categories/java,那麼就可以採用PUT方法。不過很多人直接把POST、GET、PUT、DELETE直接對應上CRUD,例如在一個典型的rails實現的RESTful應用中就是這麼做的。1.3. HTTP status codes
狀態碼指示 HTTP 請求的結果:
- 1XX:資訊
- 2XX:成功
- 3XX:轉發
- 4XX:客戶端錯誤
5XX:服務端錯誤
1.4. 媒體型別
HTTP頭中的 Accept 和 Content-Type 可用於描述HTTP請求中傳送或請求的內容。如果客戶端請求JSON響應,那麼可以將 Accept 設為 application/json。相應地,如果傳送的內容是XML,那麼可以設定 Content-Type 為 application/xml 。
2. REST API 設計最佳實踐
這裡介紹一些設計 REST API 的最佳實踐,大家先記住下面這句話:
URL 是個句子,其中資源是名詞、HTTP 方法是動詞。
2.1. 使用名詞來表示資源
下面是一些例子:
- GET - /users:返回使用者列表
- GET - /users/100:返回一個特定使用者
- POST - /users:建立一個新使用者
- PUT - /users/200:更新一個特定使用者
- DELETE - /users/711:刪除一個特定使用者
不要使用動詞: /getAllsers/getUserById/createNewUser/updateUser/deleteUser2.2 在 HTTP 頭中使用適當的序列化格式
客戶端和服務端都需要知道通訊所用的格式,這個格式要在 HTTP 頭中指定:
- Content-Type 定義請求格式
Accept 定義一個可接受的響應格式列表
2.3 Get 方法和查詢引數不應當改變狀態
使用 PUT, POST 和 DELETE 方法來改變狀態,不要使用 GET 方法來改變狀態:
- GET
/users/711?activate或 GET
/users/711/activate2.4. 使用子資源表示關聯
如果一個資源與另一個資源關聯,使用子資源:
- GET /cars/711/drivers/ 返回711號汽車的駕駛員列表
GET /cars/711/drivers/4 返回711號汽車的第4號駕駛員
2.5. 使用適當的 HTTP 方法 (動詞)
再回顧一下這句話:
URL 是個句子,其中資源是名詞、HTTP 方法是動詞。
- GET:獲取在URI資源中指定的表述,響應訊息體包含所請求資源的細節。
- POST:建立一個URI指定的新資源,請求訊息體提供新資源的細節。注意,POST也可以觸發一些操作,而不一定是要建立新資源。
- PUT:建立或替代指定URI的資源。請求訊息體指定要建立或更新的資源。
DELETE:移除指定URI的資源。
2.6. HTTP 響應狀態碼
當客戶端通過API向服務端發起一個請求時,客戶端應當知道反饋:是否失敗、通過或者請求錯誤。HTTP 狀態碼是一批標準化程式碼,在不同的場景下有不同的解釋。伺服器應當總是返回正確的狀態碼。
下面是重要的HTTP程式碼分類:2xx (成功分類):這些狀態碼程式碼請求動作被接收且被伺服器成功處理。
- 200:Ok 表示 GET、PUT 或 POST 請求的標準狀態碼。
- 201:Created(已建立)表示例項已被建立,多用於 POST 方法。
- 204:No Content(無內容)表示請求已被成功處理但沒有返回任何內容。常用於 DELETE 方法返回。
3xx (轉發分類)
- 304:Not Modified(無修改)表示客戶端已經快取此響應,無須再次傳輸相同內容。
4xx (客戶端錯誤分類):這些狀態碼代表客戶端提交了一個錯誤請求。
- 400:Bad Request(錯誤請求)表示客戶端請求沒被處理,因為服務端無法理解客戶端請求。
- 401:Unauthorized(無授權)表示客戶端無權訪問資源,應當加上所需的認證資訊後再次請求。
- 403:Forbidden(禁止訪問)表示請求有效且客戶端已獲授權,但客戶端無權訪問該資源。
- 404:Not Found(沒發現)表示所請求的資源現在不可用。
- 410:Gone(移除)表示所請求的資源已被移除。
5xx (服務端錯誤分類)
- 500:Internal Server Error(內部伺服器錯誤)表示請求有效,但是服務端發生了異常。
- 503:Service Unavailable(服務不可用)表示伺服器關閉或不可用,通常是指伺服器處於維護狀態。
2.7. 名稱規約
你可以遵循任何名稱規約,只要保持跨應用一致性即可。如果請求體和響應體是 JSON 型別,那麼請遵循駝峰名稱規約。
2.8. 搜尋、排序、過濾與分頁
上面一些示例都是在一個資料集上的簡單查詢,對於複雜的資料,我們需要在 GET 方法 API 上加一些引數來處理。下面是一些示例:
- 排序:這個例子中,客戶想獲取排序的公司列表,GET /companies 應當在查詢時接受多種排序引數。譬如 GET /companies?sort=rank_asc 將以等級升序的方式對公司進行排序。
- 過濾:要過濾資料集,我們可以通過查詢引數傳遞不同的選項。比如 GET /companies?category=banking&location=india 將過濾分類為銀行且位於印度的公司。
- 搜尋:在公司列表中搜尋公司名的 API 端點應當是 GET /companies?search=Digital。
分頁:當資料集太大時,我們應當將資料集分割成小的資料塊,這樣有利於提升服務端效能,也方便客戶端處理響應。如 GET /companies?page=23 意味著獲取公司列表的第 23 頁資料。
2.9. Restful API 版本
一般使用不帶點的簡單數字表示版本,數字前加字母v代表版本號,如下所示:
- /blog/api/v1
http://api.yourservice.com/v1...
2.10. 處理 JSON 錯誤體
API 錯誤處理機制是很重要的,而且要好好規劃。極力推薦總是在返回欄位中包含錯誤訊息。一個 JSON 錯誤體應當為開發者提供一些有用的資訊:錯誤訊息、錯誤程式碼以及詳細描述。下面是一個較好的示例:
{ "code": 1234, "message": "Something bad happened :(", "description": "More details about the error here" }
2.11. 如何建立 Rest API URL
推薦使用下面格式的 URL:
- http(s)://{域名(:埠號)}/{表示REST API的值}/{API版本}/{識別資源的路徑}
- http(s)://{表示REST API的域名(:埠號)}/{API 版本}/{識別資源的路徑}
如下所示: - http://example.com/api/v1/mem...
http://api.example.com/v1/mem...
3. 開發基於 Spring Boot 的 Restful Web 服務
Spring Boot 提供了構建企業應用中 RESTful Web 服務的極佳支援。
3.1. 引入依賴
要構建 RESTful Web 服務,我們需要在構建配置檔案中加上 Spring Boot Starter Web 依賴。
對於 Maven 使用者,使用以下的程式碼在 pom.xml 檔案中加入依賴:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
對於 Gradle 使用者,使用以下的程式碼在 build.gradle 檔案中加入依賴:
compile('org.springframework.boot:spring-boot-starter-web')
3.2. Rest 相關注解
在繼續構建 RESTful web 服務前,建議你先要熟悉下面的註解:
Rest Controller
@RestController 註解用於定義 RESTful web 服務。它提供 JSON、XML 和自定義響應。語法如下所示:
@RestController public class ProductServiceController { }
Request Mapping
@RequestMapping 註解用於定義請求 URI 以訪問 REST 端點。我們可以定義 Request 方法來消費 produce 物件。預設的請求方法是 GET:
@RequestMapping(value = "/products") public ResponseEntity<Object> getProducts() { } Request Body @RequestBody 註解用於定義請求體內容型別。 public ResponseEntity<Object> createProduct(@RequestBody Product product) { }
Path Variable
@PathVariable 註解被用於定義自定義或動態的請求 URI,Path variable 被放在請求 URI 中的大括號內,如下所示:
public ResponseEntity<Object> updateProduct(@PathVariable("id") String id) { }
Request Parameter
@RequestParam 註解被用於從請求 URL 中讀取請求引數。預設情況下是必須的,也可以為請求引數設定預設值。如下所示:
public ResponseEntity<Object> getProduct(
@RequestParam(value = "name", required = false, defaultValue = "honey") String name) {
}3.3. 編寫 REST API
GET API
下面的示例程式碼定義了 HTTP GET 請求方法。在這個例子裡,我們使用 HashMap 來在儲存 Product。注意我們使用了 POJO 類來儲存產品。
在這裡,請求 URI 是 /products,它會從 HashMap 倉儲中返回產品列表。下面的控制器類檔案包含了 GET 方法的 REST 端點:package com.tutorialspoint.demo.controller; import java.util.HashMap; import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.tutorialspoint.demo.model.Product; @RestController public class ProductServiceController { private static Map<String, Product> productRepo = new HashMap<>(); static { Product honey = new Product(); honey.setId("1"); honey.setName("Honey"); productRepo.put(honey.getId(), honey); Product almond = new Product(); almond.setId("2"); almond.setName("Almond"); productRepo.put(almond.getId(), almond); } @RequestMapping(value = "/products") public ResponseEntity<Object> getProduct() { return new ResponseEntity<>(productRepo.values(), HttpStatus.OK); } }
POST API
HTTP POST 請求用於建立資源。這個方法包含請求體。我們可以通過傳送請求引數和路徑變數來定義自定義或動態 URL。
下面的示例程式碼定義了 HTTP POST 請求方法。在這個例子中,我們使用 HashMap 來儲存 Product,這裡產品是一個 POJO 類。
這裡,請求 URI 是 /products,在產品被存入 HashMap 倉儲後,它會返回字串。package com.tutorialspoint.demo.controller; import java.util.HashMap; import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.tutorialspoint.demo.model.Product; @RestController public class ProductServiceController { private static Map<String, Product> productRepo = new HashMap<>(); @RequestMapping(value = "/products", method = RequestMethod.POST) public ResponseEntity<Object> createProduct(@RequestBody Product product) { productRepo.put(product.getId(), product); return new ResponseEntity<>("Product is created successfully", HttpStatus.CREATED); } }
PUT API
HTTP PUT 請求用於更新已有的資源。這個方法包含請求體。我們可以通過傳送請求引數和路徑變數來定義自定義或動態 URL。
下面的例子展示瞭如何定義 HTTP PUT 請求方法。在這個例子中,我們使用 HashMap 更新現存的產品。此處,產品是一個 POJO 類。
這裡,請求 URI 是 /products/{id},在產品被存入 HashMap 倉儲後,它會返回字串。注意我們使用路徑變數 {id} 定義需要更新的產品 ID:package com.tutorialspoint.demo.controller; import java.util.HashMap; import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.tutorialspoint.demo.model.Product; @RestController public class ProductServiceController { private static Map<String, Product> productRepo = new HashMap<>(); @RequestMapping(value = "/products/{id}", method = RequestMethod.PUT) public ResponseEntity<Object> updateProduct(@PathVariable("id") String id, @RequestBody Product product) { productRepo.remove(id); product.setId(id); productRepo.put(id, product); return new ResponseEntity<>("Product is updated successsfully", HttpStatus.OK); } }
DELETE API
HTTP Delete 請求用於刪除存在的資源。這個方法不包含任何請求體。我們可以通過傳送請求引數和路徑變數來定義自定義或動態 URL。
下面的例子展示如何定義 HTTP DELETE 請求方法。這個例子中,我們使用 HashMap 來移除現存的產品,用 POJO 來表示。
請求 URI 是 /products/{id} 在產品被從 HashMap 倉儲中刪除後,它會返回字串。 我們使用路徑變數 {id} 來定義要被刪除的產品 ID。package com.tutorialspoint.demo.controller; import java.util.HashMap; import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.tutorialspoint.demo.model.Product; @RestController public class ProductServiceController { private static Map<String, Product> productRepo = new HashMap<>(); @RequestMapping(value = "/products/{id}", method = RequestMethod.DELETE) public ResponseEntity<Object> delete(@PathVariable("id") String id) { productRepo.remove(id); return new ResponseEntity<>("Product is deleted successsfully", HttpStatus.OK); } }