• .4-浅析express源码之applicaiton模块(3)-compile函数


      基本上application模块的api都看的差不多了,但是在app.set中还有一个遗漏点,如下:

    app.set = function set(setting, val) {
        // ...设值
    
        // 触发特殊compile函数
        switch (setting) {
            case 'etag':
                this.set('etag fn', compileETag(val));
                break;
            case 'query parser':
                this.set('query parser fn', compileQueryParser(val));
                break;
            case 'trust proxy':
                this.set('trust proxy fn', compileTrust(val));
    
                Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
                    configurable: true,
                    value: false
                });
    
                break;
        }
    
        return this;
    };

      在对etag、query parser、trust proxy属性进行设置时,会根据值设置对应的fn属性。

      这几个值都比较特殊,在官网有对option进行解释,下面逐个讲解。

    etag

      首先来看etag,概念可参考wiki:https://en.wikipedia.org/wiki/HTTP_ETag,简单描述一下该字段。

    1、每一次资源内容变更会生成一个新的ETag。

    2、标准中未对ETag的格式做规定,常规情况下可使用时间戳的加密字符串。

    3、etag分为weak、strong两种模式,区别在于weak的头部有个W/,强模式要求内容是字节级别的相等,总之非常严格。

    4、客户端使用post类请求搭配If-Match头部可防止由服务器资源更新导致的并发错误,出错会返回状态码412(先决条件出错)。

    5、客户端使用get类请求搭配RANGE字段可保证返回同样的范围资源,出错会返回状态码416(资源范围不匹配)。

    6、服务器可根据客户端发送的If-None-Match字段跟服务器上资源的ETag做对比,若匹配则返回304状态码,表示资源仍然可用。

      可选的设值有三种:

    1、布尔值

      true代表使用weak ETag,默认值。

      false代表关闭ETag选项。

    2、字符串

      'weak'、'strong'分别代表使用weak ETag、strong ETag。

    3、函数

      自定义ETag生成函数。

      compileETag的函数源码如下:

    exports.compileETag = function(val) {
        var fn;
    
        if (typeof val === 'function') {
            return val;
        }
        /**
         * true/weak => weak ETag
         * strong => strong ETag
         * false => 禁ETag
         */
        switch (val) {
            case true:
                fn = exports.wetag;
                break;
            case false:
                break;
            case 'strong':
                fn = exports.etag;
                break;
            case 'weak':
                fn = exports.wetag;
                break;
            default:
                throw new TypeError('unknown value for etag function: ' + val);
        }
    
        return fn;
    }

      如果传入不合法的值会报错, 函数比较简单,就不分析了,输出的etag、wetag也很简单,如下:

    exports.etag = createETagGenerator({ weak: false })
    exports.wetag = createETagGenerator({ weak: true })
    function createETagGenerator(options) {
        // 返回一个函数
        return function generateETag(body, encoding) {
            // 返回一个Buffer
            var buf = !Buffer.isBuffer(body) ?
                Buffer.from(body, encoding) :
                body;
            // 传入Buffer与weak选项
            return etag(buf, options)
        }
    }

      这个body暂时还不知道是什么东西,会将其转化为一个Buffer。

      etag函数来源于引入的工具模块,源码如下所示:

    function etag(entity, options) {
        if (entity == null) {
            throw new TypeError('argument entity is required')
        }
    
        // 判断实例是否是stat对象
        var isStats = isstats(entity);
        // weak变量来源于options或者实例类型
        var weak = options && typeof options.weak === 'boolean' ?
            options.weak :
            isStats;
    
        // validate argument
        if (!isStats && typeof entity !== 'string' && !Buffer.isBuffer(entity)) {
            throw new TypeError('argument entity must be string, Buffer, or fs.Stats')
        }
    
        // 生成ETag
        var tag = isStats ?
            stattag(entity) :
            entitytag(entity);
        // 根据weak属性返回对应类型的ETag
        return weak ?
            'W/' + tag :
            tag;
    }

      函数分为四步:

    1、判断实例类型

    2、指定weak的值

    3、生成ETag

    4、根据weak的值生成完整的ETag

      首先是stat类型判断,源码如下:

    function isstats(obj) {
        // 用instanceof直接判断
        if (typeof Stats === 'function' && obj instanceof Stats) {
            return true
        }
    
        // 鸭子类型
        return obj && typeof obj === 'object' &&
            'ctime' in obj && toString.call(obj.ctime) === '[object Date]' &&
            'mtime' in obj && toString.call(obj.mtime) === '[object Date]' &&
            'ino' in obj && typeof obj.ino === 'number' &&
            'size' in obj && typeof obj.size === 'number'
    }

      鸭子类型的判断其实并不完整,一个Stat对象的值很多,参考链接:http://nodejs.cn/api/fs.html#fs_class_fs_stats

      不过对于生成ETag来说,这几个估计就够了。

      第二步根据options或者类型指定weak的值。

      第三步根据类型生成对应的ETag,源码如下:

    function stattag(stat) {
        // 把两个属性转换为16进制字符串
        var mtime = stat.mtime.getTime().toString(16);
        var size = stat.size.toString(16);
        // 拼接
        return '"' + size + '-' + mtime + '"'
    }
    function entitytag(entity) {
        if (entity.length === 0) {
            // 直接返回加密后的空字符串 这是个常量
            return '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"'
        }
    
        // 加密处理
        var hash = crypto
            .createHash('sha1')
            .update(entity, 'utf8')
            .digest('base64')
            .substring(0, 27);
    
        // 计算内容长度
        var len = typeof entity === 'string' ?
            Buffer.byteLength(entity, 'utf8') :
            entity.length;
        // 拼接长度与hash值
        return '"' + len.toString(16) + '-' + hash + '"';
    }

      从这里可以发现,生成的ETag并没有特殊的格式要求,唯一的要求就是不重复。

      第四步,就是根据weak属性在ETag前面加个W/了。

      至此,ETag部分完结。

    query parser

      这个超简单,直接看源码:

    exports.compileQueryParser = function compileQueryParser(val) {
        var fn;
    
        if (typeof val === 'function') {
            return val;
        }
        /**
         * true/simple => 内置querystring模块
         * extended => qs模块
         * false => 不解析
         */
        switch (val) {
            case true:
                fn = querystring.parse;
                break;
            case false:
                fn = newObject;
                break;
            case 'extended':
                fn = parseExtendedQueryString;
                break;
            case 'simple':
                fn = querystring.parse;
                break;
            default:
                throw new TypeError('unknown value for query parser function: ' + val);
        }
    
        return fn;
    }

      这个属性是指定参数解析方式的,可选的值也有2种:

    1、布尔值

      true与simple一样,使用node内置的querystring模块的parse方法。false则代表不进行parse,返回空对象。

    2、字符串

      simple略。extended代表使用qs模块的parse方法解析,代码如下:

    // var qs = require('qs');
    function parseExtendedQueryString(str) {
        return qs.parse(str, {
            // 该选项允许解析后对象的键覆盖原型方法
            allowPrototypes: true
        });
    }

      这两种方法都可以用来解析URL的参数,整体来看区别如下:

    1、内置的querystring模块api比较简单(一般情况我觉得都够用了),并且返回的对象并不继承于Object,所以原型方法均无法使用。

    2、qs模块的方法非常多,解析各种奇怪的字符串,返回的对象为Object类型(也可指定返回Object.create(null)类型)。

    trust proxy

      这个属性有中文的解释,可以去看一下:http://www.expressjs.com.cn/guide/behind-proxies.html

      源码过了一下,没有什么意思,而且也不是很懂,所以暂时跳过啦。。。

      这个模块算是完结了。

  • 相关阅读:
    如何利用迅雷下载百度云?
    Windows 8.1/Server 2012 R2/Embedded 8.1 with Update 3(MSDN最新版)
    Visual Studio 2013 and .NET 4.6
    明朝那些事儿的作者的经历
    嵌入式OS的现状、智能的物联网与未来的机器人
    系统战略+极致运营=战无不胜的千机团
    惠普笔记本按开机键后电源灯亮的,但是屏幕一直是黑的,只有大写锁定键闪烁,闪3次一个循环,听得到风扇
    Android平台免Root无侵入AOP框架Dexposed使用详解
    iOS-OC-APP热更新,动态更新(仿QQ打开或关闭某个功能)
    ios app 实现热更新(无需发新版本实现app添加新功能)
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/8857526.html
Copyright © 2020-2023  润新知