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