node JS 中 express 中介軟體實現原理分析

舊城tk發表於2020-12-09

一、express

  1. express 中介軟體,如下所示:
  • app.use 用來註冊中介軟體,先收集起來
  • 遇到 http 請求,根據 pathmethod 判斷觸發哪些
  • 實現 next 機制,即上一個通過 next 觸發下一個
  1. express 中介軟體的實現內部原理,程式碼如下所示:
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 = {}
    // 判斷第一個引數是否是 string 型別
    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.all.push(info)
  }


  post () {
    const info = this.register.apply(this, arguments)
    this.routes.all.push(info)
  }


  match (method, url) {
    let stack = []
    if (url === '/favicon.ico') {
      return stack
    }

    // 獲取 routes 
    let cutRoutes = []
    cutRoutes = cutRoutes.concat(this.routes.all)
    cutRoutes = cutRoutes.concat(this.routes[method])

    cutRoutes.forEach(routeInfo => {
      if (url.indexOf(routeInfo.path) === 0) {
        // url === '/api/get-cookie' 且 routeInfo.path === '/'
        // url === '/api/get-cookie' 且 routeInfo.path === '/api'
        // url === '/api/get-cookie' 且 routeInfo.path === '/api/get-cookie'
        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()
}

  1. express 中介軟體原理的應用,程式碼如下所示:
const express = require('./like-express')


// 本次 http 請求的例項
const app = express()

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

app.use((req, res, next) => {
  // 假設在處理 cookie
  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('處理 /api 路由')
  next()
})

app.post('/api', (req, res, next) => {
  console.log('處理 /api 路由')
  next()
})


// 模擬登入驗證
function loginCheck(req, res, next) {
  setTimeout(() => {
    console.log('模擬登入成功')
    next()
  })
}


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

app.use((req, res, next) => {
  console.log('處理 404')
  res.json({
    errno: -1,
    msg: '404 not found'
  })
})


app.listen(8000, () => {
  console.log('server is running on port 8000')
})

相關文章