搭建 Restful Web 服務

1. 理解 REST

  REST 全稱是 Representational State Transfer,中文意思是表徵性狀態轉移。它首次出如今2000年Roy Fielding的博士論文中,Roy Fielding是HTTP規範的主要編寫者之一。值得注意的是REST並無一個明確的標準,而更像是一種設計的風格。若是一個架構符合REST的約束條件和原則,咱們就稱它爲RESTful架構。
  理論上REST架構風格並非綁定在HTTP上,只不過目前HTTP是惟一與REST相關的實例。java

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請求都是冪等的,不管對資源操做多少次, 結果老是同樣的,後面的請求並不會產生比第一次更多的影響。git

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應用中就是這麼作的。web

1.3. HTTP status codes

  狀態碼指示 HTTP 請求的結果:spring

  • 1XX:信息
  • 2XX:成功
  • 3XX:轉發
  • 4XX:客戶端錯誤
  • 5XX:服務端錯誤

1.4. 媒體類型

  HTTP頭中的 Accept 和 Content-Type 可用於描述HTTP請求中發送或請求的內容。若是客戶端請求JSON響應,那麼能夠將 Accept 設爲 application/json。相應地,若是發送的內容是XML,那麼能夠設置 Content-Type 爲 application/xml 。json

2. REST API 設計最佳實踐

  這裏介紹一些設計 REST API 的最佳實踐,你們先記住下面這句話:api

URL 是個句子,其中資源是名詞、HTTP 方法是動詞。

2.1. 使用名詞來表示資源

  下面是一些例子:緩存

  • GET - /users:返回用戶列表
  • GET - /users/100:返回一個特定用戶
  • POST - /users:建立一個新用戶
  • PUT - /users/200:更新一個特定用戶
  • DELETE - /users/711:刪除一個特定用戶

  不要使用動詞:安全

  • /getAllsers
  • /getUserById
  • /createNewUser
  • /updateUser
  • /deleteUser

2.2 在 HTTP 頭中使用適當的序列化格式

  客戶端和服務端都須要知道通訊所用的格式,這個格式要在 HTTP 頭中指定:服務器

  • Content-Type 定義請求格式
  • Accept 定義一個可接受的響應格式列表

2.3 Get 方法和查詢參數不該當改變狀態

  使用 PUT, POST 和 DELETE 方法來改變狀態,不要使用 GET 方法來改變狀態:架構

  • GET /users/711?activate
  • GET /users/711/activate

2.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表明版本號,以下所示:

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 版本}/{識別資源的路徑}

  以下所示:

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);
   }
}
相關文章
相關標籤/搜索