基本上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. Delphi cxGrid使用汇总(一)

    1. 去掉cxGrid中台头的Box解决:在tableview1的ptionsview的groupbybox=false; 2.统计功能解决:(1) tableview1. tableview1的op ...

  2. NW.js安装原生node模块node-printer控制打印机

    1.安装原生node模块 #全局安装nw-gyp npm install -g nw-gyp #设置目标NW.js版本 set npm_config_target=0.31.4 #设置构建架构,ia3 ...

  3. 在Node中使用ES6语法

    Node本身已经支持部分ES6语法,但是import export,以及async await(Node 8 已经支持)等一些语法,我们还是无法使用.为了能使用这些新特性,我们就需要使用babel把E ...

  4. is和 == 的区别以及编码.解码

    一.is和==的区别 1,id( ) id( )是python的一个内置函数,通过id( )我们可以查看到一个变量表的值在内存中的地址: s = 2 print(id(s)) # 1514368064 ...

  5. vue-cli初始化一个项目

    1,换成淘宝源: npm config set registry https://registry.npm.taobao.org/ 检查是否修改成功 npm config get registry 2 ...

  6. Data - 数据思维

    数据思维 数据思维全解析 如何建立数据分析的思维框架 做数据分析时,你的方法论是什么? 数据分析全流程资料,适合各路人马 百度内部培训资料PPT:数据分析的道与术 学会数据分析背后的挖掘思维,分析就完 ...

  7. 在ubuntu中我们使用sudo apt-get install 或者dpkg -i *.deb安装软件时,常常提示“有未能满足的依赖关系“,解决方法

    很早之前在ubuntu安装软件时遇到的问题,今天打开ubuntu看到了,总结如下: 在ubuntu中我们使用sudo apt-get install 或者dpkg -i *.deb安装软件常常提示“有 ...

  8. 3DMax——室内设计:墙体+吊顶

    1.导入CAD平面图 2.将导入的平面图全部选中→颜色设置为其他颜色→设置为组(设置为组,是为了后期选材质方便) 3.选中图形,选择移动工具,输入坐标为0,右键选择冻结当前选择 4.右键“角度捕捉切换 ...

  9. c++拷贝构造函数(深拷贝、浅拷贝)——转

    拷贝构造函数: 拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类的一个引用变量,该参数是const类型,不可变的.例如:类A的拷贝构造函数的形式为A(A& ...

  10. 【从0到1学Web前端】CSS伪类和伪元素 分类: HTML+CSS 2015-06-02 22:29 1065人阅读 评论(0) 收藏

    1.CSS中的伪类 CSS 伪类用于向某些选择器添加特殊的效果. 语法: selector : pseudo-class {property: value} CSS 类也可与伪类搭配使用 select ...