这里将在上次路由的代码上进行变更,所以不会给出完整的代码,只会给出删改的部分,但是思路会说到的:
- 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")
})
- 根据需求,此时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;
}
- 更新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();
}
- 注册中间件的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);
}
- 更改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;
}
- 在express上挂载上Router()方法用于创建一个新的路由层:
const Router = require("./router")
function createApplication(){
return new Application()
}
// 提供Router函数给用户独立创建一个路由的层: express.Router()去创建路由层
createApplication.Router = Router;
- 经过如上的改变,已经可以使得我们的express可以支持了中间件的能力。