理解RESTful API

近日妹子向我求助RESTful API到底是個什麼東西。原因是她們公司一個新啟動的項目因為RESTful API起了爭執。服務端同學堅持要用RESTful API,而前端同學則認為服務端用RESTful API就會讓前端的調用變得更麻煩。最終爭議了一下午還是不了了之。有趣的是他們組的大部分人都不太了解REST是個什麼東西。

實際上一些抽象的東西是不如一些具體的技術好講解的,就像你給新人講面向對象一樣,這東西得靠時間,靠悟。我之前做過開放平台API的項目。對於RESTful API還算有些了解。萬幸沒有丟人,口乾舌燥之後總算講明白一些。但這東西真正理解還得多悟、多思考、多練習。當然,如果你有更好的理解,可在評論區與我留言分享!我會第一時間反饋!

一、REST

REST,即Representational State Transfer的縮寫,翻譯過來就是”表現層狀態轉化”。不得不承認,我在剛開始看到這個名詞的時候是一臉懵逼。好了,現在我們放棄對這個名詞的理解。

實際上,REST只是一種軟件架構風格。注意了,它並不是一種具體的技術。而更像是一種約束與規範性的東西,它包含了很多原則與限制。而如果一個架構符合REST的原則,就可以稱它為RESTful架構。

1.1 資源

在REST中最重要的一個概念就是資源。在面向對象的世界裏,我們提倡萬物皆對象,而在REST的世界裏則是萬物皆資源。所謂”資源”,就是網絡上的一個實體,或者說是網絡上的一個具體信息。它可以是一段文本、一張圖片、一首歌曲、一種服務,總之就是一個具體的存在。

1.2 表現層

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

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

1.3 狀態轉化

訪問一個網站,就代表了客戶端和服務器的一個互動過程。在這個過程中,勢必涉及到數據和狀態的變化。

當下的互聯網通信協議HTTP協議,是一個無狀態協議。這意味着,所有的狀態都保存在服務器端。因此,如果客戶端想要操作服務器,必須通過某種手段,讓服務器端發生”狀態轉化”(State Transfer)。而這種轉化是建立在表現層之上的,所以就是”表現層狀態轉化”。

在HTTP協議裏面,就可以使用HTTP動詞來對服務器端資源進行操作,實現「表現層狀態轉化」。如:GET、POST、PUT、DELETE。它們分別對應四種基本操作:GET用來獲取資源,POST用來新建資源(也可以用於更新資源),PUT用來更新資源,DELETE用來刪除資源。

以網站中常見的用戶CRUD操作為例:

現在,我們再回過頭來理解REST(表現層狀態轉化)——REST是一種通過表現層來操作改變資源狀態的軟件架構風格

二、RESTful API

RESTful API 就是REST風格的API。它使用URI來描述資源,使用Html、Json、XML等格式表現,通過HTTP動詞來操作資源來實現狀態轉化,使用HTTP狀態碼反映處理結果

2.1 URI

URI通常由三部分組成:

  1. 訪問資源的命名機制;

  2. 存放資源的主機名;

  3. 資源自身的名稱。

例如://localhost/post/1 (對應URL//localhost/post/1.html)

我們可以這樣解釋它:

  1. 這是一個可以通過https協議訪問的資源,

  2. 位於主機 localhost上,

  3. 通過「post/1」可以對該資源進行唯一標識(注意,這個不一定是完整的路徑)

注意:以上三點只不過是對實例的解釋,以上三點並不是URI的必要條件,URI只是一種概念,怎樣實現無所謂,只要它唯一標識一個資源就可以了。URI只代表資源的實體,不代表它的形式。嚴格地說,如上面網址最後的”.html”後綴名是不必要的,因為這個後綴名表示格式,屬於”表現層”範疇,而URI應該只代表”資源”的位置。

2.2 HTTP動詞

常用的HTTP動詞有下面這些

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

  • POST:在服務器新建一個資源。——非冪等

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

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

  • DELETE:從服務器刪除資源。——冪等

  • HEAD:獲取資源的元數據。

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

2.3 HTTP狀態碼

HTTP協議本身就給我們提供了豐富的狀態碼,以用來反映服務器端處理的結果。而在真正使用中絕大對數人僅僅了解會使用200,404,500之流。這就好比36板斧,你始終是會那三板斧。而RESTful Api規範的HTTP狀態碼的使用,使HTTP協議的優點發揮到了極致。

例如:

  • 200 OK – [GET]:服務器成功返回用戶請求的數據,該操作是冪等的(Idempotent)。
  • 201 CREATED – [POST/PUT/PATCH]:用戶新建或修改數據成功。
  • 202 Accepted – [*]:表示一個請求已經進入後台排隊(異步任務)
  • 204 NO CONTENT – [DELETE]:用戶刪除數據成功。
  • 406 Not Acceptable – [GET]:用戶請求的格式不可得(比如用戶請求JSON格式,但是只有XML格式)。
  • 410 Gone -[GET]:用戶請求的資源被永久刪除,且不會再得到的。
  • 422 Unprocesable entity – [POST/PUT/PATCH] 當創建一個對象時,發生一個驗證錯誤。
  • 500 INTERNAL SERVER ERROR – [*]:服務器發生錯誤,用戶將無法判斷發出的請求是否成功。

注意:當狀態碼是4或5開頭的時候就應該像用戶返回錯誤信息。一般來說,返回的信息中將error作為鍵名,出錯信息作為鍵值即可。

{
    error: "Invalid API key"
}

如下表是常用的HTTP狀態碼和描述

CODE HTTP Operation Body Contents Decription
200 GET,PUT 資源 操作成功
201 POST 資源,元數據 對象創建成功
202 POST,PUT,DELETE,PATCH N/A 請求已被接受
204 DELETE,PUT,PATCH N/A 操作已經執行成功,但是沒有返回結果
301 GET link 資源已被移除
303 GET link 重定向
304 GET N/A 資源沒有被修改
400 GET,POST,PUT,DELETE,PATCH 錯誤提示(消息) 參數列表錯誤(缺少,格式不匹配)
401 GET,POST,PUT,DELETE,PATCH 錯誤提示(消息) 未授權
403 GET,POST,PUT,DELETE,PATCH 錯誤提示(消息) 訪問受限,授權過期
404 GET,POST,PUT,DELETE,PATCH 錯誤提示(消息) 資源,服務未找到
405 GET,POST,PUT,DELETE,PATCH 錯誤提示(消息) 不允許的HTTP方法
409 GET,POST,PUT,DELETE,PATCH 錯誤提示(消息) 資源衝突,或資源被鎖定
415 GET,POST,PUT,DELETE,PATCH 錯誤提示(消息) 不支持的數據(媒體)類型
429 GET,POST,PUT,DELETE,PATCH 錯誤提示(消息) 請求過多被限制
500 GET,POST,PUT,DELETE,PATCH 錯誤提示(消息) 系統內部錯誤
501 GET,POST,PUT,DELETE,PATCH 錯誤提示(消息) 接口未實現

2.4.示例

我們以Web網站中常用的用戶增刪查改為例。設計普通的API接口完成增刪查改大致如下:

//添加用戶
//localhost/createuser
//刪除id為1的用戶
//localhost/deleteuser?userid=1
//獲取用戶列表
//localhost/getuser
//獲取id為1的用戶
//localhost/getuser?userid=1
//更新id為1的用戶
//localhost/updateuser?userid=1

我們通過調用上面不同的url傳遞響應的參數來完成用戶的增刪查改。

而使用RESTful 風格的api該如何完成呢?

在這個例子中很明顯,用戶就是我們的資源,使用uri來描述資源就是

//localhost/user

表現層可以是Json也可以是xml或者其它。

我們使用HTTP的動詞來操作用戶這個資源。

  • 使用GET的方式請求//localhost/user代表查詢用戶列表
  • 使用GET的方式請求//localhost/user/1代表查詢id為1的用戶
  • 使用POST的方式請求//localhost/user代表創建一個用戶
  • 使用PUT的方式請求//localhost/user/1代表修改id為1的用戶
  • 使用DELETE的方式請求//localhost/user/1代表刪除id為1的用戶。

可以看到這種風格看起來要更為優雅與簡潔,面向資源,一目了然,具有自解釋性,充分的發揮了HTTP協議的優點。

2.5 設計上的難點和誤區

2.5.1 URI 包含動詞

因為”資源”表示一種實體,所以應該是名詞,URI不應該有動詞,動詞應該放在HTTP協議中。

舉例來說,某個URI是/posts/show/1,其中show是動詞,這個URI就設計錯了,正確的寫法應該是/posts/1,然後用GET方法表示show。

如果某些動作是HTTP動詞表示不了的,你就應該把動作做成一種資源。比如網上匯款,從賬戶1向賬戶2匯款500元,錯誤的URI是:

POST /accounts/1/transfer/500/to/2

正確的寫法是把動詞transfer改成名詞transaction,資源不能是動詞,但是可以是一種服務:

POST /transaction HTTP/1.1
Host: 127.0.0.1
   
from=1&to=2&amount=500.00
2.5.2 URI中加入版本號

另一個設計誤區,就是在URI中加入版本號

//localhost/app/1.0/foo

//localhost/app/1.1/foo

//localhost/app/2.0/foo

因為不同的版本,可以理解成同一種資源的不同表現形式,所以應該採用同一個URI。版本號可以在HTTP請求頭信息的Accept字段中進行區分(參見Versioning REST Services):

Accept: localhost.foo+json; version=1.0

Accept: localhost.foo+json; version=1.1

Accept: localhostfoo+json; version=2.0
2.5.3 面向資源≠面向單表操作

注意,面向資源不等於面向單表操作。不知道為什麼很多人會把資源對應到數據庫里的單張表。其實他們沒有任何關係。資源可以是一個文件,可以是緩存里的數據,也可以是數據庫里多張表聚合的結果。比如用戶這個資源。通常我們設計數據庫的時候出於性能或範式的考慮用戶的信息不會放在一張表裡。但是在API設計的時候用戶就是一個資源,這個資源的數據有可能來自一張表也有可能是多張表,甚至緩存。

2.5.4 複雜與特殊的場景如何設計

跟萬物皆對象一樣,使用「萬物皆資源」的思想設計實際項目中的API時,經常會遇到一個問題就是「這玩意到底是個什麼資源?………………算了,我就直接寫吧,不管什麼風格了」

  • 比如,登錄(login)和登出(logout)應該怎麼REST化?
  • 比如,多條件複合搜索條件太多在GET里寫不下怎麼辦?
  • 比如,批量資源的操作id躲到URL都寫不下,難道要寫幾千個UPDATE或DELETE?

其實在真正理解了REST後,這些都不是什麼無解的難題,如果無解,那隻能說明你還沒真正理解,抽象成資源的能力還不到家:

  • 登錄(login)和登出(logout)其實本質上只是對session資源的創建和刪除;

    //登錄——使用HTTP POST請求
    POST /localhost/session
    //登出——使用HTTP DELETE請求
    DELETE /localhost/session
    
  • 我們可以把search本身抽象成資源,使用POST創建,如果不需持久化,可以直接在Response中返回結果,如果需要(如翻頁、長期緩存等),直接保存搜索結果並303跳轉到資源地址就行了;

    //HTTP POST創建資源search
    POST /localhost/search
    
  • 批量操作id多到連url都寫不下的請求,應該創建task,用GET返回task狀態甚至執行進度;

    //HTTP POST創建Task 
    POST /localhost/task
    
    //HTTP GET獲取TASK執行結果
    GET /localhost/task
    

2.6 優缺點與適用場景

任何一門技術或者思想都有其優缺點,雖然其誕生的初衷都是為了解決我們的問題,而不是帶來更大的災難。REST同樣如此。它的優點很明顯,優雅、規範,行為和資源分離,更容易理解。

但是也有其缺點,它面向資源,這種設計思路是反程序員直覺的,因為在本地業務代碼中仍然是一個個的函數,是動作,但表現在接口形式上則完全是資源的形式,對於後端開發人員要求高,有些業務邏輯難以被抽象為資源的增刪改查。甚至有些時候RESTful其實是個效率很低的東西,為了實現一個資源,你需要定義它的一套方式,如果要聯合查詢又會要求對其衍生或定義一個新的資源。它提供的接口一般是「粗」粒度的,它通常返回的都是完整的數據模型,難以查詢符合特殊要求的數據,有些特殊的業務要比普通的API需要更多次HTTP請求。

REST面對的疑問跟當年剛開始流行面向對象時的情況是一樣的。它適合很多情況,但並不適合所有情況。它更適合與一些開放平台API,如新浪微博、GitHub、京東、淘寶開放平台等,開放API之所以開放,就是因為它不知道你到底需要什麼返回結果,既然不知道,那麼我乾脆都返回給你,有客戶端自由組合成自己想要的數據。而對於內部開發,有其局限性,內部開發由於需求非常明確,有些時候出於性能或靈活性的考慮,服務端簡單粗暴的丟出來完整的數據模型由客戶端自己處理顯然是不合適的。

對於一些內部的開發,適合用RESTful API的我們仍然可以用,對於一些不合適的,我們仍然可以借鑒一些RESTFul中的優點,來設計我們的API。比如簡潔的URI(每個人看到一坨超長的API地址,內心都是拒絕的),充分利用HTTP狀態碼等。

最後

RESTful API是REST風格的API,它是一種API設計風格,規範了API設計中的一些原則。它讓我們的API更加優雅、規範。但也尤其缺點,在實際使用過程中我們應該充分的取理解它,綜合考量其使用場景。

如果大家想要取學習並使用它,建議可以參考Github開放API 或者Elasticsearch API,看一看他們是如何設計的API,對於自己項目中的每一個場景多思考,去網上一些開源RESTful API找一找有沒有相同場景的例子。

很多人會盲目追新,又對REST的概念和理念一知半解,最後搞出一個半吊子的怪胎,不僅沒有設計出優雅規範的API,甚至還引起了更大的麻煩,最後還自我標榜用了流行的RESTful API。

其實REST規範最終還是為了開發者和軟件產品服務的,如果它能帶來便利、減少混亂,就值得用;反之,如果帶來的麻煩比解決的還多,跟風追流行就不可取了。其它任何技術也是如此!

Tags: