深入了解Express:挑战手写Express(2), 支持中间件

这里将在上次路由的代码上进行变更,所以不会给出完整的代码,只会给出删改的部分,但是思路会说到的:


  • Express中是支持中间件机制的,而中间件其实就是一个函数,在express执行过程中,中间件使用next()进行串联,而我们上次实现的路由机制中,我们也实现了Router层,Route层的next方法,使得在路由匹配的过程可以逐层向下匹配。 所以这里的实现机制就是利用上次的 路由实现,将中间件也和路由放到一个stack中,使得在这个匹配中可以使用一套next执行机制串联中间件和路由。
  • 除了中间件之外,Express中还支持了二级路由的实现,也就是用户可以使用express.Router()的方式新创建一个路由层,放到一级路由的后面进行二级路由的实现,所以 这里我们也 将会支持.
    需求实例
const express = require("express")
const app = express();

// 中间件
app.use(function(req, res, next){
    console.log("Ware: 1", Date.now());
    next()
})
app.get("/", function(req, res, next){
    console.log("1");
    res.end("ok")
})

// 新建一个路由层, 在路由层中也可以使用use方法添加中间件
const user = express.Router();
user.use(function(req, res, next){
    console.log("Ware2", Date.now());
    next()
})
user.get("/2", function(req, res, next){    // 路由层的路由,而之后实际的匹配路由是: /user/2
    res.end("2");
})
// 处理二级路由
app.use("/user", user);


app.listen(3000, function (){
    console.log("server start on 3000")
})
  1. 根据需求,此时Router将不可以再是一个类,因为我们可以使用Express.Router()的方式来创建一个新的 Router,所以需要将Router改为一个函数,函数中返回一个Router实例的形式
function Router(){
    // 这里加一个router方法的原因是为了兼容express.Router()进行二级路由的使用
    // 及app.use("/path", router) 这样的写法
    function router(req, res, next){
        router.handle(req, res, next);
    }
    // 实现继承:将proto上挂载的属性方法放到router函数对象上去
    // 这里的proto对象是将之前的Router.prototype的Router原型上的方法放到了一个对象中,被 router继承
    Object.setPrototypeOf(router, proto)
    router.stack = [];
    return router;
}
  1. 更新handle方法:,让其支持中间件入栈(stack)
proto.handle = function(req, res, out){
    let idx = 0, self = this;
    // removed指的是是否被移除的字符串,它的作用是在每一匹配完一级路由后将一级路由从path中删除
    // 进入二级路由匹配,及 "/user/1" 进入二级路由匹配时应该变成"/2"这样的形式
    // 同时在进入二级路由中未被正确匹配时,返回下一层的时候在将一级路由被删掉的部分给补上
    let removed = "";
    let {pathname} = url.parse(req.url, true)
    // 执行下一个路由层
    function next(){
        if(removed.length > 0){
            // 做这个操作的目的是当在二级路由中没有匹配的对应的路径时
            // 在退出这一层处理进入下一层的时候,将进入之前removed掉的url部分给它拼接回来
            req.url = removed + req.url;
            removed = "";
        }
        // 当前Layer层匹配完毕,则进入下一层
        if(idx >= self.stack.length){
            return out(err)
        }
        let layer = self.stack[idx++];
        if(layer.match(pathname)){
            if(!layer.route){
                // 如果没有route,则表明这一层是中间件层
                removed = layer.path;  // 取出当前匹配路路径,如 /user/abc  则在这一层取出就是 /user,然后更改req.url的值,这样的目的是使得在二级路由中进行匹配是没有问题的
                req.url = req.url.slice(removed.length)
                layer.handle_request(req, res, next);
            }else{
                // 到这里说明是一个路由:
                if(layer.route && layer.route.handle_method(req.method)){
                    layer.handle_request(req, res, next);
                }
            }
        }else{
            next();
        }
    }
    next();
}
  1. 注册中间件的use方法:
    3.1: 在Application中的use方法,提供app.use()进行中间件的注册:
// 用户添加中间件的方法
// 中间件和路由是添加到一个stack(数组)中的,这样做的目的是使得中间件和路由
// 之间可以使用next()进行串联起来
Application.prototype.use = function(){
    this.lazyrouter();
    // 将传递use的参数全部传递给router中的use方法,这样在router中将中间件处理后放到Router的stack中
    this._router.use.apply(this._router, arguments)
}

3.2: 使用express.Router()创建的路由中使用use()方法创建路由中间件,也就是Router中的use方法 :

// 路由中间件机制
proto.use = function(path, handler){
    if(typeof handler !== "function"){
        // 如果没有传递path,则说明是一个中间件,则第一个参数就是中间件执行函数
        // 此时就将path设置为 "/"
        handler = path;
        path = "/"
    }
    // 中间件也是一个Layer层,和Route是一样的,这样将他们放到一个stack中才方便执行
    // 但是中间件没有route属性,而只有一个执行函数,所以将其route设置为undefined用于区别路由和中间件
    let layer = new Layer(path, handler);
    layer.route = undefined; 
    this.stack.push(layer);
}
  1. 更改Layer中的match方法,用于处理中间件的匹配
Layer.prototype.match = function(path){
	// 普通的路由匹配
    if(this.path == path){
        return true;
    }
   	// 中间件处理,中间件中的 route是undefined
    if(!this.route){
        // 如果没有route,则说明这是中间件层
        return path.slice(0, this.path.length) == this.path;
    }
    return false;
}
  1. 在express上挂载上Router()方法用于创建一个新的路由层:
const Router = require("./router")
function createApplication(){
    return new Application() 
}

// 提供Router函数给用户独立创建一个路由的层: express.Router()去创建路由层
createApplication.Router =  Router;

  • 经过如上的改变,已经可以使得我们的express可以支持了中间件的能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值