簡(jiǎn)介
隨著移動(dòng)開(kāi)發(fā)和前端開(kāi)發(fā)的崛起,越來(lái)越多的 Web 后端應(yīng)用都傾向于實(shí)現(xiàn) Restful API。
Restful API 是一個(gè)簡(jiǎn)單易用的前后端分離方案,它只需要對(duì)客戶(hù)端請(qǐng)求進(jìn)行處理,然后返回結(jié)果即可, 無(wú)需考慮頁(yè)面渲染,一定程度上減輕了后端開(kāi)發(fā)人員的負(fù)擔(dān)。
然而,正是由于 Restful API 不需要考慮頁(yè)面渲染,導(dǎo)致它不能在頁(yè)面上展示錯(cuò)誤信息。
那就意著當(dāng)出現(xiàn)錯(cuò)誤的時(shí)候,它只能通過(guò)返回一個(gè)錯(cuò)誤的響應(yīng),來(lái)告訴用戶(hù)和開(kāi)發(fā)者相應(yīng)的錯(cuò)誤信息,提示他們接下來(lái)應(yīng)該怎么辦。
本文將討論 Restful API 中的錯(cuò)誤處理方案。
當(dāng) Restful API 需要拋出錯(cuò)誤的時(shí)候,我們要考慮的是:這個(gè)錯(cuò)誤應(yīng)該包含哪些信息。
我們先看看 Github, Google, Facebook, Twitter, Twilio 的錯(cuò)誤信息是怎樣的。
Github (use http status)
{ "message": "Validation Failed", "errors": [ { "resource": "Issue", "field": "title", "code": "missing_field" } ] }
Google (use http status)
{ "error": { "errors": [ { "domain": "global", "reason": "insufficientFilePermissions", "message": "The user does not have sufficient permissions for file {fileId}." } ], "code": 403, "message": "The user does not have sufficient permissions for file {fileId}." } }
Facebook (use http status)
{ "error": { "message": "Message describing the error", "type": "OAuthException", "code": 190, "error_subcode": 460, "error_user_title": "A title", "error_user_msg": "A message", "fbtrace_id": "EJplcsCHuLu" } }
Twitter (use http status)
{ "errors": [ { "message": "Sorry, that page does not exist", "code": 34 } ] }
Twilio (use http status)
{ "code": 21211, "message": "The "To" number 5551234567 is not a valid phone number.", "more_info": "https://www.twilio.com/docs/errors/21211", "status": 400 }
觀察這些結(jié)構(gòu)可以發(fā)現(xiàn)它們都有一些共同的地方:
都利用了 Http 狀態(tài)碼
有些返回了業(yè)務(wù)錯(cuò)誤碼
都提供了給用戶(hù)看的錯(cuò)誤提示信息
有些提供了給開(kāi)發(fā)者看的錯(cuò)誤信息
Http 狀態(tài)碼在 Restful API 中利用 Http 狀態(tài)碼來(lái)表明錯(cuò)誤類(lèi)型再合適不過(guò)了,因?yàn)?Http 狀態(tài)碼定義了很多抽象的錯(cuò)誤類(lèi)型。
雖然 Http 狀態(tài)碼定義了非常多的錯(cuò)誤類(lèi)型,但實(shí)際應(yīng)用中,我們常用的狀態(tài)碼并不多,通常都是下面這幾方面:
API 正常工作 (200, 201)
客戶(hù)端錯(cuò)誤 (400, 401, 403, 404)
服務(wù)端錯(cuò)誤 (500, 503)
業(yè)務(wù)錯(cuò)誤碼很多時(shí)候,我們根據(jù)業(yè)務(wù)類(lèi)型來(lái)自定義錯(cuò)誤碼。
這些業(yè)務(wù)錯(cuò)誤碼與 Http 狀態(tài)碼并不重疊,這時(shí)候我們可以返回業(yè)務(wù)錯(cuò)誤碼,用來(lái)提示用戶(hù)/開(kāi)發(fā)者錯(cuò)誤類(lèi)型。
當(dāng)出現(xiàn)錯(cuò)誤的時(shí)候,我們需要提示用戶(hù)如何處理這種情況,通常這種錯(cuò)誤信息都是必須的。
可以看到上面幾個(gè)例子中都有返回給用戶(hù)看的錯(cuò)誤信息。
若我們的 API 需要開(kāi)放給第三方開(kāi)發(fā)者,那么我們就需要考慮返回一些給開(kāi)發(fā)者看的錯(cuò)誤信息。
設(shè)計(jì)錯(cuò)誤類(lèi)型我們剛才提到過(guò),可以利用 Http 狀態(tài)碼來(lái)為錯(cuò)誤類(lèi)型進(jìn)行分類(lèi)。
通常我們所說(shuō)的分類(lèi)通常是對(duì)客戶(hù)端錯(cuò)誤進(jìn)行分類(lèi), 即 4xx 類(lèi)型的錯(cuò)誤。
而這些錯(cuò)誤類(lèi)型中,我們最常用的是:
400 Bad Request
由于包含語(yǔ)法錯(cuò)誤,當(dāng)前請(qǐng)求無(wú)法被服務(wù)器理解。除非進(jìn)行修改,否則客戶(hù)端不應(yīng)該重復(fù)提交這個(gè)請(qǐng)求。
通常在請(qǐng)求參數(shù)不合法或格式錯(cuò)誤的時(shí)候可以返回這個(gè)狀態(tài)碼。
401 Unauthorized
當(dāng)前請(qǐng)求需要用戶(hù)驗(yàn)證。
通常在沒(méi)有登錄的狀態(tài)下訪問(wèn)一些受保護(hù)的 API 時(shí)會(huì)用到這個(gè)狀態(tài)碼。
403 Forbidden
服務(wù)器已經(jīng)理解請(qǐng)求,但是拒絕執(zhí)行它。與401響應(yīng)不同的是,身份驗(yàn)證并不能提供任何幫助。
通常在沒(méi)有權(quán)限操作資源時(shí)(如修改/刪除一個(gè)不屬于該用戶(hù)的資源時(shí))會(huì)用到這個(gè)狀態(tài)碼。
404 Not Found
請(qǐng)求失敗,請(qǐng)求所希望得到的資源未被在服務(wù)器上發(fā)現(xiàn)。
通常在找不到資源時(shí)返回這個(gè)狀態(tài)碼。
盡管我們可以通過(guò) Http 狀態(tài)碼來(lái)表示錯(cuò)誤的類(lèi)型,
但在實(shí)際應(yīng)用中,如果僅僅使用 Http 狀態(tài)碼的話,我們的代碼中就遍布 Http 狀態(tài)碼:
// Node.js if (!res.body.title) { res.statusCode = 400 } if (!user) { res.statusCode = 401 } if (!post) { res.statusCode = 404 }
上面的實(shí)現(xiàn)方式在小項(xiàng)目中還可以接受,當(dāng)項(xiàng)目變大、需求變多的時(shí)候,維護(hù)起來(lái)就變得很麻煩了。
為了提高錯(cuò)誤的可讀性和可維護(hù)性,我們需要對(duì)各種錯(cuò)誤進(jìn)行分類(lèi)。
我個(gè)人習(xí)慣把錯(cuò)誤分成以下幾種類(lèi)型:
格式錯(cuò)誤 (FORMAT_INVALID)
數(shù)據(jù)不存在 (DATA_NOT_FOUND)
數(shù)據(jù)已存在 (DATA_EXISTED)
數(shù)據(jù)無(wú)效 (DATA_INVALID)
登錄錯(cuò)誤 (LOGIN_REQUIRED)
權(quán)限不足 (PERMISSION_DENIED)
錯(cuò)誤分類(lèi)之后,我們拋錯(cuò)誤的時(shí)候就變得更加直觀了:
if (!res.body.title) { throw new Error(ERROR.FORMAT_INVALID) } if (!user) { throw new Error(ERROR.LOGIN_REQUIRED) } if (!post) { throw new Error(ERROR.DATA_NOT_FOUND) } if (post.creator.id !== user.id) { throw new Error(ERROR.PERMISSION_DENIED) }
這種形式比上面的寫(xiě)死狀態(tài)碼的方式方便很多,而且維護(hù)起來(lái)也更加簡(jiǎn)單。
但有一個(gè)問(wèn)題,就是不能根據(jù)錯(cuò)誤類(lèi)型來(lái)返回指定的錯(cuò)誤信息。
要實(shí)現(xiàn)根據(jù)錯(cuò)誤類(lèi)型來(lái)返回指定的錯(cuò)誤信息,我們可以通過(guò)自定義錯(cuò)誤的方式來(lái)實(shí)現(xiàn)。
假設(shè)我們自定義錯(cuò)誤的結(jié)構(gòu)如下:
{ "type": "", "code": 0, "message": "", "detail": "" }
我們需要做到如下幾點(diǎn):
根據(jù)錯(cuò)誤類(lèi)型來(lái)自動(dòng)設(shè)置 type, code, message
detail 為可選項(xiàng),用來(lái)描述該錯(cuò)誤的具體原因
const ERROR = { FORMAT_INVALID: "FORMAT_INVALID", DATA_NOT_FOUND: "DATA_NOT_FOUND", DATA_EXISTED: "DATA_EXISTED", DATA_INVALID: "DATA_INVALID", LOGIN_REQUIRED: "LOGIN_REQUIRED", PERMISSION_DENIED: "PERMISSION_DENIED" } const ERROR_MAP = { FORMAT_INVALID: { code: 1, message: "The request format is invalid" }, DATA_NOT_FOUND: { code: 2, message: "The data is not found in database" }, DATA_EXISTED: { code: 3, message: "The data has exist in database" }, DATA_INVALID: { code: 4, message: "The data is invalid" }, LOGIN_REQUIRED: { code 5, message: "Please login first" }, PERMISSION_DENIED: { code: 6, message: "You have no permission to operate" } } class CError extends Error { constructor(type, detail) { super() Error.captureStackTrace(this, this.constructor) let error = ERROR_MAP[type] if (!error) { error = { code: 999, message: "Unknow error type" } } this.name = "CError" this.type = error.code !== 999 ? type : "UNDEFINED" this.code = error.code this.message = error.message this.detail = detail } }
自定義好錯(cuò)誤之后,我們調(diào)用起來(lái)就更加簡(jiǎn)單了:
// in controller if (!user) { throw new CError(ERROR.LOGIN_REQUIRED, "You should login first") } if (!req.body.title) { throw new CError(ERROR.FORMAT_INVALID, "Title is required") } if (!post) { throw new CError(ERROR.DATA_NOT_FOUND, "The post you required is not found") }
最后,還剩下一個(gè)問(wèn)題,根據(jù)錯(cuò)誤類(lèi)型來(lái)設(shè)置狀態(tài)碼,然后返回錯(cuò)誤信息給客戶(hù)端。
捕獲錯(cuò)誤信息在 Controller 中拋出自定義錯(cuò)誤后,我們需要捕獲該錯(cuò)誤,才能返回給客戶(hù)端。
假設(shè)我們使用 koa 2 作為 web 框架來(lái)開(kāi)發(fā) restful api,那么我們要做的是添加錯(cuò)誤處理的中間件:
module.exports = async function errorHandler (ctx, next) { try { await next() } catch (err) { let status switch (err.type) { case ERROR.FORMAT_INVALID: case ERROR.DATA_EXISTED: case ERROR.DATA_INVALID: status = 400 break case ERROR.LOGIN_REQUIRED: status = 401 case ERROR.PERMISSION_DENIED: status = 403 case ERROR.DATA_NOT_FOUND: status = 404 break default: status = 500 } ctx.status = status ctx.body = err } } // in app.js app.use(errorHandler) app.use(router.routes())
通過(guò)這種方式,我們就能優(yōu)雅地處理 Restful API 中的錯(cuò)誤信息了。
參考資料https://zh.wikipedia.org/zh-hans/HTTP%E7%8A%B6%E6%80%81%E7%A0%81
https://www.loggly.com/blog/n...
http://blog.restcase.com/rest...
https://apigee.com/about/blg/...
http://stackoverflow.com/ques...
http://goldbergyoni.com/check...
http://blogs.mulesoft.com/dev...
https://developers.facebook.c...
https://developers.google.com...
https://developer.github.com/...
https://dev.twitter.com/overv...
https://www.twilio.com/docs/a...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/75712.html
摘要:返回值結(jié)構(gòu)在完成了上面的部署之后,接下來(lái)我們來(lái)看看返回結(jié)果應(yīng)該怎么樣來(lái)確定。因?yàn)榉祷刂抵?,我們常常要?duì)數(shù)據(jù)進(jìn)行區(qū)分分組,或者按照從屬關(guān)系打包,所以,我們?cè)俜祷貢r(shí),最好有包裹的思想,把數(shù)據(jù)存放在不同的包裹中進(jìn)行返回。 在項(xiàng)目中,需要為APP撰寫(xiě)API。剛開(kāi)始接觸的時(shí)候,并沒(méi)有考慮太多,就想提供URL,APP端通過(guò)該URL進(jìn)行查詢(xún)、創(chuàng)建、更新等操作即可。但再對(duì)相關(guān)規(guī)范進(jìn)行了解后,才發(fā)現(xiàn),A...
摘要:指定篩選條件選擇合適的狀態(tài)碼應(yīng)答中,需要帶一個(gè)很重要的字段。返回結(jié)果針對(duì)不同操作,服務(wù)器向用戶(hù)返回的結(jié)果應(yīng)該符合以下規(guī)范。如果狀態(tài)碼是,就應(yīng)該向用戶(hù)返回出錯(cuò)信息。 什么是 RESTful 什么是REST REST(英文:Representational State Transfer,又稱(chēng)具象狀態(tài)傳輸)是Roy Thomas Fielding博士于2000年在他的博士論文 中提出來(lái)的一種...
摘要:勵(lì)以最少的安裝方式進(jìn)行最佳實(shí)踐。上面的例子接收了一個(gè)對(duì)象并準(zhǔn)備將其序列化。裝飾器會(huì)通過(guò)進(jìn)行轉(zhuǎn)換。從對(duì)象中提取的唯一字段是。是一個(gè)特殊的字段,它接受端點(diǎn)名稱(chēng)并為響應(yīng)中的端點(diǎn)生成一個(gè)。可以查看項(xiàng)查看完整列表。 大綱 簡(jiǎn)介 安裝 快速入門(mén) 一個(gè)最小的 api 例子 資源豐富的路由 端點(diǎn) 參數(shù)解析 數(shù)據(jù)格式化 完整 TODO 應(yīng)用例子 簡(jiǎn)介 Flask-RESTful是一個(gè)Flas...
摘要:和的區(qū)別方法注解作用于級(jí)別注解為一個(gè)定義一個(gè)異常處理器類(lèi)注解作用于整個(gè)工程注解定義了一個(gè)全局的異常處理器需要注意的是的優(yōu)先級(jí)比高即拋出的異常如果既可以讓標(biāo)注的方法處理又可以讓標(biāo)注的類(lèi)中的方法處理則優(yōu)先讓標(biāo)注的方法處理處理中的異常為了方便地展 @ControllerAdvice 和 @ExceptionHandler 的區(qū)別 ExceptionHandler, 方法注解, 作用于 Co...
摘要:通常情況下,偽都是基于第一層次與第二層次設(shè)計(jì)的。為了解決這個(gè)版本不兼容問(wèn)題,在設(shè)計(jì)的一種實(shí)用的做法是使用版本號(hào)。例如,建議第三位版本號(hào)通常表示兼容升級(jí),只有不兼容時(shí)才需要變更服務(wù)版本。 原文地址:梁桂釗的博客 博客地址:blog.720ui.com 歡迎關(guān)注公眾號(hào):「服務(wù)端思維」。一群同頻者,一起成長(zhǎng),一起精進(jìn),打破認(rèn)知的局限性。 有一段時(shí)間沒(méi)怎么寫(xiě)文章了,今天提筆寫(xiě)一篇自己對(duì) API 設(shè)...
閱讀 3501·2021-11-18 10:07
閱讀 1597·2021-11-04 16:08
閱讀 1527·2021-11-02 14:43
閱讀 1099·2021-10-09 09:59
閱讀 854·2021-09-08 10:43
閱讀 1092·2021-09-07 09:59
閱讀 977·2019-12-27 11:56
閱讀 1039·2019-08-30 15:56