NodeJs 实战——原生 NodeJS 轻仿 Express 框架从需求到实现
ccwgpt 2024-09-29 09:48 42 浏览 0 评论
确认需求
- 将路由与应用分离,便于代码的维护和功能拓展
- 优化路由模块,提升匹配效率
Router 与 Application 分离
为了将路由与应用分离,这里我们新增一个 Router.js 文件,用来封装一个路由管理的类 Router,代码如下。
// 路由管理类 function Application() { // 用来保存路由的数组 this.stack = [ { path: '*', method: '*', handle: function(req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('404'); } } ]; } Router.prototype.get = function(path, handle) { // 将请求路由压入栈内 this.stack.push({ path, method: 'GET', handle }); }; Router.prototype.handle = function() { // 循环请求过来放入router数组的对象,当请求方法和路劲与对象一致时,执行回调handler方法 for (var i = 1, len = this.stack.length; i < len; i++) { if ( (req.url === this.stack[i].path || this.stack[i].path === '*') && (req.method === this.stack[i].method || this.stack[i].method === '*') ) { return this.stack[i].handle && this.stack[i].handle(req, res); } } return this.stack[0].handle && this.stack[0].handle(req, res); };
修改原有的 application.js 文件内容
var Router = require('./router'); var http = require('http'); function Application() {} Application.prototype = { router: new Router(), get: function(path, fn) { return this.stack.get(path, fn); }, listen: function(port, cb) { var self = this; var server = http.createServer(function(req, res) { if (!res.send) { res.send = function(body) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(body); }; } return self.router.handle(req, res); }); return server.listen.apply(server, arguments); } }; exports = module.exports = Application;
经过上面的修改,路由方面的操作只会与 Router 类本身有关,达到了与 Application 分离的目的,代码结构更加清晰,便于后续功能的拓展。
优化路由模块,提升匹配效率
经过上面的实现,路由系统已经可以正常运行了。但是我们深入分析一下,可以发现我们的路由匹配实现是会存在性能问题的,当路由不断增多时,this.stack 数组会不断的增大,匹配的效率会不断降低,为了解决匹配的效率问题,需要仔细分析路由的组成部分。 可以看出,一个路由是由:路径(path)、请求方式(method)和处理函数(handle)组成的。path 和 method 的关系并不是简单的一对一的关系,而是一对多的关系。如下图,所示,对于同一个请求链接,按照RestFul API 规范 可以实现如下类似的功能。
基于此,我们可以将路由按照路径来分组,分组后,匹配的效率可以显著提升。对此,我们引入层(Layer)的概念。 这里将 Router 系统中的 this.stack 数组的 每一项,代表一个 Layer。每个 Layer 内部含有三个变量。
- path,表示路由的请求路径
- handle,代表路由的处理函数(只匹配路径,请求路径一致时的处理函数)
- route,代表真正的路由,包括 method 和 handle 整体结构如下图所示
-------------------------------------- | 0 | 1 | -------------------------------------- | Layer | Layer | | |- path | |- path | | |- handle | |- handle | | |- route | |- route | | |- method | |- method | | |- handle | |- method | -------------------------------------- router 内部
创建Layer类,匹配path
function Layer(path, fn) { this.handle = fn; this.name = fn.name || '<anonumous>'; this.path = path; } /** * Handle the request for the layer. * * @param {Request} req * @param {Response} res */ Layer.prototype.handle_request = function(req, res) { var fn = this.handle; if (fn) { fn(req, res); } }; /** * Check if this route matches `path` * * @param {String} path * @return {Boolean} */ Layer.prototype.match = function(path) { if (path === this.path || path === '*') { return true; } return false; }; module.exports = Layer;
修改 Router 类,让路由经过 Layer 层包装
var Layer = require('./layer'); // 路由管理类 function Router() { // 用来保存路由的数组 this.stack = [ new Layer('*', function(req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('404'); }) ]; } Router.prototype.get = function(path, handle) { // 将请求路由压入栈内 this.stack.push(new Layer(path, handle)); }; Router.prototype.handle = function(req, res) { var self = this; for (var i = 1, len = self.stack.length; i < len; i++) { if (self.stack[i].match(req.url)) { return self.stack[i].handle_request(req, res); } } return self.stack[0].handle_request(req, res); }; module.exports = Router;
创建Route类,匹配method
创建Route类,该类主要是在Layer层中匹配请求方式(method),执行对应的回调函数。这里只实现了get请求方式,后续版本会对这一块进行扩展。
var Layer = require('./layer'); function Route (path) { this.path = path; this.stack = []; // 用于记录相同路径不同method的路由 this.methods = {}; // 用于记录是否存在该请求方式 } /** * Determine if the route handles a given method. * @private */ Route.prototype._handles_method = function (method) { var name = method.toLowerCase(); return Boolean(this.methods[name]); } // 这里只实现了get方法 Route.prototype.get = function (fn) { var layer = new Layer('/', fn); layer.method = 'get'; this.methods['get'] = true; this.stack.push(layer); return this; } Route.prototype.dispatch = function(req, res) { var self = this, method = req.method.toLowerCase(); for(var i = 0, len = self.stack.length; i < len; i++) { if(method === self.stack[i].method) { return self.stack[i].handle_request(req, res); } } } module.exports = Route;
修改Router类,将route集成其中。
var Layer = require('./layer'); var Route = require('./route'); // 路由管理类 function Router() { // 用来保存路由的数组 this.stack = [ new Layer('*', function(req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('404'); }) ]; } Router.prototype.get = function(path, handle) { var route = this.route(path); route.get(handle); return this; }; Router.prototype.route = function route(path) { var route = new Route(path); var layer = new Layer(path, function(req, res) { route.dispatch(req, res); }); layer.route = route; this.stack.push(layer); return route; }; Router.prototype.handle = function(req, res) { var self = this, method = req.method; for (var i = 1, len = self.stack.length; i < len; i++) { if (self.stack[i].match(req.url) && self.stack[i].route && self.stack[i].route._handles_method(method)) { return self.stack[i].handle_request(req, res); } } return self.stack[0].handle_request(req, res); }; module.exports = Router;
总结
我们这里主要是创建了一个完整的路由系统,并在原始代码基础上引入了Layer和Route两个概念。 目录结构如下
express | |-- lib | | | |-- express.js //负责实例化application对象 | |-- application.js //包裹app层 | |-- router | | | |-- index.js //Router类 | |-- layer.js //Layer类 | |-- route.js //Route类 | |-- test | | | |-- index.js #测试用例 | |-- index.js //框架入口
application代表一个应用程序,express负责实例化application对象。Router代表路由组件,负责应用程序的整个路由系统。组件内部由一个Layer数组构成,每个Layer代表一组路径相同的路由信息,具体信息存储在Route内部,每个Route内部也是Layer对象,但是Route内部的Layer和Router内部的Layer是存在一定的差异性。
- Router内部的Layer,主要包含path、route属性
- Route内部的Layer,主要包含method、handle属性 当发起一个请求时,会先扫描router内部的每一层,而处理每层的时候会先对比URI,相同则扫描route的每一项,匹配成功则返回具体的信息,没有任何匹配则返回未找到。
相关推荐
- 一个基于.Net Core遵循Clean Architecture原则开源架构
-
今天给大家推荐一个遵循CleanArchitecture原则开源架构。项目简介这是基于Asp.netCore6开发的,遵循CleanArchitecture原则,可以高效、快速地构建基于Ra...
- AI写代码翻车无数次,我发现只要提前做好这3步,bug立减80%
-
写十万行全是bug之后终于找到方法了开发"提示词管理助手"新版本那会儿,我差点被bug整崩溃。刚开始两周,全靠AI改代码架构,结果十万行程序漏洞百出。本来以为AI说没问题就稳了,结果...
- OneCode低代码平台的事件驱动设计:架构解析与实践
-
引言:低代码平台的事件驱动范式在现代软件开发中,事件驱动架构(EDA)已成为构建灵活、松耦合系统的核心范式。OneCode低代码平台通过创新性的注解驱动设计,将事件驱动理念深度融入平台架构,实现了业务...
- 国内大厂AI插件评测:根据UI图生成Vue前端代码
-
在IDEA中安装大厂的AI插件,打开ruoyi增强项目:yudao-ui-admin-vue31.CodeBuddy插件登录腾讯的CodeBuddy后,大模型选择deepseek-v3,输入提示语:...
- AI+低代码技术揭秘(二):核心架构
-
本文档介绍了为VTJ低代码平台提供支持的基本架构组件,包括Engine编排层、Provider服务系统、数据模型和代码生成管道。有关UI组件库和widget系统的信息,请参阅UI...
- GitDiagram用AI把代码库变成可视化架构图
-
这是一个名为gitdiagram的开源工具,可将GitHub仓库实时转换为交互式架构图,帮助开发者快速理解代码结构。核心功能一键可视化:替换GitHubURL中的"hub...
- 30天自制操作系统:第六天:代码架构整理与中断处理
-
1.拆开bootpack.c文件。根据设计模式将对应的功能封装成独立的文件。2.初始化pic:pic(可编程中断控制器):在设计上,cpu单独只能处理一个中断。而pic是将8个中断信号集合成一个中断...
- AI写代码越帮越忙?2025年研究揭露惊人真相
-
近年来,AI工具如雨后春笋般涌现,许多人开始幻想程序员的未来就是“对着AI说几句话”,就能轻松写出完美的代码。然而,2025年的一项最新研究却颠覆了这一期待,揭示了一个令人意外的结果。研究邀请了16位...
- 一键理解开源项目:两个自动生成GitHub代码架构图与说明书工具
-
一、GitDiagram可以一键生成github代码仓库的架构图如果想要可视化github开源项目:https://github.com/luler/reflex_ai_fast,也可以直接把域名替换...
- 5分钟掌握 c# 网络通讯架构及代码示例
-
以下是C#网络通讯架构的核心要点及代码示例,按协议类型分类整理:一、TCP协议(可靠连接)1.同步通信//服务器端usingSystem.Net.Sockets;usingTcpListene...
- 从复杂到优雅:用建造者和责任链重塑代码架构
-
引用设计模式是软件开发中的重要工具,它为解决常见问题提供了标准化的解决方案,提高了代码的可维护性和可扩展性,提升了开发效率,促进了团队协作,提高了软件质量,并帮助开发者更好地适应需求变化。通过学习和应...
- 低代码开发当道,我还需要学习LangChain这些框架吗?| IT杂谈
-
专注LLM深度应用,关注我不迷路前两天有位兄弟问了个问题:当然我很能理解这位朋友的担忧:期望效率最大化,时间用在刀刃上,“不要重新发明轮子”嘛。铺天盖地的AI信息轰炸与概念炒作,很容易让人浮躁与迷茫。...
- 框架设计并不是简单粗暴地写代码,而是要先弄清逻辑
-
3.框架设计3.框架设计本节我们要开发一个UI框架,底层以白鹭引擎为例。框架设计的第一步并不是直接撸代码,而是先想清楚设计思想,抽象。一个一个的UI窗口是独立的吗?不是的,...
- 大佬用 Avalonia 框架开发的 C# 代码 IDE
-
AvalonStudioAvalonStudio是一个开源的跨平台的开发编辑器(IDE),AvalonStudio的目标是成为一个功能齐全,并且可以让开发者快速使用的IDE,提高开发的生产力。A...
- 轻量级框架Lagent 仅需20行代码即可构建自己的智能代理
-
站长之家(ChinaZ.com)8月30日消息:Lagent是一个专注于基于LLM模型的代理开发的轻量级框架。它的设计旨在简化和提高这种模型下代理的开发效率。LLM模型是一种强大的工具,可以...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- mfc框架 (52)
- abb框架断路器 (48)
- beego框架 (52)
- java框架spring (58)
- grpc框架 (65)
- tornado框架 (48)
- 前端框架bootstrap (54)
- orm框架有哪些 (51)
- 知识框架图 (52)
- ppt框架 (55)
- 框架图模板 (59)
- 内联框架 (52)
- cad怎么画框架 (58)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)