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等等。