1. 程式人生 > >理解RESTful API

理解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操作為例: ![](https://hunter-image.oss-cn-beijing.aliyuncs.com/REST/RESTful%20API.png) 現在,我們再回過頭來理解REST(表現層狀態轉化)——REST是一種通過**表現層**來操作改變**資源**的**狀態**的軟體架構**風格**。 ### 二、RESTful API RESTful API 就是REST風格的API。它**使用URI來描述資源,使用Html、Json、XML等格式表現,通過HTTP動詞來操作資源來實現狀態轉化,使用HTTP狀態碼反映處理結果**。 #### 2.1 URI URI通常由三部分組成: 1. 訪問資源的命名機制; 2. 存放資源的主機名; 3. 資源自身的名稱。 例如:https://localhost/post/1 (對應URLhttps://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介面完成增刪查改大致如下: ``` //新增使用者 http://localhost/createuser //刪除id為1的使用者 http://localhost/deleteuser?userid=1 //獲取使用者列表 http://localhost/getuser //獲取id為1的使用者 http://localhost/getuser?userid=1 //更新id為1的使用者 http://localhost/updateuser?userid=1 ``` 我們通過呼叫上面不同的url傳遞響應的引數來完成使用者的增刪查改。 而使用RESTful 風格的api該如何完成呢? 在這個例子中很明顯,使用者就是我們的資源,使用uri來描述資源就是 ``` http://localhost/user ``` 表現層可以是Json也可以是xml或者其它。 我們使用HTTP的動詞來操作使用者這個資源。 * 使用GET的方式請求`http://localhost/user`代表查詢使用者列表 * 使用GET的方式請求`http://localhost/user/1`代表查詢id為1的使用者 * 使用POST的方式請求`http://localhost/user`代表建立一個使用者 * 使用PUT的方式請求`http://localhost/user/1`代表修改id為1的使用者 * 使用DELETE的方式請求`http://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中加入版本號**: ``` http://localhost/app/1.0/foo http://localhost/app/1.1/foo http://localhost/app/2.0/foo ``` 因為不同的版本,可以理解成同一種資源的不同表現形式,所以應該採用同一個URI。版本號可以在HTTP請求頭資訊的Accept欄位中進行區分(參見[Versioning REST Services](http://www.informit.com/articles/article.aspx?p=1566460)): ``` 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](https://developer.github.com/v3/) 或者[Elasticsearch API](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs.html),看一看他們是如何設計的API,對於自己專案中的每一個場景多思考,去網上一些開源RESTful API找一找有沒有相同場景的例子。 很多人會盲目追新,又對REST的概念和理念一知半解,最後搞出一個半吊子的怪胎,不僅沒有設計出優雅規範的API,甚至還引起了更大的麻煩,最後還自我標榜用了流行的RESTful API。 其實REST規範最終還是為了開發者和軟體產品服務的,如果它能帶來便利、減少混亂,就值得用;反之,如果帶來的麻煩比解決的還多,跟風追流行就不可取了。其它任何技術也是如此!