三余无梦

冬者岁之余,夜者日之余,阴雨者时之余也

Visionmedia/send Reading

send入口

1
exports = module.exports = send;

send 函数返回 SendStream 构造函数

1
2
3
function send(req, path, options) {
  return new SendStream(req, path, options);
}

来看SendStream

  • 参数
    • req 即 request
    • path : string 路径
    • options:
1
2
3
function SendStream(req, path, options) {
  ....
}
  • 继承 Stream.prototype
1
SendStream.prototype.__proto__ = Stream.prototype;
  • 下面都是 SendStream 原型链的方法

    • hidden: 赋值 this._hidden, return this
    • index: 赋值默认的 index path, this._index, return this
    • root: 赋值根路径, this._root
    • maxage: 赋值最大缓存时间 , this._maxage
    • error: 根据status–>触发error
    • redirect: 如果有监听事件directory的监听者 那么触发directory
    • isMalicious: 检测pathname是否有潜在的问题,判断方法如果 没有_rootpath包含.. 那么 就是有异常的路径;
    • hasTrailingSlash: 判断路径最后一位是不是/
    • hasLeadingDot: 就是文件是否有后缀 形如aa.html
    • isCachable: 是否缓存, 判断方法 (res.statusCode >= 200 && res.statusCode < 300) || 304 == res.statusCode
    • isFresh: 判断缓存是否是新的 判断方法 引用模块 fresh
    • removeContentHeaderFields 去除包含 content的头 key
    • onStatError:
  • 主体方法 pipe , 参数 res

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var self = this
, args = arguments
, path = this.path
, root = this._root

this.res = res;
//判断uri的有效性
path = utils.decode(path);
if (-1 == path) return this.error(400);

//如果`path`里包含`\0` ,表明`path`为空
if (~path.indexOf('\0')) return this.error(400);

//连接 this._root 和 path
if (root) path = normalize(join(this._root, path));

//如果异常路径禁止访问
if (this.isMalicious()) return this.error(403);

//此时`path`已经是 `this._root`和 `path`的结合, 所以此种情况不会出现,如果有root
if (root && 0 != path.indexOf(root)) return this.error(403)

//如果_hidden为`false`,那么不支持隐藏文件,此时文件如果以`.`开头那么就不支持 
if (!this._hidden && this.hasLeadingDot()) return this.error(404);

//index 文件支持
if (this._index && this.hasTrailingSlash()) path += this._index

//最后看path 的信息
fs.stat(path, function(err, stat){
  if (err) return self.onStatError(err); //上面已介绍
  if (stat.isDirectory) return self.redirect(self.path); //见上
  self.emit('file', path, stat); //见下
  self.send(path, stat);//见下
});
  • 方法 send
    • path: 路径
    • stat: 文件状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var options = this.options;
var len = stat.size;
var res = this.res;
var req = this.req;
var ranges = req.headers.range; //截取部分文件之用
var offset = options.start || 0;

//赋值header
this.setHeader(stat);

//赋值 content-type
this.type(path);

//条件 GET 支持, isFresh() 见 [fresh](https://github.com/visionmedia/node-fresh/blob/master/index.js)
if (this.isConditionGET()
&& this.isCachable()
&& this.isFresh()) {
  return this.notModified();
}

//
len = Math.max(0, len - offset);
if (options.end != undefined) {
  var bytes = options.end - offset + 1;
  if (len > bytes) len = bytes;
}

// Range 支持
if (ranges) {
  ... 
}

// content-length
res.setHeader('Content-Length', len);

// HEAD support
if ('HEAD' == req.method) return res.end();

this.stream(path, options); //见下
  • 方法 stream
    • 参数
      • path : 路径
      • options
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var self = this;
var res = this.res;
var req = this.req;

//pipe 把流数据加入 `res`管道
var stream = fs.createReadStream(path, options);
this.emit('stream', stream);
stream.pipe(res);

//socket 关闭, 

req.on('close', stream.destroy.bind(stream));

//error处理
steam.on('error', function(err){
  //不回复
  if (res._header) {
      console.error(err.stack);
      req.destroy();
      return;
  }
  erro.status = 500;
  self.emit('error', err);
});

//end
stream.on('end', function(){
  self.emit('end');   
});