美文网首页
一个例子 - 看尽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