express中间件原理

2023-03-13 15:59:45 浏览数 (2)

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

在like-express文件中
代码语言:javascript复制
/*简单的实现中间件原理
思路:
定义一个类,类里面有和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文件中
代码语言:javascript复制
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输入接口测试即可

0 人点赞