编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

从思路到实例,帮你一步步梳理怎样用Node.js实现express框架

wxchong 2024-10-22 17:54:50 开源技术 8 ℃ 0 评论

express的基本用法

const express = require("express");  
const app = express();  
app.get("/test", (req, res, next) => {  
  console.log("*1");  
//   res.end("2");  
  next();  
});  
app.get("/test", (req, res, next) => {  
  console.log("*2");  
  res.end("2");  
});  
app.listen(8888, (err) => {  
  !err && console.log("监听成功");  
}); 

当我访问localhost:8888/test时候,返回了:2,服务端打印了

*1  
*2 

从上面的部分可以看到什么?

1.express的默认引入被调用后会返回一个app对象

2.app.listen会启动进程监听的接口

3.每次收到请求,对应的url和method会触发相应挂载在app上对应的回调函数

4.对next调用,然后触发下一个

现在我们可以一起来尝试实现一个简单的express框架

我们用class来定义express的文件入口

class express {  
}  
module.exports = express; 

需要的原生模块http,创建进程监听端口

const { createServer } = require("http"); 

对class进行listen方法的定义从而监听端口

class express {  
  listen(...args) {  
    createServer(cb).listen(...args);  
  }  
} 

通过上述情况,我们就可以通过class的listen去调用http模块的listen了,我们可以先放下这个部分的cb之后再说,但是你必须要知道的是只要接受请求就必然调用cb函数,这个是被createServer 原生模块封装好的,非常便捷。

实现从接收到请求出发

实现app.get app.post等方法

按照目前我们所接到的响应,就会对cb的回调函数进行触发,我们可以先尝试打印出来,看看到底是什么参数?

class express {  
  cb() {  
    return (req, res) => {  
      console.log(res, res, "开始行动");  
    };  
  }  
  listen(...args) {  
    createServer(this.cb()).listen(...args);  
  }  
} 

根据这个代码块我们可以看到,req和res是我们需要的可读流和可写流。现在我们在写get和post的方法。

constructor() {  
    this.routers = {  
      get: [],  
      post: [],  
    };  
  }  
  get(path, handle) {  
    this.routers.get.push({  
      path,  
      handle,  
    });  
  }  
  post(path, handle) {  
    this.routers.post.push({  
      path,  
      handle,  
    });  
  } 

在初始化的过程中,我们需要定义get,post的数组去储存相对于的path和handle。如果这个时候需要触发路由回调则首先需要核对对应求情方式下对应的url的handle方法,最后再触发回调。这个时候就有一个问题出现了,我们如何找到对应请求方式下的url对应的handle?在接到请求时候就要遍历一次这里要考虑匹配多个路由,意味着,我们可能遇到像最开始一样,有两个 get 方式的 test 路由 。

cb() {  
  return (req, res) => {  
    const method = req.method.toLowerCase();  
    console.log(this.routers[method], ",method");  
    const url = req.url;  
    this.routers[method].forEach((item) => {  
      item.path === url && item.handle(req, res);  
    });  
  };  
}  
listen(...args) {  
  createServer(this.cb()).listen(...args);  
} 

我们仔细看后可以发现,上面的代码块根据method寻找到了相对应的数组,遍历找到请求的路由,然后再触发回调,这个时候是可以正常返回数据没有问题的。

[ { method: 'get', path: '/test', handle: [Function] } ] ,method 

此时最简单的express已经完成了,但是我们好像忘了最重要的中间件

完成最重要的中间件功能

我们首先必须了解,express的中间件有两种,一个是带路由的,路由决定了其是否触发。另一种并不带路由器,比如静态资源,这种类型的话是用户访问任何路由都要触发一次的。所以我们需要一个all数实现储存的目的。

constructor() {  
   this.routers = {  
     get: [],  
     post: [],  
     all: [],  
   };  
 } 

之前直接使用push的方式比较“简单粗暴”,如果用户对功能有特殊要求,则可能需要特殊处理,比如中间件功能。举个例子:

handleAddRouter(path, handle) {  
   let router = {}; 
    if (typeof path === "string") {  
     router = {  
       path,  
       handle,  
     };  
   } else {  
     router = {  
       path: "/",  
       handle: path, 
     };  
   }  
   return router;  
 }  
 get(path, handle) {  
   const router = this.handleAddRouter(path, handle);  
   this.routers.get.push(router);  
 }  
 post(path, handle) {  
   const router = this.handleAddRouter(path, handle);  
   this.routers.post.push(router); 
  }  
 use(path, handle) {  
   const router = this.handleAddRouter(path, handle);  
   this.routers.all.push(router);  
 } 

我在这儿得说一下,每次添加之前,都得对handleAddRouter进行触发,如果是path为空的中间件,那么path帮它设置成'/'。这样,我们仅仅剩下遗留的最后一个点,next的实现。当我们加入数组all之后,中间件的数量不会维持在一个,这样就存在一个问题,打过来一个请求,触发多个路由。

实现next

我们先从思路入手,首先找到所有匹配路由,然后再逐个执行。通过定义search方法,找到所有匹配的路由

search(method, url) {  
    const matchedList = [];  
    [...this.routers[method], ...this.routers.all].forEach((item) => {  
      item.path === url && matchedList.push(item.handle);  
    });  
    return matchedList;  
  }  
  cb() {  
    return (req, res) => {  
      const method = req.method.toLowerCase();  
      const url = req.url;  
      const matchedList = this.search(method, url);  
    };  
  } 

matchedList就是我们想要找到的所有路由,为了完成next,我们要将req ,res , matchedList存入闭包中,定义handle方法 。

handle(req, res, matchedList) {  
   const next = () => {  
     const midlleware = matchedList.shift();  
     if (midlleware) {  
       midlleware(req, res, next);  
     }  
   };  
   next();  
 }  
 cb() {  
   return (req, res) => {  
     const method = req.method.toLowerCase();  
     const url = req.url;  
     const matchedList = this.search(method, url);  
     this.handle(req, res, matchedList);  
   };  
 } 

综上所述,我们就完成了next的方法,只需要手动调用即可。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表