Express中间件及原理
@ 姜波 | 星期日,八月 16 日,2020 年 | 2 分钟阅读 | 更新于 星期日,八月 16 日,2020 年

环境

安装 express

npm install express-generator -g
express express-test

添加 dev 环境

npm install nodemon cross-env --save-dev

package.json

{
	"scripts":{
    "dev":"cross-env NODE_ENV=dev nodemon ./bin/www"
  }
}

中间件

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

app.use((req, res, next) => {
  console.log("处理中间件");
  next();
});
app.use("/api", (req, res, next) => {
  console.log("处理 /api 路由");
  next();
});
app.get("/api", (req, res, next) => {
  console.log("处理get /api 路由");
  next();
});
app.post("/api", (req, res, next) => {
  console.log("处理post /api 路由");
  next();
});
app.get("/api/a", (req, res, next) => {
  console.log("处理get /api/a");
});
app.post("/api/b", (req, res, next) => {
  console.log("处理post /api/b");
});
app.use((req, res, next) => {
  console.log("处理 404");
});

app.listen(3000, () => {
  console.log("server is running on port 3000");
});

原理

  • app.use 用来注册中间件,先收集起来
  • 遇到 http 请求,根据 path 和 method 判断触发哪些
  • 实现 next 机制,即上一个通过 next 触发下一个

实现

const http = require("http");
const slice = Array.prototype.slice;

class LikeExpress {
  constructor() {
    // 存放中间件的列表
    this.routes = {
      all: [], // app.use(...)
      get: [], // app.get(...)
      post: [], // app.post(...)
    };
  }
  register(path) {
    const info = {};
    if (typeof path === "string") {
      info.path = path;
      // 从第二个参数开始,转换为数组存入 stack
      info.stack = slice.call(arguments, 1);
    } else {
      info.path = "/";
      // 从第一个参数开始,转换为数组存入 stack
      info.stack = slice.call(arguments, 0);
    }
    return info;
  }
  use() {
    const info = this.register.apply(this, arguments);
    this.routes.all.push(info);
  }
  get() {
    const info = this.register.apply(this, arguments);
    this.routes.get.push(info);
  }
  post() {
    const info = this.register.apply(this, arguments);
    this.routes.post.push(info);
  }
  match(method, url) {
    let stack = [];
    if (url === "/favicon.ico") {
      return stack;
    }

    // 获取routes
    let curRoutes = [];
    curRoutes = curRoutes.concat(this.routes.all);
    curRoutes = curRoutes.concat(this.routes[method]);

    curRoutes.forEach((routeInfo) => {
      if (url.indexOf(routeInfo.path) === 0) {
        stack = stack.concat(routeInfo.stack);
      }
    });
    return stack;
  }
  // 核心的next机制
  handle(req, res, stack) {
    const next = () => {
      // 拿到第一个匹配的中间件
      const middleware = stack.shift();
      if (middleware) {
        // 执行中间件函数
        middleware(req, res, next);
      }
    };
    next();
  }
  callback() {
    return (req, res) => {
      res.json = (data) => {
        res.setHeader("Content-type", "application/json");
        res.end(JSON.stringify(data));
      };
      const url = req.url;
      const method = req.method.toLowerCase();

      const resultList = this.match(method, url);
      this.handle(req, res, resultList);
    };
  }
  listen(...args) {
    const server = http.createServer(this.callback());
    server.listen(...args);
  }
}

// 工厂函数
module.exports = () => {
  return new LikeExpress();
};

session

npm install express-session --save

app.js

const session = require("express-session");

app.use(
  session({
    secret: "abcdefg",
    cookie: {
      // path: '/', // 默认配置
      // httpOnly: true, // 默认配置
      maxAge: 24 * 60 * 60 * 1000,
    },
  })
);

redis

npm install redis connect-redis --save

app.js

const redis = require('redis')
const redisClient = redis.createClient(6379, 127.0.0.1) // 端口号,IP

const session = require('express-session')
const RedisStore = require('connect-redis')(session)

const sessionStore = new RedisStore({
  client: redisClient
})
app.use(session({
  secret: 'abcdefg',
  cookie: {
    // path: '/', // 默认配置
    // httpOnly: true, // 默认配置
    maxAge: 24 * 60 * 60 * 1000
  },
  store: sessionStore
}))

logs

app.js

const fs = require("fs");
const path = require("path");
const logger = require("morgan");

const ENV = process.env.NODE_ENV;
if (ENV !== "production") {
  // 开发环境
  app.use(logger("dev"));
} else {
  // 生产环境
  const logFileName = path.join(__dirname, "logs", "access.log");
  const writeStream = fs.createWriteStream(logFileName, {
    flags: "a",
  });
  app.use(
    logger("combined", {
      stream: writeStream,
    })
  );
}

公众号

Image text

QQ

Image text

微信

Image text

微信打赏

Image text

社交链接