基本上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

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

  这个模块算是完结了。

.4-浅析express源码之applicaiton模块(3)-compile函数的更多相关文章

  1. .3-浅析express源码之applicaiton模块(2)-app.render

    这个模块还漏了一个稍微复杂点的API,就是app.render,首先看官网的定义: app.render(view, [locals], callback) view为对应的文件名,locals为一个 ...

  2. .2-浅析express源码之applicaiton模块(1)-咸鱼方法

    上一节讲了express的入口文件,当执行主函数,会调用app.init方法,这个方法就来源于application模块. 这个模块有很多方法,目前仅仅过一下初始化方法: app.init = fun ...

  3. .5-浅析express源码之Router模块(1)-默认中间件

    模块application已经完结,开始讲Router路由部分. 切入口仍然在application模块中,方法就是那个随处可见的lazyrouter. 基本上除了初始化init方法,其余的app.u ...

  4. .7-浅析express源码之Router模块(3)-app[METHODS]

    之前的讨论都局限于use方法,所有方式的请求会被通过,这一节讨论express内部如何处理特殊请求方法. 给个流程图咯~ 分别给出app.METHODS与router.METHODS: // app. ...

  5. .6-浅析express源码之Router模块(2)-router.use

    这一节继续深入Router模块,首先从最常用的use开始. router.use 方法源码如下: proto.use = function use(fn) { var offset = 0; var ...

  6. express源码剖析--Router模块

    1.加载模块执行代码: methods.forEach(function(method){ //method是http协议的各种请求方法,如:get,put,headee,post Route.pro ...

  7. express源码分析之Router

    express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于express的使用这里不再多说,如有需要可以移步到www.expressjs.com自行查看express的 ...

  8. nginx源码分析之模块初始化

    在nginx启动过程中,模块的初始化是整个启动过程中的重要部分,而且了解了模块初始化的过程对应后面具体分析各个模块会有事半功倍的效果.在我看来,分析源码来了解模块的初始化是最直接不过的了,所以下面主要 ...

  9. 读Zepto源码之Event模块

    Event 模块是 Zepto 必备的模块之一,由于对 Event Api 不太熟,Event 对象也比较复杂,所以乍一看 Event 模块的源码,有点懵,细看下去,其实也不太复杂. 读Zepto源码 ...

随机推荐

  1. Sql Server Report 导出到EXCEL 指定行高

    在SQL SERVER REPORT 2005做报表的时候,发现在report中指定的行高没有用.google了一下,找到了解决方法. Make both CanGrow and CanShrink ...

  2. html\css不同长度文本左右对齐 text-align:justify的使用

    在平常的开发过程中,对于text-align一般用到的是left,center,right,这三个属性都不会陌生.然而,对于justify的使用我却是很陌生.首先有个比较简单的例子. 如下代码: &l ...

  3. 如何在C#中引入CPLEX的dll(CPLEX系列-教程一)

    以前写在CSDN上的文章.转到博客园之后,打算把这个教程移过来,顺便完善后面的教程.主要是在Asp.Net+EF6里面使用cplex,完成一个最优生产计划的决策.当时在查找如何在C#中引用cplex时 ...

  4. Git项目下载部分文件或文件夹

    我们常常要在Github下载一些源码.示例等,但有时候项目库会比较大,而我关心的只是其中很少的一部分内容,由于众所周知的原因,我们下载git库是比较慢的,过大的项目经常会下载失败,所以只下载部分内容就 ...

  5. DOM扩展:DOM API的进一步增强[总结篇-下]

    本文承接<DOM扩展:DOM API的进一步增强[总结篇-上]>,继续总结DOM扩展相关的功能和API. 3.6 插入标记 DOM1级中的接口已经提供了向文档中插入内容的接口,但是在给文档 ...

  6. Android---------------ContentProvider的学习

    1.Uri  uri = Intent.getData()------------->可以获得Uri的地址 2.Cursor cursor = getContentResolver().quer ...

  7. ReactiveCocoa 源码阅读记录。

    1:RACSingle 需要订阅信号 RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACS ...

  8. PHP中eval函数的危害与正确禁用方法

    其实 eval()是无法用php.ini中的 disable_functions禁止掉的 :eval是zend的,因此不是PHP_FUNCTION 函数: 如果想禁掉eval可以用php的扩展 Suh ...

  9. 跟着刚哥学Redis

    NoSQL 简介 NoSQL(NoSQL = Not Only SQL ),意即"不仅仅是SQL".是对不同于传统的关系型数据库的数据库管理系统的统称.它泛指非关系型的数据库.随着 ...

  10. [vuejs] 终端npm run dev 不能自动打开浏览器运行项目解决办法

    终端执行: npm run dev 出现: I Your application is running here: http://localhost:8080 但并没有打开浏览器运行项目 解决办法: ...