解析npm包send源码

1.模块的成员,也是sendStream类的成员变量

options  //调用时传递的参数
path    //调用时传递的路径
req     //http.IncomingMessage类
_etag   //http中的etag,
_dotfiles   //隐藏文件,只能是ignore、allow、deny、undefined三种
_hidden     //为ture,可以访问隐藏文件,否则不能访问
_extensions //可能的文件类型名
_index      //url没有指明具体的文件时,返回一个默认文件。类型是数组
_lastModified  //http,最后的修改时间
_maxage        //没有为一年
_root          //没有为null
               //继承Stream原型上的方法,只有一个pipe和require('events').EventEmitter中所有的成员和方法,也就是说他继承了stream和event.EventEmitter。
SendStream.prototype.__proto__ = Stream.prototype; 

2.公有方法

2.1.setHeader

 SendStream.prototype.setHeader = function setHeader(path, stat){ 
var res = this.res; //促发headers事件 this.emit('headers', res, path, stat); /** * 设置headers的四个字段:Accept-Ranges、Cache-Control、Last-Modified、ETag */ if (!res.getHeader('Accept-Ranges')) res.setHeader('Accept-Ranges', 'bytes'); if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + Math.floor(this._maxage / 1000)); if (this._lastModified && !res.getHeader('Last-Modified')) { var modified = stat.mtime.toUTCString() //mtime ,文件修改时间 debug('modified %s', modified) res.setHeader('Last-Modified', modified) //上次修改时间 } if (this._etag && !res.getHeader('ETag')) {
//stat为
fs.Stats 类对象,产生的etag为size + '-' + mtime + '"'
var val = etag(stat)
debug('etag %s', val)
    res.setHeader('ETag', val)
  }
};

2.2  type

SendStream.prototype.type = function(path){
  var res = this.res;
  /*
    设置响应首部字段Content-Type 
   */
  if (res.getHeader('Content-Type')) return;
  var type = mime.lookup(path); 
  var charset = mime.charsets.lookup(type);
  debug('content-type %s', type);
  res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
};

3.sendIndex

SendStream.prototype.sendIndex = function sendIndex(path){
  var i = -1;
  var self = this;
  function next(err){
      /*
          self._index为数组,储存目录path下的默认下文件(可能是目录)。
          只能搜索self._index.length次。
      */
    if (++i >= self._index.length) {
      if (err) return self.onStatError(err);
      //没有找到,向客户端返回404。
      return self.error(404);
    }
   //在指定的path目录下查找index文件,也就是默认文件。
    var p = join(path, self._index[i]);

    debug('stat "%s"', p);
    fs.stat(p, function(err, stat){
      if (err) return next(err);
      if (stat.isDirectory()) return next();
      self.emit('file', p, stat);
      self.send(p, stat);
    });
  }

  next();
};

4.redirect 

SendStream.prototype.redirect = function redirect(path) {
  //三部分 1. 有监听directory事件,执行
  //      2. path为目录。执行Forbidden
  //      3. 返回重定向,错误码301(Moved Permanently)设置响应头Content-type,Content-Length,Location
  if (listenerCount(this, 'directory') !== 0) {
    this.emit('directory')
    return
  }
  //Forbidden,路径的最后字符串为“/”,不能访问
  if (this.hasTrailingSlash()) {  
    this.error(403)
    return
  }

  var loc = path + '/'
  var msg = 'Redirecting to <a href="' + escapeHtml(loc) + '">' + escapeHtml(loc) + '</a>
'
  var res = this.res

  // redirect
  res.statusCode = 301
  res.setHeader('Content-Type', 'text/html; charset=UTF-8')
  res.setHeader('Content-Length', Buffer.byteLength(msg))
  res.setHeader('X-Content-Type-Options', 'nosniff')
  res.setHeader('Location', loc)
  res.end(msg)
}

 5. sendFile

SendStream.prototype.sendFile = function sendFile(path) {
  var i = 0
  var self = this
  debug('stat "%s"', path);
  fs.stat(path, function onstat(err, stat) {
    if (err && err.code === 'ENOENT'&& !extname(path) && path[path.length - 1] !== sep) {
      // not found, check extensions,path类似于 /anthonyliu/sy
      return next(err)
    }
    if (err) return self.onStatError(err)
//路径是默认,报错。
if (stat.isDirectory()) return self.redirect(self.path) self.emit('file', path, stat) self.send(path, stat) }) /** * next是对path有扩展名来。变量所有设置的扩展名, */ function next(err) { if (self._extensions.length <= i) { return err ? self.onStatError(err) : self.error(404) } var p = path + '.' + self._extensions[i++] debug('stat "%s"', p) fs.stat(p, function (err, stat) { if (err) return next(err) if (stat.isDirectory()) return next() //中了。 self.emit('file', p, stat) self.send(p, stat) }) } }

6.notModified

SendStream.prototype.removeContentHeaderFields = function removeContentHeaderFields() {
  var res = this.res
  var headers = Object.keys(res._headers || {})
  //res去掉_headers中除了content-location中的所有字段
  for (var i = 0; i < headers.length; i++) {
    var header = headers[i]
    if (header.substr(0, 8) === 'content-' && header !== 'content-location') {
      res.removeHeader(header)
    }
  }
}

/**
 * Respond with 304 not modified.
 *
 * @api private
 */

SendStream.prototype.notModified = function(){
  var res = this.res;
  debug('not modified');
  this.removeContentHeaderFields();
  res.statusCode = 304;
  //标志返回给客户端了。该方法会通知服务器,所有响应头和响应主体都已被发送,即服务器将其视为已完成
  res.end();
};

 7.send

SendStream.prototype.send = function(path, stat){
  var len = stat.size;
  var options = this.options
  var opts = {}
  var res = this.res;
  var req = this.req;
  var ranges = req.headers.range;
  var offset = options.start || 0;
  //顺序是:setHeader. content-Type....  notModified。最后是range。设置content-Length,到stream
  if (res._header) {
    // impossible to send now
    return this.headersAlreadySent();
  }

  debug('pipe "%s"', path)

  // set header fields
  this.setHeader(path, stat);

  // set content-type
  this.type(path);

  // conditional GET support
  if (this.isConditionalGET()
    && this.isCachable()
    && this.isFresh()) {
    return this.notModified();
  }

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

  // Range support  -1表示range字段有错误,-2。range字段失效,需求文件全部返回。
  if (ranges) {
    ranges = parseRange(len, ranges);

    // If-Range support
    if (!this.isRangeFresh()) {
      debug('range stale');
      ranges = -2;
    }

    // unsatisfiable
    if (-1 == ranges) {
      debug('range unsatisfiable');
      res.setHeader('Content-Range', 'bytes */' + stat.size);
      return this.error(416);
    }

    // valid (syntactically invalid/multiple ranges are treated as a regular response)
    if (-2 != ranges && ranges.length === 1) {
      debug('range %j', ranges);

      // Content-Range
      res.statusCode = 206;
      res.setHeader('Content-Range', 'bytes '
        + ranges[0].start
        + '-'
        + ranges[0].end
        + '/'
        + len);

      offset += ranges[0].start;
      len = ranges[0].end - ranges[0].start + 1;
    }
  }

  // clone options
  for (var prop in options) {
    opts[prop] = options[prop]
  }

  // set read options
  opts.start = offset
  opts.end = Math.max(offset, offset + len - 1)

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

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

  this.stream(path, opts)
};

8.stream

SendStream.prototype.stream = function(path, options){
  // TODO: this is all lame, refactor meeee
  var finished = false;
  var self = this;
  var res = this.res;
  var req = this.req;

  // 读文件,通过pipe把数据导向res,在这个过程中监听2个事件:error和end
  // 当读完数据,关闭读取流
  var stream = fs.createReadStream(path, options);
  this.emit('stream', stream);
  stream.pipe(res);

  // response finished, done with the fd
  onFinished(res, function onfinished(){
    finished = true;
    destroy(stream);
  });

  // error handling code-smell
  stream.on('error', function onerror(err){
    // request already finished
    if (finished) return;

    // clean up stream
    finished = true;
    destroy(stream);

    // error
    self.onStatError(err);
  });

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

9 pipe

SendStream.prototype.pipe = function(res){
  var self = this
    , args = arguments
    , root = this._root;

  // references
  this.res = res;

  // decode the path
  var path = decode(this.path)
  if (path === -1) return this.error(400)

  // null byte(s) //匹配到了,出错。 
  if (~path.indexOf('')) return this.error(400);

  var parts
  //无论是有root还是没有root,都会把对path进行处理。形成paths数组和path的绝对路径。
  if (root !== null) {
    // malicious path
    if (upPathRegexp.test(normalize('.' + sep + path))) {
      debug('malicious path "%s"', path)
      return this.error(403)  //forbidden
    }

    // join / normalize from optional root dir
    path = normalize(join(root, path))
    root = normalize(root + sep)

    // explode path parts
    parts = path.substr(root.length).split(sep)
  } else {
    // ".." is malicious without "root"
    if (upPathRegexp.test(path)) {
      debug('malicious path "%s"', path)
      return this.error(403)
    }

    // explode path parts
    parts = normalize(path).split(sep)

    // resolve the path
    path = resolve(path)
  }

  // dotfile handling
  // 如果解析的路径中包含.文件,那么就需要对_dotfiles和_hidden来分析
  if (containsDotFile(parts)) {
    var access = this._dotfiles
    // legacy support
    
    if (access === undefined) {
      //使用_hidden字段的时候有两个标准,
      //1是_dotfiles没有被定义;
      //2是文件的名字为隐藏文件,_hidden为true,访问该文件,否则404
      access = parts[parts.length - 1][0] === '.'
        ? (this._hidden ? 'allow' : 'ignore')
        : 'allow'
    }

    debug('%s dotfile "%s"', access, path)
switch (access) { case 'allow': break case 'deny': return this.error(403) case 'ignore': default: return this.error(404) } } // index file support,没有明确给出文件名的情况 if (this._index.length && this.path[this.path.length - 1] === '/') { this.sendIndex(path); return res; } this.sendFile(path); return res; };

 在代码中,有一段费解的代码:

SendStream.prototype.__proto__ = Stream.prototype;
//为什么不直接共享原型了。譬如:
SendStream.prototype = Stream.prototype;

共享原型有个弊端,在子类改变原型,父类原型也被迫改变,如果每个子类对象继承了父类原型。那么子类改变原型,即不会改变父类原型,例如:

var A = function(x){
    this.x = x;
}
var B = function(){
    this.x =10;
}
B.prototype = {
    getX:function(){
        console.log(this.x);
    }
}
A.prototype.__proto__ = B.prototype;
var a =  new A(9);
a.getX();        //9
//A {}
//{ getX: [Function: getX] }
console.log(A.prototype); 
console.log(B.prototype);
var b =  new B();
b.getX();   //10
A.prototype.getX =function(){
    console.log(this.x*this.x);
}
//A { getX: [Function] }
//{ getX: [Function: getX] }
console.log(A.prototype);
console.log(B.prototype);
b.getX();   //10
a.getX();   //81

如果是:

A.prototype= B.prototype;
b.getX();  //100

 整个包的作用是重写了stream的api,利用继承node的stream,和创造一个文件读取流来提供更多的服务,如pipe,redirect,sendIndex等等。

原文地址:https://www.cnblogs.com/liuyinlei/p/6916542.html