怎样从Express API发送一致的错误响应

2020 年 11 月 20 日

怎样从Express API发送一致的错误响应

作者 | 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() 方法:


res.status(400).end();
复制代码


遵循 HTTP 标准


默认情况下,从 Express 应用程序的响应中发送的 HTTP 状态代码为 200(OK)。它会告诉客户端请求成功,并且它们可以继续解析并从响应中提取所需的任何数据。要在发送响应时指示一个错误,应使用 HTTP 标准定义的两个错误范围之一里的 HTTP 状态代码:


  • Client error 4xx:客户端做错了什么事情。

  • Server error 5xx:应用程序出了点问题。


MDN Web 文档对 HTTP 标准定义的所有 HTTP 响应状态代码以及它们的含义都提供了很好的参考。


当你确定了在不同情况下你的 API 应发送的错误状态代码后,你需要一种将这些状态代码转换为错误的方法,这就是 http-errors 库的用途。


如何使用 http-errors 库创建错误


设置


首先,你需要安装 http-errors 库:


npm install 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 中默认错误处理程序的行为,有两点需要特别注意:


  1. 它将在错误对象(error.statusCode)上寻找一个 statusCode 属性——正确,就像在使用 http-error 创建的错误中存在的属性一样。如果 statusCode 在 4xx 或 5xx 范围内,则它会将其设置为响应的状态码,否则将状态码设置为 500(内部服务器错误)。

  2. 在开发中,它将在响应中发送接收到错误的完整堆栈跟踪信息(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》

2020 年 11 月 20 日 15:47737

评论

发布
暂无评论
发现更多内容

程序员如何解决中年危机?我的阿里春招之路分享,顺利通过阿里Android岗面试

欢喜学安卓

android 程序员 面试 移动开发

QoS简介

网络技术平台

架构师训练营第 1 期 week13 总结

张建亮

极客大学架构师训练营

Rancher开源Harvester:基于K8S的超融合基础架构软件

RancherLabs

Kubernetes rancher

架构之书:我们从何处来?我们是谁?我们向何处去?

lidaobing

架构 编程的未来

ES6中的Promise和Generator详解

程序那些事

新特性 ES6 Promise 程序那些事 Generator

星环科技自动特征工程论文被ICA3PP2020接收

星环科技

AI 数据集

Superset 助力企业级大数据 Ad-hoc 查询

麻婆豆腐没麻婆

数据分析 Apache Superset BI数美

完美!凭借这份阿里大佬分享的4170页Java高手真经笔记!offer拿到手软

马士兵老师

Java 程序员 编程语言 电子书 架构资料

网易区块链打造可信数字身份认证应用新场景,赋能科技峰会

CECBC区块链专委会

数字身份

架构师训练营第 1 期 week13

张建亮

极客大学架构师训练营

re:Invent 重磅回顾 | AWS 重塑机器学习的四大亮点,触及每一位 AI 工作者

亚马逊AWS官方博客

云计算 AWS

【智简联接,万物互联】华为云·云享专家董昕:Serverless和微服务下, IoT的变革蓄势待发

华为云开发者社区

Serverless 物联网 IoT

波场链智能合约软件系统开发|波场链智能合约APP开发

开發I852946OIIO

系统开发

全球最火的程序员学习路线!2020年GitHub上那些优秀Android开源库总结,吊打面试官系列!

欢喜学安卓

android 程序员 面试 移动开发

闲聊个人服务:革「to C」的命

欧雷

产品 去中心化

阅站无数!不过我只推荐下面这些

cxuan

推荐 网站

架构师训练营第 1 期第 13 周学习总结

好吃不贵

极客大学架构师训练营

工业区块链正在改变什么?

CECBC区块链专委会

环保

技术选型背后的国家利益:区块链自主化道路的交锋

CECBC区块链专委会

科技

生产环境全链路压测建设历程12:通过生产压测发现的问题摘录

数列科技杨德华

全链路压测

TeamLeader不可不知的三种团队建设形式

Alan

团队管理 个人成长 28天写作营

架构师 3 期 3 班 -week4- 总结

zbest

总结 week4

直播报名 | 携程技术沙龙——前端测试技术创新与实践

携程技术中心

AI 数据分析

Kafka实战宝典:Kafka的控制器controller详解

数据社

kafka 七日更

图解Janusgraph系列-并发安全:锁机制(本地锁+分布式锁)分析

洋仔聊编程

janusgraph 图数据库

游戏服务器多钱一个月呢?

德胜网络-阳

阿里技术官面鹅厂,被高并发问蒙,含泪整理全网最全线程并发文档

周老师

Java 编程 程序员 架构 面试

甲方日常 70

句子

工作 随笔杂谈 日常

闭嘴,别再问什么是锁了

程序员老猫

乐观锁 悲观锁 分布式锁 java锁 公平锁

利用Python进行数据分析(原书第2版)免费下载

计算机与AI

Python 数据分析 数据科学

怎样从Express API发送一致的错误响应-InfoQ