自定义 Express 中间件详解

Express 是 Node.js 应用程序中最常用的 Web 框架之一,它提供了灵活的中间件机制,帮助开发者管理 HTTP 请求、响应、路由等功能。本文将详细介绍使用 Express 中间件时的注意事项,并深入探讨如何创建自定义中间件来增强 Express 应用的功能。

Express 中间件概述

什么是中间件?

在 Express 中,中间件是指那些处理 HTTP 请求与响应过程的函数。它们可以在接收到请求和发送响应之间执行各种操作,例如修改请求对象、响应对象、调用下一个中间件、处理错误等。

中间件函数的基本格式如下:

1
2
3
4
function middleware(req, res, next) {
// 执行一些逻辑
next(); // 调用下一个中间件
}

req 是请求对象,res 是响应对象,next 是用于调用下一个中间件的函数。如果不调用 next 函数,请求就会挂起。

中间件的类型

Express 中的中间件可以分为以下几类:

  • 应用级中间件 :通过 app.use()app.METHOD() 绑定到应用实例上。
  • 路由级中间件 :绑定到特定路由处理程序上,用于处理某个路由的请求。
  • 错误处理中间件 :处理应用程序中的错误,具有四个参数 (err, req, res, next)
  • 内置中间件 :Express 自带的功能,比如处理静态文件的 express.static
  • 第三方中间件 :由社区或开发者提供的中间件,常见的如 body-parsercors 等。

Express 中间件的工作原理

Express 中间件是按照定义的顺序依次执行的,形成一个“中间件堆栈”。当一个中间件处理完某个请求后,它可以通过调用 next() 将控制权交给下一个中间件。如果某个中间件没有调用 next(),则请求会被挂起,后续的中间件将不会执行。

中间件堆栈

中间件函数按照它们定义的顺序执行,因此顺序非常重要。一个中间件可以拦截请求并进行相应的处理,或将其传递给下一个中间件。以下是一个中间件堆栈的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
app.use((req, res, next) => {
console.log('中间件 1');
next();
});

app.use((req, res, next) => {
console.log('中间件 2');
next();
});

app.get('/', (req, res) => {
res.send('Hello World!');
});

在这个例子中,当请求到达根路径 / 时,两个中间件会按顺序执行,依次打印出 “中间件 1” 和 “中间件 2”。

使用 Express 中间件的注意事项

始终调用 next()

中间件的核心在于调用 next() 函数。如果你在中间件中没有调用 next(),当前请求就会挂起,无法进入下一个中间件或路由处理函数。这是开发者在使用 Express 中间件时最常犯的错误之一。

1
2
3
4
app.use((req, res, next) => {
console.log('处理中间件');
// 忘记调用 next(),请求会挂起
});

错误处理中间件

Express 提供了专门的错误处理中间件,用来捕获应用中的异常。错误处理中间件必须有四个参数:errreqresnext。通常,当一个中间件中出现错误时,可以将错误传递给错误处理中间件,而不是简单地抛出异常。

1
2
3
4
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('服务器内部错误');
});

错误处理中间件应该放在其他中间件之后,否则无法捕获错误。

顺序很重要

中间件的顺序决定了它们在请求生命周期中的执行时机。举个例子,假设有一个身份验证中间件和一个日志中间件,如果身份验证中间件先执行,那么即便用户未通过身份验证,日志也会记录该请求。反之,如果日志中间件先执行,那么所有请求都会记录日志。

1
2
app.use(logRequest); // 日志中间件
app.use(authenticateUser); // 身份验证中间件

如何编写自定义中间件

Express 的强大之处在于它允许开发者轻松地编写自己的中间件。自定义中间件与其他中间件类似,接收 reqresnext 三个参数,并通过 next() 将控制权传递给下一个中间件。

创建一个简单的自定义中间件

以下是一个记录请求时间的自定义中间件:

1
2
3
4
5
6
7
function requestTime(req, res, next) {
req.requestTime = Date.now();
console.log(`请求时间:${req.requestTime}`);
next();
}

app.use(requestTime);

这个中间件会在每个请求中添加一个 requestTime 属性,记录请求的时间,并输出日志。

自定义响应处理中间件

自定义中间件不仅可以处理请求,还可以直接修改响应。例如,以下中间件会根据请求的 Content-Type 自动设置响应头:

1
2
3
4
5
6
function setResponseHeaders(req, res, next) {
res.setHeader('X-Custom-Header', 'Express Middleware Example');
next();
}

app.use(setResponseHeaders);

这个中间件为每个响应添加了一个自定义的 X-Custom-Header 响应头。

条件性中间件

在某些情况下,你可能希望中间件只在特定条件下执行。你可以通过在中间件中添加逻辑来实现这一点,例如只对特定的请求方法或路径生效:

1
2
3
4
5
6
7
8
function logGetRequests(req, res, next) {
if (req.method === 'GET') {
console.log('GET 请求记录');
}
next();
}

app.use(logGetRequests);

这个中间件只会记录 GET 请求,而忽略其他方法的请求。

中间件的实际应用场景

处理 CORS 问题

跨域资源共享(CORS)是开发 Web API 时常见的问题。你可以通过自定义中间件或使用第三方中间件来解决 CORS 问题。以下是一个允许所有请求跨域的简单中间件:

1
2
3
4
5
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});

或者,你可以使用 cors 中间件:

1
2
const cors = require('cors');
app.use(cors());

日志记录

记录请求日志是监控应用的重要方式。开发者可以使用现成的中间件如 morgan,也可以编写自己的日志中间件:

1
2
3
4
5
6
function logRequest(req, res, next) {
console.log(`${req.method} ${req.url}`);
next();
}

app.use(logRequest);

处理静态文件

Express 自带的 express.static 中间件非常适合用于提供静态文件,如图片、CSS 和 JavaScript 文件:

1
app.use(express.static('public'));

总结

中间件是 Express 框架的核心概念之一,它为开发者提供了强大的扩展能力。通过合理地使用和编写中间件,开发者可以轻松实现各种复杂的功能。无论是应用级、路由级还是错误处理中间件,理解它们的顺序、错误处理机制以及如何编写自定义中间件,都将极大地提高 Express 应用的灵活性与可维护性。