zoukankan      html  css  js  c++  java
  • 解析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等等。

  • 相关阅读:
    C#呓语
    引起超时的原因及表解锁的方法<转>
    如何使用数据库引擎优化顾问优化数据库 <转>
    缩短IIS应用池回收时间,减少IIS假死<转>
    Microsoft Silverlight 4 Tools for Visual Studio 2010中文版本
    系统统一验证(IHttpHandlerFactory)<转>
    解决CSS BUG的顺口溜<转>
    重建索引提高SQL Server性能<转>
    .NET调用osql.exe执行sql脚本创建表和存储过程<转>
    SQL SERVER性能优化综述<转>
  • 原文地址:https://www.cnblogs.com/liuyinlei/p/6916542.html
Copyright © 2011-2022 走看看