之前在使用express的时候从来没有想过为什么可以这样写,中间件可以这样用。今天决定把中间件原理给写一遍。不多cc,直接上代码。
在like-express文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
/*简单的实现中间件原理 思路: 定义一个类,类里面有和express对应的use get post函数, 使用的时候,创建实例,并使用这些函数。将这些函数里面的参数,如app.use('/',f,f),进行解析, 全部存入到对象的对应属性(这些属性应该都为对象数组,每个对象为path和stackk属性组成)中 在http服务中会对用户输入的接口进行拦截,这时我们对其进行处理,对客户端发过来不同的method和不同的url返回对应要执行的stack(stack存的是函数数组), 最后写一个next核心机制去执行这些函数。 */ const http = require('http') const slice = Array.prototype.slice //数组原型上的slice(start,end),从已有的数组中返回选定的元素。 class LikeExpress{ //构造函数 constructor(){ //存放中间件的列表 this.routes = { all:[], //对应app.use();是一个对象数组,每个对象为path和stackk属性组成 get:[], //app.get() post:[] //app.post } } //将path和stack放入到info中,stack存的是函数,返回info register(path){ const info = {} //将path和stack放入到info中,stack存的是函数 if(typeof path === 'string'){ info.path = path //从第二个参数开始,转换为数组,存入stack info.stack = slice.call(arguments,1) //arguments为函数参数数组;slice.call(数组,起始位置,结束位置) }else{ info.path = '/' //从第一个参数开始,转换为数组,存入stack info.stack = slice.call(arguments,0) //arguments为函数参数数组;slice.call(数组,起始位置,结束位置) } return info } //实例中的use函数,来将用户输入实参存入到对应的routes中all数组,存入的是一个对象,又path,stack属性 use(){ const info = this.register.apply(this,arguments) //apply改变第一个this为第二个this的指向,arguments为当前函数的参数数组;apply函数必须要有两个参数(新指向,参数数组) this.routes.all.push(info) } get(){ const info = this.register.apply(this,arguments) //apply改变this指向为当前类中的this this.routes.get.push(info) } post(){ const info = this.register.apply(this,arguments) //apply改变this指向为当前类中的this this.routes.post.push(info) } //匹配用户使用的use,get,post方法,返回用户输入的对应路由的后端输入函数 match(method,url){ let stack = [] //不处理/favicon.ico请求 if(url === '/favicon.ico'){ return stack } //获取后端输入的routes,根据method进行筛选 let curRoutes = [] curRoutes = curRoutes.concat(this.routes.all) //concat数组拼接函数 curRoutes = curRoutes.concat(this.routes[method]) //遍历筛选后的对象数组,拦截用户输入的路由,返回后端输入的函数 curRoutes.forEach(routeInfo =>{ if(url.indexOf(routeInfo.path === 0)){ //有bug,如果是get或者post客户端输入'/api/test/111',后端拦截的是'/api/test',依旧返回stack //客户端访问url === '/api/get-cookie' 且 后端拦截的 routeInfo.path === '/' //客户端访问url === '/api/get-cookie' 且 后端拦截的 routeInfo.path === '/api' //客户端访问url === '/api/get-cookie' 且 后端拦截的 routeInfo.path === '/api/get-cookie' stack = stack.concat(routeInfo.stack) } }) return stack } //核心的next机制,去执行match后的函数 handle(req,res,stack){ const next = ()=>{ //依次拿到匹配的中间件 const middleware = stack.shift() //shift()函数为从数组中取出第一个元素,并将其删除 if(middleware){ //执行中间件函数 middleware(req,res,next) } } next() } //http服务入口文件 callback(){ return (req,res) =>{ //res加入json函数 res.json = (data)=>{ res.setHeader('Content-type','application/json') res.end( JSON.stringify(data) ) } const url = req.url const method = req.method.toLowerCase() const resultList = this.match(method,url) //返回拦截用户输入的路由,返回的后端输入的函数 this.handle(req,res,resultList) //next核心机制,去执行这些函数 } } listen(...args){ const server = http.createServer(this.callback()) //开启http服务 server.listen(...args) //监听端口 } } //工厂函数 module.exports = ()=>{ return new LikeExpress() } |
在app.js文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
const express = require('./like-express') //本次http请求的实例 const app = express() app.use((req,res,next)=>{ console.log('请求开始...',req.method,req.url) next() }) function loginChech(req,res,next){ setTimeout(()=>{ console.log('模拟登录成功') next() }) } app.get('/api/get-test',loginChech,(req,res,next)=>{ console.log(req.method,'处理路由') res.json({ errno:0, msg:"测试成功" }) next() }) app.post('/api/post-test',(req,res,next)=>{ console.log(req.method,'处理路由') next() }) app.listen(3000,()=>{ console.log('server is running on port 3000') }) |
最后在控制台node app启动进程即可,在浏览器或者postman输入接口测试即可
未经允许不得转载:一点博客-青梅煮码-共享博客 » express中间件原理
评论抢沙发