使用Node.js实现一个express框架

开发 前端
本文介绍了如何使用Node.js来实现一个express框架的,一起来看看吧。

 [[336666]]

手写一个express系列

express的基本用法 

  1. const express = require("express");  
  2. const app = express();  
  3. app.get("/test", (req, res, next) => {  
  4.   console.log("*1");  
  5. //   res.end("2");  
  6.   next();  
  7. });  
  8. app.get("/test", (req, res, next) => {  
  9.   console.log("*2");  
  10.   res.end("2");  
  11. });  
  12. app.listen(8888, (err) => {  
  13.   !err && console.log("监听成功");  
  14. }); 
  •  当我访问localhost:8888/test时候,返回了:2,服务端打印了 
  1. *1  
  2. *2 
  •  从上面可以看到什么?
    •  express默认引入调用后返回一个app对象
    •  app.listen 会启动进程监听端口
    •  每次收到请求,对应的url和method会触发相应挂载在app上对应的回调函数
    •  调用 next 方法,会触发下一个

一起来实现一个简单的express框架

  •  定义属于我们的express文件入口,这里使用class来实现 
  1. class express {  
  2.  
  3. module.exports = express
  •  需要的原生模块http,创建进程监听端口 
  1. const { createServer } = require("http"); 
  •  给 class 定义 listen 方法,监听端口 
  1. class express {  
  2.   listen(...args) {  
  3.     createServer(cb).listen(...args);  
  4.   }  
  •  这样就可以通过调用 class 的 listen 去调用 http 模块的 listen 了,这里的cb我们可以先不管,你要知道每次接受到请求,必然会调用 cb 函数,这个是 createServer 原生模块帮我们封装好的

实现接收到请求触发

  •  实现app.get app.post等方法
    •  目前我们接受到响应,就会触发 cb 这个回调函数,那我们打印下,看看是什么参数? 
  1. class express {  
  2.   cb() {  
  3.     return (req, res) => {  
  4.       console.log(res, res, "开始行动");  
  5.     };  
  6.   }  
  7.   listen(...args) {  
  8.     createServer(this.cb()).listen(...args);  
  9.   }  
  •  发现此时的 req 和 res 正是我们想要的可读流和可写流.
  •  开始编写 get 和 post 方法
    •  这里注意,有路由是'/'的,这种是不管任何路由都会触发一次 
  1. constructor() {  
  2.     this.routers = {  
  3.       get: [],  
  4.       post: [],  
  5.     };  
  6.   }  
  7.   get(path, handle) {  
  8.     this.routers.get.push({  
  9.       path,  
  10.       handle,  
  11.     });  
  12.   }  
  13.   post(path, handle) {  
  14.     this.routers.post.push({  
  15.       path,  
  16.       handle,  
  17.     });  
  18.   } 
  •  初始化时候定义 get、post 的数组储存对应的 path 和 handle.
  •  需要触发路由回调的时候,首先要找到对应的请求方式下对应的 url 的 handle 方法,然后触发回调.
  •  如何找到对应请求方式下的 url 对应的 handle 方法? 在接到请求时候就要遍历一次
    •  这里要考虑匹配多个路由,意味着,我们可能遇到像最开始一样,有两个 get 方式的 test 路由 
  1. cb() {  
  2.   return (req, res) => {  
  3.     const method = req.method.toLowerCase();  
  4.     console.log(this.routers[method], ",method");  
  5.     const url = req.url;  
  6.     this.routers[method].forEach((item) => {  
  7.       item.path === url && item.handle(req, res);  
  8.     });  
  9.   };  
  10.  
  11. listen(...args) {  
  12.   createServer(this.cb()).listen(...args);  
  •  上面根据 method 找到对应的数组,遍历找到请求的路由,触发回调,此时已经能正常返回数据了 
  1. [ { method: 'get', path: '/test', handle: [Function] } ] ,method 
  •  此时最简单的express已经完成了,但是我们好像忘了最重要的中间件

完成最重要的中间件功能

  •  首先要知道,express中间件分两种,一种带路由的,那就是根据路由决定是否触发
  •  另外一种就是不带路由的,像静态资源这种. 是用户访问任何路由都要触发一次的
  •  那我们需要一个 all 数组储存这种任意路由都需要匹配触发的 
  1. constructor() {  
  2.    this.routers = {  
  3.      get: [],  
  4.      post: [],  
  5.      all: [],  
  6.    };  
  7.  } 
  •  之前的直接通过 push 方式是太粗暴.如果用户需要中间件功能,不传路由,那就要做特殊处理,这里通过一个中间函数处理下
  •  改造get、post方法,定义handleAddRouter方法 
  1. handleAddRouter(path, handle) {  
  2.    let router = {}; 
  3.     if (typeof path === "string") {  
  4.      router = {  
  5.        path,  
  6.        handle,  
  7.      };  
  8.    } else {  
  9.      router = {  
  10.        path: "/",  
  11.        handle: path, 
  12.      };  
  13.    }  
  14.    return router;  
  15.  }  
  16.  get(path, handle) {  
  17.    const router = this.handleAddRouter(path, handle);  
  18.    this.routers.get.push(router);  
  19.  }  
  20.  post(path, handle) {  
  21.    const router = this.handleAddRouter(path, handle);  
  22.    this.routers.post.push(router); 
  23.   }  
  24.  use(path, handle) {  
  25.    const router = this.handleAddRouter(path, handle);  
  26.    this.routers.all.push(router);  
  27.  } 
  •  每次添加之前,先触发一次handleAddRouter,如果是 path 为空的中间件,直接传入函数的,那么 path 帮它设置成'/'
  •  我们还遗留了一个点,next的实现,因为我们现在加了all这个数组后,意味着可能有多个中间件,那么可能一次请求打过来,就要触发多个路由

这里要注意,promise.then 源码实现和 express 的 next、以及 koa 的洋葱圈、redux 的中间件实现,有着一丁点相似,当你能真的领悟前后端框架源码时候,发现大都相似

  •  阅读我的文章,足以击破所有前后端源码.而且可以手写出来, 我们只学最核心的,抓重点学习,野蛮生长!

实现next

  •  思路:
    •  首先要找到所有匹配的路由
    •  然后逐个执行(看 next 的调用)
  •  定义search方法,找到所有匹配的路由 
  1. search(method, url) {  
  2.     const matchedList = [];  
  3.     [...this.routers[method], ...this.routers.all].forEach((item) => {  
  4.       item.path === url && matchedList.push(item.handle);  
  5.     });  
  6.     return matchedList;  
  7.   }  
  8.   cb() {  
  9.     return (req, res) => {  
  10.       const method = req.method.toLowerCase();  
  11.       const url = req.url;  
  12.       const matchedList = this.search(method, url);  
  13.     };  
  14.   } 
  •  matchedList就是我们想要找到的所有路由
  •  为了完成next,我们要将req ,res , matchedList存入闭包中,定义handle方法 
  1. handle(req, res, matchedList) {  
  2.    const next = () => {  
  3.      const midlleware = matchedList.shift();  
  4.      if (midlleware) {  
  5.        midlleware(req, res, next);  
  6.      }  
  7.    };  
  8.    next();  
  9.  }  
  10.  cb() {  
  11.    return (req, res) => {  
  12.      const method = req.method.toLowerCase();  
  13.      const url = req.url;  
  14.      const matchedList = this.search(method, url);  
  15.      this.handle(req, res, matchedList);  
  16.    };  
  17.  } 
  •  这样我们就完成了next方法,只要手动调用 next 就会调用下一个匹配到的路由回调函数
  •  不到一百行代码,就完成了这个简单的express框架

写在最后

  •  只要你根据我这些文章去认真自己实现一次,一年内拿个 P6 应该没什么问题
  •  大道至简,希望你能通过这些文章真的学到框架的原理,进而自己能写出一些框架,走向更高的层级
  •  我是Peter,曾经 20 万人超级群桌面软件的架构师,现在就职于明源云,担任分公司前端负责人,目前深圳这边需要招聘两位中高级前端,3D数据可视化方向,期待你的到来
  •  如果感觉本文对你有帮助,别忘了点个在看和关注. 我们的技术团队也会不断产出原创文章, 一起见证各位的成长 

 

责任编辑:庞桂玉 来源: segmentfault
相关推荐

2011-10-25 09:28:30

Node.js

2022-10-18 18:43:40

Node.js低代码

2017-04-24 08:31:26

Node.jsExpress.jsHTTP

2019-04-15 11:00:46

框架Node.JS开发

2020-05-29 15:33:28

Node.js框架JavaScript

2023-05-12 07:31:58

NuxtVue.js

2020-07-15 08:06:04

Node.js框架开发

2014-07-11 14:16:15

AbsurdJSExpress

2021-07-16 04:56:03

NodejsAddon

2021-08-20 16:05:28

JavaScript node.js 应用安全

2020-10-29 16:00:03

Node.jsweb前端

2019-08-29 10:58:02

Web 开发框架

2022-05-23 10:26:50

Node.jsJavaScrip

2011-06-17 10:29:04

Nodejavascript

2022-06-05 13:52:32

Node.jsDNS 的原理DNS 服务器

2014-04-10 09:43:00

Node.jsTwilio

2020-02-25 12:27:59

Node.jsWeb开发前端

2012-01-10 10:04:43

Node.js

2022-08-28 16:30:34

Node.jsDocker指令

2012-03-07 14:32:41

Node.js
点赞
收藏

51CTO技术栈公众号