1. 简介
REST 是一种服务端无状态的架构设计,客户端可以自由访问和操作其资源。
通常,REST服务通过HTTP来发布它们管理的一组资源,并且提供相应的API允许客户端获取或更改这些资源的状态。
在本篇,我们将学习一些REST API 错误处理的实践,包括为用户提供相关信息的有用方法、大型网站的示例以及Spring REST应用的具体实现。
2. Http状态码
当客户端发送http请求到服务端——服务端成功接收到请求——服务端必须通知客户端请求是否正确处理。Http通过下面五种状态码告知:
100-(消息)—— 服务器接受到请求
200-(成功)——服务器按照预期完成请求处理
300-(重定向)——客户端需要执行进一步的操作来完成请求
400-(客户端出错)——客户端请求无效
500-(服务器出错)——服务器出错,请求无法正常执行
基于以上返回码,客户端可以推测请求执行结果
3. 异常处理
首先基于返回给客户端的状态码。其次,我们可能需要在返回的正文里提供更多信息。
3.1简单响应:
处理错误的最简单方法是应答适当的状态码。
通常包括下面这些:
400 错误的请求——客户端请求无效——例如请求体或参数错误
401 鉴权失败——客户端未通过服务端的认证
403 不允许——客户端通过服务器鉴权但没有权限访问请求的资源
404 未找到——请求的资源无效
412预处理失败——一个或多个请求头字段出错
500服务器内部错误——服务端处理出错
503服务不可用——请求服务当前正忙
一般的,(服务端)不应该暴露500错误到客户端。500状态错误表示服务器发生某类错误,例如处理REST服务请求过程出现未知异常。因此,内部错误与客户端无关。
然而,对于400的状态码,我们应该积极处理或捕捉内部错误。例如,因为客户端请求的资源不存在导致的错误,应该被当作一个404错误,而不是500。
通常这些状态码能够让客户端理解大致发生的错误性质。例如,我们知道403是因为没有权限访问请求所需要的资源。
不过在很多情况下,我们需要在响应时提供更多补充细节。
3.2默认Spring 响应错误
以下这些原则普遍存在于 Spring 内在的错误处理机制中。
例如,假设我们有一个书籍管理的简单SpringREST应用,有一个终端通过ID检索书籍:
1. curl -X GET -H "Accept: application/json" http://localhost:8080/api/book/1
如果这里没有一本ID是1的书,我们期望我们的控制器将抛出BookNotFoundExceptin。
在终端上执行GET请求,我们将看到抛出这个异常,并且响应以下正文:
1. {
2. "timestamp":"2019-09-16T22:14:45.624+0000",
3. "status":500,
4. "error":"Internal Server Error",
5. "message":"No message available",
6. "path":"/api/book/1"
7. }
注意这个默认错误处理器包含一个时间戳(timestamp),一个HTTP状态码(status),一条标题(error),一条消息(message,默认为空),发生异常的URL请求路径(path)。
这些字段方便客户端或开发人员排查问题故障,同时也作为标准的错误处理字段。
此外,当抛出BookNotFoundException异常时,注意Spring 自动返回了500错误码。为了简化,某些API会针对所有错误返回500状态码(或400状态码,正如下面将在Facebook 和 Twitter 的API看到的那样)。但最好尽可能的用这些更加具体的错误码。
3.3更多响应细节
从上文Spring的示例可以看出,有时状态码不足以显示错误细节。在需要时,我们可以使用响应正文提供额外信息给客户端。提供详细的响应应该包含:
Error——错误的唯一标识符
Message——简短易懂的消息
Detail——错误详细的说明
例如,假如客户端使用不正确的证书发送请求,我们可以返回一个403响应,内容如下:
1. {
2. "error": "auth-0001",
3. "message": "Incorrect username and password",
4. "detail": "Ensure that the username and password included in the request are correct"
5. }
error字段不在任何状态码中。而且是我们的应用中特有的。对这个错误字段没有特殊的约定,一般它是唯一的。
通常,此字段仅包含字母数字组合和一些连接符,例如短杠(-)或下划线(_)。例如,0001,auth-0001,和incorrect-user-pass,其他通俗的错误示例。
message部分通常认为是可以在用户界面上显示的,如果支持国际化我们会翻译它。所以,当客户端发送的请求包含一个Accept-Language 的请求头指向了英语,标题应直接翻译成英语。
detail部分是提供给客户端的开发人员而不是直接的最终的用户,所以不需翻译。
另外,提供了URL帮助字段,客户端可以找到更多帮助信息。
1. {
2. "error": "auth-0001",
3. "message": "Incorrect username and password",
4. "detail": "Ensure that the username and password included in the request are correct",
5. "help": "https://example.com/help/error/auth-0001"
6. }
有时,会需要报告告知一个请求的多个错误。这种情况下,应返回一个错误列表:
1. {
2. "errors": [
3. {
4. "error": "auth-0001",
5. "message": "Incorrect username and password",
6. "detail": "Ensure that the username and password included in the request are correct",
7. "help": "https://example.com/help/error/auth-0001"
8. },
9. ...
10. ]
11.}
当出现单个的错误时,返回一个仅包含一个元素的列表。注意,对于简单的应用返回多个错误或许有点过于复杂。多数情况下,响应返回第一个或最重要的就足够了。
3.4标准响应体
大多数REST-API都遵循相似的约定,具体细节却往往不同,包括字段名称和响应正文中包含的信息。这些差异使得代码库和框架难以统一地处理错误。
为了标准化REST API错误处理,IETF设计了RFC7807,它创建了一个通用的错误处理模式。
该模式由五部分组成:
1. type — 对错误进行分类的URI标识符
title — 简短易懂的消息
3. status — HTTP响应码(可选)
detail — 错误说明
instance — 标识特定错误的URL实例
除了使用我们自定义的错误响应主体,还可以将主体转换为:
1. {
2. "type": "/errors/incorrect-user-pass",
3. "title": "Incorrect username or password.",
4. "status": 403,
5. "detail": "Authentication failed due to incorrect username or password.",
6. "instance": "/login/log/abc123"
7. }
注意type字段对错误类型进行分类,而instance分别以类似于类和对象的方式标识错误发生。
通过URI, 客户端可以按照路径查找错误的更多信息,就像使用HATEOAS链接导航REST API一样。
遵循RFC 7807文档规范不是必须,但它有利于统一的标准。
4. 示例
上述在一些流行的REST API中很常见。虽然字段或格式的特定名称在不同站点之间可能有所不同,但一般模式几乎是通用的。
4.1 Twitter
例如,发送一个GET请求,但不提供所需的身份验证数据:
curl -X GET https://api.twitter.com/1.1/statuses/update.json?include_entities=true
Twitter API响应 400错误并返回如下正文:
1. {
2. "errors": [
3. {
4. "code":215,
5. "message":"Bad Authentication data."
6. }
7. ]
8. }
响应是一个包含单个错误错误码和消息的列表。在Twitter的例子中,没有提供详细的消息,并且使用一个通用的400错误(而不是更具体的401错误)来表示身份验证失败。
有时更通用的状态代码更容易实现。它允许开发人员捕获异常组,而不必区分返回的状态码。但是,尽可能应该使用最具体的状态代码。
4.2 Facebook
与Twitter类似,Facebook的GraphREST API 也在其响应中包含详细信息。
例如,执行一个POST请求Facebook Graph API进行身份验证:
1. curl -X GET https://graph.facebook.com/oauth/access_token?client_id=foo&client_secret=bar&grant_type=baz
收到如下错误:
1. {
2. "error": {
3. "message": "Missing redirect_uri parameter.",
4. "type": "OAuthException",
5. "code": 191,
6. "fbtrace_id": "AWswcVwbcqfgrSgjG80MtqJ"
7. }
8. }
和Twitter一样,Facebook也采用400的一般性错误(而不是更具体的400级错误)来表示失败。除了消息和数字代码外,Facebook还包括一个type字段,用于对错误进行分类,以及一个跟踪ID(fbtrace_id),作为内部支持标识符( internalsupport identifier)。
5. 结语
通过此文,我们研究了REST API错误处理的一些最佳实践,包括:
提供特定的状态码
附加信息在响应正文中
统一的异常处理方式
尽管错误处理的细节会因应用程序的不同而不同,但这些通用原则几乎适用于所有REST API,应尽可能地遵循这些原则。
这不仅允许客户端以兼容的方式处理错误,还简化了我们实现REST API时开发的代码。
译者:Innocent ,选题/校稿:热海 ,Java程序员社区出品
via:https://www.baeldung.com/rest-api-error-handling-best-practices
长按打开更多惊喜