您现在的位置是:网站首页 > 中间件的执行顺序与控制文章详情

中间件的执行顺序与控制

中间件的概念与作用

Express中间件本质上是函数,能够访问请求对象(req)、响应对象(res)以及应用程序的请求-响应周期中的下一个中间件函数。这些函数可以执行以下操作:

  • 修改请求和响应对象
  • 结束请求-响应周期
  • 调用堆栈中的下一个中间件
const express = require('express');
const app = express();

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

中间件的类型

Express中的中间件主要分为以下几种类型:

应用级中间件

使用app.use()或app.METHOD()绑定到app对象上的中间件:

// 对所有请求生效
app.use((req, res, next) => {
  console.log('Application-level middleware');
  next();
});

// 只对GET请求生效
app.get('/user', (req, res, next) => {
  console.log('GET-specific middleware');
  next();
});

路由级中间件

绑定到express.Router()实例上的中间件:

const router = express.Router();

router.use((req, res, next) => {
  console.log('Router-level middleware');
  next();
});

错误处理中间件

专门处理错误的中间件,需要四个参数:

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

内置中间件

Express自带的中间件,如express.static:

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

第三方中间件

社区提供的中间件,如body-parser:

const bodyParser = require('body-parser');
app.use(bodyParser.json());

中间件的执行顺序

中间件的执行顺序完全取决于它们在代码中被定义的顺序。Express会按照中间件被添加的顺序依次执行它们:

app.use((req, res, next) => {
  console.log('First middleware');
  next();
});

app.use((req, res, next) => {
  console.log('Second middleware');
  next();
});

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

在这个例子中,控制台会先输出"First middleware",然后是"Second middleware",最后才会处理GET请求。

控制中间件流程

next()函数的作用

next()函数将控制权传递给下一个中间件。如果不调用next(),请求将会挂起:

app.use((req, res, next) => {
  // 不调用next(),请求将不会继续
  if (req.headers['x-auth']) {
    next();
  } else {
    res.status(401).send('Unauthorized');
  }
});

提前终止请求

可以在中间件中直接发送响应来终止请求链:

app.use((req, res, next) => {
  if (!req.user) {
    return res.status(401).send('Please login');
  }
  next();
});

错误处理流程

使用next(err)可以将错误传递给错误处理中间件:

app.get('/user/:id', (req, res, next) => {
  User.findById(req.params.id, (err, user) => {
    if (err) {
      return next(err); // 跳转到错误处理中间件
    }
    res.send(user);
  });
});

中间件的组合与模块化

可以将多个中间件组合成数组,这在路由处理中特别有用:

const logOriginalUrl = (req, res, next) => {
  console.log('Request URL:', req.originalUrl);
  next();
};

const logMethod = (req, res, next) => {
  console.log('Request Type:', req.method);
  next();
};

const logStuff = [logOriginalUrl, logMethod];

app.get('/user', logStuff, (req, res) => {
  res.send('User Info');
});

异步中间件的处理

处理异步操作时,必须确保在异步操作完成后调用next():

app.use(async (req, res, next) => {
  try {
    const data = await someAsyncOperation();
    req.data = data;
    next();
  } catch (err) {
    next(err);
  }
});

中间件的性能考虑

减少不必要的中间件

每个中间件都会增加请求处理时间,应该只包含必要的中间件:

// 只在开发环境使用日志中间件
if (process.env.NODE_ENV === 'development') {
  app.use(morgan('dev'));
}

中间件的优化顺序

将最常用的路由放在前面,减少不必要的中间件执行:

// 静态文件路由放在前面
app.use(express.static('public'));

// 其他路由放在后面
app.use('/api', apiRouter);

常见中间件模式

认证中间件

典型的认证中间件实现:

const authMiddleware = (req, res, next) => {
  const token = req.headers['authorization'];
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }
  
  try {
    const decoded = jwt.verify(token, SECRET_KEY);
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(403).json({ error: 'Invalid token' });
  }
};

app.get('/profile', authMiddleware, (req, res) => {
  res.json({ user: req.user });
});

请求数据处理中间件

处理请求数据的中间件组合:

app.use(express.json()); // 解析JSON body
app.use(express.urlencoded({ extended: true })); // 解析表单数据
app.use(cookieParser()); // 解析cookies

跨域处理中间件

自定义CORS中间件:

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  next();
});

调试中间件执行

可以使用特殊的调试中间件来跟踪执行流程:

app.use((req, res, next) => {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
  next();
});

app.use((req, res, next) => {
  console.log('Request headers:', req.headers);
  next();
});

高级中间件技巧

动态加载中间件

根据条件动态加载中间件:

const config = require('./config');

if (config.featureFlags.analytics) {
  app.use(analyticsMiddleware());
}

中间件的条件执行

基于请求属性的条件执行:

app.use((req, res, next) => {
  if (req.path.startsWith('/api')) {
    console.log('API request detected');
  }
  next();
});

中间件的重写功能

修改请求或响应对象:

app.use((req, res, next) => {
  // 添加自定义方法到response对象
  res.jsonSuccess = (data) => {
    res.json({ success: true, data });
  };
  next();
});

// 使用自定义方法
app.get('/data', (req, res) => {
  res.jsonSuccess({ message: 'It works!' });
});

中间件的测试策略

编写可测试的中间件:

// 中间件函数
const loggerMiddleware = (req, res, next) => {
  console.log(`Logged ${req.method} ${req.url}`);
  next();
};

// 测试用例
test('logger middleware should call next', () => {
  const req = { method: 'GET', url: '/test' };
  const res = {};
  const next = jest.fn();
  
  loggerMiddleware(req, res, next);
  
  expect(next).toHaveBeenCalled();
});

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步