Skip to content

浅析express中间件

Posted on:2020年8月8日 at 23:49

Express 是一个轻量的Node.js http框架。

先来看一个 next 的核心 demo

const arr = [
  next => {
    console.log(1);
    next();
  },
  next => {
    console.log(2);
    next();
  },
  next => {
    console.log(3);
  },
  next => {
    // 不会执行 因为上一个函数中没有执行next
    console.log(4);
    next();
  },
  next => {
    // 不会执行 因为前面的函数中没有执行next
    console.log(5);
  },
];

function next() {
  const temp = arr.shift();
  if (temp) {
    temp(next);
  }
}

next();

code 开始 (新建一个 ware.js 文件)

const http = require("http"); // node http 模块

/* 模拟express 的中间件的机制 */

class Middleware {
  constructor() {
    // 定义仓库 用于存放中间件和对应的path    example => [{path: '/', dispose: [function]}, {path: '/api', dispose: [function, function]}]
    this.routers = {
      all: [], // 存放所有的中间件信息
      get: [], // 存放get中间件信息
      post: [], // 存放post中间件信息
    };
    // ... 其他注册方法类似
  }

  /* 初始化中间件的注册 */
  init(...arg) {
    const item = {};
    const [firstArg] = arg;
    if (typeof firstArg === "string") {
      // 如果传入的第一个参数是字符串,则表示传入了路径
      item.path = firstArg;
      item.dispose = arg.slice(1); // function[]
    } else {
      // 如果传入的第一个参数不是字符串,则表示未传入路径, 没传入路径时其本质就是作用在根路径上
      item.path = "/";
      item.dispose = arg;
    }

    return item;
  }

  use(...args) {
    // 把用户通过use注册的中间件信息存入到all仓库中
    const allItem = this.init(...args);
    this.routers.all.push(allItem);
  }

  get(...args) {
    // 把用户通过get注册的中间件信息存入到get仓库中
    const getItem = this.init(...args);
    this.routers.get.push(getItem);
  }

  post(...args) {
    // 把用户通过post注册的中间件信息存入到post仓库中
    const postItem = this.init(...args);
    this.routers.post.push(postItem);
  }

  /* 根据请求方法找到中间件 */
  findWareItem(url, method) {
    // 用一个数组储存匹配到的中间键
    const temp = [];
    // 定义一个数组将当前需要用到的中间件信息仓库都存入 (all仓库 + 对应的请求方法仓库)
    const fullWare = [...this.routers.all, ...this.routers[method]];
    // 如果我们的请求路径中包含了中间键信息仓库的path,那么我们就把这个中间键存放到数组中并返回
    fullWare.forEach(item => {
      if (url.indexOf(item.path) === 0) {
        temp.push(...item.dispose);
      }
    });

    return temp;
  }

  /* next核心机制 */
  handleWare(req, res, currentWare) {
    function next() {
      // 获取当前请求中间件仓库的第一个中间件并从当前中间件仓库中将其删除
      const firstWareItem = currentWare.shift();
      if (firstWareItem) {
        /* !!!执行中间件 */
        // 如果没有在中间件中执行 next 那那么下一个中间件就无法执行
        firstWareItem(req, res, next);
      }
    }

    // 先立即执行下自身next
    next();
  }

  serverHandle() {
    return (req, res) => {
      let { url, method } = req; // 直接从请求中拿到请求的方法和url
      method = method.toLowerCase();

      // 当请求来的时候我们需要根据method从用于中间件信息仓库中取出对应中间件 (直接忽略favicon.ico请求)
      const currentWare = url.includes("favicon.ico")
        ? []
        : this.findWareItem(url, method);
      // 得到当前的请求的所有中间键后交给handleWare来处理next
      this.handleWare(req, res, currentWare);
    };
  }

  listen(...args) {
    const app = http.createServer(this.serverHandle()); // 这里只是为了模拟得到一个http服务
    app.listen(...args); // 直接交给node原生的http模块处理
  }
}

module.exports = () => {
  return new Middleware();
};

测试 (同一目录下新建一个 demo.js 文件)

const demo = require("./ware");
const app = demo();

app.use((req, res, next) => {
  console.log("请求开始");
  next(); // 放行
});

app.get("/index", (req, res, next) => {
  console.log(`${req.method}  ${req.url}`);
  res.end(`${req.method}  ${req.url}`);
});

app.post("/foo", (req, res, next) => {
  console.log(`${req.method}  ${req.url}`);
  res.end(`${req.method}  ${req.url}`);
});

app.listen(5000, () => {
  console.log("http://localhost:5000");
});

Usage

node demo.js

浏览器访问 http://localhost:5000/index 显示 GET /index