100行代码写出简易版express

595 阅读3分钟

express 简介

基于 Node.js 平台,快速、开放、极简的 Web 开发框架

简单来说,封装了node中http核心模块,专注于业务逻辑的开发。

实现思路

  1. 通过use,get,post等方法将中间件收集起来,
  2. 遇到htpp请求,根据path,mehtod 触发哪些中间件
  3. 实现一个next机制

有了整体的实现思路那么就可以根据实现思路流程那么就可以直接上手撸码拉!

一. 中间件的收集思路

  1. 在构造函数当中定义各个请求的中间件列表,all为use收集起来的,因为不管get, post,put...等请求都需要去执行一下该列表中收集起来的中间件,所以单独收集。
  2. 定义多个对外调用接口, use,get,post。。等,因为其他一致,这里就只写了 常用的三个。
  3. 在use,get..等调用该方法时,可能直接传入一个func,也可能是多个,或者第一个参数为string,那么就需要对method,path进行判断,根据不同的请求,分别放入不同的中间件列表中

下面即为实现源码

class express {
  constructor() {
    // 存放 中间件列表
    this.routers = {
      ALL:[], //  app.use(...)
      GET: [], // app.get(...)
      POST: []  //app.post(...)
    }
  }

  register() {
    // 存储中间件的相关信息,
    let info = {};
    const slice = Array.prototype.slice;

    if (typeof arguments[0] === 'string') {
      info.path = arguments[0];
      // 从第二个参数开始,将数据放入到 stack 的数组中
      info.stack = slice.call(arguments, 1); 
    } else {
      // 第一个参数为func 作为 将path 定位 / 作为根
      info.path = '/';
      info.stack = slice.call(arguments, 0); 
    }

    // 通过slice 最终返回的是一个Array 类型
    return info;
  }

  use() {
    this.routers.ALL.push(this.register.apply(this, arguments));
  }

  get() {
    this.routers.GET.push(this.register.apply(this, arguments));
  }

  post() {
    this.routers.POST.push(this.register.apply(this, arguments));
  }
}  

二. htpp请求触发哪些中间件实现思路

  1. 通过node的http核心模块创建一个server,对请求进行监听
  2. 根据 method 在构造函数的router 中找到对应的需要执行的中间件
  3. 根据url 判断对应的中间件列表中哪些是该请求需要执行的中间件
  4. 最终返回该请求需要执行的中间件

下面即为实现源码

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

      const curRoutes = [...this.routers.ALL, ...this.routers[method]];

      curRoutes.forEach(routerInfo => {
       //url = '/api/getuserinfo' 且 routerInfo.path= ‘/’;
       //url = '/api/getuserinfo' 且 routerInfo.path= ‘/api’;
       //url = '/api/getuserinfo' 且 routerInfo.path= ‘/api/getuserinfo’;
       
       if (url.indexOf(routerInfo.path) === 0) {
        stack = [...stack, ...routerInfo.stack];
       }
      });

      return stack;
  }

  callback() {
    return (req, res) => {
      // 定义json 方法
      res.json = (data) => {
        res.setHeader('Centent-type', 'application/json');
        res.end(JSON.stringify(data));
      }
      const { url, method = method.toLowerCase() } = req;
      // 该请求的执行栈列表
      const resultList = this.match(method, url);
      // next 的机制实现
      this.handle(req, res, resultList);
    }
  }

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

三. next机制实现思路

  1. 在callback 中将req,res,和该请求需要执行的中间件列表传入
  2. 定义next 函数,将中间件列表的第一个取出,并并将next传入给该中间件
  3. 执行next
 handle(req, res, stack) {
    const next = () => {
      // 拿到第一个匹配的中间件
      const middleware = stack.shift();
      // 执行中间件函数 
      if (middleware) middleware(req, res, next)
    }
    next();
  }

最终整体代码

const http = require('http');

class express {
  constructor() {
    // 存放 中间件列表
    this.routers = {
      ALL:[], //  app.use(...)
      GET: [], // app.get(...)
      POST: []  //app.post(...)
    }
  }

  register() {
    // 存储中间件的相关信息,
    let info = {};
    const slice = Array.prototype.slice;

    if (typeof arguments[0] === 'string') {
      info.path = arguments[0];
      // 从第二个参数开始,将数据放入到 stack 的数组中
      info.stack = slice.call(arguments, 1); 
    } else {
      info.path = '/';
      info.stack = slice.call(arguments, 0); 
    }

    return info;
  }

  use() {
    const info = this.register.apply(this, arguments);
    this.routers.ALL.push(info);
  }

  get() {
    const info = this.register.apply(this, arguments);
    this.routers.GET.push(info);
  }

  post() {
    const info = this.register.apply(this, arguments);
    this.routers.GET.push(info);
  }

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

    const curRoutes = [...this.routers.ALL, ...this.routers[method]];

    curRoutes.forEach(routerInfo => {
      //url = '/api/getuserinfo' 且 routerInfo.path= ‘/’;
      //url = '/api/getuserinfo' 且 routerInfo.path= ‘/api’;
      //url = '/api/getuserinfo' 且 routerInfo.path= ‘/api/getuserinfo’;
       
      if (url.indexOf(routerInfo.path) === 0) {
        stack = [...stack, ...routerInfo.stack];
      }
    });

    return stack;
  }

  /**
   * @param  {} req
   * @param  {} res  
   * @param  {} stack 该请求的执行栈
   * 处理核心next 机制
   */ 
  handle(req, res, stack) {
    const next = () => {
      // 拿到第一个匹配的中间件
      const middleware = stack.shift();
      // 执行中间件函数 
      if (middleware) middleware(req, res, next)
    }
    next();
  }

  callback() {
    return (req, res) => {
      // 定义json 方法
      res.json = (data) => {
        res.setHeader('Centent-type', 'application/json');
        res.end(JSON.stringify(data));
      }
      const { url, method = method.toLowerCase() } = req;
      // 该请求的执行栈列表
      const resultList = this.match(method, url);
      this.handle(req, res, resultList);
    }
  }

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

module.exports = () => new express();