作者 | Simon Plenderleith 译者 | 王强策划 | 李俊辰
使用 Express 构建 API 时,可能很难知道如何发送一致的错误响应。这个框架似乎并未为此提供什么特殊功能,因此你需要自己去解决问题。某些时候,你可能会想知道自己是否在以“正确的方式”操作。
正如我在《使用 Express 构建现代 API 的 5 种最佳实践》博客文章中提到的那样:
构建 API 来发明自己的错误响应格式是非常诱人的,但是 HTTP 响应状态代码是一个很好的起点,因为它们可以传达特定的错误状态。
如果你发明了自己的格式,就必须在 API 中构建一堆额外的逻辑,并且你可能还要确保对它们进行了全面的测试。没有人想在错误响应代码中还看到错误,不是吗?最重要的是,它也需要客户端,例如前端 JavaScript,来实现用于处理 API 错误响应特殊格式的额外代码。
如果有一种更简单的方法,一种经过实践检验的标准方法来发送错误响应,那岂不是很好吗?幸运的是,这种方法是存在的!HTTP 标准定义了状态代码,你可以在 API 响应中使用这些状态代码来指示请求是否成功,或是否发生了错误。
下面是一个 HTTP 错误响应示例,带有 400 状态代码,指示来自客户端的“错误请求”(Bad Request):
< HTTP/1.1 400 Bad Request
< Content-Type: text/html; charset=utf-8
< Content-Length: 138
< Date: Wed, 28 Oct 2020 20:11:07 GMT
复制代码
如果要发送这样的错误响应,可以使用 Express 的 res.status() 方法:
遵循 HTTP 标准
默认情况下,从 Express 应用程序的响应中发送的 HTTP 状态代码为 200(OK)。它会告诉客户端请求成功,并且它们可以继续解析并从响应中提取所需的任何数据。要在发送响应时指示一个错误,应使用 HTTP 标准定义的两个错误范围之一里的 HTTP 状态代码:
MDN Web 文档对 HTTP 标准定义的所有 HTTP 响应状态代码以及它们的含义都提供了很好的参考。
当你确定了在不同情况下你的 API 应发送的错误状态代码后,你需要一种将这些状态代码转换为错误的方法,这就是 http-errors 库的用途。
如何使用 http-errors 库创建错误
设置
首先,你需要安装 http-errors 库:
然后,你需要在应用程序中 require() 它,(在 require express 之后就可以了):
const createHttpError = require("http-errors");
复制代码
http-errors 库提供了两种不同的方法来创建错误对象。
方式 1:指定一个 HTTP 状态代码数字
第一种方法是指定一个 HTTP 状态代码数字,例如:
const error = createHttpError(400, "Invalid filter");
复制代码
如果需要,你可以传递一个现有的,要扩展的错误对象之类,而不是传递一个错误消息字符串。
const error = new Error("Invalid filter");
const httpError = createHttpError(400, error);
复制代码
警告! 传递现有错误时请务必小心,因为如果在错误响应中发送了有关应用程序的信息,则可能会暴露这个应用程序的敏感信息。正如你将在下一节中看到的那样,Express 中的默认错误处理程序具有内置的防护措施来帮助你避免这种情况,但是请务必注意在 API 响应中公开的详细信息,这一点很重要。
如果你想指定在响应中发送错误时要添加的额外标头,则 http-errors 允许你通过传递属性对象来实现此目的,例如:
const error = createHttpError(400, "Invalid filter", {
headers: {
"X-Custom-Header": "Value"
}
});
复制代码
方式 2:使用一个命名的 HTTP 错误构造器
创建错误对象的第二种方法是使用 http-errors 提供的命名 HTTP 错误构造器之一,例如:
const error = new createHttpError.BadRequest("Invalid filter");
复制代码
与第一种方法的区别在于,这里你只能传递错误消息字符串,不允许你传递现有的错误对象或属性对象。对于不需要它们的情况来说,我认为第二种方法更易于维护。这意味着你不必在每次重新查看代码时都要查找一下 HTTP 状态代码的含义。
这些错误对象里面有什么?
以下是将始终存在于使用 http-errors 创建的错误对象上的属性,还有示例值:
{
message: "Invalid filter",
// This statusCode property is going to come in very handy!
statusCode: 400,
stack: `BadRequestError: Invalid filter
at /home/simonplend/dev/express-error-responses/app.js:33:17
at Layer.handle [as handle_request] (/home/simonplend/dev/express-error-responses/node_modules/express/lib/router/layer.js:95:5)
at next (/home/simonplend/dev/express-error-responses/node_modules/express/lib/router/route.js:137:13)`
}
复制代码
现在,让我们看一下创建错误对象后可以执行的操作。
Express 中的默认错误处理程序
Express 提供了一个默认的错误处理程序。
当你从中间件或路由处理程序调用 next() 回调函数,并将错误对象(比如 next(error))传递给它时,将调用这个错误处理程序。
关于 Express 中默认错误处理程序的行为,有两点需要特别注意:
它将在错误对象(error.statusCode)上寻找一个 statusCode 属性——正确,就像在使用 http-error 创建的错误中存在的属性一样。如果 statusCode 在 4xx 或 5xx 范围内,则它会将其设置为响应的状态码,否则将状态码设置为 500(内部服务器错误)。
在开发中,它将在响应中发送接收到错误的完整堆栈跟踪信息(error.stack),例如:
BadRequestError: Invalid sort parameter, must be either: first_name, last_name
at /home/simonplend/dev/express-error-responses/app.js:17:17
at Layer.handle [as handle_request] (/home/simonplend/dev/express-error-responses/node_modules/express/lib/router/layer.js:95:5)
at next (/home/simonplend/dev/express-error-responses/node_modules/express/lib/router/route.js:137:13)
at Route.dispatch (/home/simonplend/dev/express-error-responses/node_modules/express/lib/router/route.js:112:3)
at Layer.handle [as handle_request] (/home/simonplend/dev/express-error-responses/node_modules/express/lib/router/layer.js:95:5)
at /home/simonplend/dev/express-error-responses/node_modules/express/lib/router/index.js:281:22
at Function.process_params (/home/simonplend/dev/express-error-responses/node_modules/express/lib/router/index.js:335:12)
at next (/home/simonplend/dev/express-error-responses/node_modules/express/lib/router/index.js:275:10)
at expressInit (/home/simonplend/dev/express-error-responses/node_modules/express/lib/middleware/init.js:40:5)
at Layer.handle [as handle_request] (/home/simonplend/dev/express-error-responses/node_modules/express/lib/router/layer.js:95:5)
复制代码
在生产环境中,即当环境变量 NODE_ENV 设置为 production 时,它将忽略堆栈跟踪,仅发送与 HTTP 状态代码相对应的名称,例如 Bad Request。
在生产中显示堆栈跟踪是一件坏事:它会暴露你的应用程序的内部详细信息,从而带来安全风险,使应用程序更容易受到潜在攻击者的攻击。因为它们将提供有关应用程序的结构以及你正在使用的库的细节情报。
总结
好的,我们现在了解了 HTTP 状态代码、如何创建包含状态代码的 JavaScript 错误对象,以及 Express 中的默认错误处理程序如何使用它们。现在可以总结一下了!
const express = require("express");
/**
* We'll be using this library to help us create errors with
* HTTP status codes.
*/
const createHttpError = require("http-errors");
/**
* In a real application this would run a query against a
* database, but for this example it's always returning a
* rejected `Promise` with an error message.
*/
function getUserData() {
return Promise.reject(
"An error occurred while attempting to run the database query."
);
}
/**
* Express configuration and routes
*/
const PORT = 3000;
const app = express();
/**
* This example route will potentially respond with two
* different types of error:
*
* - 400 (Bad Request) - The client has done something wrong.
* - 500 (Internal Server Error) - Something has gone wrong in your application.
*/
app.get("/user", (request, response, next) => {
const validSort = ["first_name", "last_name"];
const sort = request.query.sort;
const sortIsValid = sort && validSort.includes(sort);
if (!sortIsValid) {
/**
* This error is created by specifying a numeric HTTP status code.
*
* 400 (Bad Request) - The client has done something wrong.
*/
const error = new createHttpError.BadRequest(
`Invalid sort parameter, must be either: ${validSort.join(", ")}`
);
/**
* Because we're passing an error object into the `next()` function,
* the default error handler in Express will kick in and take
* care of sending an error response for us.
*
* It's important that we return here so that none of the
* other code in this route handler function is run.
*/
return next(error);
}
getUserData()
.then(userData => response.json(userData))
.catch(error => {
/**
* This error is created by using a named HTTP error constructor.
*
* An existing error is being passsed in and extra headers are
* being specified that will be sent with the response.
*
* 500 (Internal Server Error) - Something has gone wrong in your application.
*/
const httpError = createHttpError(500, error, {
headers: {
"X-Custom-Header": "Value",
}
});
/**
* Once again, the default error handler in Express will kick
* in and take care of sending an error response for us when
* we pass our error object to `next()`.
*
* We don't technically need to return here, but it's
* good to get into the habit of doing this when calling
* `next()` so you don't end up with weird bugs where
* an error response has been sent but your handler function
* is continuing to run as if everything is ok.
*/
return next(httpError);
});
});
app.listen(PORT, () =>
console.log(`Example app listening at http://localhost:${PORT}`)
);
复制代码
注意:Express 中的默认错误处理程序会发送一个 HTML 响应正文。如果要发送 JSON 响应正文,则需要编写自己的错误处理程序。我将在以后的博客文章中具体介绍!
原文链接
《How to Send Consistent Error Responses From Your ExpressAPI》
评论