sumeru
Version:
A Realtime Javascript RIA Framework For Mobile WebApp
529 lines (450 loc) • 31.3 kB
JavaScript
var fw = require(__dirname + '/../src/newPkg.js')();
require(__dirname + '/../src/log.js')(fw);
var config = fw.config;
//startup a server
var path = require('path'),
fs = require('fs'),
zlib = require('zlib'),
fileUpload = require(__dirname + "/fileUpload.js");//用于文件处理
module.exports = function(req, res){
//localBase 为sumeru和 apps所在的根目录。
var frkDir = __dirname + '/../../',
localBase = process.appDir,
filePath = req.url,
range = typeof req.headers.range == 'string' ? req.headers.range : undefined;
filePath = decodeURI(filePath.replace(/\.\.\//g, ''));
localBase = path.normalize(localBase);
var view_from_cache = fw.config.get('view_from_cache');
//黑名单的配置需要同步至bae的lighttpd配置中
var deniedList = [ //黑名单
new RegExp('^/publish/'),
new RegExp('^/server_config/')
];
for (var i=0; i < deniedList.length; i++) {
if (deniedList[i].test(filePath)) {
res.writeHead(403);//forbidden
res.end();
return;
};
};
//把问号后面去掉
var uriObj = fw.uri.getInstance(filePath);
//清除?后面的东西
filePath = filePath.replace(/\?.*/,"");
if ( !fs.existsSync(localBase+filePath) ){//存在文件,则保持
filePath = uriObj.path;
}
if(filePath == '/'){
filePath = localBase + '/index.html';
} else {
var sumerujs = '/sumeru.js';
var sumerudir = /sumeru\//g;
var viewdir = /\/view\//;
var sumeruPath = path.join(localBase, fw.config.get('sumeruPath'));
if(sumerujs === filePath){
filePath = sumeruPath + '/src/sumeru.js';
}else if(view_from_cache && viewdir.test(filePath)){//hack it!在bin/view目录下读取编译后的
if (filePath.indexOf("/bin")==0){
filePath = localBase + filePath;
}else{
filePath = localBase +"/bin"+ filePath;
}
}else if(sumerudir.test(filePath)){
filePath = sumeruPath + '/' +
filePath.slice(filePath.lastIndexOf('sumeru/') + 'sumeru/'.length);
}else{
filePath = localBase + filePath;
}
}
// fw.log('file server accessing ' + path.normalize(filePath));
var extensionName = path.extname(filePath).toLowerCase(),
contentType = 'text/html';
var extMap = sumeru.config.get("mime");
if (!extMap){//兼容没有设置mime情况
extMap = {
'.js' : 'text/javascript',
'.css' : 'text/css',
'.jpg' : 'image/jpeg',
'.jpeg' : 'image/jpeg',
'.png' : 'image/jpeg',
'.ico' : 'image/jpeg',
'.json' : 'text/json',
'.manifest' : 'text/cache-manifest'
};
}
var _binaryMap = ['.jpg','.jpeg','.png','.gif','.bmp','.mp3', '.ico', '.png'],
binaryMap = {};
_binaryMap.forEach(function(item){
binaryMap[item] = true;
});
if(extMap[extensionName]){
contentType = extMap[extensionName];
}
//ADDED BY SUNDONG
if (uriObj.router && uriObj.router.type == 'file'){//deal upload
/*
pattern : '/files', //pattern用于定义匹配上传文件的uri
type : 'file',
max_size_allowed:'10M',//support k,m
file_ext_allowed:'' ,//allow all use '' , other use js array ["jpg",'gif','png','ico']
upload_dir:"public",//default dir is public
rename:function(filename){//if rename_function is defined,the uploaded filename will be deal with this function.
return filename+"_haha";
}
*/
if (req.method.toLowerCase() == 'post') {
// parse a file upload
var form = new fileUpload({hash:"sha1"});
//uriObj.router
if (uriObj.router.max_size_allowed) {
form.maxFieldsSize = uriObj.router.max_size_allowed;
}
var pubdir = (uriObj.router.upload_dir||"upload");//相对路径
var wholepubdir = process.appDir + '/'+pubdir + "/";//绝对路径
fs.exists(wholepubdir,function(exists){
if (!exists){
fs.mkdirSync(wholepubdir);
}
});
//form.uploadDir = process.tmpDir;//在build/build.js中定义临时目录 //process.env.TMP || process.env.TMPDIR || process.env.TEMP || '/tmp' || process.cwd();//localBase + '/upload/';上传到临时目录
var tmpdir = (fw.config.get("tmp_dir") || "tmp");
form.uploadDir = process.appDir + '/'+tmpdir + "/";
fs.exists(form.uploadDir,function(exists){
if (!exists){
fs.mkdirSync(form.uploadDir);
}
form.parse(req, function(err, fields, files) {
res.writeHead(200, {'content-type': 'text/plain'});
//上传成功。重命名到上传目录
for(var n in files){
if (files[n].size == 0 && files[n].name == "" ) continue;
//if rename function exists,rename first.
if (typeof uriObj.router.rename === 'function'){
var tmp = form._dealFileExtention(files[n].name);
var filepart1 = tmp[0],
filepart2 = tmp[1];
files[n].name = (uriObj.router.rename(filepart1)||"") + filepart2;
}
//then find a name to rename & save
var pubfilepath = form._parseFilePath(wholepubdir + files[n].name);
fs.renameSync(files[n].path, pubfilepath);//files[n].path 是临时文件的路径,savefilepath是排除了重复的文件
var pubfilelink = (fw.config.get("site_url") || "")+ "/" + pubdir + pubfilepath.substring(pubfilepath.lastIndexOf("/"));
res.end(form._outputSuccess(pubfilelink));
break;//暂不支持一个form多个上传文件,其实支持也很简单但是没有必要,正常情况一个文件一个文件上传处理即可。
}
res.end(form._outputError((err && err.toString() )|| "no files"));
});
});
return;
}
res.end('need request post...');
return ;
}
fs.exists(filePath, function(exists){
if(exists){
res.setHeader('Date', new Date().toString());
//res.setHeader('Expires', new Date(new Date().getTime() + 24 * 3600 * 1000).toString());
//必须设置下面三个参数, 才能保证浏览器正常更新服务器的文件
res.setHeader('Cache-Control' , 'no-cache');
res.setHeader('Expires', '0');
res.setHeader('Vary', 'Accept-Encoding');
var acceptEncoding = req.headers['accept-encoding'];
if(!acceptEncoding){
acceptEncoding = '';
}
fs.readFile(filePath, 'utf-8', function(error, entireContent){
if(error){
res.writeHead(500);
res.end();
} else {
//进行view partial的拼装,处理include逻辑
if(filePath.indexOf('/view/') != -1 || filePath.indexOf('/viewEngine/') != -1){
if (view_from_cache){//不拼装,直接返回
res.writeHead(200, {'Content-Type' : contentType});
res.end(entireContent, 'utf-8');
return ;
}
var asyncPartialMap = {},
//partialCount = 0,
//检测循环引用
isCircularRef = function(token, stack){
var found = false;
for (var i = 0, l = stack.length; i < l; i++){
if(stack[i] == token){
found = true;
fw.dev('circular Ref Found :', token, stack);
return found;
}
}
return found;
},
parsePartial = function(parseToken, refStack, content, callback){
//先去掉所有HTML的注释
var commentRegExp = /(<!--([\s\S]*?)-->)/mg; // means <!--xxx-->
fw.dev('view remove comment', content.match(commentRegExp));
entireContent = entireContent.replace(commentRegExp, '');
content = content.replace(commentRegExp, '');
var _refStack = JSON.parse(JSON.stringify(refStack));
_refStack.push(parseToken);
/*NOTES:这里为了考虑循环引用的路径一致,禁止了include时使用../的能力。
在写正则的时候为了方便,直接把策略写成了include的token中不能包括小数点。
这样等于同时也禁止了写.html这类后缀的能力,感觉挺好,先这样*/
var syntaxMatcher = content.match(/{{>\s*([^}\.\s*]+)}}/mig);
if(syntaxMatcher === null){
syntaxMatcher = [];//兼容下列循环和length判断
}
var branchNum = syntaxMatcher.length || 1;
//partialCount += branchNum;
fw.dev('branchNum', branchNum, parseToken);
if(syntaxMatcher.length == 0){
//替换为实际内容(再无进一步include命令的)
fw.dev('hit plain text', parseToken);
asyncPartialMap[parseToken] = content;
var replaceSeg = new RegExp('{{>\\s*' + parseToken + '\\s*}}');
entireContent = entireContent.replace(replaceSeg, content);
callback();
return;
} else {
var haveReachedParse = false;
for(var i = 0, l = syntaxMatcher.length; i < l; i++){
var matched = syntaxMatcher[i].match(/{{>\s*([^}\.\s*]+)}}/i);
//因为整体逻辑中有readFile导致异步,创建闭包保持matched的值不被后续循环污染
(function(matched){
if(matched.length == 2){
var partialName = matched[1];
//check circular include
if(isCircularRef(partialName, _refStack)){
entireContent = entireContent.replace(matched[0], "<p style='color:red'>circular include found : " + partialName + '</p>');
//如果本次循环都全部触发了循环引用检查,那么手动触发一次回调,使上层递归继续执行
//partialCount--;
if(--branchNum == 0){
callback();
}
/*if(i == l - 1 && !haveReachedParse){
callback();
} else {
fw.dev('partialCount ciruclar - before', partialCount);
//partialCount--;
fw.dev('partialCount circular - after', partialCount);
}*/
//这里其实是continue的意思,但由于构造了闭包,所以直接return当前闭包即可
return;
}
haveReachedParse = true;
fw.dev('checkCache', (partialName), typeof asyncPartialMap[(partialName)]);
if(typeof asyncPartialMap[partialName] == 'undefined'){
fs.readFile(path.dirname(filePath) + '/' + partialName + '.html', 'utf-8', function(error, content){
if(error){
fw.log(error);
res.writeHead(500);
res.end('error occured when processing view partial:' + partialName, 'utf-8');
return;
}
//开始解析之前,先把内容include进来
fw.dev('pre replace ---', matched[0]);
entireContent = entireContent.replace(matched[0], content);
asyncPartialMap[partialName] = content;
parsePartial(partialName, _refStack, content, function(){
/*fw.dev('partialCount - before', partialCount);
partialCount--;
fw.dev('partialCount - after', partialCount);
*/
if(--branchNum == 0){
callback();
}
});
});
} else {
fw.dev('hitCacheViewPartial', matched[0]);
entireContent = entireContent.replace(matched[0], asyncPartialMap[partialName]);
parsePartial(matched[1], _refStack, asyncPartialMap[matched[1]], function(){
/*fw.dev('partialCount cache - before', partialCount);
partialCount--;
fw.dev('partialCount cache - after', partialCount);
*/
if(--branchNum == 0){
callback();
}
});
//entireContent = entireContent.replace(matched[0], asyncPartialMap[partialName]);
//callback();
}
}
})(matched);
}
}
};
var currentPageToken = path.basename(filePath).split('.')[0];
/*
var tplRoleRegExp = /<!--[\s]*tpl-role[\s]*=[\s]*([\s\S]*?)-->([\s\S]*)<!--[\s]*\/tpl-role[\s]*-->/m;
// means <!--tpl-role=page_unit-->**<!--/tpl-role-->
var reverseTplRoleRegExp = /!!--[\s]*tpl-role[\s]*=[\s]*([\s\S]*?)--!!([\s\S]*)!!--[\s]*\/tpl-role[\s]*--!!/m;
// 被临时转义以后的tpl-role语法标记的匹配正则
*/
parsePartial(currentPageToken, [], entireContent, function(){
/*到了这里,所有的include块都已经被解析完成,如果还存在{{>,要不然是触犯了正则匹配中的小数点,要不然是触犯了循环引用。
无论如何,在这里都处理掉剩余的{{> 避免handlebars报错
* */
entireContent = entireContent.replace(/{{>\s*([^}\s*]+)}}/mig, "<p style='color:red'>error including : $1</p>");
/*
//将被临时替换的tpl-role从!!--换回<!--注释形式
entireContent = entireContent.replace(reverseTplRoleRegExp, function($0){
$0 = $0.replace(/!!--/g,'<!--');
$0 = $0.replace(/--!!/g, '-->');
return $0;
});
*/
res.writeHead(200, {'Content-Type' : contentType});
res.end(entireContent, 'utf-8');
//fw.dev('partialCount', partialCount);
/*if(partialCount == 0){
/*到了这里,所有的include块都已经被解析完成,如果还存在{{>,要不然是触犯了正则匹配中的小数点,要不然是触犯了循环引用。
无论如何,在这里都处理掉剩余的{{> 避免handlebars报错
entireContent = entireContent.replace(/{{>\s*([^}\s*]+)}}/mig, "<p style='color:red'>error including : $1</p>");
res.writeHead(200, {'Content-Type' : contentType});
res.end(entireContent, 'utf-8');
} else {
partialCount++;
}*/
});
} else if(extensionName == '.html'){
//view from cache : true以后,controller的模板也会走入这里
if (uriObj.controller === 0){
res.writeHead(404);
res.end();
return ;
}else if (filePath.search("json.html")!=-1){
res.writeHead(200, {"Content-Type": "application/json"});
}else{
res.writeHead(200, {"Content-Type": "text/html"});
}
if (fw.config.get("site_url")){
entireContent = entireContent.replace(/<base[\s\S]*?>/g,"").replace("<head>",'<head><base href="'+fw.config.get("site_url")+'" />');
}
if ( uriObj.controller !=null) {
/*
server_auth 说明
1.在请求来时注册client,进行注册逻辑
2.将clientId传入controller
3.在controller的每一个subscribe中传入clientId
4.由于后续客户端渲染开始,因此不清理client,交给后续socket断开或者心跳清理
* */
//1. 解析出clientId
var clientId = Library.cookie.parseCookie(req.headers.cookie,'clientId');
var authMethod = Library.cookie.parseCookie(req.headers.cookie,'authMethod');
//2.认证,进行clientId进行auth认证
var client = fw.clientTracer.findClient(clientId);
var verify_callback = function(){
// conn.userInfo = this.userInfo;
//3.hou
var domarr = entireContent.split('<body');
if (domarr.length!=2){
try{
fw.router.start_routeing(req.url,clientId,"",function(page){
res.end(page);
});
}catch(e){
fw.dev("error when server render....");
res.end();
}
return ;
}
res.write(domarr[0]+'<body');//这部分无需等待
try{
//split by <body>,speedup render js,css
fw.router.start_routeing(req.url,clientId,domarr[1],function(page){
res.end(page);
});
}catch(e){
fw.dev("error when server render....");
console.dir(e);
res.end(domarr[1]);
}
client && client.removeListener("verifty",verify_callback);
};
if (client && client.userInfo === false){
client.on('verify',verify_callback);
client.__verify(authMethod);
}else{
verify_callback();
}
}else if (uriObj.controller === 0){
}else{
res.end(entireContent);
}
} else if(extensionName == '.js' || extensionName == '.css'){
var raw = fs.createReadStream(filePath, {
encoding : 'utf8'
});
if(acceptEncoding.match(/\bdeflate\b/)){
res.setHeader('Content-Encoding' , 'deflate');
res.writeHead(200, {'Content-Type' : contentType});
raw.pipe(zlib.createDeflate()).pipe(res);
} else if (acceptEncoding.match(/\bgzip\b/)){
res.setHeader('Content-Encoding' , 'gzip');
res.writeHead(200, {'Content-Type' : contentType});
raw.pipe(zlib.createGzip()).pipe(res);
} else {
res.writeHead(200, {'Content-Type' : contentType});
raw.pipe(res);
}
} else if(binaryMap[extensionName] == true){
fs.stat(filePath, function(err, stat){
var info = {
start : 0,
end : stat.size - 1,
size : stat.size,
isRangeRequest : false
};
if(range !== undefined && (range = range.match(/bytes=(.+)-(.+)?/)) != null){
// Check range contains numbers and they fit in the file.
// Make sure info.start & info.end are numbers (not strings) or stream.pipe errors out if start > 0.
info.start = Library.objUtils.isNumber(range[1]) && range[1] >= 0 && range[1] < info.end ? range[1] - 0 : info.start;
info.end = Library.objUtils.isNumber(range[2]) && range[2] > info.start && range[2] <= info.end ? range[2] - 0 : info.end;
info.rangeRequest = true;
}
info.length = info.end - info.start + 1;
var resCode = 200;
var header = {
'Prama' : 'public',
'Cache-Control' : 'public',
'Connection' : 'keep-alive',
'Content-Type' : contentType,
'Content-Length' : stat.size,
'Content-Disposition' : 'inline;filename=' + path.basename(filePath) + ';',
'Last-Modified' : stat.mtime.toUTCString(),
'Content-Transfer-Encoding' : 'binary'
};
if(info.rangeRequest){
resCode = 206;
header['Status'] = '206 Partial Content';
header['Accept-Ranges'] = 'bytes';
header['Content-Range'] = 'bytes ' + info.start + '-' + info.end + '/' + info.size;
}
res.writeHead(200, header);
var util = require('util');
var raw = fs.createReadStream(filePath, {
flags : 'r',
start : info.start,
end : info.end
});
util.pump(raw, res);
});
//浏览器对manifest文件不进行缓存
}else if(extensionName === '.manifest'){
res.setHeader('Cache-Control' , 'no-cache');
res.setHeader('Expires', '0');
res.writeHead(200, {'Content-Type' : contentType});
res.end(entireContent, 'utf-8');
}else {
res.writeHead(200, {'Content-Type' : 'application/octet-stream'});//for download
res.end(entireContent, 'utf-8');
}
}
});
} else {
res.writeHead(404);
res.end();
}
});
};