实现Express中间件原理

实现Express中间件原理


一、定义一个Express class

class Express {
  constructor() {
    // 存放路由中间件 此处只演示get、post方法
    this.routes = {
      all: [],
      get: [],
      post: [],
    }
  }

二、基本的use、get、post方法

1、register方法帮助注册中间件

/**
   * 用于当调用实例的use、get、post等方法时注册中间件(处理路径)
   */
  register(path) {
    /**
     * 可能传入多个参数,同时第一个参数可能是一个请求路径,对该路径进行处理
     */
    const info = {}
    if (typeof path === "string") {
      /**
       * 如果第一个参数传入的是路径则截取出第一个参数以外的参数作为中间件存入
       */
      info.path = path
      info.middleware = slice.call(arguments, 1)
    } else {
      /**
       * 如果第一个参数没有传入路径则默认未'/'根路径,即所有请求都会经过该中间件的处理
       */
      info.path = "/"
      info.middleware = slice.call(arguments, 0)
    }
    return info
  }

2、方法实现

/**
   * 在所有调用注册use或者路由注册的方法中执行register帮助处理参数并注册
   */
  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)
  }

三、listen监听请求的具体实现

1、listen方法

listen(...args) {
    const server = http.createServer(this.callback())
    server.listen(...args)
  }

2、listen方法依赖的callback

callback() {
    return (req, res) => {
      /**
       * 模拟实现res.json,方便测试
       */
      res.json = (data) => {
        res.setHeader("Content-Type", "application/json")
        res.end(JSON.stringify(data))
      }
      // 根据method和url匹配对应的路由以及中间件
      const { url, method } = req
      const middlewareList = this.matchMiddleware(url, method)

      // 将匹配到中间件交给middlewareHandler执行
      this.middlewareHandler(middlewareList)
    }
  }

3、匹配路由中间件以及中间件执行的实现

1、matchMiddleware匹配中间件

/**
   * 匹配指定请求的路由以及中间件
   * @param {请求的路径} url
   * @param {请求的方法} method
   */
  matchMiddleware(url, method) {
    let middlewareList = []
    if (url === "/favicon.ico") {
      return middlewareList
    }

    /**
     * 先匹配请求的method
     */
    method = method.toLowerCase()
    const currentRoutes = [...this.routes.all, ...this.routes[method]]
    /**
     * 再根据请求路径匹配
     */
    middlewareList = currentRoutes.map((route) => {
      if (route.path.indexOf(url) === 0) {
        return route.middleware
      }
    })
    return middlewareList
  }

2、middleHandler中间件执行方法

middlewareHandler(req, res, middlewareList) {
    const next = () => {
      // 取出第一个中间件
      const middleware = middlewareList.shift()
      // 执行该中间件函数,
      //并传入next,当该中间件执行该next函数时会接着取到下一个中间件,
      //以此类推执行下去,便实现了中间件依次连续执行
      middleware(req, res, next)
    }
    // 第一次传入中间件,马上执行
    next()
  }

四、完整代码

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

class Express {
  constructor() {
    // 存放路由中间件 此处只演示get、post方法
    this.routes = {
      all: [],
      get: [],
      post: [],
    }
  }

  /**
   * 用于当调用实例的use、get、post等方法时注册中间件(处理路径)
   */
  register(path) {
    /**
     * 可能传入多个参数,同时第一个参数可能是一个请求路径,对该路径进行处理
     */
    const info = {}
    if (typeof path === "string") {
      /**
       * 如果第一个参数传入的是路径则截取出第一个参数以外的参数作为中间件存入
       */
      info.path = path
      info.middleware = slice.call(arguments, 1)
    } else {
      /**
       * 如果第一个参数没有传入路径则默认未'/'根路径,即所有请求都会经过该中间件的处理
       */
      info.path = "/"
      info.middleware = slice.call(arguments, 0)
    }
    return info
  }

  /**
   * 在所有调用注册use或者路由注册的方法中执行register帮助处理参数并注册
   */
  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)
  }

  /**
   * 匹配指定请求的路由以及中间件
   * @param {请求的路径} url
   * @param {请求的方法} method
   */
  matchMiddleware(url, method) {
    let middlewareList = []
    if (url === "/favicon.ico") {
      return middlewareList
    }

    /**
     * 先匹配请求的method
     */
    method = method.toLowerCase()
    const currentRoutes = [...this.routes.all, ...this.routes[method]]
    /**
     * 再根据请求路径匹配
     */
    middlewareList = currentRoutes.map((route) => {
      if (route.path.indexOf(url) === 0) {
        return route.middleware
      }
    })
    return middlewareList
  }

  middlewareHandler(req, res, middlewareList) {
    const next = () => {
      // 取出第一个中间件
      const middleware = middlewareList.shift()
      // 执行该中间件函数,
      //并传入next,当该中间件执行该next函数时会接着取到下一个中间件,
      //以此类推执行下去,便实现了中间件依次连续执行
      middleware(req, res, next)
    }
    // 第一次传入中间件,马上执行
    next()
  }

  callback() {
    return (req, res) => {
      /**
       * 模拟实现res.json,方便测试
       */
      res.json = (data) => {
        res.setHeader("Content-Type", "application/json")
        res.end(JSON.stringify(data))
      }
      // 根据method和url匹配对应的路由以及中间件
      const { url, method } = req
      const middlewareList = this.matchMiddleware(url, method)

      // 将匹配到中间件交给middlewareHandler执行
      this.middlewareHandler(middlewareList)
    }
  }

  listen(...args) {
    const server = http.createServer(this.callback())
    server.listen(...args)
  }
}

module.exports = () => {
  return new Express()
}

总结

中间件的重要机制首先是注册,然后是如何去执行中间件以及next()的实现,如何借助next()去控制各个中间件之间的链接,这里我主要依赖于middleHandler方法的实现。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值