NodeJS -- 实现一个简易版的express框架

本文介绍了如何实现一个简易版的Express框架,涵盖路由、中间件功能。通过示例展示了如何开启服务器、处理不同HTTP方法、路径参数以及中间件的使用,特别是next函数在控制请求流程中的关键作用。同时提到了内置中间件对req对象的增强。
摘要由CSDN通过智能技术生成

我们仅仅实现express框架的路由和中间件功能。

我们先来看一下效果咋样。


const express = require("../express");
const app = express();

app.get("/getName", function (req, res, next) {
    res.end("hello world");
    next();
})
app.use("/", function (req, res, next) {
    console.log("这是第一个中间件")
    next("worry");
})
app.use("/getName", function (err,req, res, next) {
    console.log(err + "这是错误处理中间件")
})
app.listen(8000, () => {
    console.log("8000服务器已经启动");
});

访问http://localhost:8000/getName(hello world已经打印)

终端显示。

我们在看看带有参数的路径

app.get("/getName/:name/:age", function (req, res, next) {
 res.end("over")
 console.log(req.params);
});

app.listen(8000, () => {
    console.log("8000服务器已经启动");
});

 

大致上先实现了这些功能,我们来看一下怎么做的。先看一下整体的结构

app.listen 这个函数用于开启一个服务器,代码很简单。(app是回调函数)

    app.listen = function (...args) { //就是开启一个服务器
        let server = http.createServer(app);
        server.listen.apply(server, args);
    };

接下来就是在app上面挂载一些类似get,post的路由(也是中间件)。为了方便,我们遍历http.MEHODS上面的方法,然后给每个方法绑定一个回调。这里还需要对路径进行一些处理,比如上面出现的/getName/:name/:age等这样的,需要利用正则处理一些,为了方便后面的匹配工作,而且需要把参数都保留下来。

    http.METHODS.forEach((method) => { //遍历http上面的一些方法
        method = method.toLocaleLowerCase(); //转小写
        app[method] = function (path, handle) {
            if (path.includes(":")) { //判断是否有: 表面使用参数/getName/:name/:age
                let paramterName = [];
                path = path.replace(/:([^\/]+)/g, function () {
                    paramterName.push(arguments[1]); //保存参数
                    return "([^\/]+)" //将path有参数的位置换成正则表达式
                })
                //getName/:name/:age
                //getName/([^\/]+)/([^\/]+)
                routers.push({//方法 路径 回调 参数
                    method,path,handle, paramterName
                });
            } else {//如果没有就直接添加 方法 路径 回调
                routers.push({
                    method,path,handle
                });
            }
        }
    });

对于路由匹配。这里还有一个特殊的all,也就是只匹配路径,不看方法(因此把method改为all)

    app.all = function (path, handle) {
        routers.push({
            method: "all",path, handle
        })
    }

下面是中间件的使用use,在express中use可以一个参数,也可以俩个(回调函数必须有,路径可以不写默认为"/")

    app.use = function (path, handle) {
        if (typeof handle !== "function") { //表明没有写路径 path
            handle = path;
            path = "/"; 
        }
        routers.push({
            method: "middle", path, handle
        })
    }

下面就是重点,app函数里面的next函数。next函数主要负责将控制权交给下一个中间件,如果当前中间件没有终结请求,并且next没有被调用,那么请求将被挂起,后边定义的中间件将得不到被执行的机会。路由和中间件也不一样,因为中间件是前缀匹配/getName/1  就能与/getName匹配成功。当next()里面加了参数,相当于出现“错误”,我们需要去寻找错误处理中间件。如果当前中间件不满足要求,那么就去下一个中间件。

对express中next函数的一些理解

 function app (req, res) {
        const { pathname } = url.parse(req.url, true);
        let next = (function () {
            let index = 0;
            function next (err) {
                if (index >= routers.length) {  return ; } //应该退出
                let route = routers[index++]; //获取当前中间件
                if (err) { // 遇到错误
                    if (route.method === "middle") {//如果是中间件
                        if (route.handle.length === 4) { //而且是错误处理中间件
                            if (route.path === "/" || pathname.startsWith(route.path+"/")
                            || route.path === pathname) {
                                return route.handle(err, req, res, next);
                            } else next(err);
                        } else next(err);
                    } else {
                        next(err);
                    }
                } else {
                    if (route.method === "middle") { //中间件
                        if (route.path === "/" || pathname.startsWith(route.path+"/")
                        || route.path === pathname) { //中间件匹配前缀 匹配成功
                           if (route.handle.length !== 4)  return route.handle(req, res, next);
                           else next();
                        } else {
                            next(); //继续调用
                        }
                    } else { //路由组件
                        if (route.paramterName) { //判断路径上面是否有参数
                            let match = pathname.match(route.path);
                            if (match) { //如果匹配成功 
                                req.params = {};//将参数挂载到req对象上面
                                for (let i=0; i<route.paramterName.length; i++) {
                                    req.params[route.paramterName[i]] = match[i+1];
                                }
                                return route.handle(req, res, next);
                            } else {
                                next();
                            }
                        } else {//路径里面没有参数
                            if ((route.method === req.method.toLocaleLowerCase() || route.method === "all")
                            && (route.path === pathname || route.path === "*")) {
                                return route.handle(req, res, next);
                            } else {
                                next();
                            } 
                        }    
                    }
                }
            }
            return next;
        })();
        next();   
    };

内置中间件就是在req上面挂载三个属性而已。

源代码:


const http = require("http");
const url = require("url");

function express () {
    let routers = []; //路由数组保存方法 路径 回调函数
    function app (req, res) {
        const { pathname } = url.parse(req.url, true);
        let next = (function () {
            let index = 0;
            function next (err) {
                if (index >= routers.length) {  return ; } //应该退出
                let route = routers[index++]; //获取当前中间件
                if (err) { // 遇到错误
                    if (route.method === "middle") {//如果是中间件
                        if (route.handle.length === 4) { //而且是错误处理中间件
                            if (route.path === "/" || pathname.startsWith(route.path+"/")
                            || route.path === pathname) {
                                return route.handle(err, req, res, next);
                            } else next(err);
                        } else next(err);
                    } else {
                        next(err);
                    }
                } else {
                    if (route.method === "middle") { //中间件
                        if (route.path === "/" || pathname.startsWith(route.path+"/")
                        || route.path === pathname) { //中间件匹配前缀 匹配成功
                           if (route.handle.length !== 4)  return route.handle(req, res, next);
                           else next();
                        } else {
                            next(); //继续调用
                        }
                    } else { //路由组件
                        if (route.paramterName) { //判断路径上面是否有参数
                            let match = pathname.match(route.path);
                            if (match) { //如果匹配成功 
                                req.params = {};//将参数挂载到req对象上面
                                for (let i=0; i<route.paramterName.length; i++) {
                                    req.params[route.paramterName[i]] = match[i+1];
                                }
                                return route.handle(req, res, next);
                            } else {
                                next();
                            }
                        } else {//路径里面没有参数
                            if ((route.method === req.method.toLocaleLowerCase() || route.method === "all")
                            && (route.path === pathname || route.path === "*")) {
                                return route.handle(req, res, next);
                            } else {
                                next();
                            } 
                        }    
                    }
                }
            }
            return next;
        })();
        next();   
    };
     
    app.listen = function (...args) { //就是开启一个服务器
        let server = http.createServer(app);
        server.listen.apply(server, args);
    };
    //all只匹配路径 不匹配方法
    app.all = function (path, handle) {
        routers.push({
            method: "all",path, handle
        })
    }
    http.METHODS.forEach((method) => { //遍历http上面的一些方法
        method = method.toLocaleLowerCase(); //转小写
        app[method] = function (path, handle) {
            if (path.includes(":")) { //判断是否有: 表面使用参数/getName/:name/:age
                let paramterName = [];
                path = path.replace(/:([^\/]+)/g, function () {
                    paramterName.push(arguments[1]); //保存参数
                    return "([^\/]+)" //将path有参数的位置换成正则表达式
                })
                //getName/:name/:age
                //getName/([^\/]+)/([^\/]+)
                routers.push({//方法 路径 回调 参数
                    method,path,handle, paramterName
                });
            } else {//如果没有就直接添加 方法 路径 回调
                routers.push({
                    method,path,handle
                });
            }
        }
    });

    //处理中间件
    app.use = function (path, handle) {
        if (typeof handle !== "function") { //表明没有写路径 path
            handle = path;
            path = "/"; 
        }
        routers.push({
            method: "middle", path, handle
        })
    }


    //内置中间件

    app.use(function (req, res, next) { //处理 req.query req.hostname req.path
        const urlObj = url.parse(req.url, true);
        req.query = urlObj.query;
        req.path = req.pathname;
        req.hostname = req.headers["host"].split(":")[0];
        next();
    })
    return app;
}


module.exports = express;

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值