Express的实现及中间件

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');
  })
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,458评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,030评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,879评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,278评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,296评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,019评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,633评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,541评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,068评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,181评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,318评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,991评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,670评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,183评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,302评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,655评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,327评论 2 358

推荐阅读更多精彩内容