你怎么理解 RESTful

學(xué)習(xí)完整課程請(qǐng)移步 互聯(lián)網(wǎng) Java 全棧工程師

本節(jié)視頻

概述

2000 年涕俗,Roy Thomas Fielding 博士在他那篇著名的博士論文《Architectural Styles and the Design of Network-based Software Architectures》中提出了幾種軟件應(yīng)用的架構(gòu)風(fēng)格熬丧,REST 作為其中的一種架構(gòu)風(fēng)格在這篇論文的第5章中進(jìn)行了概括性的介紹售睹。

REST 是“REpresentational State Transfer”的縮寫(xiě),可以翻譯成“表現(xiàn)狀態(tài)轉(zhuǎn)換”猬错,但是在絕大多數(shù)場(chǎng)合中我們只說(shuō) REST 或者 RESTful。Fielding 在論文中將 REST 定位為“分布式超媒體應(yīng)用(Distributed Hypermedia System)”的架構(gòu)風(fēng)格,它在文中提到一個(gè)名為“HATEOAS(Hypermedia as the engine of application state)”的概念。

我們利用一個(gè)面向最終用戶的 Web 應(yīng)用來(lái)對(duì)這個(gè)概念進(jìn)行簡(jiǎn)單闡述:這里所謂的應(yīng)用狀態(tài)(Application State)表示 Web 應(yīng)用的客戶端的狀態(tài)室埋,簡(jiǎn)單起見(jiàn)可以理解為會(huì)話狀態(tài)。資源在瀏覽器中以超媒體的形式呈現(xiàn)伊约,通過(guò)點(diǎn)擊超媒體中的鏈接可以獲取其它相關(guān)的資源或者對(duì)當(dāng)前資源進(jìn)行相應(yīng)的處理姚淆,獲取的資源或者針對(duì)資源處理的響應(yīng)同樣以超媒體的形式再次呈現(xiàn)在瀏覽器上。由此可見(jiàn)屡律,超媒體成為了驅(qū)動(dòng)客戶端會(huì)話狀態(tài)的轉(zhuǎn)換的引擎腌逢。

借助于超媒體這種特殊的資源呈現(xiàn)方式,應(yīng)用狀態(tài)的轉(zhuǎn)換體現(xiàn)為瀏覽器中呈現(xiàn)資源的轉(zhuǎn)換超埋。如果將超媒體進(jìn)一步抽象成一般意義上的資源呈現(xiàn)(Representation )方式搏讶,那么應(yīng)用狀態(tài)變成了可被呈現(xiàn)的狀態(tài)(REpresentational State)。應(yīng)用狀態(tài)之間的轉(zhuǎn)換就成了可被呈現(xiàn)的狀態(tài)裝換(REpresentational State Transfer)霍殴,這就是 REST媒惕。

REST 是一種很籠統(tǒng)的概念,它代表一種架構(gòu)風(fēng)格来庭。

版本號(hào)

在 RESTful API 中妒蔚,API 接口應(yīng)該盡量兼容之前的版本。但是,在實(shí)際業(yè)務(wù)開(kāi)發(fā)場(chǎng)景中肴盏,可能隨著業(yè)務(wù)需求的不斷迭代科盛,現(xiàn)有的 API 接口無(wú)法支持舊版本的適配,此時(shí)如果強(qiáng)制升級(jí)服務(wù)端的 API 接口將導(dǎo)致客戶端舊有功能出現(xiàn)故障菜皂。實(shí)際上贞绵,Web 端是部署在服務(wù)器,因此它可以很容易為了適配服務(wù)端的新的 API 接口進(jìn)行版本升級(jí)恍飘,然而像 Android 端榨崩、IOS 端、PC 端等其他客戶端是運(yùn)行在用戶的機(jī)器上常侣,因此當(dāng)前產(chǎn)品很難做到適配新的服務(wù)端的 API 接口蜡饵,從而出現(xiàn)功能故障,這種情況下胳施,用戶必須升級(jí)產(chǎn)品到最新的版本才能正常使用溯祸。

為了解決這個(gè)版本不兼容問(wèn)題,在設(shè)計(jì) RESTful API 的一種實(shí)用的做法是使用版本號(hào)舞肆。一般情況下焦辅,我們會(huì)在 url 中保留版本號(hào),并同時(shí)兼容多個(gè)版本椿胯。

【GET】  /v1/users/{user_id}  // 版本 v1 的查詢用戶列表的 API 接口
【GET】  /v2/users/{user_id}  // 版本 v2 的查詢用戶列表的 API 接口

現(xiàn)在筷登,我們可以不改變版本 v1 的查詢用戶列表的 API 接口的情況下,新增版本 v2 的查詢用戶列表的 API 接口以滿足新的業(yè)務(wù)需求哩盲,此時(shí)前方,客戶端的產(chǎn)品的新功能將請(qǐng)求新的服務(wù)端的 API 接口地址。雖然服務(wù)端會(huì)同時(shí)兼容多個(gè)版本廉油,但是同時(shí)維護(hù)太多版本對(duì)于服務(wù)端而言是個(gè)不小的負(fù)擔(dān)惠险,因?yàn)榉?wù)端要維護(hù)多套代碼。這種情況下抒线,常見(jiàn)的做法不是維護(hù)所有的兼容版本班巩,而是只維護(hù)最新的幾個(gè)兼容版本,例如維護(hù)最新的三個(gè)兼容版本嘶炭。在一段時(shí)間后抱慌,當(dāng)絕大多數(shù)用戶升級(jí)到較新的版本后,廢棄一些使用量較少的服務(wù)端的老版本API 接口版本眨猎,并要求使用產(chǎn)品的非常舊的版本的用戶強(qiáng)制升級(jí)抑进。

注意的是,“不改變版本 v1 的查詢用戶列表的 API 接口”主要指的是對(duì)于客戶端的調(diào)用者而言它看起來(lái)是沒(méi)有改變睡陪。而實(shí)際上寺渗,如果業(yè)務(wù)變化太大夕凝,服務(wù)端的開(kāi)發(fā)人員需要對(duì)舊版本的 API 接口使用適配器模式將請(qǐng)求適配到新的API 接口上。

資源路徑

RESTful API 的設(shè)計(jì)以資源為核心户秤,每一個(gè) URI 代表一種資源。因此逮矛,URI 不能包含動(dòng)詞鸡号,只能是名詞。注意的是须鼎,形容詞也是可以使用的鲸伴,但是盡量少用。一般來(lái)說(shuō)晋控,不論資源是單個(gè)還是多個(gè)汞窗,API 的名詞要以復(fù)數(shù)進(jìn)行命名。此外赡译,命名名詞的時(shí)候仲吏,要使用小寫(xiě)、數(shù)字及下劃線來(lái)區(qū)分多個(gè)單詞蝌焚。這樣的設(shè)計(jì)是為了與 json 對(duì)象及屬性的命名方案保持一致裹唆。例如,一個(gè)查詢系統(tǒng)標(biāo)簽的接口可以進(jìn)行如下設(shè)計(jì)只洒。

【GET】  /v1/tags/{tag_id} 

同時(shí)许帐,資源的路徑應(yīng)該從根到子依次如下

/{resources}/{resource_id}/{sub_resources}/{sub_resource_id}/{sub_resource_property}

我們來(lái)看一個(gè)“添加用戶的角色”的設(shè)計(jì),其中“用戶”是主資源毕谴,“角色”是子資源成畦。

【POST】  /v1/users/{user_id}/roles/{role_id} // 添加用戶的角色

有的時(shí)候,當(dāng)一個(gè)資源變化難以使用標(biāo)準(zhǔn)的 RESTful API 來(lái)命名涝开,可以考慮使用一些特殊的 actions 命名循帐。

/{resources}/{resource_id}/actions/{action}

舉個(gè)例子,“密碼修改”這個(gè)接口的命名很難完全使用名詞來(lái)構(gòu)建路徑忠寻,此時(shí)可以引入 action 命名惧浴。

【PUT】  /v1/users/{user_id}/password/actions/modify // 密碼修改

請(qǐng)求方式

可以通過(guò) GET、 POST奕剃、 PUT衷旅、 PATCH、 DELETE 等方式對(duì)服務(wù)端的資源進(jìn)行操作纵朋。其中:

  • GET:用于查詢資源
  • POST:用于創(chuàng)建資源
  • PUT:用于更新服務(wù)端的資源的全部信息
  • PATCH:用于更新服務(wù)端的資源的部分信息
  • DELETE:用于刪除服務(wù)端的資源柿顶。

這里,使用“用戶”的案例進(jìn)行回顧通過(guò) GET操软、 POST嘁锯、 PUT、 PATCH、 DELETE 等方式對(duì)服務(wù)端的資源進(jìn)行操作家乘。

【GET】          /users                # 查詢用戶信息列表
【GET】          /users/1001           # 查看某個(gè)用戶信息
【POST】         /users                # 新建用戶信息
【PUT】          /users/1001           # 更新用戶信息(全部字段)
【PATCH】        /users/1001           # 更新用戶信息(部分字段)
【DELETE】       /users/1001           # 刪除用戶信息

查詢參數(shù)

RESTful API 接口應(yīng)該提供參數(shù)蝗羊,過(guò)濾返回結(jié)果。其中仁锯,offset 指定返回記錄的開(kāi)始位置耀找。一般情況下,它會(huì)結(jié)合 limit 來(lái)做分頁(yè)的查詢业崖,這里 limit 指定返回記錄的數(shù)量野芒。

【GET】  /{version}/{resources}/{resource_id}?offset=0&limit=20

同時(shí),orderby 可以用來(lái)排序双炕,但僅支持單個(gè)字符的排序狞悲,如果存在多個(gè)字段排序,需要業(yè)務(wù)中擴(kuò)展其他參數(shù)進(jìn)行支持妇斤。

【GET】  /{version}/{resources}/{resource_id}?orderby={field} [asc|desc]

為了更好地選擇是否支持查詢總數(shù)摇锋,我們可以使用 count 字段,count 表示返回?cái)?shù)據(jù)是否包含總條數(shù)站超,它的默認(rèn)值為 false乱投。

【GET】  /{version}/{resources}/{resource_id}?count=[true|false]

上面介紹的 offset、 limit顷编、 orderby 是一些公共參數(shù)戚炫。此外,業(yè)務(wù)場(chǎng)景中還存在許多個(gè)性化的參數(shù)媳纬。我們來(lái)看一個(gè)例子双肤。

【GET】  /v1/categorys/{category_id}/apps/{app_id}?enable=[1|0]&os_type={field}&device_ids={field,field,…}

注意的是,不要過(guò)度設(shè)計(jì)钮惠,只返回用戶需要的查詢參數(shù)茅糜。此外,需要考慮是否對(duì)查詢參數(shù)創(chuàng)建數(shù)據(jù)庫(kù)索引以提高查詢性能素挽。

狀態(tài)碼

使用適合的狀態(tài)碼很重要蔑赘,而不應(yīng)該全部都返回狀態(tài)碼 200,或者隨便亂使用预明。這里缩赛,列舉在實(shí)際開(kāi)發(fā)過(guò)程中常用的一些狀態(tài)碼,以供參考撰糠。

狀態(tài)碼 描述
200 請(qǐng)求成功
201 創(chuàng)建成功
400 錯(cuò)誤的請(qǐng)求
401 未驗(yàn)證
403 被拒絕
404 無(wú)法找到
409 資源沖突
500 服務(wù)器內(nèi)部錯(cuò)誤

異常響應(yīng)

當(dāng) RESTful API 接口出現(xiàn)非 2xx 的 HTTP 錯(cuò)誤碼響應(yīng)時(shí)酥馍,采用全局的異常結(jié)構(gòu)響應(yīng)信息。

HTTP/1.1 400 Bad Request
Content-Type: application/json
{
    "code": "INVALID_ARGUMENT",
    "message": "{error message}",
    "cause": "{cause message}",
    "request_id": "01234567-89ab-cdef-0123-456789abcdef",
    "host_id": "{server identity}",
    "server_time": "2014-01-01T12:00:00Z"
}

請(qǐng)求參數(shù)

在設(shè)計(jì)服務(wù)端的 RESTful API 的時(shí)候阅酪,我們還需要對(duì)請(qǐng)求參數(shù)進(jìn)行限制說(shuō)明旨袒。例如一個(gè)支持批量查詢的接口汁针,我們要考慮最大支持查詢的數(shù)量。

【GET】     /v1/users/batch?user_ids=1001,1002      // 批量查詢用戶信息
參數(shù)說(shuō)明
- user_ids: 用戶ID串砚尽,最多允許 20 個(gè)施无。

此外,在設(shè)計(jì)新增或修改接口時(shí)必孤,我們還需要在文檔中明確告訴調(diào)用者哪些參數(shù)是必填項(xiàng)帆精,哪些是選填項(xiàng),以及它們的邊界值的限制隧魄。

【POST】     /v1/users                             // 創(chuàng)建用戶信息
請(qǐng)求內(nèi)容
{
    "username": "lusifer",                 // 必填, 用戶名稱, max 10
    "realname": "魯斯菲爾",               // 必填, 用戶名稱, max 10
    "password": "123456",              // 必填, 用戶密碼, max 32
    "email": "topsale@vip.qq.com",     // 選填, 電子郵箱, max 32
    "weixin": "Lusifer",            // 選填,微信賬號(hào), max 32
    "sex": 1                           // 必填, 用戶性別[1-男 2-女 99-未知]
}

響應(yīng)參數(shù)

針對(duì)不同操作隘蝎,服務(wù)端向用戶返回的結(jié)果應(yīng)該符合以下規(guī)范购啄。

【GET】     /{version}/{resources}/{resource_id}      // 返回單個(gè)資源對(duì)象
【GET】     /{version}/{resources}                    // 返回資源對(duì)象的列表
【POST】    /{version}/{resources}                    // 返回新生成的資源對(duì)象
【PUT】     /{version}/{resources}/{resource_id}      // 返回完整的資源對(duì)象
【PATCH】   /{version}/{resources}/{resource_id}      // 返回完整的資源對(duì)象
【DELETE】  /{version}/{resources}/{resource_id}      // 狀態(tài)碼 200,返回完整的資源對(duì)象嘱么。
                                                      // 狀態(tài)碼 204狮含,返回一個(gè)空文檔

如果是單條數(shù)據(jù),則返回一個(gè)對(duì)象的 JSON 字符串曼振。

HTTP/1.1 200 OK
{
    "id" : "01234567-89ab-cdef-0123-456789abcdef",
    "name" : "example",
    "created_time": 1496676420000,
    "updated_time": 1496676420000,
    ...
}

如果是列表數(shù)據(jù)几迄,則返回一個(gè)封裝的結(jié)構(gòu)體。

HTTP/1.1 200 OK
{
    "count":100,
    "items":[
        {
            "id" : "01234567-89ab-cdef-0123-456789abcdef",
            "name" : "example",
            "created_time": 1496676420000,
            "updated_time": 1496676420000,
            ...
        },
        ...
    ]
}

一個(gè)完整的案例

最后冰评,我們使用一個(gè)完整的案例將前面介紹的知識(shí)整合起來(lái)映胁。這里,使用“獲取用戶列表”的案例甲雅。

【GET】     /v1/users?[&keyword=xxx][&enable=1][&offset=0][&limit=20] 獲取用戶列表
功能說(shuō)明:獲取用戶列表
請(qǐng)求方式:GET
參數(shù)說(shuō)明
- keyword: 模糊查找的關(guān)鍵字解孙。[選填]
- enable: 啟用狀態(tài)[1-啟用 2-禁用]。[選填]
- offset: 獲取位置偏移抛人,從 0 開(kāi)始弛姜。[選填]
- limit: 每次獲取返回的條數(shù),缺省為 20 條妖枚,最大不超過(guò) 100廷臼。 [選填]
響應(yīng)內(nèi)容
HTTP/1.1 200 OK
{
    "count":100,
    "items":[
        {
            "id" : "01234567-89ab-cdef-0123-456789abcdef",
            "name" : "example",
            "created_time": 1496676420000,
            "updated_time": 1496676420000,
            ...
        },
        ...
    ]
}
失敗響應(yīng)
HTTP/1.1 403 UC/AUTH_DENIED
Content-Type: application/json
{
    "code": "INVALID_ARGUMENT",
    "message": "{error message}",
    "cause": "{cause message}",
    "request_id": "01234567-89ab-cdef-0123-456789abcdef",
    "host_id": "{server identity}",
    "server_time": "2014-01-01T12:00:00Z"
}
錯(cuò)誤代碼
- 403 UC/AUTH_DENIED    授權(quán)受限
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市绝页,隨后出現(xiàn)的幾起案子荠商,更是在濱河造成了極大的恐慌,老刑警劉巖续誉,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件结啼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡屈芜,警方通過(guò)查閱死者的電腦和手機(jī)郊愧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)朴译,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人属铁,你說(shuō)我怎么就攤上這事眠寿。” “怎么了焦蘑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵盯拱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我例嘱,道長(zhǎng)狡逢,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任拼卵,我火速辦了婚禮奢浑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘腋腮。我一直安慰自己雀彼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布即寡。 她就那樣靜靜地躺著徊哑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪聪富。 梳的紋絲不亂的頭發(fā)上莺丑,一...
    開(kāi)封第一講書(shū)人閱讀 51,208評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音墩蔓,去河邊找鬼窒盐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛钢拧,可吹牛的內(nèi)容都是我干的蟹漓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼源内,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼葡粒!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起膜钓,我...
    開(kāi)封第一講書(shū)人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤嗽交,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后颂斜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體夫壁,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年沃疮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了盒让。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梅肤。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖邑茄,靈堂內(nèi)的尸體忽然破棺而出姨蝴,到底是詐尸還是另有隱情,我是刑警寧澤肺缕,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布左医,位于F島的核電站,受9級(jí)特大地震影響同木,放射性物質(zhì)發(fā)生泄漏浮梢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一彤路、第九天 我趴在偏房一處隱蔽的房頂上張望秕硝。 院中可真熱鬧,春花似錦斩萌、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至霎苗,卻和暖如春姆吭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背唁盏。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工内狸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厘擂。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓昆淡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親刽严。 傳聞我的和親對(duì)象是個(gè)殘疾皇子昂灵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容