express极简实现

express极简实现

let http = require('http');
let url = require('url');
let createApplication = () => {
    let app = (req, res) => {
        /* 获取路径和方法 */
        let { pathname } = url.parse(req.url, true);
        let m = req.method.toLowerCase();
        /* index记录next执行次数 */
        let index = 0;
        function next(err) {
            //遍历完方法都没有返回页面
            if (index === app.routes.length)
                return res.end(`Cannot ${m} ${pathname}`);
            let { method, path, handler } = app.routes[index++];
            if (err) {/* 错误处理 */
                if (handler.length === 4) {
                    handler(err, req, res, next);
                } else {
                    next();
                }
            } else {
                /* 判断是路由还是中间件 */
                if (method === 'middle') {
                    if (path === '/' || path === pathname ||
                        pathname.startsWith(path + '/')) {/* 开头路由相同 */
                        //执行中间件的回调,跳过错误处理中间件
                        handler.length === 4 ? next() : handler(req, res, next);
                    } else {
                        next();//不匹配遍历下一个
                    }
                } else {/* 路由 */
                    /* 方法、路径相同执行回调,不同则判断下一个routes */
                    if ((method === m || method === 'all') &&
                        (path === pathname || path === '*')) {
                        handler(req, res);
                    } else { next(); }
                }
            }
        }
        next();/* 默认先执行一次 */
    }
    /* 把请求方法存入数组 */
    app.routes = [];
    http.METHODS.forEach(method => {
        /* 变为小写 */
        method = method.toLowerCase();
        app[method] = (path, handler) => {
            /* 将方法路径回调存到数组 */
            let layer = { method, path, handler };
            app.routes.push(layer);
        }
    })
    /* all方法监听所有请求 */
    app.all = (path, handler) => {
        let layer = { method: 'all', path, handler }
        app.routes.push(layer);
    }
    /* 中间件 */
    app.use = (path, handler) => {
        /* 若没有传递path */
        if (typeof handler !== 'function') {
            handler = path;
            path = '/';
        }
        app.routes.push({ method: 'middle', path, handler });
    }
    /* 监听端口 */
    app.listen = function () {
        /* 创建服务并监听 */
        let sever = http.createServer(app);
        sever.listen(...arguments);
    }
    return app;
}
module.exports = createApplication;

1. 实现端口监听

创建服务后通过argument传参即可

app.listen = function () {
    /* 创建服务并监听 */
    let sever = http.createServer(app);
    sever.listen(...arguments);
}

2. 实现请求

  1. 将所有请求method,path,handler放到routes数组中,执行时根据请求头中获得的methodpathname得到对应的handler,然后执行handler
/* 把普通请求方法存入数组 */
app.routes = [];
http.METHODS.forEach(method => {
    /* 变为小写 */
    method = method.toLowerCase();
    app[method] = (path, handler) => {
        /* 将方法路径回调存到数组 */
        let layer = { method, path, handler };
        app.routes.push(layer);
    }
})
  1. app.all('*',handler)表示所有方法所有路由都可以触发,all表示所有方法,*表示所有路由,为了实现这一功能可以将all作为一个特殊的method*作为一个特殊路径,减少条件约束
/* -------------设置特殊的method------------- */
app.all = (path, handler) => {
    let layer = { method: 'all', path, handler }
    app.routes.push(layer);
}
/* -------------执行时的判断条件------------- */
/* 获取路径和方法 */
let { pathname } = url.parse(req.url, true);
let m = req.method.toLowerCase();
app.routes.forEach(v => {
    let { method, path, handler } = v;
    /* 方法、路径相同执行回调,不同则判断下一个routes */
    if ((method === m || method === 'all') &&
        (path === pathname || path === '*')) {
        handler(req, res);
    } else {
        next();
    }
})

3.中间件实现

1. 实现拦截功能
(1)设置app.use默认路径,并且方法记录为middle,后续可以针对method特殊处理

app.use = (path, handler) => {
    /* 若没有传递path */
    if (typeof handler !== 'function') {
        handler = path;
        path = '/';
    }
    app.routes.push({ method: 'middle', path, handler });
}

(2)设置next函数,index记录已经遍历routes数组个数,等于数组长度还未返回页面,说明不存在该页面
(3)根据method判断是否为中间件,然后匹配路由,若路由匹配成功则执行handerhandler内调用了next才会继续往下执行next,从而实现拦截功能

let index=0;
function next() {
    //遍历完都没有返回页面
    if (index === app.routes.length)
        return res.end(`Cannot ${m} ${pathname}`);
    let { method, path, handler } = app.routes[index++];
    /* 判断是路由还是中间件 */
    if (method === 'middle') {
        if (path === '/' || path === pathname ||
            pathname.startsWith(path + '/')) {/* 开头路由相同 */
            //执行中间件的回调,跳过错误处理中间件
            handler.length === 4 ? next() : handler(req, res, next);
        } else {
            next();//不匹配遍历下一个
        }
    } else {/* 路由 */
        /* 方法、路径相同执行回调,不同则判断下一个routes */
        if ((method === m || method === 'all') &&
            (path === pathname || path === '*')) {
            handler(req, res);
        } else { 
            next(); 
        }
    }
}
next();/* 默认先执行一次 */

示例:
/* 中间件处理数据 */
app.use('/detail', (req, res, next) => {
    console.log('详情1');
    res.setHeader('Content-Type', 'text/html;charset=utf-8')
    next();/* 控制是否往下执行 */
})
app.get('/detail/a', (req, res) => {
    console.log('详情2');
    res.end('详情');
})

2. 错误处理跳转
当某个中间件内部给next传参后,next会跳过中间的步骤,一直传递参数往后找,直到找到错误处理中间件,执行错误处理handler函数.

function next(err) {
    //省略...
    if (err) {/* 错误处理中间件传递4个参数 */
        if (handler.length === 4) {
            handler(err, req, res, next);
        } else {
            next(err);
        }
    } else {
        //省略...
    }
}
示例:
/* next传递错误后,直接运行错误中间件(含4个传入参数), */
app.use('/err', (req, res, next) => {
    let err = "err1";
    console.log(err)
    next(err);
})
app.get('/err/v', (req, res) => {
    res.end('errv');
})
app.use((err, req, res, next) => {/* 错误处理 */
    console.log(err);
    res.end(err)
})
/* 
输入网址 http://localhost:8888/err/v
输出 err1 err1
*/
posted @ 2020-04-25 18:03  aeipyuan  阅读(175)  评论(0编辑  收藏  举报