zoukankan      html  css  js  c++  java
  • .5-浅析express源码之Router模块(1)-默认中间件

      模块application已经完结,开始讲Router路由部分。

      切入口仍然在application模块中,方法就是那个随处可见的lazyrouter。

      基本上除了初始化init方法,其余的app.use、app.route、app.param等等,所有涉及到路由的方法都会调用一次这个函数,用来初始化一个应用的内部路由。

      而这个内部路由对于每个应用来说是唯一的,可以看下源码:

    app.lazyrouter = function lazyrouter() {
        if (!this._router) {
            // 生成一个实例
            this._router = new Router({
                caseSensitive: this.enabled('case sensitive routing'),
                strict: this.enabled('strict routing')
            });
            // params解析中间件调用
            this._router.use(query(this.get('query parser fn')));
            // express框架自定义的内部中间件
            this._router.use(middleware.init(this));
        }
    };

      很清晰的步骤,在生成一个Router实例后,调用了两个中间件。

      这里有一个问题,为什么不在初始化的函数中直接生成一个默认路由呢?

      原因在于设置路由的相关参数需要调用app.set方法,这个方法明显需要有app实例,如果在获取app实例的时候就初始化了一个路由,这个路由的参数就没办法配置了。因此,在获取app实例后,必须先对路由参数进行配置,然后再调用对应的app.use等方法。

      简单看一眼构造函数:

    var proto = module.exports = function(options) {
        var opts = options || {};
        // 跟app一样的函数
        function router(req, res, next) {
            router.handle(req, res, next);
        }
        // 原型方法挂载
        setPrototypeOf(router, proto)
    
        router.params = {};
        router._params = [];
        /**
         * caseSensitive => 区分大小写 /foo vs /Foo
         * mergeParmas => 保留父路由参数
         * strict => 严格模式 /foo vs /foo/
         */
        router.caseSensitive = opts.caseSensitive;
        router.mergeParams = opts.mergeParams;
        router.strict = opts.strict;
        router.stack = [];
    
        return router;
    };

      默认情况下,三个参数的值均为undefined,构造函数没有任何初始化的操作,直接返回了router函数。

      接下来是两个中间件。

    query

      先把那行代码单独贴出来:

    // var query = require('./middleware/query');
    this._router.use(query(this.get('query parser fn')));

      前面有讲解3个特殊键的set会触发对应的compile方法设置fn,这里的query parser fn就是之一。

      默认情况下,query parser值为extended,对应的query parser fn为qs.parse方法,因此这里query方法的参数为一个函数。

      看一眼query方法:

    module.exports = function query(options) {
        // options为函数 merge后opts也是函数
        var opts = merge({}, options)
        var queryparse = qs.parse;
        // 参数修正
        if (typeof options === 'function') {
            queryparse = options;
            opts = undefined;
        }
        // 兼容 设置配置参数
        if (opts !== undefined && opts.allowPrototypes === undefined) opts.allowPrototypes = true;
        // 中间件标准结构
        return function query(req, res, next) {
            if (!req.query) {
                var val = parseUrl(req).query;
                req.query = queryparse(val, opts);
            }
    
            next();
        };
    };

      这里的形参options既可以是配置参数,也可以是预设的解析方法。

      如果将query parser设为false,这里的options就是一个空对象,express还是会指定一个parser,即源码中的qs.parse。搞了半天,设置false或者extended都是默认的qs.parse。

      在确实了对应的parse方法与参数后,就开始进行url解析,先处理url,获取query参数,再解析query设置到req对象上。

    parseUrl

      讲这个之前,需要稍微理解下nodejs的url模块,特别是Url与URL。

      这两东西在网上没查到详细的区别,通过试API,发现差别还挺大:

    1、Url为遗留API,构造函数不接受参数,通过无参构造后,可以调用parse方法解析一个url路径来获得一个实例,实例属性包含protocol、auth等一系列东西。

    2、URL为WHATWG API,推荐使用的新API,可以直接通过new操作传一个url进去获得实例,属性同样包含那些,但是在键名与分类略有区别。

      详细情况可见:http://nodejs.cn/api/url.html#url_url_strings_and_url_objects。

      虽然URL是新东西而且node推荐使用,但是在express源码的这个方法中依然使用的是老Url,入口函数如下:

    function parseurl(req) {
        // 这个属性是原生的
        var url = req.url;
    
        if (url === undefined) return undefined;
        // 尝试获取缓存属性
        var parsed = req._parsedUrl;
        // 判断有没有缓存
        if (fresh(url, parsed)) return parsed;
    
        // 解析url
        parsed = fastparse(url);
        parsed._raw = url;
        // 添加缓存并返回结果
        return (req._parsedUrl = parsed)
    };

      所有的解析都基于一个原生的属性,即req.url,该属性返回请求的原始URL。

      这里的获取缓存就不看了,比较简单,直接看如何快速解析url路径:

    function fastparse(str) {
        // 当路径结构为纯path(例如:/path/ext?a=1)时,直接调用node原生的parse方法
        if (typeof str !== 'string' || str.charCodeAt(0) !== 0x2f /* / */ ) {
            return parse(str)
        }
    
        var pathname = str
        var query = null
        var search = null
    
        // This takes the regexp from https://github.com/joyent/node/pull/7878
        // 这个issue主要讲当url是纯路径时 用node原生的Url.parse会更快
        for (var i = 1; i < str.length; i++) {
            switch (str.charCodeAt(i)) {
                /**
                 * 遇到问号开始切割路径
                 * http://www.baidu.com?a=1 => 
                 * {
                 *      pathname: http://www.baidu.com,
                 *      query: a=1,
                 *      search: ?a=1,
                 * }
                 */
                case 0x3f:
                    /* ?  */
                    if (search === null) {
                        pathname = str.substring(0, i)
                        query = str.substring(i + 1)
                        search = str.substring(i)
                    }
                    break
                    // 遇到其余不合理的情况调用原生方法
                case 0x09:
                    /* 	 */
                case 0x0a:
                    /* 
     */
                case 0x0c:
                    /* f */
                case 0x0d:
                    /* 
     */
                case 0x20:
                    /*    */
                case 0x23:
                    /* #  */
                case 0xa0:
                case 0xfeff:
                    return parse(str)
            }
        }
        // 生成一个Url对象或者空对象
        var url = Url !== undefined ?
            new Url() : {};
        // 添加对应的属性
        url.path = str
        url.href = str
        url.pathname = pathname
        url.query = query
        url.search = search
    
        return url
    }

      看似很长,实则很简单。简单来说,就是根据问号来切割url,特殊情况就全部扔给内置模块解析,最后返回url对象。

      在获取到对应的url尾部参数后,调用parser方法解析生成一个参数对象挂载到req上,所以在实际应用中,我们可以直接调用req.query来得到请求参数值。

    middleware.init

      这个中间件是express自定义的,也不知道叫什么,所以直接用调用名作为小标题了。

      源码如下:

    exports.init = function(app) {
        return function expressInit(req, res, next) {
            // 这玩意儿默认生效的
            if (app.enabled('x-powered-by')) res.setHeader('X-Powered-By', 'Express');
            // 属性各种挂载
            req.res = res;
            res.req = req;
            req.next = next;
            // 本地模块原型设置
            setPrototypeOf(req, app.request)
            setPrototypeOf(res, app.response)
    
            res.locals = res.locals || Object.create(null);
    
            next();
        };
    };

      这个中间件的主要作用就是把内置模块的属性、方法全部加到原生的req、res上面去,后面就能使用express的方法了。

      解析完毕。

  • 相关阅读:
    pycharm运行html文件报404错误
    css3 鼠标悬浮动画效果
    子代选择器和后代选择器的区别
    前端入门
    爬虫Scrapy框架
    BeautifulSoup
    爬虫之selenium使用
    爬虫之BeautifulSoup
    urllib模块
    爬虫基础
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/8863429.html
Copyright © 2011-2022 走看看