阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 中间件的基本概念与工作原理

中间件的基本概念与工作原理

作者:陈川 阅读数:52373人阅读 分类: Node.js

中间件是Express框架的核心概念之一,用于处理HTTP请求和响应的过程中插入自定义逻辑。它通过链式调用实现请求的预处理、后处理或拦截,为Web应用提供灵活的扩展能力。

中间件的定义与作用

中间件本质上是具有特定签名的函数,接收三个参数:req(请求对象)、res(响应对象)和next(回调函数)。其典型结构如下:

function middleware(req, res, next) {
  // 处理逻辑
  next(); // 传递给下一个中间件
}

作用主要体现在:

  1. 修改请求或响应对象(如添加头部信息)
  2. 终止请求-响应周期(如权限验证失败时返回403)
  3. 调用下一个中间件(通过next()

中间件的工作流程

Express应用的请求处理过程可以看作是一个中间件管道。当HTTP请求到达时,会按照中间件注册顺序依次执行:

  1. 请求进入第一个中间件
  2. 中间件处理完毕后调用next()
  3. 控制权移交至下一个中间件
  4. 重复上述过程直到响应被发送

示例流程:

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"。

中间件的类型

应用级中间件

通过app.use()app.METHOD()绑定到应用实例:

// 对所有请求生效
app.use((req, res, next) => {
  console.log('Time:', Date.now());
  next();
});

// 只对GET /user生效
app.get('/user', (req, res, next) => {
  res.send('USER');
});

路由级中间件

作用于特定路由路径:

const userRouter = express.Router();
userRouter.use((req, res, next) => {
  console.log('User route middleware');
  next();
});
app.use('/users', userRouter);

错误处理中间件

接收四个参数(err, req, res, next):

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

内置中间件

Express提供的开箱即用中间件:

  • express.json():解析JSON请求体
  • express.urlencoded():解析URL编码请求体
  • express.static():托管静态文件
app.use(express.json());
app.use(express.static('public'));

第三方中间件

社区提供的功能扩展:

const morgan = require('morgan');
app.use(morgan('dev'));  // 日志记录

中间件的执行顺序

中间件的执行顺序严格依赖注册顺序。以下示例展示顺序的重要性:

// 这个中间件不会被执行
app.use((req, res, next) => {
  console.log('This will never run');
  next();
});

// 因为前一个中间件终止了流程
app.use((req, res, next) => {
  res.send('Early response');
});

// 正确顺序应该是:
app.use((req, res, next) => {
  const isValid = checkAuth(req);
  if (!isValid) return res.status(401).end();
  next();
});

app.get('/protected', (req, res) => {
  res.send('Protected content');
});

中间件的组合与模块化

多个中间件可以组合使用:

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

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

app.get('/user', [logOriginalUrl, logMethod], (req, res) => {
  res.send('User Info');
});

模块化组织示例:

// logger.js
module.exports = function(req, res, next) {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  next();
};

// app.js
const logger = require('./logger');
app.use(logger);

中间件的错误处理机制

错误处理中间件需要特殊定义:

// 同步错误会自动捕获
app.get('/error', (req, res) => {
  throw new Error('BROKEN');
});

// 异步错误需要显式传递
app.get('/async-error', (req, res, next) => {
  fs.readFile('/nonexistent', (err, data) => {
    if (err) next(err);  // 必须手动传递错误
    else res.send(data);
  });
});

// 错误处理中间件
app.use((err, req, res, next) => {
  res.status(500).json({ error: err.message });
});

中间件的性能优化

  1. 过滤不必要的中间件执行:
app.use('/api', (req, res, next) => {
  // 只对/api路径生效
  next();
});
  1. 使用条件中间件:
const isAdmin = (req, res, next) => {
  if (req.user.role === 'admin') return next();
  res.status(403).end();
};

app.get('/admin', isAdmin, (req, res) => {
  res.send('Admin panel');
});
  1. 中间件缓存:
const cache = {};

app.use('/data', (req, res, next) => {
  const key = req.originalUrl;
  if (cache[key]) return res.json(cache[key]);
  
  // 模拟耗时操作
  setTimeout(() => {
    const data = { value: Math.random() };
    cache[key] = data;
    res.json(data);
  }, 1000);
});

中间件的实际应用场景

身份验证

const authenticate = (req, res, next) => {
  const token = req.headers.authorization;
  if (!token) return res.status(401).send('Unauthorized');
  
  try {
    req.user = verifyToken(token);
    next();
  } catch (err) {
    res.status(403).send('Invalid token');
  }
};

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

请求数据处理

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.post('/submit', (req, res) => {
  console.log(req.body);  // 自动解析的请求体
  res.send('Data received');
});

响应时间监控

app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.url} - ${duration}ms`);
  });
  next();
});

CORS处理

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

高级中间件模式

中间件工厂

返回中间件的函数:

function createLogger(format) {
  return function(req, res, next) {
    console.log(`[${format}] ${req.method} ${req.url}`);
    next();
  };
}

app.use(createLogger('short'));

可配置中间件

function configurableMiddleware(options = {}) {
  return function(req, res, next) {
    if (options.logRequest) {
      console.log(`${req.method} ${req.url}`);
    }
    if (options.addTimestamp) {
      req.timestamp = Date.now();
    }
    next();
  };
}

app.use(configurableMiddleware({
  logRequest: true,
  addTimestamp: true
}));

中间件组合工具

const compose = require('koa-compose'); // 也可以自己实现

const middleware1 = (req, res, next) => {
  console.log('Middleware 1');
  next();
};

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

app.use(compose([middleware1, middleware2]));

中间件的测试方法

使用supertest测试中间件:

const request = require('supertest');
const app = require('../app');

describe('Auth Middleware', () => {
  it('should block unauthorized access', async () => {
    const res = await request(app)
      .get('/protected')
      .expect(401);
  });

  it('should allow authorized access', async () => {
    const res = await request(app)
      .get('/protected')
      .set('Authorization', 'valid-token')
      .expect(200);
  });
});

中间件的调试技巧

  1. 使用调试占位符:
app.use((req, res, next) => {
  console.log('Request reached middleware');
  next();
  console.log('Request left middleware');
});
  1. 检查中间件堆栈:
app.use((req, res, next) => {
  console.log('Current stack:', this._router.stack);
  next();
});
  1. 使用调试工具:
const debug = require('debug')('app:middleware');
app.use((req, res, next) => {
  debug('Request headers:', req.headers);
  next();
});

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌