1. Overview
1.概述
REST is a stateless architecture in which clients can access and manipulate resources on a server. Generally, REST services utilize HTTP to advertise a set of resources that they manage and provide an API that allows clients to obtain or alter the state of these resources.
REST是一种无状态架构,客户可以访问和操作服务器上的资源。一般来说,REST服务利用HTTP来宣传他们所管理的一组资源,并提供一个API,允许客户获取或改变这些资源的状态。
In this tutorial, we’ll learn about some of the best practices for handling REST API errors, including useful approaches for providing users with relevant information, examples from large-scale websites and a concrete implementation using an example Spring REST application.
在本教程中,我们将学习一些处理REST API错误的最佳实践,包括为用户提供相关信息的有用方法、来自大型网站的例子以及使用Spring REST应用实例的具体实现。
2. HTTP Status Codes
2.HTTP状态代码
When a client makes a request to an HTTP server — and the server successfully receives the request — the server must notify the client if the request was successfully handled or not.
当客户端向HTTP服务器发出请求时–并且服务器成功接收了该请求–服务器必须通知客户端该请求是否被成功处理。
HTTP accomplishes this with five categories of status codes:
HTTP通过五类状态代码来实现这一目标。
- 100-level (Informational) – server acknowledges a request
- 200-level (Success) – server completed the request as expected
- 300-level (Redirection) – client needs to perform further actions to complete the request
- 400-level (Client error) – client sent an invalid request
- 500-level (Server error) – server failed to fulfill a valid request due to an error with server
Based on the response code, a client can surmise the result of a particular request.
根据响应代码,客户端可以推测出特定请求的结果。
3. Handling Errors
3.处理错误
The first step in handling errors is to provide a client with a proper status code. Additionally, we may need to provide more information in the response body.
处理错误的第一步是向客户提供一个适当的状态代码。此外,我们可能需要在响应体中提供更多信息。
3.1. Basic Responses
3.1.基本反应
The simplest way we handle errors is to respond with an appropriate status code.
我们处理错误的最简单方法是用一个适当的状态代码进行回应。
Here are some common response codes:
以下是一些常见的响应代码。
- 400 Bad Request – client sent an invalid request, such as lacking required request body or parameter
- 401 Unauthorized – client failed to authenticate with the server
- 403 Forbidden – client authenticated but does not have permission to access the requested resource
- 404 Not Found – the requested resource does not exist
- 412 Precondition Failed – one or more conditions in the request header fields evaluated to false
- 500 Internal Server Error – a generic error occurred on the server
- 503 Service Unavailable – the requested service is not available
While basic, these codes allow a client to understand the broad nature of the error that occurred. We know that if we receive a 403 error, for example, we lack permissions to access the resource we requested. In many cases, though, we need to provide supplemental details in our responses.
虽然很基本,但这些代码允许客户了解所发生的错误的广泛性质。我们知道,如果我们收到一个403错误,例如,我们缺乏访问我们请求的资源的权限。不过,在许多情况下,我们需要在我们的回应中提供补充细节。
500 errors signal that some issues or exceptions occurred on the server while handling a request. Generally, this internal error is not our client’s business.
500错误的信号是,在处理一个请求时,服务器上发生了一些问题或异常情况。一般来说,这种内部错误与我们客户的业务无关。
Therefore, to minimize these kinds of responses to the client, we should diligently attempt to handle or catch internal errors and respond with other appropriate status codes wherever possible.
因此,为了尽量减少对客户的这类回应,我们应该努力尝试处理或捕捉内部错误,并尽可能用其他适当的状态代码进行回应。
For example, if an exception occurs because a requested resource doesn’t exist, we should expose this as a 404 rather than a 500 error.
例如,如果因为请求的资源不存在而发生异常,我们应该将其暴露为404而不是500错误。
This is not to say that 500 should never be returned, only that it should be used for unexpected conditions — such as a service outage — that prevent the server from carrying out the request.
这并不是说永远不应该返回500,只是说它应该用于意外情况–例如服务中断–阻止服务器执行请求。
3.2. Default Spring Error Responses
3.2.默认的Spring错误响应
These principles are so ubiquitous that Spring has codified them in its default error handling mechanism.
这些原则是如此的普遍,以至于Spring将它们编入了其默认的错误处理机制。
To demonstrate, suppose we have a simple Spring REST application that manages books, with an endpoint to retrieve a book by its ID:
为了证明这一点,假设我们有一个简单的Spring REST应用程序,它管理书籍,有一个端点可以通过其ID检索书籍。
curl -X GET -H "Accept: application/json" http://localhost:8082/spring-rest/api/book/1
If there is no book with an ID of 1, we expect that our controller will throw a BookNotFoundException.
如果没有ID为1的书,我们希望我们的控制器会抛出一个BookNotFoundException。
Performing a GET on this endpoint, we see that this exception was thrown, and this is the response body:
在这个端点上执行一个GET,我们看到这个异常被抛出,这是响应体。
{
"timestamp":"2019-09-16T22:14:45.624+0000",
"status":500,
"error":"Internal Server Error",
"message":"No message available",
"path":"/api/book/1"
}
Note that this default error handler includes a timestamp of when the error occurred, the HTTP status code, a title (the error field), a message if messages are enabled in the default error (and is blank by default), and the URL path where the error occurred.
请注意,这个默认的错误处理程序包括一个错误发生的时间戳、HTTP状态代码、一个标题(error字段)、一个消息(如果默认错误中启用了消息)以及错误发生的URL路径。
These fields provide a client or developer with information to help troubleshoot the problem and also constitute a few of the fields that make up standard error handling mechanisms.
这些字段为客户或开发者提供了有助于排除问题的信息,也构成了构成标准错误处理机制的一些字段。
Also note that Spring automatically returns an HTTP status code of 500 when our BookNotFoundException is thrown. Although some APIs will return a 500 status code or other generic ones, as we will see with the Facebook and Twitter APIs, for all errors for the sake of simplicity, it is best to use the most specific error code when possible.
还要注意的是,当我们的BookNotFoundException被抛出时,Spring会自动返回一个HTTP状态代码为500。虽然有些API会返回500状态码或其他通用的状态码,就像我们在Facebook和Twitter API中看到的那样,但为了简单起见,对于所有的错误,最好尽可能地使用最具体的错误代码。
In our example, we can add a @ControllerAdvice so that when a BookNotFoundException is thrown, our API gives back a status of 404 to denote Not Found instead of 500 Internal Server Error.
在我们的例子中,我们可以添加一个@ControllerAdvice,这样,当抛出BookNotFoundException时,我们的API会反馈一个404的状态来表示Not Found而不是500Internal Server Error。
3.3. More Detailed Responses
3.3.更详细的答复
As seen in the above Spring example, sometimes a status code is not enough to show the specifics of the error. When needed, we can use the body of the response to provide the client with additional information.
从上面的Spring例子中可以看出,有时状态代码不足以显示错误的具体内容。需要时,我们可以使用响应的主体来为客户提供额外的信息。
When providing detailed responses, we should include:
在提供详细答复时,我们应包括。
- Error – a unique identifier for the error
- Message – a brief human-readable message
- Detail – a lengthier explanation of the error
For example, if a client sends a request with incorrect credentials, we can send a 401 response with this body:
例如,如果一个客户发送了一个证书不正确的请求,我们可以发送一个401的响应,其中包括这个正文。
{
"error": "auth-0001",
"message": "Incorrect username and password",
"detail": "Ensure that the username and password included in the request are correct"
}
The error field should not match the response code. Instead, it should be an error code unique to our application. Generally, there is no convention for the error field, expect that it be unique.
error字段不应该与响应代码相匹配。相反,它应该是我们应用程序特有的错误代码。一般来说,对于error字段没有约定,希望它是唯一的。
Usually, this field contains only alphanumerics and connecting characters, such as dashes or underscores. For example, 0001, auth-0001 and incorrect-user-pass are canonical examples of error codes.
通常,这个字段只包含字母数字和连接字符,如破折号或下划线。例如,0001、auth-0001和incorrect-user-pass是错误代码的典型例子。
The message portion of the body is usually considered presentable on user interfaces. Therefore, we should translate this title if we support internationalization. So if a client sends a request with an Accept-Language header corresponding to French, the title value should be translated to French.
正文的message部分通常被认为是可以在用户界面上呈现的。因此,如果我们支持国际化,我们应该翻译这个标题。因此,如果客户端发送了一个带有Accept-Language头的请求,对应于法语,那么title值应该被翻译成法语。
The detail portion is intended for use by developers of clients and not the end user, so the translation is not necessary.
detail部分是供客户的开发人员使用的,而不是最终用户,所以不需要翻译。
Additionally, we could also provide a URL — such as the help field — that clients can follow to discover more information:
此外,我们还可以提供一个URL–如help字段–客户可以按照它来发现更多的信息。
{
"error": "auth-0001",
"message": "Incorrect username and password",
"detail": "Ensure that the username and password included in the request are correct",
"help": "https://example.com/help/error/auth-0001"
}
Sometimes, we may want to report more than one error for a request.
有时,我们可能想为一个请求报告一个以上的错误。
In this case, we should return the errors in a list:
在这种情况下,我们应该在一个列表中返回错误。
{
"errors": [
{
"error": "auth-0001",
"message": "Incorrect username and password",
"detail": "Ensure that the username and password included in the request are correct",
"help": "https://example.com/help/error/auth-0001"
},
...
]
}
And when a single error occurs, we respond with a list containing one element.
而当一个错误发生时,我们用一个包含一个元素的列表来回应。
Note that responding with multiple errors may be too complicated for simple applications. In many cases, responding with the first or most significant error is sufficient.
请注意,对简单的应用来说,用多个错误进行响应可能太复杂。在许多情况下,用第一个或最重要的错误来回应就足够了。
3.4. Standardized Response Bodies
3.4.标准化的反应机构
While most REST APIs follow similar conventions, specifics usually vary, including the names of fields and the information included in the response body. These differences make it difficult for libraries and frameworks to handle errors uniformly.
虽然大多数REST API遵循类似的惯例,但具体内容通常有所不同,包括字段的名称和响应体中包含的信息。这些差异使得库和框架很难统一处理错误。
In an effort to standardize REST API error handling, the IETF devised RFC 7807, which creates a generalized error-handling schema.
为了实现REST API错误处理的标准化,IETF设计了RFC 7807,它创建了一个通用的错误处理模式。
This schema is composed of five parts:
这个模式由五个部分组成。
- type – a URI identifier that categorizes the error
- title – a brief, human-readable message about the error
- status – the HTTP response code (optional)
- detail – a human-readable explanation of the error
- instance – a URI that identifies the specific occurrence of the error
Instead of using our custom error response body, we can convert our body:
而不是使用我们的自定义错误响应体,我们可以转换我们的体。
{
"type": "/errors/incorrect-user-pass",
"title": "Incorrect username or password.",
"status": 401,
"detail": "Authentication failed due to incorrect username or password.",
"instance": "/login/log/abc123"
}
Note that the type field categorizes the type of error, while instance identifies a specific occurrence of the error in a similar fashion to classes and objects, respectively.
请注意,type字段对错误的类型进行分类,而instance则分别以类似于类和对象的方式识别错误的具体发生。
By using URIs, clients can follow these paths to find more information about the error in the same way that HATEOAS links can be used to navigate a REST API.
通过使用URI,客户可以按照这些路径来查找有关错误的更多信息,与HATEOAS链接可用于浏览REST API的方式相同。
Adhering to RFC 7807 is optional, but it is advantageous if uniformity is desired.
遵循RFC 7807是可选的,但如果需要统一性,则是有利的。
4. Examples
4.实例
The above practices are common throughout some of the most popular REST APIs. While the specific names of fields or formats may vary between sites, the general patterns are nearly universal.
上述做法在一些最受欢迎的REST API中是常见的。虽然不同网站的字段或格式的具体名称可能有所不同,但一般模式几乎是通用的。
4.1. Twitter
4.1.推特
Let’s send a GET request without supplying the required authentication data:
让我们发送一个GET请求,不提供所需的认证数据。
curl -X GET https://api.twitter.com/1.1/statuses/update.json?include_entities=true
The Twitter API responds with an error with this body:
Twitter的API回应了一个错误,其正文是这样的。
{
"errors": [
{
"code":215,
"message":"Bad Authentication data."
}
]
}
This response includes a list containing a single error, with its error code and message. In Twitter’s case, no detailed message is present, and a general error — rather than a more specific 401 error — is used to denote that authentication failed.
这个响应包括一个包含单个错误的列表,其中有错误代码和信息。在Twitter的案例中,没有详细的信息,一个一般的错误–而不是一个更具体的401错误–被用来表示认证失败。
Sometimes a more general status code is easier to implement, as we’ll see in our Spring example below. It allows developers to catch groups of exceptions and not differentiate the status code that should be returned. When possible, though, the most specific status code should be used.
有时,一个更通用的状态代码更容易实现,正如我们在下面的Spring例子中看到的那样。它允许开发人员捕捉一组异常,并且不区分应该返回的状态代码。不过,如果可能的话,应该使用最具体的状态代码。
4.2. Facebook
4.2.脸书
Similar to Twitter, Facebook’s Graph REST API also includes detailed information in its responses.
与Twitter类似,Facebook的Graph REST API也在其响应中包含详细信息。
Let’s perform a POST request to authenticate with the Facebook Graph API:
让我们执行一个POST请求,用Facebook Graph API进行认证。
curl -X GET https://graph.facebook.com/oauth/access_token?client_id=foo&client_secret=bar&grant_type=baz
We receive this error:
我们收到这个错误。
{
"error": {
"message": "Missing redirect_uri parameter.",
"type": "OAuthException",
"code": 191,
"fbtrace_id": "AWswcVwbcqfgrSgjG80MtqJ"
}
}
Like Twitter, Facebook also uses a generic error — rather than a more specific 400-level error — to denote a failure. In addition to a message and numeric code, Facebook also includes a type field that categorizes the error and a trace ID (fbtrace_id) that acts as an internal support identifier.
与Twitter一样,Facebook也使用一个通用的错误–而不是一个更具体的400级错误–来表示失败。除了消息和数字代码外,Facebook还包括一个对错误进行分类的类型字段和一个跟踪ID(fbtrace_id),作为内部支持标识符。
5. Conclusion
5.总结
In this article, we examined some of the best practices of REST API error handling:
在这篇文章中,我们研究了REST API错误处理的一些最佳实践。
- Providing specific status codes
- Including additional information in response bodies
- Handling exceptions in a uniform manner
While the details of error handling will vary by application, these general principles apply to nearly all REST APIs and should be adhered to when possible.
虽然错误处理的细节因应用而异,但这些一般原则几乎适用于所有的REST API,并应尽可能地遵守。
Not only does this allow clients to handle errors in a consistent manner, but it also simplifies the code we create when implementing a REST API.
这不仅使客户能够以一致的方式处理错误,而且还简化了我们在实现REST API时创建的代码。
The code referenced in this article is available over on GitHub.
本文引用的代码可在GitHub上找到over。