express的实现 | 子路由

1,211 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第9天,点击查看活动详情

王志远,微医前端技术部

先看应用

应用规则:

  • 创建子路由系统从而进行解耦合,项目更加模块化
const express = require('express');


const app = express();

const user = express.Router();
user.get('/add',function (req,res){
    res.end('user add')
})
user.get('/remove',function (req,res){
    res.end('user remove')
})
const article = express.Router();
article.get('/add',function (req,res){
    res.end('article add')
})
article.get('/remove',function (req,res){
    res.end('article remove')
})


app.use('/user', user);
app.use('/article', article);

app.listen(3000)

实现思路

首先,得明白它是一个中间件,这也就意味着express.Router返回的是一个函数;且它是路由,意味着Router函数不仅可以被new还得支持直接执行时返回一个路由系统。其二,子路由需要拼接父路由在进行匹配

express.Router返回的是一个函数,同时被 new 时又得返回一个对象

那我们就可以根据【函数被 new 时如果存在返回值且是一个引用类型的话,则返回此引用类型对象】的特点,首先定义Router的返回值是一个中间件函数;然后定义一个对象,将所有之前的属性和方法放在这个对象身上,将这个对象放置在中间件函数的原型链上;这样就实现了new和直接执行返回值都符合的情况。

实现父子路径拼接

在请求到来时,会符合中间件的匹配逻辑,这时我们直接执行子路由的handle从而让请求进入子路由的处理中;

注意一点,子路由中定义的是不包含前缀的路径,所以需要记录下中间件的path,从而先截取,再匹配;

具体实现

express.Router返回的是一个函数,同时被 new 时又得返回一个对象
const url = require('url');
const Layer = require('./layer');
const Route = require('./route');
const methods = require('methods');

function Router() { // 能充当构造函数 也可以充当类 , 无论是 new 还是 call 都返回一个函数
    router.stack = [];
    router.methods = {};
    let router = (req,res,next) => {
        router.handle(req,res,next)
    }
    router.removed = ""
    router.__proto__ = proto;
    return router
}

let proto = {};
// 外层的 layer 考虑路径   里层的 layer 考虑方法 = 同一个类
proto.prototype.route = function(path) {
    let route = new Route();
    let layer = new Layer(path, route.dispatch.bind(route)); // 每次调用 get 方法, 都会产生一个 layer 实例和一个 route 实例

    // 这个关联目的是可以在 layer 获取 route 的信息
    layer.route = route; // 路由中的 layer 都有一个 route 属性 和 我们的 route 关联起来
    this.stack.push(layer)
    return route;
}
proto.prototype.use = function(path, ...handlers) {
    if (!handlers[0]) { // 只传递了一个函数 
        handlers.push(path); // app.use(function(){})  app.use()
        path = '/'
    }
    handlers.forEach(handler => {
        let layer = new Layer(path, handler);
        layer.route = undefined; // 不写也是 undefined , 主要告诉你 中间件没有 route
        this.stack.push(layer);
    })
}
// app.get
methods.forEach(method => {
    // app.get => handlers   rouer.get
    proto.prototype[method] = function(path, handlers) { // handlers 是用户定义 get 时传递过来的所有执行函数  (数组)
        if(!Array.isArray(handlers)){
            handlers = Array.from(arguments).slice(1);
        }
        let route = this.route(path); // 创建一个 route 实例
        // 创建一个 layer  还要创建一个 route,将 handlers 传递给 route
        route[method](handlers);

    }
})

proto.prototype.handle = function(req, res, done) {
    let { pathname } = url.parse(req.url);
    let method = req.method.toLowerCase()
    let idx = 0;
    let removed = '';
    const next = (err) => { // 中间件 和内部的 next 方法 出错都会走这个 next
        if (idx >= this.stack.length) return done(); // 路由处理不了 传递给应用层
        let layer = this.stack[idx++];
        if(removed.length){
            req.url = removed + req.url;
           removed = '';
        }
        if (err) {
            // 如果有错误 , 找错误处理中间件
            if(!layer.route){ // 中间件
                if(layer.handler.length === 4){
                    layer.handler(err,req,res,next)
                }else{
                    next(err);
                }
            }else{ // 路由
                next(err);
            }
        } else {
            // 无论是路由还是中间件 前提是路径必须匹配
            if (layer.match(pathname)) { // match 还没有更改
            
                req.params =  layer.params
                if (!layer.route) { // 没有说明是中间件
                    // 正常中间件不走错误
                    if(layer.handler.length !== 4){
                        // 进入到中间件的时候 需要将中间件的路径移除掉
                        //add
                        if(layer.path !== '/'){
                            removed = layer.path; // 要删除的部分  中间件要是/ 就不要删除了
                            req.url = req.url.slice(layer.path.length) ;
                        }


                        layer.handle_request(req, res, next); // 直接执行中间件函数
                    }else{
                        next();
                    }
                } else {
                    // 路由必须匹配方法
                    if (layer.route.methods[method]) { // 这个 next 可以让路由层扫描下一个 layer
                        layer.handle_request(req, res, next); // route.dispatch
                    } else {
                        next();
                    }
                }
            } else {
                next();
            }
        }
    }
    next(); // 请求来了取出第一个执行

}
module.exports = Router;