怎样从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:47716

评论

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

每周花6小时跟清华大牛马士兵学Java:多线程高并发、JVM调优、算法、设计模式等

Geek_71bb95

Java 程序员 面试 算法 编程语言

十年 Java 开发经验,走了五年弯路,整理了一份 Java 架构师进阶路线及进阶资料!

Java成神之路

Java 编程 程序员 面试 编程语言

第四周 系统架构作业

钟杰

架构师训练营第 1 期

十三、深入Python字典和集合

刘润森

Python

网上赌被黑系统维护出不了款怎么办

其实很简单

互联网 网络安全 信息安全 网络

大区块链的必然性

CECBC区块链专委会

区块链技术

职场求生攻略答疑篇之 4 —— 社会有多真实,人心就有多虚伪

臧萌

职场 职场成长

面试多次被拒,‘两个月’61天,我收到了蚂蚁金服P7级的offer

周老师

Java 编程 程序员 架构 面试

COSCon'20 & Apache Roadshow 来了,数据技术专场欢迎您

海豚调度

SpringBoot整合Jpa项目(含Jpa 原生sql语句介绍)

小Q

Java 架构 微服务 springboot jpa

来点不一样的: 精选200个Java技术面试真题,详解应聘Java程序员常见考点,在Github上标星89.6K

996小迁

编程 程序员 架构 面试

Java进阶架构师面试手册:核心框架篇整理,助我斩获65W架构师Offer!

Java架构追梦

Java 学习 架构 面试 框架

轻量级业务中台开发框架,以DDD思想为基础,融合中台核心要素,赋能中台建设

高鹏

中台 业务中台 DDD 框架 中台架构

阿里大牛原创技术好文精选整理:Redis+Nginx+设计模式+Spring全家桶+SQL+Dubbo

Geek_71bb95

Java 程序员 面试 编程语言

[周末荐片]Undercover Billionaire

亚伦碎语

生活

一文带你轻松了解Python导入模块的各种命令

计算机与AI

Python

Flink时间服务和计时器-6-5

小知识点

scala 大数据 flink

第11周总结

Vincent

极客时间 极客大学

第11周作业

Vincent

极客时间 极客大学

分布式系统中的CAP、ACID、BASE概念

云流

编程 分布式

Redis - redis.conf - 中文翻译

学习个球

redis 缓存 翻译

区块链要如何解决供应链金融痛点?

CECBC区块链专委会

区块链 金融

马化腾的区块链理想

CECBC区块链专委会

区块链 马化腾

架构师训练营第四周学习总结

Gosling

「架构师训练营第 1 期」

Linux的信号

菜鸟小sailor 🐕

c++

三步法解析Express源码

执鸢者

面试 前端 Node Express

学习笔记:数据结构与算法之贪心算法

Liuchengz.

贪心算法

十四、深入Python条件和循坏

刘润森

Python

微服务已成Java开发的面试门槛,你连SpringCloud都不会怎么涨薪

Geek_71bb95

Java spring 编程 程序员 面试

架构师训练营第四周课后作业

Gosling

「架构师训练营第 1 期」

iOS底层原理之—dyld与objc的关联

iOSer

ios ios开发 iOS Developer dyld objc

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