express中间件浅析

186 阅读2分钟

再学习express的时候,对其中间件的实现原理狠感兴趣。自己私下模拟了一个简易demo,分享给大家。下面直接上代码。

const http = require('http');
const path = require('path');

class MiniExpress {
  constructor() {
    // 收集app 的中间件 路由执行 函数
    this.routes = {
      all: [], // 所有的匹配路径的请求 都执行的 中间件函数
      get: [], // get 请求 匹配的 中间件执行函数
      post: [] // post 请求
      // put delete 可自行扩展
    }
    // 初始中间件
    this.use(function (req, res, next) {
      // 实现 json 方法
      res.json = function (obj = {}) {
        res.setHeader('Content-type', 'application/json');
        res.end(JSON.stringify(obj))
      }
      next()
    })
  }
  // 第一个参数可能是路径 也可能是函数,并且 支持 多个函数操作
  use(...args) {
    this.handleLayer('all', ...args)
  }

  handleLayer(method = 'all', ...args) {
    let layer;
    if (typeof args[0] == 'string') {
      layer = {
        path: args.shift(),
        stack: args,
      }
    } else {
      layer = {
        path: '/',
        stack: args
      }
    }
    this.routes[method].push(layer)
  }

  get(...args) {
    this.handleLayer('get', ...args)
  }

  post() {
    this.handleLayer('post', ...args)
  }

  // 请求方式和路径 匹配 合适 的中间件
  match(method, url) {
    let midderwares = [];
    // 处理浏览器 网页 图标
    if (url == '/favicon.ico') {
      return midderwares
    }

    // 处理 对所有请求方式 都生效的中间件 
    this.routes.all.filter(m => url.indexOf(m.path) == 0).forEach(m => {
      midderwares = midderwares.concat(m.stack)
    });

    // 处理对应请求方式
    const res = this.routes[method].filter(m => url.indexOf(m.path) == 0).forEach(m => {
      midderwares = midderwares.concat(m.stack)
    });
    return midderwares;
  }

  // 处理请求的方法
  hanldRequest(req, res) {
    // console.log(req)
    let method = req.method.toLocaleLowerCase(),
      url = req.url;
    const stack = this.match(method, url);
    // 依次执行 中间件 数组

    function next() {
      const curMidware = stack.shift();
      // 如果是 处理函数
      if (curMidware) {
        // 传入正常中间件的3个参数
        curMidware(req, res, next);
      }
    }
    next()
  }

  listen(...res) {
    const server = http.createServer(this.hanldRequest.bind(this));
    server.listen(...res);
  }
}

module.exports = MiniExpress;

测试代码:

const MiniExpress = require('./mini_express');
const app = new MiniExpress();

app.use(function (req, res, next) {
  console.log('11111')
  next()
})

app.use('/api/list', function (req, res, next) {
  console.log('22222222222222')
  next()
})

app.get('/api/list', function check(req, res, next) {
  console.log('user is pass checked');
  next()
}, function (req, res, next) {
  res.json({
    code: 200,
    list: [
      { id: 1, text: 'ddd' },
      { id: 2, text: 'eee' }
    ]
  })
})

app.listen(8000, () => {
  console.log('server is listening at port 8000');
})

/*
测试 结果
server is listening at port 8000
11111
22222222222222
user is pass checked

*/

要实现express的中间件这样的效果,我们就需要记录所有的中间处理函数及其对应的路径和请求方式。然后,对每一次请求都匹配对应处理函数,最后就会得到每次请求的 中间件函数队列,然后利用指针来依次执行所有的中间件处理函数。这种思想也可用于异步和动画队列等问题的应用中。