手把手实现Express 中间件的注册和调用

303 阅读2分钟

Express在使用中间件时主要用到了下面几个方法:

use方法: path非必传,可传入多个中间件;path部分匹配

get方法:其他同use方法;path需要全匹配

post方法:其他同use方法;path需要全匹配

listen方法:监听某个http端口,执行其他回调

like-express.js 代码:


const http = require('http')
class LikeExpressTest {
  constructor() {
    // 存放所有注册的中间件
    this.middlewares = {
      all: [],
      get: [],
      post: []
    }
  }

  // 获取注册时path和中间件的表
  getRoute(argList) {
    const [firstParam] = argList
    let routes = []
    if (typeof firstParam !== 'string') {
      // 没有传path; path就是 '/' 
      argList.forEach(item => {
        const infoObj = { path: '/', route: item }
        routes.push(infoObj)
      })
    } else {
      let routesTmp = argList.slice(1)
      routesTmp.forEach(item => {
        const infoObj = { path: firstParam, route: item }
        routes.push(infoObj)
      })
    }
    return routes
  }

  use() {
    const argList = [...arguments]
    this.middlewares.all.push(...this.getRoute(argList))
  }

  get() {
    const argList = [...arguments]
    this.middlewares.get.push(...this.getRoute(argList))
  }

  post() {
    const argList = [...arguments]
    this.middlewares.post.push(...this.getRoute(argList))
  }

  // 获取 path,method相匹配的中间件
  checkRoutes(path, method) {
    const result = []
    // all:所有path为'/',或者左到右匹配了部分path的中间件
    this.middlewares.all.forEach((item) => {
      if(item.path === '/' || path.indexOf(item.path + '/')  === 0) {
        result.push(item.route)
      }
    })
    // 非all: 和链接上path 相等的中间件
    this.middlewares[method].forEach(item => {
      if(item.path === path) {
        result.push(item.route)
      }
    })
    return result
  }

  callback() {
    return (req, res) => {
      res.setHeader("Content-type", "application/json");
      const path = req.url.split('?')[0]
      const method = req.method.toLowerCase()

      // 获取和path 和 method匹配的中间件
      const routes = this.checkRoutes(path, method)

      res.json = (data) => {
        res.end(JSON.stringify(data))
      }

      // 取出下一个中间件,如果存在,去执行
      const next = () => {
        const middleware = routes.shift()
        if (middleware) {
          middleware(req, res, next)
        } else {
          res.end('')
        }
      }
      next()
    }
  }

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

module.exports = LikeExpressTest

在test.js 中调用这个Express:

const express = require('./like-express')

const app = new express()

app.use((req, res, next) => {
  console.log('请求开始...', req.method, req.url)
  next()
})

app.use((req, res, next) => {
  console.log('处理cookie... ***')
  req.cookie = {
    userId: 'abc123'
  }
  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()
})

function loginCheck(req, res, next) {
  setTimeout(() => {
    console.log('模拟登陆成功 **')
    next()
  });
}

app.get('/api/get-cookie', loginCheck, (req, res, next) => {
  console.log('get cookie ***** ')
  res.json({
    errno: 0,
    msg: 'xixixi'
  })
})

app.post('/api/user/update', loginCheck, (req, res, next) => {
  res.json({
    errno: 0,
    data: {name: 'haha', id: 1}
  })
})

app.listen(5000, () => {
  console.log('5000 端口,欢迎访问~~')
})

执行node test.js ,然后访问链接 http://localhost:5000/api/get-cookie ,执行结果是:

请求开始... GET /api/get-cookie
处理cookie... ***
处理api路由
模拟登陆成功 **
get cookie *****

以上就是中间件的注册和调用的方法。

难点是 callback 方法中,next方法的实现。在next内部调用了自己,实现中间件的循环调用。