美文网首页
一个例子 - 看尽express.js原理

一个例子 - 看尽express.js原理

作者: Jason_Zeng | 来源:发表于2019-08-15 00:26 被阅读0次

本文只是实现了express的基本用法的原理,具体代码结构和真正的express.js代码结构不一样,可以说是简化版的express,重要的是理解express的一些基本用法的实现原理,比如路由,中间件,路径参数,错误处理等,这样我们在使用的时候就能了然于心!

测试用例

const express = require("./express.js");
// 初始化express
const app = express();
// 中间件匹配所有路径
app.use(function (req,res,next) {
    console.log('全部匹配');
    next();
});
app.use('/world', function (req,res,next) {
    console.log('只匹配/world');
    //next("特色错误");
    next();
});
app.get("/hello",function(req,res){
    res.end("hello");
});
app.post("/world",function(req,res) {
    res.end("world");
});
// 路径参数
app.post("/man/:age/:name",function(req,res) {
    console.log(req.params);
    res.end("man");
});
// 匹配剩余所有其他路径请求
app.all("*",function(req,res) {
    res.end("all")
});
// 错误中间件
app.use(function (err, req, res, next) {
    console.log(err);
    res.end(err);
});
// 监听端口
app.listen(8080);

express.js源码

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

// 返回expres的实例方法,需要用户调用
const createApplication = function () {
    // 真正的请求方法
    let app = function (req, res) {
        var urlObj = url.parse(req.url, true);
        const pathname  = urlObj.pathname;
        const method = req.method.toLowerCase();

        // 增加一些通用参数
        req.path = pathname;
        req.query = urlObj.query;
        req.hostname = req.headers['host'].split(':')[0];

        // 当只有简单的路由匹配的时候可以用循环来处理,因为不会被中断
        // for (let index = 0; index < app.routes.length; index++) {
        //     const route = app.routes[index];
        //     if ((method === route.method || route.method === "all" )
        //      && (pathname === route.path || route.path === "*")) {
        //         return route.handler(req, res);
        //     }
        // }
        
        // 但是要实现遍历所以要声明一个索引递增来控制
        let index = 0;
        // 当要加入中间件的时候,需要用next函数来递归调用,把控制权传递给外部来实现中间件原理
        function next(err) {
            // 所有路由不能匹配时报错
            if (index >= app.routes.length) {
                return res.end(`Cannot find ${method} ${pathname}`);
            }
            let route = app.routes[index++];

            // 外部传入参数时,则定义为err, 直接调用拥有四个参数的处理函数
            if (err) {
                // route.handler.length : 表示函数的参数是四个
                if (route.method == "middle" && route.handler.length == 4) {
                    // console.log("错误处理啦", req, res);
                    route.handler(err, req, res, next);
                } else { // 没有找到继续向下找错误处理函数 
                    next(err);
                }
            } else {
                if (route.method == "middle") {  // 处理中间件部分
                    // route.path == "/": 表示全部匹配 
                    // pathname.startsWith(route.path + "/") : 表示路由匹配以这个开头的所有 ,如 /user/to/...
                    // pathname == route.path : 表示相等 如 /user
                    if (route.path == "/" || pathname.startsWith(route.path + "/") || pathname == route.path ) {
                         route.handler(req,res,next); // 注意这里不能return 因为中间件只是处理,不返回
                    } else {
                        next();
                    }
                } else { 
                    // 处理路径参数部分
                    if (route.params) {
                        /**
                         *  "a/27/jason".match(new RegExp("a/([^\/]+)/([^\/]+)"))
                         *  匹配如下
                         *  0: "a/v/d"
                         *  1: "27"
                         *  2: "jason"
                         */
                        let macther = pathname.match(new RegExp(route.path));
                        // 用于存放参数
                        let params = {};
                        for (let j = 0; j < route.params.length; j++) {
                            params[route.params[j]] = macther[j+1];
                        }
                        // 放入req中
                        // req.params = {age:27, name:jason};
                        req.params = params;
                        // 执行回调
                        route.handler(req, res);
                    }
                    // 处理路由部分
                    // 只有当路由路径完全匹配或者为all全部匹配时才执行处理函数
                    if ((method === route.method || route.method === "all" ) && (pathname === route.path || route.path === "*")) {
                        route.handler(req, res); // 这里要return,因为请求处理完了
                    } else {
                        next();
                    }
                }
            }
        }
        // 默认调用
        next();
        //res.end(`cannot find ${req.method} ${pathname}`);
    }

    // 中间件函数,类型和路由一样,只是方法名都叫middle, 这种思想值得学习
    app.use = function(path, handler) {
        // 匹配只传回调函数的情况
        if (typeof path == "function" && typeof handler != 'function') {
            handler = path;
            path = "/";
        }
        app.routes.push({
            method:"middle",
            path,
            handler
        });
    }

    // 路由表
    app.routes = [];

    // 循环遍历请求方法名, 生成对应的请求函数
    http.METHODS.forEach(function (method) {
        method = method.toLowerCase();
        app[method] = function (path, handler) {
            let layer = {
                method,
                path,
                handler
            };
            // 如果路径中有冒号,则是存在参数的路径
            if (path.includes(":")) {
                let paramsArray = [];
                // 替换处理参数,并存入带正则的路径到路由表
                /**
                 * path = "/a/:age/:name"
                 * 匹配后
                 * layer.path = /a/([^\/]+)/([^\/]+);
                 * layer.params = ["age","name"];
                 */
                layer.path = path.replace(/:([^\/]+)/g,function() {
                    // 把匹配的参数名存入数组
                    paramsArray.push(arguments[1]);
                    // 换成一个正则,留作以后路由匹配时用,这里十分巧妙,一举两得
                    return "([^\/]+)";
                })
                // 存入路由中,留做以后路由匹配时用
                //console.log(layer);
                layer.params = paramsArray;

            }
            app.routes.push(layer);
        }
    });

    // 监听所有请求路径
    app.all = function (path, handler) {
        app.routes.push({
            method: "all",
            path,
            handler
        });
    }

    // 监听函数,此时才启动服务,传递参数到真正的函数
    app.listen = function () {
        // 这里把app函数传入才是真正的监听函数
        let server = http.createServer(app);
        server.listen.apply(server,arguments);
        // 下面会把arguments当作一个对象传入, 不符合实际
        //http.createServer(app).listen(arguments);
    }
    return app;
}
module.exports = createApplication;

相关文章

网友评论

      本文标题:一个例子 - 看尽express.js原理

      本文链接:https://www.haomeiwen.com/subject/ojnojctx.html