express中间件原理

    之前在使用express的时候从来没有想过为什么可以这样写,中间件可以这样用。今天决定把中间件原理给写一遍。不多cc,直接上代码。


在like-express文件中

/*简单的实现中间件原理
思路:
定义一个类,类里面有和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文件中

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输入接口测试即可

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容