美文网首页
Express的实现及中间件

Express的实现及中间件

作者: 我的钱包瘪瘪的 | 来源:发表于2019-11-12 10:13 被阅读0次

express

express的实现

  1. 使用app.use注册中间件, 先收集起来
  2. 遇到http请求, 根据path, method判断触发哪些
  3. 通过next()执行下一步
    const http = require('http');
    const slice = Array.prototype.slice

    class LikeExpress {
      constructor() {
        // 存放中间件的列表
        this.routes = {
          all: [],    // 存放用use函数注册的中间件
          get: [],    // 用get函数注册的中间件
          post: [],   // 用post函数注册的中间件
          put: [],    // 等等http方法
        }
      }
      // 通用注册中间件的方法
      register (path) {
        const info = {};
        if (typeof path === 'string') {
          // 存储当前的地址
          info.path = path;
          // 当前注册的所有中间件, 除了请求地址, 从第二个参数开始, 转为数组
          info.stack = slice.call(arguments, 1)
        } else {
          info.path = '/';
          // 当前注册的所有中间件, 除了请求地址, 从第一个参数开始, 转为数组
          info.stack = slice.call(arguments, 0)
        }
        return info
      }

      // 实现 app.use()方法
      use () {
        // 将当前函数形参全部传入, 返回info, path 和注册的所有中间件
        const info = this.register.apply(this, arguments);
        this.routes.all.push(info);
      }

      get () {
        // 将当前函数形参全部传入, 返回info, path 和注册的所有中间件
        const info = this.register.apply(this, arguments);
        this.routes.get.push(info);
      }

      post () {
        // 将当前函数形参全部传入, 返回info, path 和注册的所有中间件
        const info = this.register.apply(this, arguments);
        this.routes.all.post(info);
      }

      // 根据请求方式/请求地址筛选, 返回符合本次请求的所有中间件
      match (method, url) {
        let stack = [];
        if (url === '/favicon.ico') {
          return stack;
        }

        // 获取routes
        let curRoutes = [];
        // 通过use注册的中间件, 都需要调用
        curRoutes = curRoutes.concat(this.routes.all)
        // 通过get/post的, 直接用method区分, 筛选请求方式
        curRoutes = curRoutes.concat(this.routes[method])
  
        curRoutes.forEach(routeInfo => {
          // 筛选url
          // url === '/api/list'
          if (url.indexOf(routeInfo.path) === 0) {
            // routeInfo.path === '/' || '/api' || '/api/lis',都符合, 都需要加进去执行
            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的方法
          res.json = (data) => {
            res.setHeader('Content-type', 'application/json');
            res.end(JSON.stringify(data));
          }

          const url = req.url;
          const method = req.method.toLowerCase(); // 把method变成小写
          // 通过method和url, 筛选出符合本次请求的, 所有中间件, 然后都需要执行
          // 已经匹配好的中间件列表
          const resultList = this.match(method, url);
          this.handle(req, res, resultList);
        }
      }

      listen (...args) {
        // 直接就创建server服务
        const server = http.createServer(this.callBack());
        // 把port什么的直接结构到了服务对象的listen中
        server.listen(...args);
      }
    }
    // 工厂函数, 确保每次都产生express
    module.exports = () => {
      return new LikeExpress()
    }

express的中间件机制

  1. 根据上一步express的实现, express中间件的实现
  2. app.use()可同时注册三个中间件
  const express = require('express');

  // 本次http的实例
  const app = express();
  // 第一个参数没有路由, 那所有路由都会执行一次
  // 执行完才能next(), 执行下一步
  app.use((req, res, next) => {
    console.log('请求开始', req.method, req.url);
    next();
  })
  // 模拟处理cookie的中间件, next()
  app.use((req, res, next) => {
    // 假设处理cookie
    req.cookies = {
      sessionid: 123
    }
    next();
  })
  // 模拟异步处理post data的中间件, next()
  app.use((req, res, next) => {
    // 假设处理post data
    // 异步
    setTimeout(() => {
      req.body = {
        a: 12,
        b: 123
      }
      next();
    })
  })
  // 匹配到'/api'接口的都执行一遍, next()
  app.use('/api', (req, res, next) => {
    console.log(`处理'/api'路由`)
    next();
  })
  // 匹配到 get '/api'都执行一遍, next()
  app.get('/api', (req, res, next) => {
    console.log(`get /api路由`)
    next();
  })

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

  // 模拟登陆验证
  const loginCheck = (req, res, next) => {
    setTimeout(() => {
      // console.log('登陆失败');
      // res.json({
      //   errno: -1,
      //   msg: '登陆失败'
      // })
      next()
      console.log('登陆成功');
    })
  }
  // 匹配到get '/api/get-cookie'执行, 是否登录, 登录才能next()
  // loginCheck 也算中间件
  app.get('/api/get-cookie', loginCheck, (req, res, next) => {
    res.json({
      errno: 0,
      data: req.cookies
    })
    // 没有next了
    // next();
  })

  app.post('/api/get-body', (req, res, next) => {
    res.json({
      errno: 0,
      data: req.body
    })
    // 没有next了
    // next();
  })
  // 未匹配的的, 设置404
  app.use((req, res, next) => {
    console.log('处理404');
    res.json({
      errno: -1,
      msg: '404 not found'
    })
  })

  app.listen(3000, () => {
    console.log('ok');
  })

相关文章

网友评论

      本文标题:Express的实现及中间件

      本文链接:https://www.haomeiwen.com/subject/yqjfictx.html