Restful API中的错误处理方法

简介

随着移动开发和前端开发的崛起,越来越多的 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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • Java 调用Restful API接口的几种方式(HTTPS)

    摘要:最近有一个需求,为客户提供一些Restful API 接口,QA使用postman进行测试,但是postman的测试接口与java调用的相似但并不相同,于是想自己写一个程序去测试Restful API接口,由于使用的是HTTPS,所以还要考虑到对于HTTPS的处理.由于我也是首次使用Java调用restful接口,所以还要研究一番,自然也是查阅了一些资料. 分析:这个问题与模块之间的调用不同,比如我有两个模块front end 和back end,front end提供前台展示,back

  • 浅谈java调用Restful API接口的方式

    摘要:最近有一个需求,为客户提供一些RestfulAPI接口,QA使用postman进行测试,但是postman的测试接口与java调用的相似但并不相同,于是想自己写一个程序去测试RestfulAPI接口,由于使用的是HTTPS,所以还要考虑到对于HTTPS的处理.由于我也是首次使用Java调用restful接口,所以还要研究一番,自然也是查阅了一些资料. 分析:这个问题与模块之间的调用不同,比如我有两个模块frontend和backend,frontend提供前台展示,backend提供数据支

  • Restful API中的错误处理方法

    简介 随着移动开发和前端开发的崛起,越来越多的 Web 后端应用都倾向于实现 Restful API. Restful API 是一个简单易用的前后端分离方案,它只需要对客户端请求进行处理,然后返回结果即可, 无需考虑页面渲染,一定程度上减轻了后端开发人员的负担. 然而,正是由于 Restful API 不需要考虑页面渲染,导致它不能在页面上展示错误信息. 那就意着当出现错误的时候,它只能通过返回一个错误的响应,来告诉用户和开发者相应的错误信息,提示他们接下来应该怎么办. 本文将讨论 Restf

  • 解决python中0x80072ee2错误的方法

    解决python中出现x80072ee2错误的方法: 在官网上直接下载"python-3.7.2-amd64.exe"并安装即可: 因为如果安装"python-3.7.2-amd64-webinstall.exe",自动访问外网,导致无法访问. 安装错误信息: Setup failed One or more issues caused the setup to fail.Please fix the issues and then retry setup.For

  • 详解Spring Boot实战之Restful API的构建

    上一篇文章讲解了通过Spring boot与JdbcTemplate.JPA和MyBatis的集成,实现对数据库的访问.今天主要给大家分享一下如何通过Spring boot向前端返回数据. 在现在的开发流程中,为了最大程度实现前后端的分离,通常后端接口只提供数据接口,由前端通过Ajax请求从后端获取数据并进行渲染再展示给用户.我们用的最多的方式就是后端会返回给前端一个JSON字符串,前端解析JSON字符串生成JavaScript的对象,然后再做处理.本文就来演示一下Spring boot如何实现

  • PHP使Laravel为JSON REST API返回自定义错误的问题

    我正在开发某种RESTful API.发生一些错误时,我会抛出一个App :: abort($code,$message)错误. 问题是:我希望他用键"代码"和"消息"抛出一个json形成的数组,每个数组都包含上述数据. Array ( [code] => 401 [message] => "Invalid User" ) 有没有人知道是否可能,如果是,我该怎么做? 去你的app / start / global.php. 这将将40

  • 基于Go语言构建RESTful API服务

    目录 什么是 RESTful API 一个简单的 RESTful API RESTful JSON API Gin 框架 引入 Gin 框架 使用 Gin 框架 新增一个用户 获取特定的用户 总结 在实际开发项目中,你编写的服务可以被其他服务使用,这样就组成了微服务的架构:也可以被前端调用,这样就可以前后端分离.那么,本文主要介绍什么是 RESTful API,以及 Go 语言是如何玩转 RESTful API 的. 什么是 RESTful API RESTful API 是一套规范,它可以规范

  • PHP中Restful api 错误提示返回值实现思路

    RESTful架构是一种流行的互联网软件架构,它结构清晰,符合标准,易于理解,扩展方便. REST是Representational State Transfer的缩写,翻译为"表现层状态转化".表现层其实就是资源,因此可以理解为"资源状态转化". 网络应用上的任何实体都可以看作是一种资源,通过一个URI(统一资源定位符)指向它. 序言 不管是微博还是淘宝,他们都有自己的错误返回值格式规范,以及错误代码说明,这样不但手机端用起来方便,给人的感觉也清晰明了,高大上.遇

  • SpringBoot+Spring Security+JWT实现RESTful Api权限控制的方法

    摘要:用spring-boot开发RESTful API非常的方便,在生产环境中,对发布的API增加授权保护是非常必要的.现在我们来看如何利用JWT技术为API增加授权保护,保证只有获得授权的用户才能够访问API. 一:开发一个简单的API 在IDEA开发工具中新建一个maven工程,添加对应的依赖如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-b

  • Yii2中Restful API原理实例分析

    本文实例分析了Yii2中Restful API原理.分享给大家供大家参考,具体如下: Yii2 有个很重要的特性是对 Restful API的默认支持, 通过短短的几个配置就可以实现简单的对现有Model的RESTful API 这里通过分析rest部分源码,简单剖析下yii2 实现 restful 的原理,并通过一些定制实现 对 关联模型的RESTful api 操作. ~ 代表 extends from 的关系 | | rest/ | | |-Action.php ~ `\yii\base\

  • Spring MVC集成springfox-swagger2构建restful API的方法详解

    前言 在集成springfox-swagger2之前,我也尝试着集成了swagger-springmvc,方式差不多,但是swagger-springmvc相对麻烦一点,因为要把它的静态文件copy到自己的项目中.所以还是用新版本的. 至于两者有什么不同,为什么进行版本变更请参见官方说明文档 方法如下 这里先写下需要的pom.xml配置(我引用的2.4.0,相对稳定) <dependency> <groupId>io.springfox</groupId> <ar

  • Spring Boot集成springfox-swagger2构建restful API的方法教程

    前言 之前跟大家分享了Spring MVC集成springfox-swagger2构建restful API,简单写了如何在springmvc中集成swagger2.这边记录下在springboot中如何集成swagger2.其实使用基本相同. 方法如下: 首先还是引用相关jar包.我使用的maven,在pom.xml中引用相关依赖(原来我使用的是2.2.0的,现在使用2.4.0的): <dependency> <groupId>io.springfox</groupId&g

随机推荐