express中间件原理

作者: tangrl | 来源:发表于2020-04-16 21:06 被阅读0次

        之前在使用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输入接口测试即可

    相关文章

      网友评论

        本文标题:express中间件原理

        本文链接:https://www.haomeiwen.com/subject/uxcjvhtx.html