rrestjs
Version:
HIgh performance node.js ROA & RESTFUL web framework.
147 lines (143 loc) • 5.77 kB
JavaScript
/*
*RestStatic.js 是rrestjs框架静态文件输出的模块
*
*为res.sendfile提供了支持,主要功能是设置响应头,并且响应静态文件
*
*增加了静态文件的2次状态缓存,加速响应,减少i/o操作
*
*exports fn(res, path, cb)
*/
var fs = require('fs'),
path = require('path'),
RestUtils = require('./RestUtils'),
Buffer = require('buffer').Buffer,
mime = require('mime'),
restzlib = require('./RestZlib'),
msg = require('./msg/msg'),
RestStatic = {//读取配置文件
option:{
getOnly : _restConfig.staticGetOnly,
maxAge : _restConfig.staticMaxAge,
},
mtimeBuffer:{},//2级缓存,减少一次i/o操作
Lv2MaxAge : _restConfig.staticLv2MaxAge,//2级缓存存在时间
zlibArray : _restConfig.ZlibArray, //选择性gzip输出
};
RestStatic.setHeaders = function(res, option){ //设置各种静态响应头
var charset = mime.charsets.lookup(option.type);
if (!res.getHeader('Date')) res.setHeader('Date', new Date().toUTCString());
if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + (option.maxAge / 1000));
if (!res.getHeader('Last-Modified')) res.setHeader('Last-Modified', option.mtime.utc);
if (!res.getHeader('Content-Type')) res.setHeader('Content-Type', option.type + (charset ? '; charset=' + charset : ''));
if (!res.getHeader('Etag')) res.setHeader('Etag', option.mtime.utcmd5);
res.setHeader('Accept-Ranges', 'bytes');
};
RestStatic.sendfiles = function(req, res, filepath, stype, cb){ //输出文件的方法
var acceptEncoding = req.headers['accept-encoding'], //获取客户端是否支持gzip
type = restzlib.check(acceptEncoding),
stype = stype,
canZlib = RestStatic.zlibArray.some(function(value){//判断文件类型是否在gizp输出数组中
return value == stype;
});
fs.readFile(filepath, function(err, data){
if(err){ //如果错误,则输出404
RestUtils.errorRes404(res, msg.resmsg.notFound);
return cb(err);
}
if(type && canZlib) restzlib.send(res, data, type); //gzip输出
else{
res.setHeader('Content-Length', data.length); //正常输出
data = RestUtils.headReq(req, data);//如果是head请求,则值响应http头,返回空
res.end(data);
}
cb();
});
};
RestStatic.ranges = function(res, ranges, stats, cb){ //ranges支持
var len = stats.size,
ranges = RestUtils.parseRange(len, ranges, cb),
opts = {};
if (ranges) {
opts.start = ranges[0].start;
opts.end = ranges[0].end;
len = opts.end - opts.start + 1;
res.statusCode = 206;
res.setHeader('Content-Range', 'bytes '
+ opts.start
+ '-'
+ opts.end
+ '/'
+ stats.size);
} else {
cb('Range error:'+RestUtils.error(416));
return false;
}
return true;
}
RestStatic.ismodify = function(req, res, option, stats, filepath){ //判断是否让浏览器使用缓存
if(res._nocache) return false;//如果手动设置res.cache(false)或者res.cache('public',-1)则表示不使用缓存
var UTC = stats.mtime instanceof Date ? stats.mtime.toUTCString() : stats.mtime.utc
option.mtime = {
utc : UTC,
utcmd5 : RestUtils.md5(UTC)
};
RestStatic.setHeaders(res, option);
RestStatic.mBuffer(filepath, option.mtime); //更新缓存或建立缓存
if(RestUtils.conditionalGET(req) && RestUtils.modified(req, res)){
RestUtils.notModified(res, option)
return true;
}//如果已经缓存
else return false;
}
RestStatic.send = module.exports = function(res, filepath, cb){ //核心函数,主入口
var option = RestUtils.merge({}, RestStatic.option),
cb = cb || function(){},
req = res._restReq,
mBuffer = RestStatic.mtimeBuffer,
stype = option.type = mime.lookup(filepath),
mtime;
if(option.getOnly && 'GET' != req.method && 'HEAD' != req.method){
var errstr = msg.resmsg.getOnlyError;
RestUtils.forbidden(res, errstr);
return cb(errstr);
}//如果不是get请求,则禁止
if (mtime = mBuffer[filepath]){//如果找到2级缓存
if(RestStatic.ismodify(req, res, option, {mtime : mtime}, filepath)) return cb(); //响应304,客户端读取缓存
RestStatic.sendfiles(req, res, filepath, stype, cb); //输出文件
}
else{ //如果没有找到2级缓存
fs.stat(filepath, function(err, stats){
if (err){
RestUtils.errorRes404(res, msg.resmsg.notFound);
return cb(err);
}
else if (stats.isDirectory()){
RestUtils.errorRes404(res, msg.resmsg.notFound);
return cb(msg.errmsg.staticSendError);
}
if(RestStatic.ismodify(req, res, option, stats, filepath)) return cb(); //获取了状态判断是否缓存
var ranges = req.headers.range;
if(ranges){//如果是ranges
if(RestStatic.ranges(res, ranges, stats, cb)) RestStatic.sendfiles(req, res, filepath, stype, cb);//如果ranges合法,则输出
}
else RestStatic.sendfiles(req, res, filepath, stype, cb);//如果不是ranges,则直接输出
});
}
};
RestStatic.mBuffer = function(key, mtime){
var mBuffer = RestStatic.mtimeBuffer;
if(!mBuffer[key] && (++mBuffer.length > _restConfig.staticLv2Number)){
RestStatic.clearmBuffer();//如果mBuffer不存在,并且长度已经超过1000了,清空2级缓存Buffer
}
mBuffer[key] = mtime;
};
RestStatic.clearmBuffer = function(){//清除2级缓存的方法
RestStatic.mtimeBuffer = {
length:0,
}
};
RestStatic.loopClear = function(){ //定期清空2级缓存
setInterval(function(){
RestStatic.clearmBuffer();
},RestStatic.Lv2MaxAge)
}();