REST教程


REST教程

越來越多的人開始意識到,網站即軟件,而且是一種新型的軟件。這種"互聯網軟件"采用客戶端/服務器模式,建立在分布式體系上,通過互聯網通信,具有高延時(high latency)、高並發等特點。

網站開發,完全可以采用軟件開發的模式。但是傳統上,軟件和網絡是兩個不同的領域,很少有交集;軟件開發主要針對單機環境,網絡則主要研究系統之間的通信。互聯網的興起,使得這兩個領域開始融合,現在我們必須考慮,如何開發在互聯網環境中使用的軟件。而RESTful架構,就是目前最流行的一種互聯網軟件架構。它結構清晰、符合標准、易於理解、擴展方便,所以正得到越來越多網站的采用。

REST風格與RESTful架構

REST這個詞,是Roy Thomas Fielding在他2000年的博士論文中提出的。他是HTTP協議(1.0版和1.1版)的主要設計者、Apache服務器軟件的作者之一、Apache基金會的第一任主席。

他的這篇論文一經發表,就引起了關注,並且立即對互聯網開發產生了深遠的影響。他對互聯網軟件的架構原則,定名為REST,即Representational State Transfer的縮寫。這個詞組的翻譯是"表現層狀態轉化"。如果一個架構符合REST原則,就稱它RESTful架構。要理解RESTful架構,最好的方法就是去理解Representational State Transfer這個詞組到底是什么意思,它的每一個詞代表了什么涵義。

資源(Resources)

所謂"資源",就是網絡上的一個實體,或者說是網絡上的一個具體信息。它可以是一段文本、一張圖片、一首歌曲、一種服務,總之就是一個具體的實在。你可以用一個URI(統一資源定位符)指向它,每種資源對應一個特定的URI。要獲取這個資源,訪問它的URI就可以,因此URI就成了每一個資源的地址或獨一無二的識別符。

表現層(Representation)

"資源"是一種信息實體,它可以有多種外在表現形式。我們把"資源"具體呈現出來的形式,叫做它的"表現層"(Representation)。比如,文本可以用txt格式表現,也可以用HTML格式、XML格式、JSON格式表現,甚至可以采用二進制格式;圖片可以用JPG格式表現,也可以用PNG格式表現。它的具體表現形式,應該在HTTP請求的頭信息中用Accept和Content-Type字段指定,這兩個字段才是對"表現層"的描述。

狀態轉化(State Transfer)

訪問某一個網站,就代表了客戶端和服務器的一個交互過程。在這個過程中,勢必涉及到數據和狀態的變化。互聯網通信協議HTTP協議,是一個無狀態協議。這意味着,所有的狀態都保存在服務器端。因此,如果客戶端想要操作服務器,必須通過某種手段,讓服務器端發生"狀態轉化"(State Transfer)。而這種轉化是建立在表現層之上的,所以就是"表現層狀態轉化"。而客戶端用到的手段,只能是HTTP協議。具體來說,就是HTTP協議里面,四個表示操作方式的動詞:GET、POST、PUT、DELETE。它們分別對應四種基本操作:GET用來獲取資源,POST用來新建資源(也可以用於更新資源),PUT用來更新資源,DELETE用來刪除資源。

綜述

綜合上面的解釋,我們總結一下什么是RESTful架構:

  1. 每一個URI代表一種資源;

  2. 客戶端和服務器之間,傳遞這種資源的某種表現層;

  3. 客戶端通過四個HTTP動詞,對服務器端資源進行操作,實現"表現層狀態轉化"。

RESTful API設計規范

網絡應用程序,分為前端和后端兩個部分。當前的發展趨勢,就是前端設備層出不窮(手機、平板、桌面電腦、其他專用設備……),並且很多時候前端和后端是分離開來開發和部署。因此,必須有一種統一的機制,方便不同的前端設備與后端進行通信。這導致API構架的流行,RESTful API是目前比較成熟的一套互聯網應用程序的API設計理論。

域名

RESTful API通常使用https協議並提供相應的域名給客戶端進行訪問。並且盡量將API部署在專用的二級域名下,例如:

https://api.example.com

如果API相對簡單,可以直接部署在一級域名下,例如:

https://example.com/api/

版本

版本號是為了保證API 的穩定性,並且向下兼容。在 API 沒有變化的時候,API 實現的更新和升級,都應該確保原有客戶端請求不出現問題。這種方式通常在 URI 中增加一段用於標識版本,例如/v1/v2等。例如:

https://api.example.com/v1/

或者

https://example.com/api/v1/

路徑

路徑又稱"終點"(endpoint),表示API的具體網址。在RESTful架構中,每個網址代表一種資源(resource),所以網址中不能有動詞,只能有名詞,而且所用的名詞往往與數據庫的表格名對應。一般來說,數據庫中的表都是同種記錄的"集合"(collection),所以API中的名詞也應該使用復數。

舉例來說,有一個API提供動物園(zoo)的信息,還包括各種動物和雇員的信息,則它的路徑應該設計成下面這樣。

https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees

HTTP動詞

對於資源的具體操作類型,由HTTP動詞表示。常用的HTTP動詞有下面七個(括號里是對應的相關操作)。

  • GET(SELECT):從服務器取出資源(一項或多項)。

  • POST(CREATE):在服務器新建一個資源。

  • PUT(UPDATE):在服務器更新資源(客戶端提供改變后的完整資源)。

  • PATCH(UPDATE):在服務器更新資源(客戶端提供改變的屬性)。

  • DELETE(DELETE):從服務器刪除資源。

  • HEAD:獲取資源的元數據。(不常用)

  • OPTIONS:獲取信息,關於資源的哪些屬性是客戶端可以改變的。(不常用)

例如:

GET /zoos:列出所有動物園
POST /zoos:新建一個動物園
GET /zoos/id:獲取某個指定動物園的信息
PUT /zoos/id:更新某個指定動物園的信息(提供該動物園的全部信息)
PATCH /zoos/id:更新某個指定動物園的信息(提供該動物園的部分信息)
DELETE /zoos/id:刪除某個動物園
GET /zoos/id/animals:列出某個指定動物園的所有動物
DELETE /zoos/id/animals/id:刪除某個指定動物園的指定動物

過濾信息

如果記錄數量很多,服務器不可能都將它們返回給用戶。API應該提供參數,過濾返回結果。例如下面是一些常見的請求過濾參數。

?limit=10:指定返回記錄的數量
?offset=10:指定返回記錄的開始位置。
?page=2&per_page=100:指定第幾頁,以及每頁的記錄數。
?sortby=name&order=asc:指定返回結果按照哪個屬性排序,以及排序順序。
?animal_type_id=1:指定篩選條件

狀態碼

服務器向用戶返回的狀態碼和提示信息,常見的有以下一些(方括號中是該狀態碼對應的HTTP動詞)。

  • 200 OK - [GET]:服務器成功返回用戶請求的數據,該操作是冪等的(Idempotent)。

  • 201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數據成功。

  • 202 Accepted - [*]:表示一個請求已經進入后台排隊(異步任務)

  • 204 NO CONTENT - [DELETE]:用戶刪除數據成功。

  • 400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發出的請求有錯誤,服務器沒有進行新建或修改數據的操作,該操作是冪等的。

  • 401 Unauthorized - [*]:表示用戶沒有權限(令牌、用戶名、密碼錯誤)。

  • 403 Forbidden - [*] 表示用戶得到授權(與401錯誤相對),但是訪問是被禁止的。

  • 404 NOT FOUND - [*]:用戶發出的請求針對的是不存在的記錄,服務器沒有進行操作,該操作是冪等的。

  • 406 Not Acceptable - [GET]:用戶請求的格式不可得(比如用戶請求JSON格式,但是只有XML格式)。

  • 410 Gone -[GET]:用戶請求的資源被永久刪除,且不會再得到的。

  • 422 Unprocesable entity - [POST/PUT/PATCH] 當創建一個對象時,發生一個驗證錯誤。

  • 500 INTERNAL SERVER ERROR - [*]:服務器發生錯誤,用戶將無法判斷發出的請求是否成功。

錯誤處理

如果狀態碼是4xx或者5xxx,就應該向用戶返回出錯信息。一般來說,返回的信息中將error作為鍵名,出錯信息作為鍵值即可。

{
   error: "Invalid API key"
}

HATEOAS 約束

HATEOAS(Hypermedia as the engine of application state)是 REST 架構風格中最復雜的約束,也是構建成熟 REST 服務的核心。它的重要性在於打破了客戶端和服務器之間嚴格的契約,使得客戶端可以更加智能和自適應,而 REST 服務本身的演化和更新也變得更加容易。在了解HATEOAS之前先來看看 REST 服務按照成熟度划分成 4 個層次:

  • 第一個層次(Level 0)的 Web 服務只是使用 HTTP 作為傳輸方式,實際上只是遠程方法調用(RPC)的一種具體形式。SOAP 和 XML-RPC 都屬於此類。

  • 第二個層次(Level 1)的 Web 服務引入了資源的概念。每個資源有對應的標識符和表達。

  • 第三個層次(Level 2)的 Web 服務使用不同的 HTTP 方法來進行不同的操作,並且使用 HTTP 狀態碼來表示不同的結果。如 HTTP GET 方法來獲取資源,HTTP DELETE 方法來刪除資源。

  • 第四個層次(Level 3)的 Web 服務使用 HATEOAS。在資源的表達中包含了鏈接信息。客戶端可以根據鏈接來發現可以執行的動作。

從上述 REST 成熟度模型中可以看到,使用 HATEOAS 的 REST 服務是成熟度最高的,也是推薦的做法。對於不使用 HATEOAS 的 REST 服務,客戶端和服務器的實現之間是緊密耦合的。客戶端需要根據服務器提供的相關文檔來了解所暴露的資源和對應的操作。當服務器發生了變化時,如修改了資源的 URI,客戶端也需要進行相應的修改。而使用 HATEOAS 的 REST 服務中,客戶端可以通過服務器提供的資源的表達來智能地發現可以執行的操作。當服務器發生了變化時,客戶端並不需要做出修改,因為資源的 URI 和其他信息都是動態發現的。

HATEOAS 鏈接

HATEOAS 的核心是鏈接,由 rel 和 href 兩個屬性組成。鏈接的存在使得客戶端可以動態發現其所能執行的動作。其中屬性 rel 表明了該鏈接所代表的關系含義。應用可以根據需要為鏈接選擇最適合的 rel 屬性值。例如:

{
  "code": 200,
  "message": null,
  "data": [
      {
          "uid": 1001,
          "userName": "user1"
      },
      {
          "uid": 1002,
          "userName": "user2"
      }
  ],
  "links": [
      {
          "rel": "self",
          "href": "http://localhost:8080/api/v1/users/list"
      },
      {
          "rel": "item",
          "href":"http://localhost:8080/api/v1/users/id/1001"
      }
  ]
}

對於很多常見的鏈接關系,IANA(互聯網數字分配機構) 定義了規范的 rel 屬性值。在開發中可能使用的常見 rel 屬性值如下:

rel 屬性值 描述
self 指向當前資源本身的鏈接的 rel 屬性。每個資源的表達中都應該包含此關系的鏈接。
edit 指向一個可以編輯當前資源的鏈接。
item 如果當前資源表示的是一個集合,則用來指向該集合中的單個資源。
collection 如果當前資源包含在某個集合中,則用來指向包含該資源的集合。
related 指向一個與當前資源相關的資源。
search 指向一個可以搜索當前資源及其相關資源的鏈接。
first、last、previous、next 這幾個 rel 屬性值都有集合中的遍歷相關,分別用來指向集合中的第一個、最后一個、上一個和下一個資源。

Spring HATEOAS

如果 Web 應用基於 Spring 框架開發,那么可以直接使用 Spring 框架的子項目 HATEOAS 來開發滿足 HATEOAS 約束的 Web 服務。滿足 HATEOAS 約束的 REST 服務最大的特點在於服務器提供給客戶端的表達中包含了動態的鏈接信息,客戶端通過這些鏈接來發現可以觸發狀態轉換的動作。Spring HATEOAS 的主要功能在於提供了簡單的機制來創建這些鏈接,並與 Spring MVC 框架有很好的集成。對於已有的 Spring MVC 應用,只需要一些簡單的改動就可以滿足 HATEOAS 約束。

例子:

添加Maven依賴:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.1.1.RELEASE</version>
</dependency>
<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.6</version>
</dependency>
<dependency>
  <groupId>org.springframework.hateoas</groupId>
    <artifactId>spring-hateoas</artifactId>
    <version>0.16.0.RELEASE</version>
</dependency>

Users(實體):

public class Users {

   private Integer uid;
   private String userName;

   public Integer getUid() {
       return uid;
  }

   public void setUid(Integer uid) {
       this.uid = uid;
  }

   public String getUserName() {
       return userName;
  }

   public void setUserName(String userName) {
       this.userName = userName;
  }

   @Override
   public String toString() {
       return "Users{" +
               "uid=" + uid +
               ", userName='" + userName + '\'' +
               '}';
  }
}

ResponseVO(響應視圖對象):

/**
* 繼承了spring-hateoas的ResourceSupport,主要是調用繼承的
* add方法來創建鏈接信息
*/
public class ResponseVO<T> extends ResourceSupport {
   /**
    * 狀態碼
    */
   private Integer code;
   /**
    * 響應消息
    */
   private String message;
   /**
    * 響應數據
    */
   private T data;

   public Integer getCode() {
       return code;
  }

   public void setCode(Integer code) {
       this.code = code;
  }

   public String getMessage() {
       return message;
  }

   public void setMessage(String message) {
       this.message = message;
  }

   public T getData() {
       return data;
  }

   public void setData(T data) {
       this.data = data;
  }

   @Override
   public String toString() {
       return "ResponseVO{" +
               "code=" + code +
               ", message='" + message + '\'' +
               ", data=" + data +
               '}';
  }
}

UserController:

@RestController
@RequestMapping("/api/v1/users")
public class UserController extends BaseController{

   @Autowired
   private UserService userService;

   @GetMapping("/list")
   public ResponseVO<List<Users>> listUser(){
       List<Users> list = userService.listUsers();
       ResponseVO<List<Users>> responseVO = responseVO(list);
       responseVO.add(new Link("http://localhost:8080/api/v1/users/list"));
       responseVO.add(new Link("http://localhost:8080/api/v1/users/id/1001", "item"));
       return responseVO;
  }

   @GetMapping("/id/{uid}")
   public ResponseVO<Users> getUser(@PathVariable("uid") Integer id){
       Users user =userService.getUserById(id);
       return responseVO(user);
  }

}

最后可在Postman中請求http://localhost:8080/api/v1/users/list這個API,結果如下:

Swagger構建RESTful API

使用RESTful API作為Web服務對外提供服務的入口,基本上已經成為了標准,在提供REST API的同時,如何進行 API文檔管理是一個較為麻煩的事情,作為開發人員我們都了解API文檔的重要性,但總是嫌其編寫的麻煩,Swagger的 出現很好地幫我們解決文檔編寫的事情,開發人員可以采取自己喜歡的姿勢進行API文檔編寫,並且通過springfox可以很好的和Spring框架集成。

添加Maven依賴

<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger2</artifactId>
   <version>2.9.2</version>
</dependency>
<!-- Swagger UI提供了Web頁面以方便開發人員查看API文檔-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.9.6</version>
</dependency>

集成Swagger2

首先創建一個SwaggerConfiguration類,如下:

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {                                    
   @Bean
   public Docket api() {
       return new Docket(DocumentationType.SWAGGER_2)  
        .select()                                  
        .apis(RequestHandlerSelectors.any())              
        .paths(PathSelectors.any())                          
        .build()
        .apiInfo(apiInfo());
  }
   /**
    * Api接口描述配置
    */
   private ApiInfo apiInfo() {
       return new ApiInfoBuilder()
               // 文檔標題
              .title("移動端接口文檔")
               // 文檔描述
              .description("API文檔接入說明")
               //api說明網站
              .termsOfServiceUrl("https://github.com/baiczsy")
               //Api版本
              .version("v1")
              .build();
  }
}

使用@EnableSwagger2注解來開啟Swagger2,在整個類中最重要的就是Docket的Bean,Docket定義了 Swagger的版本、以及對外暴露的API等信息。上面的代碼在構建Docket對象后做了以下一些配置工作:

  1. 創建一個Docket對象

  2. 調用select()方法,生成ApiSelectorBuilder對象實例,該對象負責定義對外暴露的API入口

  3. 調用apis()和paths()方法並使用RequestHandlerSelectorsPathSelectors來提供Predicate,在此我們使用any()方法,將所有API都通過Swagger進行文檔管理

  4. apiInfo()方法用於設置Api的相關描述信息

配置靜態資源

接下來還需要配置Spring MVC的Resource Handler,因為后續需要使用到Swagger UI,它有些靜態資源需要加載。(注意:需要啟用Spring MVC注解驅動,可以在xml中配置<mvc:annotation-driven/>,或者在SwaggerConfiguration類上標注@EanbleWebMvc)

Java配置:

將SwaggerConfiguration類實現WebMvcConfigurer接口,並重寫addResourceHandlers方法。

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
   registry.addResourceHandler("swagger-ui.html")
    .addResourceLocations("classpath:/META-INF/resources/");

   registry.addResourceHandler("/webjars/**")
    .addResourceLocations("classpath:/META-INF/resources/webjars/");
}

或者使用Xml配置:

<mvc:resources location="classpath:/META-INF/resources/" mapping="swagger-ui.html"/>
<mvc:resources location="classpath:/META-INF/resources/webjars/" mapping="/webjars/**"/>

最后運行項目,在瀏覽器中輸入:

http://localhost:8080/swagger-ui.html

可以看到如下web頁面:

Swagger注解使用

@Api

該注解將一個Controller(Class)標注為一個swagger資源(API)。在默認情況下,Swagger-Core只會掃描解析具有@Api注解的類。該注解包含以下幾個重要屬性:

  • tags API分組標簽。具有相同標簽的API將會被歸並在一組內展示

  • value 如果tags沒有定義,value將作為Api的tags使用

  • description API的詳細描述

例子:

@RestController
@RequestMapping("/api/v1")
@Api(tags = "user", description = "用戶Api")
public class UserController {
}
@ApiOperation

用在方法上,說明方法的作用。對一個操作或HTTP方法進行描述。具有相同路徑的不同操作會被歸組為同一個操作對象。不同的HTTP請求方法及路徑組合構成一個唯一操作。此注解的屬性有:

  • value 對操作的簡單說明,長度為120個字母,60個漢字

  • notes 對操作的詳細說明

  • httpMethod HTTP請求的動作,可選值有:"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS" , "PATCH"

  • code 默認為200,有效值必須符合標准的HTTP Status Code Definitions

例子:

@GetMapping("/users")
@ApiOperation(value="查詢用戶列表", notes = "查詢用戶列", httpMethod = "GET")
public List<Users> listUser(){
   List<Users> userList = listUsers();
   return userList;
}
@ApiImplicitParams

用在方法上,包含一組@ApiImplicitParam參數說明

@ApiImplicitParam

這個注解@ApiImplicitParam必須被包含在注解@ApiImplicitParams之內,用於對方法的每一個參數進行詳細說明。可以設置以下重要參數屬性:

  • name 參數名稱

  • value 參數的簡短描述

  • required 是否為必傳參數

  • dataType 參數類型,可以為類名,也可以為基本類型(String,int、boolean等)

  • paramType 參數的傳入(請求)類型,可選的值有path, query, body, header,form

例子:

@GetMapping("/users")
@ApiOperation(value="查詢用戶信息", notes = "依據ID查詢用戶詳細信息", httpMethod = "GET")
@ApiImplicitParams({
    @ApiImplicitParam(
               name="id",
               value="用戶ID",
               required = true,
               dataType = "String",
               paramType = "path")
})
public Users getUser(@PathVariable("id") String id{
       Users user = getUserById(id);
       return user;
}
@ApiResponses

此注解標注在方法上,表示一組響應。用於包含一組@ApiResponse注解。

@ApiResponse

描述一個操作可能的返回結果。當REST API請求發生時,這個注解可用於描述所有可能的成功與錯誤碼。可以用,也可以不用這個注解去描述操作的返回類型,這個注解必須被包含在@ApiResponses注解中。可以設置以下重要參數屬性:

  • code

    HTTP請求返回碼。有效值必須符合標准的HTTP Status Code Definitions

  • message 易於理解的文本提示消息

  • response 返回類型信息,必須使用完全限定類名,如: “edu.nf.vo.ResponseVO”

  • responseContainer

    如果返回類型為集合類型,可以設置相應的值。有效值為 "List", "Set", "Map",其他任何無效的值都會被忽略

例子:

@GetMapping("/users")
@ApiOperation(value="查詢用戶列表", notes = "查詢用戶列", httpMethod = "GET")
@ApiResponses({
     @ApiResponse(code = 200, message = "success",
                   response = Users.class, responseContainer = "List"),
@ApiResponse(code = 500, message = "服務器內部錯誤")
})
public List<Users> listUser() {
    List<Users> userList = listUsers();
    return userList;
}
@ApiModel

標注在model類上,利用這個注解可以做一些更加詳細的model結構說明。主要屬性有:

  • value model的別名,默認為類名

  • description model的詳細描述

@ApiModelProperty

標注在model的字段或get方法上,對每一個字段進行詳細的描述。主要的屬性值有:

  • value 屬性簡短描述

  • example 屬性的示例值

  • required 是否為必須值

例子:

@ApiModel(value = "users", description = "用戶對象信息")
public class Users {
   @ApiModelProperty(value = "用戶id", example = "1001")
   private String id;
   @ApiModelProperty(value = "用戶名", example = "user1")
   private String userName;
   @ApiModelProperty(value = "年齡", example = "26")
   private Integer age;
   
   //省略get和set方法...
}    

 


免責聲明!

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



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