Django-rest framework開發:Restful 接口規範

REST簡介

REST這個詞,是Roy Thomas Fielding在他2000年的博士論文(Architectural Styles and
the Design of Network-based Software Architectures
)中提出的。
在這裏插入圖片描述
Fielding是一個非常重要的人,他是HTTP協議(1.0版和1.1版)的主要設計者、Apache服務器軟件的作者之一、Apache基金會的第一任主席。所以,他的這篇論文一經發表,就引起了關注,並且立即對互聯網開發產生了深遠的影響。REST迅速取代了複雜而笨重的SOAP,成爲Web API的首選風格。RESTful作爲目前最流行的 API 設計風格,一定有着它獨有的魅力:強大、簡潔、易上手。

Fielding將他對互聯網軟件的架構原則,定名爲REST,即Representational State Transfer的縮寫。如果一個架構符合REST風格,就稱它爲RESTful架構。

要理解RESTful架構,最好的方法就是去理解Representational State Transfer這個詞組到底是什麼意思,它的每一個詞代表了什麼涵義。 如果你把這個名稱搞懂了,也就不難體會REST是一種什麼樣的設計。

資源(Resources)

REST的名稱"表徵狀態轉化"中,省略了主語。“表徵"其實指的是"資源”(Resources)的"表徵"。

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

所謂"上網",就是與互聯網上一系列的"資源"互動,調用它的URI。

表徵(Representation)

“資源"是一種信息實體,它可以有多種外在表現形式。我們把"資源"具體呈現出來的形式,叫做它的"表徵”(Representation)。

比如,文本可以用txt格式表現,也可以用HTML格式、XML格式、JSON格式表現,甚至可以採用二進制格式;圖片可以用JPG格式表現,也可以用PNG格式表現。

URI只代表資源的實體,不代表它的形式。嚴格地說,有些網址最後的".html"後綴名是不必要的,因爲這個後綴名錶示格式,屬於"表徵"範疇,而URI應該只代表"資源"的位置。它的具體表現形式,應該在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動詞,對服務器端資源進行操作,實現"表徵狀態轉化"。

REST風格

url鏈接一般都採用https協議進行傳輸

用api關鍵字標識接口url

  • https://api.baidu.com(應該儘量將API部署在專用域名之下。)
  • https://www.baidu.com/api/(確定API很簡單,不會有進一步擴展)

在url鏈接中標識數據版本

  • https://api.baidu.com/v1/
  • https://api.baidu.com/v2/

接口一般都是完成前後臺數據的交互,交互的數據我們稱之爲資源。所以網址中不能有動詞,只能有名詞,而且所用的名詞往往是複數形式

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

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

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

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

特殊的接口可以出現動詞,因爲這些接口一般沒有一個明確的資源,或是動詞就是接口的核心含義

  • https://api.baidu.com/place/search
  • https://api.baidu.com/login

資源操作由請求方式決定

對於資源的具體操作類型,由請求方式來標識:

  • GET:取出資源(一項或多項)。

  • POST:新建一個資源。

  • PUT:更新資源(提供待改變的整體資源,類似整張表)。

  • PATCH:更新資源(提供待改變的部分屬性,類似某個字段)。

  • DELETE:刪除資源。

過濾信息

如果記錄數量很多,服務器不可能都將它們返回給用戶。API應該提供參數,過濾返回結果。

下面是一些常見的參數

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

參數的設計允許存在冗餘,即允許API路徑和URL參數偶爾有重複。比如,GET /zoo/ID/animalsGET /animals?zoo_id=ID 的含義是相同的。

服務器返回響應狀態碼

  • 200:請求成功
  • 201:創建成功
  • 301:永久重定向
  • 302:暫時重定向
  • 401:請求無權限
  • 403:得到授權(與401錯誤相對),但是訪問是被禁止
  • 404:請求路徑不存在
  • 405:請求方法不存在
  • 500:服務器異常
  • 502:錯誤的網關,上游服務器響應無效
  • 504:網關超時,上游服務器無響應

狀態碼的完整信息可參見Status Code Definitions

響應結果

  • 響應數據要有狀態碼、狀態信息以及數據本身

  • 需要url請求的資源需要訪問資源的請求鏈接

Hypermedia API

返回結果中提供鏈接,連向其他API方法,使得用戶不查文檔,也知道下一步應該做什麼。

比如,當用戶向api.example.com的根目錄發出請求,會得到這樣一個文檔。

{"link": {
  "rel":   "collection https://www.example.com/zoos",
  "href":  "https://api.example.com/zoos",
  "title": "List of zoos",
  "type":  "application/vnd.yourformat+json"
}}

上面代碼表示,文檔中有一個link屬性,用戶讀取這個屬性就知道下一步該調用什麼API了。rel表示這個API與當前網址的關係(collection關係,並給出該collection的網址),href表示API的路徑,title表示API的標題,type表示返回類型。

Hypermedia API的設計被稱爲HATEOAS。Github的API就是這種設計,訪問https://api.github.com/會得到一個所有可用API的網址列表。

{
  "current_user_url": "https://api.github.com/user",
  "current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}",
  "authorizations_url": "https://api.github.com/authorizations",
  ......
  }

從上面可以看到,如果想獲取當前用戶的信息,應該去訪問https://api.github.com/user,然後就得到了下面結果。

{
  "message": "Requires authentication",
  "documentation_url": "https://developer.github.com/v3/users/#get-the-authenticated-user"
}

上面代碼表示,服務器給出了提示信息,以及文檔的網址。

其他建議:

  • API的身份認證應該使用OAuth 2.0框架。

  • 服務器返回的數據格式,應該儘量使用JSON,避免使用XML。

REST應用層級

假設我們在樓下的蛋糕店買蛋糕。“一個芝士蛋糕,一杯拿鐵,兩條吸管,謝謝”,我對前臺的服務員說,然後我們找了個角落坐了下來。

Level 0 - 面向前臺

“剛纔我們向前臺點了一杯拿鐵,這個過程可以用這段文字來描述”,說着,我在紙上寫下了這段JSON。

{
    "addOrder": {
        "orderName": "latte"
    }
}

“我們通過這段文字,告訴前臺,新增一筆訂單,訂單是一杯拿鐵咖啡”,接着,前臺給我們返回這麼一串回覆:

{
    "orderId": "123456"
}

“那我們就等着前臺喊‘訂單123456的客戶可以取餐了’,然後就可以開吃了!”

“哈哈,你真聰明,不過,在這之前,假設我們有一張會員卡,我們想查詢一下這張會員卡的餘額,這時候,要向前臺發起另一個詢問”,我繼續在紙上寫着:

{
    "queryBalance": {
        "cardId": "886333"
    }
}

“查詢卡號爲886333的卡的餘額?”

“真棒!接着,查詢的結果返回來了”

{
    "balance": "0"
}

“切,沒錢…”

“哈哈,沒錢,現在我們要跟前臺說,這杯咖啡不要了”,我在紙上寫到:

{
    "deleteOrder": {
        "orderId": "123456"
    }
}

Level 1 - 面向資源

“現在這家蛋糕店越做越大,來喝咖啡的人越來越多,單靠前臺顯然是不行的,店主決定進行分工,每個資源都有專人負責,我們可以直接面向資源操作。”

比如還是下單,請求的內容不變,但是我們多了一條消息”

/orders

{
    "addOrder": {
        "orderName": "latte"
    }
}

“/orders這個表示我們這個請求是發給哪個資源的,訂單是一種資源,我們可以理解爲是咖啡廳專門管理訂單的人,他可以幫我們處理所有有關訂單的操作,包括新增訂單、修改訂單、取消訂單等操作”

“接着還是會返回訂單的編號給我們”

{
    "orderId": "123456"
}

“下面,我們還是要查詢會員卡餘額,這次請求的資源變成了cards”

/cards

{
    "queryBalance": {
        "cardId": "886333"
    }
}

“接下來是取消訂單”

/orders

{
    "deleteOrder": {
        "orderId": "123456"
    }
}

Level 2 - 打上標籤

“接下來,店主還想繼續優化他的咖啡廳的服務流程,他發現負責處理訂單的員工,每次都要去訂單內容裏面看是新增訂單還是刪除訂單,還是其他的什麼操作,十分不方便,於是規定,所有新增資源的請求,都在請求上面寫上大大的‘POST’,表示這是一筆新增資源的請求”

“其他種類的請求,比如查詢類的,用‘GET’表示,刪除類的,用‘DELETE’表示”,添加類的,用‘POST’表示

“還有修改類的,修改分爲兩種,第一種,如果這個修改,是修改整個訂單上的食物,那麼用‘PUT’表示;第二種,如果這個修改,只是修改訂單上某一種食物,比如是換一杯咖啡,那麼這種請求用‘PATCH’表示

“來,我們再來重複上面那個過程,加一杯拿鐵”

POST /orders

{
    "orderName": "latte"
}

“請求的內容簡潔多啦,不用告訴店員是addOrder,看到POST就知道是新增”

返回的內容還是一樣"

{
    "orderId": "123456"
}

“接着是查詢會員卡餘額,這次也簡化了很多”

GET /cards

{
    "cardId": "886333"
}

“這個請求我們還可以進一步優化爲這樣”

GET /cards/886333

“沒錯,接着,取消訂單”

DELETE /orders/123456

Level 3 - 完美服務

爲避免顧客不知道操作方式,店長決定,顧客下了單之後,不僅給他們返回訂單的編號,還給顧客返回所有可以對這個訂單做的操作,比如告訴用戶如何刪除訂單。現在,我們還是發出請求,請求內容和上一次一樣”

POST /orders

{
    "orderName": "latte"
}

“但是這次返回時多了些內容”

{
    "orderId": "123456",
    "link": {
        "rel": "cancel",
        "url": "/order/123456"
    }
}

“這次返回時多了一項link信息,裏面包含了一個rel屬性和url屬性,rel是relationship的意思,這裏的關係是cancel,url則告訴你如何執行這個cancel操作,接着你就可以這樣子來取消訂單啦”

DELETE /orders/123456

上面講的Level0~Level3,來自Leonard Richardson提出的Richardson Maturity Model

在這裏插入圖片描述
Level0和Level1最大的區別,就是Level1擁有了Restful的第一個特徵——面向資源,這對構建可伸縮、分佈式的架構是至關重要的。同時,如果把Level0的數據格式換成Xml,那麼其實就是SOAP,SOAP的特點是關注行爲和處理,和麪向資源的RESTful有很大的不同。

Level0和Level1,其實都很挫,他們都只是把HTTP當做一個傳輸的通道,沒有把HTTP當做一種傳輸協議。

Level2,真正將HTTP作爲了一種傳輸協議,最直觀的一點就是Level2使用了HTTP動詞,GET/PUT/POST/DELETE/PATCH…,這些都是HTTP的規範,規範的作用自然是重大的,用戶看到一個POST請求,就知道它不是冪等的,使用時要小心,看到PUT,就知道他是冪等的,調用多幾次都不會造成問題,當然,這些的前提都是API的設計者和開發者也遵循這一套規範,確保自己提供的PUT接口是冪等的。

Level3,關於這一層,有一個古怪的名詞,叫HATEOAS(Hypertext As The Engine Of Application State),中文翻譯爲“將超媒體格式作爲應用狀態的引擎”,核心思想就是每個資源都有它的狀態,不同狀態下,可對它進行的操作不一樣。理解了這一層,再來看看REST的全稱,Representational State Transfer,中文翻譯爲“表徵狀態轉化”,是不是好理解多了?

Level3的Restful API,給使用者帶來了很大的便利,使用者只需要知道如何獲取資源的入口,之後的每個URI都可以通過請求獲得,無法獲得就說明無法執行那個請求。

現在絕大多數的RESTful接口都做到了Level2的層次,做到Level3的比較少。當然,這個模型並不是一種規範,只是用來理解Restful的工具。所以,做到了Level2,也就是面向資源和使用Http動詞,就已經很Restful了。Restful本身也不是一種規範,我比較傾向於用“風格"來形容它。如果你想深入瞭解Level3,可以閱讀《Rest in Practice》第五章。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章