简介

随着移动开发和前端开发的崛起,越来越多的 Web 后端应用都倾向于实现 Restful API。

Restful API 是一个简单易用的前后端分离方案,它只需要对客户端请求进行处理,然后返回结果即可, 无需考虑页面渲染,一定程度上减轻了后端开发人员的负担。

然而,正是由于 Restful API 不需要考虑页面渲染,导致它不能在页面上展示错误信息。

那就意着当出现错误的时候,它只能通过返回一个错误的响应,来告诉用户和开发者相应的错误信息,提示他们接下来应该怎么办。

本文将讨论 Restful API 中的错误处理方案。

设计错误信息

当 Restful API 需要抛出错误的时候,我们要考虑的是:这个错误应该包含哪些信息。

我们先看看 Github, Google, Facebook, Twitter, Twilio 的错误信息是怎样的。

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
}

观察这些结构可以发现它们都有一些共同的地方:

  • 都利用了 Http 状态码
  • 有些返回了业务错误码
  • 都提供了给用户看的错误提示信息
  • 有些提供了给开发者看的错误信息

Http 状态码

在 Restful API 中利用 Http 状态码来表明错误类型再合适不过了,因为 Http 状态码定义了很多抽象的错误类型。

虽然 Http 状态码定义了非常多的错误类型,但实际应用中,我们常用的状态码并不多,通常都是下面这几方面:

  • API 正常工作 (200, 201)
  • 客户端错误 (400, 401, 403, 404)
  • 服务端错误 (500, 503)

业务错误码

很多时候,我们根据业务类型来自定义错误码。

这些业务错误码与 Http 状态码并不重叠,这时候我们可以返回业务错误码,用来提示用户/开发者错误类型。

给用户看的错误信息

当出现错误的时候,我们需要提示用户如何处理这种情况,通常这种错误信息都是必须的。

可以看到上面几个例子中都有返回给用户看的错误信息。

给开发者看的错误信息

若我们的 API 需要开放给第三方开发者,那么我们就需要考虑返回一些给开发者看的错误信息。

设计错误类型

我们刚才提到过,可以利用 Http 状态码来为错误类型进行分类。

通常我们所说的分类通常是对客户端错误进行分类, 即 4xx 类型的错误。

而这些错误类型中,我们最常用的是:

  • 400 Bad Request

    由于包含语法错误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。

    通常在请求参数不合法或格式错误的时候可以返回这个状态码。
  • 401 Unauthorized

    当前请求需要用户验证。

    通常在没有登录的状态下访问一些受保护的 API 时会用到这个状态码。
  • 403 Forbidden

    服务器已经理解请求,但是拒绝执行它。与401响应不同的是,身份验证并不能提供任何帮助。

    通常在没有权限操作资源时(如修改/删除一个不属于该用户的资源时)会用到这个状态码。
  • 404 Not Found

    请求失败,请求所希望得到的资源未被在服务器上发现。

    通常在找不到资源时返回这个状态码。

尽管我们可以通过 Http 状态码来表示错误的类型,

但在实际应用中,如果仅仅使用 Http 状态码的话,我们的代码中就遍布 Http 状态码:

// Node.js
if (!res.body.title) {
res.statusCode = 400
} if (!user) {
res.statusCode = 401
} if (!post) {
res.statusCode = 404
}

上面的实现方式在小项目中还可以接受,当项目变大、需求变多的时候,维护起来就变得很麻烦了。

为了提高错误的可读性和可维护性,我们需要对各种错误进行分类。

我个人习惯把错误分成以下几种类型:

  • 格式错误 (FORMAT_INVALID)
  • 数据不存在 (DATA_NOT_FOUND)
  • 数据已存在 (DATA_EXISTED)
  • 数据无效 (DATA_INVALID)
  • 登录错误 (LOGIN_REQUIRED)
  • 权限不足 (PERMISSION_DENIED)

错误分类之后,我们抛错误的时候就变得更加直观了:

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)
}

这种形式比上面的写死状态码的方式方便很多,而且维护起来也更加简单。

但有一个问题,就是不能根据错误类型来返回指定的错误信息。

自定义错误类型

要实现根据错误类型来返回指定的错误信息,我们可以通过自定义错误的方式来实现。

假设我们自定义错误的结构如下:

{
"type": "",
"code": 0,
"message": "",
"detail": ""
}

我们需要做到如下几点:

  • 根据错误类型来自动设置 type, code, message
  • detail 为可选项,用来描述该错误的具体原因
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
}
}

自定义好错误之后,我们调用起来就更加简单了:

// 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')
}

最后,还剩下一个问题,根据错误类型来设置状态码,然后返回错误信息给客户端。

捕获错误信息

在 Controller 中抛出自定义错误后,我们需要捕获该错误,才能返回给客户端。

假设我们使用 koa 2 作为 web 框架来开发 restful api,那么我们要做的是添加错误处理的中间件:

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())

通过这种方式,我们就能优雅地处理 Restful API 中的错误信息了。

参考资料

https://zh.wikipedia.org/zh-hans/HTTP%E7%8A%B6%E6%80%81%E7%A0%81

https://www.loggly.com/blog/node-js-error-handling/

http://blog.restcase.com/rest-api-error-codes-101/

https://apigee.com/about/blg/technology/restful-api-design-what-about-errors

http://stackoverflow.com/questions/942951/rest-api-error-return-good-practices

http://goldbergyoni.com/checklist-best-practices-of-node-js-error-handling/

http://blogs.mulesoft.com/dev/api-dev/api-best-practices-response-handling/

https://developers.facebook.com/docs/graph-api/using-graph-api/#errors

https://developers.google.com/drive/v3/web/handle-errors

https://developer.github.com/v3/#client-errors

https://dev.twitter.com/overview/api/response-codes

https://www.twilio.com/docs/api/errors

Restful API 中的错误处理的更多相关文章

  1. 使用.NET Core在RESTful API中进行路由操作

    介绍 当列出REST API的最佳实践时,Routing(路由)总是使它位于堆栈的顶部.今天,在这篇文章中,我们将使用特定于.NET Core的REST(web)API来处理路由概念. 对于新手API ...

  2. [json-server] RESTful API 中,取主数据时,同时获取多个关联子表的数据

    项目背景: back-end:ASP.NET Core WebAPI front-end:Vue(+vue-router +vuex +axios)(webpack)(json-server + mo ...

  3. 关于 RESTful API 中 HTTP 状态码的定义

    最近正好使用了一会儿 Koa ,在这说一下自己对各个 请求码的见解和使用场景,懒人直接看 200.400.401.403.404.500 就可以了. 其中 2XX/3XX 其实都是请求成功,但是结果不 ...

  4. restful api 错误

    简介 随着移动开发和前端开发的崛起,越来越多的 Web 后端应用都倾向于实现 Restful API.Restful API 是一个简单易用的前后端分离方案,它只需要对客户端请求进行处理,然后返回结果 ...

  5. flask开发restful api

    flask开发restful api 如果有几个原因可以让你爱上flask这个极其灵活的库,我想蓝图绝对应该算上一个,部署蓝图以后,你会发现整个程序结构非常清晰,模块之间相互不影响.蓝图对restfu ...

  6. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(一)设计一套好的RESTful API

    写在前面的话 看了一下博客目录,距离上次更新这个系列的博文已经有两个多月,并不是因为不想继续写博客,由于中间这段时间更新了几篇其他系列的文章就暂时停止了,如今已经讲述的差不多,也就继续抽时间更新< ...

  7. RESTful API 编写指南

    基于一些不错的RESTful开发组件,可以快速的开发出不错的RESTful API,但如果不了解开发规范的.健壮的RESTful API的基本面,即便优秀的RESTful开发组件摆在面前,也无法很好的 ...

  8. 网上整理的对于Rest和Restful api的理解

    一.什么是Rest? REST不是"rest"这个单词,而是几个单词缩写 -- REpresentational State Transfer 直接翻译:表现层状态转移,但这个翻译 ...

  9. ASP.NET MVC和Web API中的Angular2 - 第1部分

    下载源码 - 903.5 KB 内容 第1部分:Visual Studio 2017中的Angular2设置,基本CRUD应用程序,第三方模态弹出控件 第2部分:使用Angular2管道进行过滤/搜索 ...

随机推荐

  1. abp(net core)+easyui+efcore实现仓储管理系统——展现层实现增删改查之列表视图(七)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

  2. 点菜网---Java开源生鲜电商平台-商品基础业务架构设计-商品分类(源码可下载)

    点菜网---Java开源生鲜电商平台-商品基础业务架构设计-商品分类 (源码可下载) 说明:我们搞过电商的人都可以体会到,搞生鲜电商是最复杂的,为什么复杂呢?我总结了有以下几个业务特性决定的: 1. ...

  3. 字节跳动Java研发面试99题(含答案):JVM+Spring+MySQL+线程池+锁

    JVM的内存结构 根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 1. Java虚拟机栈:线程私有:每个方法在执行的时候会创建一个栈帧,存储了局部变量表, ...

  4. R语言实战(第2版)PDF完整版带书签目录

    <R语言实战2>PDF+源代码 下载:https://pan.baidu.com/s/1gP_16Xq9eVmLJ1yOsWD9FA 提取码:l8dx 分享更多python数据分析相关电子 ...

  5. Atlassian In Action-Jira之指导思想(一)

    太上,不知有之:其次,亲而誉之:其次,畏之:其次,侮之.信不足焉,有不信焉.悠兮,其贵言.功成事遂,百姓皆谓"我自然". --<道德经> 研发管理或者系统工具的指导思想 ...

  6. hihoCoder 1308:搜索二·骑士问题(BFS预处理)

    题目链接 题意 中文题意. 思路 对于每一个骑士,可以先预处理出到达地图上某个点的需要走的步数,然后最后暴力枚举地图上每一个点,让三个骑士走过的距离之和最小即可. #include <bits/ ...

  7. centos7安装hadoop完全分布式集群

    groupadd test             //新建test工作组 useradd -g test phpq        //新建phpq用户并增加到test工作组 userdel 选项 用 ...

  8. 看看大神 Paul Graham 对如何学习编程的回答

    前言 我翻阅自己之前写的博客文章,发现在 2015 年我刚开始学习编程的时候,翻译了一段 Paul Graham 关于"How can I learn to program?"的回 ...

  9. C# 创建Windows服务demo

    一.准备工作 1.操作系统:Windows 10 X64 2.开发环境:VS2017 3.编程语言:C# 4. .NET版本:.NET Framework 4.5 二.创建Windows Servic ...

  10. 006.SQLServer AlwaysOn可用性组高可用部署

    一 数据库镜像部署准备 1.1 数据库镜像支持 有关对 SQL Server 2012 中的数据库镜像的支持的信息,请参考:https://docs.microsoft.com/zh-cn/previ ...