【全棧修鍊】422- RESTful 架構及實踐 修鍊寶典

  • 2019 年 11 月 27 日
  • 筆記

一、概念介紹

1. REST 概念

REST:(Representational State Transfer)即表現層狀態轉換,定義了資源的通用訪問格式,是一種網路應用程式的設計風格開發方式

在概念中,需要理解以下幾個名稱:

  1. 資源(Resource)

伺服器上獲取到的東西任何資源,一條用戶記錄,一個用戶的密碼,一張圖片等等都是。

  1. 資源的表述(Representation)

資源格式,是 HTML、XML、JSON、純文本、圖片等等,可以用各種各樣的格式來表述你獲取到的資源。

  1. 狀態轉移(State Transfer)

URL定位資源,用 HTTP 動詞(GET,POST,DELETE,DETC)描述操作。操作是動詞,資源是名詞。

  1. 統一介面(Uniform Interface)

即通過統一的介面對資源進行操作。

2. REST 特點

REST 通常基於使用HTTPURI,和XML以及HTML這些現有的廣泛流行的協議和標準,每一種 URI 代表一種資源。

REST 通常使用JSON數據格式。

REST 基本架構的四個方法:

  • GET– 用於獲取數據
  • PUT– 用於更新或添加數據
  • DELETE– 用於刪除數據
  • POST– 用於添加數據

下面會通過一個場景介紹。

3. REST 優點

  • 更高效利用快取來提高響應速度。
  • 讓不同的伺服器的處理一系列請求中的不同請求,提高伺服器的擴展性
  • 瀏覽器即可作為客戶端,簡化軟體需求。
  • 相對於其他疊加在HTTP協議之上的機制,REST的軟體依賴性更小。
  • 不需要額外的資源發現機制。
  • 在軟體技術演進中的長期的兼容性更好。

二、實例介紹

REST 定義了資源的通用訪問格式,接下來一個消費者為實例,介紹 RESTful API 定義:

  1. 獲取所有 users
GET /api/users
  1. 獲取指定 id 的 users
GET /api/users/100
  1. 新建一條 users 記錄
POST /api/users
  1. 更新一條 users 記錄
PUT /api/users/100
  1. 刪除一條 users 記錄
DELETE /api/users/100
  1. 獲取一個 users 的所有消費賬單
GET  /api/users/100/bill
  1. 獲取一個 user 指定時間的消費賬單
GET  /api/users/100/bill?from=201910&to=201911

以上其中 RESTful 風格 API 幾乎包含常見業務情況。

三、Nodejs 實現 RESTful API

1. 初始化 mock 數據

本案例使用 mock 數據來演示,如下:

{     "user1" : {        "name" : "leo",        "password" : "123456",        "profession" : "teacher",        "id": 1     },     "user2" : {        "name" : "pingan8787",        "password" : "654321",        "profession" : "librarian",        "id": 2     },     "user3" : {        "name" : "robin",        "password" : "888888",        "profession" : "clerk",        "id": 3     }  }

我們將實現以下 RESTful API :

2. 獲取用戶列表

這一步我們會創建 RESTful API 中的/users,使用 GET 來讀取用戶的資訊列表

// index.js  const express = require('express');  const app = express();  const fs = require("fs");    // 定義 讀取用戶的資訊列表 的介面  app.get('/users', (req, res) => {     fs.readFile( __dirname + "/" + "users.json", 'utf8', (err, data) => {         console.log( data );         res.end( data );     });  })    const server = app.listen(8081, function () {      const {address, port} = server.address();      console.log("server run in: http://%s:%s", address, port);  })

3. 添加用戶

這一步我們會創建 RESTful API 中的/users,使用 POST 來添加用戶記錄

// index.js  // 省略之前文件 只展示需要實現的介面    // mock 一條要新增的數據  const user = {     "user4" : {        "name" : "pingan",        "password" : "password4",        "profession" : "teacher",        "id": 4     }  }    // 定義 添加用戶記錄 的介面  app.post('/users', (req, res) => {     // 讀取已存在的數據     fs.readFile( __dirname + "/" + "users.json", 'utf8', (err, data) => {         data = JSON.parse( data );         data["user4"] = user["user4"];         console.log( data );         res.end( JSON.stringify(data));     });  })

4. 獲取用戶詳情

這一步我們在 RESTful API 中的 URI 後面加上/users/:id,使用 GET 來獲取指定用戶詳情

// index.js  // 省略之前文件 只展示需要實現的介面    // 定義 獲取指定用戶詳情 的介面  app.get('/users/:id', (req, res) => {     // 首先我們讀取已存在的用戶     fs.readFile( __dirname + "/" + "users.json", 'utf8', (err, data) => {         data = JSON.parse( data );         const user = data["user" + req.params.id]         console.log( user );         res.end( JSON.stringify(user));     });  })

5. 刪除指定用戶

這一步我們會創建 RESTful API 中的/users,使用 DELETE 來刪除指定用戶

// index.js  // 省略之前文件 只展示需要實現的介面    // mock 一條要刪除的用戶id  const id = 2;    app.delete('/users', (req, res) => {     fs.readFile( __dirname + "/" + "users.json", 'utf8', (err, data) => {         data = JSON.parse( data );         delete data["user" + id];         console.log( data );         res.end( JSON.stringify(data));     });  })

四、REST 最佳實踐

1. URL 設計

1.1 "動詞 + 賓語"的操作指令結構

客戶端發出的數據操作指令都是"動詞 + 賓語"的結構。

如上面提到的,GET /user這個命令,GET是動詞,/user是賓語。根據 HTTP 規範,動詞一律大寫。

動詞通常有以下五種 HTTP 方法:

GET:讀取(Read)

POST:新建(Create)

PUT:更新(Update)

PATCH:更新(Update),通常是部分更新

DELETE:刪除(Delete)

1.2 賓語必須是名詞

賓語就是 API 的 URL,是 HTTP 動詞作用的對象。它應該是名詞,不能是動詞。

比如,/users是正確的,因為 URL 是名詞,而下面就都是錯誤的了:

/getUsers  /createUsers  /deleteUsers

1.3 建議複數 URL

因為 URL 是名詞,沒有單複數的限制,但是還是建議如果是一個集合,就使用複數形式。如GET /users來讀取所有用戶列表。

1.4 避免多級 URL

避免在多層級資源時,使用多級 URL。常見案例如獲取某位用戶的購買過的某一類商品:

GET /users/100/product/120

這種 URL 語意不明,也不利拓展,建議只有第一級,其他級別用查詢字元串來表達:

GET /users/100?product=120

2. 準確的狀態碼錶示

HTTP 五大類狀態碼有100多種,每一種狀態碼都有標準的(或者約定的)解釋,客戶端只需查看狀態碼,就可以判斷出發生了什麼情況,所以伺服器應該返回儘可能精確的狀態碼。

這邊列舉幾個經常使用的狀態碼介紹:

  • 303 See Other:表示參考另一個 URL。
  • 400 Bad Request:伺服器不理解客戶端的請求,未做任何處理。
  • 401 Unauthorized:用戶未提供身份驗證憑據,或者沒有通過身份驗證。
  • 403 Forbidden:用戶通過了身份驗證,但是不具有訪問資源所需的許可權。
  • 404 Not Found:所請求的資源不存在,或不可用。
  • 405 Method Not Allowed:用戶已經通過身份驗證,但是所用的 HTTP 方法不在他的許可權之內。
  • 410 Gone:所請求的資源已從這個地址轉移,不再可用。
  • 415 Unsupported Media Type:客戶端要求的返回格式不支援。比如,API 只能返回 JSON 格式,但是客戶端要求返回 XML 格式。
  • 422 Unprocessable Entity:客戶端上傳的附件無法處理,導致請求失敗。
  • 429 Too Many Requests:客戶端的請求次數超過限額。
  • 500 Internal Server Error:客戶端請求有效,伺服器處理時發生了意外。
  • 503 Service Unavailable:伺服器無法處理請求,一般用於網站維護狀態。

3. 服務端響應

3.1 應該返回 JSON 對象

API 返回的數據格式應該是 JSON 一個對象。

3.2 發生錯誤時,不要返回 200 狀態碼

在發生錯誤時,如果還返回 200 狀態碼,前端需要解析返回數據才知道錯誤資訊,這樣實際上取消了狀態碼,是不恰當的。

正確的做法應該是在錯誤時,返回對應錯誤狀態碼,並將錯誤資訊返回:

HTTP/1.1 400 Bad Request  Content-Type: application/json    {    "error": "Invalid payoad.",    "detail": {       "surname": "This field is required."    }  }

參考資料

  1. 《維基百科 – 表現層狀態轉換》
  2. 《RESTful風格的springMVC》
  3. 《Node.js RESTful API》
  4. 《RESTful API 最佳實踐》