如今node的web框架有不少,除express 还有koa egg等等。 但它们本质上仍是基于原生node框架的http。其实框架都大差不差,主要是观摩和学习。本篇文章主要记录下本身在node爬坑之路上的经历和收获~node
本文主要实现express的功能之一, 匹配路由git
首先咱们看一下express:github
const express = require('express'); let app = new express(); app.get('/',(req,res)=>{ res.end('home Page.'); }); app.get('/center',(req,res)=>{ res.end('center Page.'); }); /** 匹配到动态路由 获取路由参数并返回 */ app.get('/product/:id/:name',(req,res)=>{ res.end(JSON.stringify(req.params)); }); /** 当以上路径都没有匹配成功时 返回404 */ app.all('*',(req,res)=>{ res.end('404'); }); let port = 3000; app.listen(port,()=>{ console.log(`Server is start on port ${port}`); });
ok.代码很简单。引入express,new了个express实例,写了几个路由,最后启了本地服务。web
代码第二行 咱们把引入的express 给new出来,说明express内部返回的是一个function。正则表达式
好.麻雀虽小 五脏俱全,咱们今天就来实现express的这些功能。express
let http = require('http'); /** express基于http */ let url = require('url'); /** 用来解析请求的路径 */ /** express引入了methods 它的做用是返回各类的请求方法 */ let methods = require('methods'); function application(){ /** 1 express返回了一个函数 * 这个函数就是http.createServer的监听函数 */ let app = (req,res) => { /** 1.1 url模块解析 拿到请求路径 好比 /user */ let { pathname } = url.parse(req.url); /** 1.2 拿到请求方法 方法是大写 记得转换为小写 */ let requestMethod = req.method.toLowerCase(); /** 1.3 经过拿到的路径和方法 在以前定义好的路由数组routes中 循环去匹配 */ for(let i = 0; i < app.routes.length; i++){ /** 1.4 解构 拿到每个路由的 路径 方法 回调 */ let { path, method, cb } = app.routes[i]; if((pathname===path||path==='*') && (requestMethod===method)||method==='all'){ /** 1.5 若是匹配到 返回回调并执行 */ return cb(req,res); } } /** 1.6 没有匹配到任何路由 */ res.end(`Cannot found ${pathname}/${requestMethod}`); } /** 2 定义一个存放全部路由的数组 */ app.routes = []; /** 2.1 往methods数组中添加一个方法 all 并循环数组 */ [...methods,'all'].forEach((method)=>{ app[method] = function(path,cb){ /** 2.2 先将每一个请求的路由地址 方法和回调保存起来 * path:路径 method:方法 cb:回调 */ let layer = { path, method, cb }; app.routes.push(layer); } }); /** 3 监听端口 */ app.listen = function(...arguments){ /** 3.1 利用http的createServer方法 将app传进去 */ let server = http.createServer(app); server.listen(...arguments); } return app; } /** 4 将方法导出出去 */ module.exports = application;
代码上面都仔细的标注了观看序号,1.2.3... 按照顺序观看便可。数组
咱们手写的整个express就是一个函数 函数里面return了一个函数。经过node原生框架http的方法 包装了该函数,最后再将整个函数module.exports导出出去。
最后咱们启动项目,经过浏览器或者postman调用接口,发现确实能实现部分的express功能,可是有一点,此时咱们能实现的仅仅是静态的路由,若是有路由参数的状况下,好比/product/:id/:name。结果就不符合预期。 改造:浏览器
代码上面都仔细的标注了观看序号,1.2.3... 按照顺序观看便可。app
let http = require('http'); let url = require('url'); let methods = require('methods'); function application(){ let app = (req,res) => { let { pathname } = url.parse(req.url); let requestMethod = req.method.toLowerCase(); for(let i = 0; i < app.routes.length; i++){ let { path, method, cb } = app.routes[i]; /** 7 若是请求路径path中 就说明该路由是动态的 */ if(path.params){ /** 8 匹配该动态路由后面的动态参数 匹配成功返回true */ if(path.test(pathname)){ /** 9 解构赋值 拿到动态路由的参数 */ let [, ...otherParams] = pathname.match(path); /** 10 经过reduce()方法 将路由参数转换为对象形式 * 并放到req.params中 */ req.params = path.params.reduce( (memo,key,index)=>( memo[key]=otherParams[index],memo ),{} ); /** 11 返回匹配到的动态路由 */ return cb(req,res); } } if((pathname===path||path==='*') && (requestMethod===method)||method==='all'){ return cb(req,res); } } res.end(`Cannot found ${pathname}/${requestMethod}`); } app.routes = []; [...methods,'all'].forEach((method)=>{ app[method] = function(path,cb){ let layer = { path, method, cb }; /** 1 定义一个空数组 来存放动态路由的参数 */ let params = []; /** 2 若是路径中包含: 说明该路由是动态路由 */ if(path.includes(':')){ /** 3 更改该动态路由的路径path为一个正则表达式 * 目的是为了等真正请求到来时 匹配到该动态路由 并拿到路由参数 */ layer.path = new RegExp(path.replace(/:([^\/]*)/g,function(){ /** 4 将动态路由参数的key 放入params数组中 */ params.push(arguments[1]); /** 5 返回了一个正则来匹配真正的动态路由参数 注意此处没有: */ return '([^\/]*)'; })); /** 6 把解析到的动态路由放到该路由路径path的params上 */ layer.path.params = params; } app.routes.push(layer); } }); app.listen = function(...arguments){ let server = http.createServer(app); server.listen(...arguments); } return app; } module.exports = application;
先经过正则匹配到该动态路由,并把该动态路由的path替换为一个正则,放到数组中,等待真正的动态路由到来时,从路由数组中拿到该动态路由的路径,也就是刚才替换的正则,来匹配该动态路由后的参数便可。框架
经过以上就能实现获取动态路由的参数 上图:
代码在git mock-express