跳到主要内容

中间件机制

中间件概述

中间件(Middleware)是Node.js Web应用中的一个核心概念,特别是在Express.js等Web框架中。中间件本质上是一个函数,它可以访问请求对象(req)、响应对象(res)以及应用程序的下一个中间件函数(next)。

中间件函数可以执行以下任务:

  • 执行任何代码
  • 修改请求和响应对象
  • 终结请求-响应循环
  • 调用堆栈中的下一个中间件函数

Express.js中间件

Express.js是Node.js最流行的Web框架之一,它的中间件机制非常强大和灵活。下面我们主要介绍Express.js的中间件机制。

Express.js中间件的基本结构

function middlewareFunction(req, res, next) {
// 执行代码

// 调用next()将控制权传递给下一个中间件
next();

// 如果需要处理错误,可以调用next(err)
// next(new Error('Something went wrong'));
}

使用中间件

在Express.js中,我们可以使用app.use()方法来使用中间件:

const express = require('express');
const app = express();

// 一个简单的中间件
app.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});

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

app.listen(3000);

中间件的类型

1. 应用级中间件

应用级中间件绑定到app对象,使用app.use()app.METHOD()方法。

const express = require('express');
const app = express();

// 没有挂载路径的中间件,应用的每个请求都会执行该中间件
app.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});

// 挂载在/api路径下的中间件,任何指向/api的请求都会执行它
app.use('/api', (req, res, next) => {
console.log('API request received');
next();
});

// 路由级中间件,只处理GET请求
app.get('/user/:id', (req, res, next) => {
res.send(`User ID: ${req.params.id}`);
});

2. 路由级中间件

路由级中间件与应用级中间件类似,但它绑定到express.Router()实例。

const express = require('express');
const app = express();
const router = express.Router();

// 路由级中间件
router.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});

router.get('/', (req, res) => {
res.send('Router home page');
});

router.get('/about', (req, res) => {
res.send('About page');
});

// 将路由挂载到应用
app.use('/router', router);

3. 错误处理中间件

错误处理中间件有四个参数:err, req, res, next。当调用next(err)时,Express会跳过所有非错误处理中间件,直接执行错误处理中间件。

const express = require('express');
const app = express();

app.get('/error', (req, res, next) => {
next(new Error('Something went wrong'));
});

// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});

4. 内置中间件

Express 4.x提供了一些内置的中间件:

  • express.static:用于提供静态资源
  • express.json:用于解析JSON请求体
  • express.urlencoded:用于解析URL编码的请求体
const express = require('express');
const app = express();

// 提供静态资源
app.use(express.static('public'));

// 解析JSON请求体
app.use(express.json());

// 解析URL编码的请求体
app.use(express.urlencoded({ extended: true }));

5. 第三方中间件

我们可以使用许多第三方中间件来扩展Express的功能。使用第三方中间件前,需要先安装它:

npm install morgan cors helmet

然后在应用中使用:

const express = require('express');
const app = express();
const morgan = require('morgan'); // 日志中间件
const cors = require('cors'); // 跨域资源共享中间件
const helmet = require('helmet'); // 安全增强中间件

// 使用第三方中间件
app.use(morgan('dev')); // 开发环境日志
app.use(cors()); // 允许跨域请求
app.use(helmet()); // 增强应用安全性

中间件的执行顺序

在Express中,中间件的执行顺序非常重要,它按照app.use()和路由定义的顺序执行。

const express = require('express');
const app = express();

// 中间件1 - 首先执行
app.use((req, res, next) => {
console.log('Middleware 1 executed');
next();
});

// 中间件2 - 第二个执行
app.use((req, res, next) => {
console.log('Middleware 2 executed');
next();
});

// 路由处理函数 - 最后执行
app.get('/', (req, res) => {
console.log('Route handler executed');
res.send('Hello World!');
});

中间件链

我们可以在一个路由处理程序中使用多个中间件,形成中间件链。

const express = require('express');
const app = express();

// 定义多个中间件函数
const middleware1 = (req, res, next) => {
console.log('Middleware 1');
req.customData = 'Data from middleware 1';
next();
};

const middleware2 = (req, res, next) => {
console.log('Middleware 2');
console.log('Received data:', req.customData);
next();
};

// 在路由中使用中间件链
app.get('/chain', [middleware1, middleware2], (req, res) => {
res.send('Middleware chain executed successfully');
});

中间件的实际应用

1. 身份验证中间件

function authenticate(req, res, next) {
const token = req.headers.authorization;

if (!token) {
return res.status(401).json({ message: '未提供身份验证令牌' });
}

try {
// 验证令牌...
const decoded = verifyToken(token);
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ message: '无效的令牌' });
}
}

// 保护需要身份验证的路由
app.get('/api/protected', authenticate, (req, res) => {
res.json({ message: '这是受保护的资源', user: req.user });
});

2. 日志中间件

function logger(req, res, next) {
const { method, url, headers } = req;
const timestamp = new Date().toISOString();

console.log(`[${timestamp}] ${method} ${url}`);
console.log('Headers:', headers);

// 记录响应状态码
const originalSend = res.send;
res.send = function(body) {
console.log(`Response status: ${res.statusCode}`);
return originalSend.call(this, body);
};

next();
}

app.use(logger);

3. 缓存中间件

const cache = new Map();

function cacheMiddleware(req, res, next) {
const cacheKey = req.originalUrl;

if (cache.has(cacheKey)) {
console.log('Cache hit for:', cacheKey);
return res.json(cache.get(cacheKey));
}

console.log('Cache miss for:', cacheKey);

// 重写res.json方法以缓存响应
const originalJson = res.json;
res.json = function(body) {
cache.set(cacheKey, body);
return originalJson.call(this, body);
};

next();
}

// 只对GET请求使用缓存中间件
app.get('/api/cacheable/*', cacheMiddleware);

4. 限流中间件

const rateLimit = require('express-rate-limit');

// 创建限流中间件
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 每个IP最多100个请求
message: {
error: '请求过于频繁,请稍后再试'
}
});

// 应用限流中间件
app.use('/api', limiter);

自定义中间件的最佳实践

  • 保持中间件的单一职责:每个中间件只做一件事
  • 命名清晰:使用描述性的名称来命名中间件函数
  • 错误处理:正确处理中间件中的错误,并调用next(err)传递给错误处理中间件
  • 避免阻塞:中间件应该快速执行,避免包含阻塞操作
  • 记录日志:对于重要的中间件,可以添加日志记录
  • 模块化:将中间件逻辑封装在单独的模块中,便于重用

中间件执行流程图

[客户端请求] → [应用级中间件] → [路由级中间件] → [路由处理函数] → [客户端响应]

[错误处理中间件]

Express与Koa的中间件比较

Express和Koa都是流行的Node.js Web框架,但它们的中间件机制有一些区别:

Express中间件

  • 使用回调函数形式
  • 按照注册顺序执行
  • 错误处理中间件有四个参数
app.use((req, res, next) => {
// 中间件逻辑
next(); // 调用下一个中间件
});

Koa中间件

  • 使用异步函数和Promise
  • 采用洋葱模型执行
  • 上下文对象(ctx)包含请求和响应
app.use(async (ctx, next) => {
// 洋葱模型外层
await next(); // 等待下一个中间件执行完成
// 洋葱模型内层(反向执行)
});

总结

中间件机制是Node.js Web开发中的核心概念,它提供了一种灵活的方式来处理HTTP请求和响应。通过合理使用中间件,我们可以:

  • 分离关注点,使代码更加模块化
  • 重用代码,提高开发效率
  • 增强应用功能,如身份验证、日志记录、缓存等
  • 更好地组织和维护应用程序逻辑

掌握中间件的使用,对于构建高效、可维护的Node.js Web应用至关重要。