sumeru
Version:
A Realtime Javascript RIA Framework For Mobile WebApp
521 lines (435 loc) • 14.7 kB
JavaScript
/**
* ClientTracer.js
*
* 配合认证系统进行用户的认证管理.
*
* 在整个server抽像出一个client端的对像表示, 并负责追踪一个独立的client端及这个端到当前server的所有socket连接.
*
* 向外在netMessag上提供以下事件消息传出
*
* SMR_AUTH_CLIENT_VERIFY : 检测到一个 client 需要验证
*
* client_connection : 检测到一个 client 连接到当前server
* client_disconnection : 检测到一个 client 断开了最后一个与当前server连接的socket
* socket_connection : 检测到一个 socket 连到接server
* socket_disconnection : 检测到一个 socket 断开与当前server的连接
*
* 接收以下事件消息,用于完成用户追踪
*
* SMR_AUTH_CLIENT_LOGIN_OK : client 登陆完成
* SMR_AUTH_CLIENT_LOGOUT_OK : client 登出完成
*
*/
var fw = require(__dirname + '/../src/newPkg.js')();
require(__dirname + '/../src/log.js')(fw);
require(__dirname + '/../src/utils.js')(fw);
require(__dirname + '/../src/netMessage.js')(fw);
var log = fw.log;
var netMessage = fw.netMessage;
/*
* 维护一个client与其所打开的所有socket的映射关系.
* 结构为
* {
* clientId_1:[socketId_1,socketId_2,socketId_3,...],
* clientId_2:[socketId_1,socketId_2,socketId_3,...],
* ......
* }
*/
var clientMgr = {};
var socket_count = 0;
var clientTracer = fw.clientTracer || fw.addSubPackage("clientTracer");
// 为serverRender中的controller模似client端auth的对像方法.
var auth = fw.auth || fw.addSubPackage("auth");
var nope = function(){}; // 懒人方法...啥也不干..
var inherits = require('util').inherits,
_extend = require('util')._extend,
EventEmitter = require('events').EventEmitter;
/**
* 清理状态非正常的client对像.
*
* 不正常的状态包括:
*
* 1 已创建对像超过5分钟,但status 仍然为"0",
* 此种情况一般收包含clientId的正常http请求产生,但是没有后续连接活动.
*
*/
// 触发间隔配置
var clearClientDiedRound = 1000 * 60 * 5;
var clearClientDied = function(){
var client = null;
var t = 1000 * 60 * 5;
var now = Date.now() - t;
for(var key in clientMgr){
client = clientMgr[key];
try{
if(client.status === 0 && client.createTime < now){
fw.dev('clearClientDied. %s',client.getClientId());
client.__destroy();
}
}catch(e){
console.error(e);
console.trace();
}
}
setTimeout(clearClientDied , clearClientDiedRound);
};
setTimeout(clearClientDied , clearClientDiedRound);
/**
*
* Class :: client
*
* 表示一个正在活动中的用户设备
*
*/
var Client = function(clientId){
if(!clientId){
throw 'Need clientId';
}
// call super.
EventEmitter.call(this);
this.getClientId = function(){
return clientId;
};
// 目标设备上所有连到当前server的socketId
this.sockets = [];
this.remoteAddress = false; // 未设置时为false;
this.userInfo = false; // 登陆信息
this.createTime = Date.now(); // client对像的创建时间,即客户端访问开始的时间戳
/*
* 0 等待client端连接
* 1 正常
* 2 所有客户端断开等待重连中
*/
this.status = 0;
this.__dataMap = {};
this.isDestroy = false;
this.__destroyTimer = null;
// 广播通知
netMessage.sendLocalMessage({clientId : clientId},'Client_Connection');
};
inherits(Client,EventEmitter);
_extend(Client.prototype,{
// -------- 以下为预留给redis实现的持久化session的getter和setter方法.
get:function(key){
if(key){
return this.__dataMap[key];
}else{
return ;
}
},
set:function(key,value){
if(key){
this.__dataMap[key] = value;
}
return value;
},
// ---------------- 以下为模似客户端的hack方法 -------------
getUserInfo:function(){
return this.userInfo || null;
},
getStatus:function(){
if(this.userInfo){
return 'logined';
}else{
return 'not_login';
}
},
getToken:function(){
return this.userInfo ? this.userInfo.token : null;
},
isLogin:function(){
return !!this.userInfo;
},
// --------------- 以下为server端方法 -----------------
/**
*
* 判断一个socketId是否被当前设备使用
*
* @param socketId
*
* @returns {Boolean}
*
*/
__haveSocket:function(socketId){
if(socketId){
return this.sockets.indexOf(socketId) != -1;
}
return false;
},
__safeIP:function(remoteAddress){
if(!remoteAddress){
return false;
}
if(this.remoteAddress === false){
this.remoteAddress = remoteAddress;
return true;
}else{
return remoteAddress == this.remoteAddress;
}
},
__socketConnection:function(socketId){
this.status = 1;
if(socketId && !this.__haveSocket(socketId)){
// 清理为destroy事件设置的计时器.
this.__destroyTimer = clearTimeout(this.__destroyTimer);
this.sockets.push(socketId);
netMessage.sendLocalMessage({clientId : this.getClientId(), socketId: socketId},'Client_SocketConnection');
}
},
__socketDisconnection:function(socketId){
var index;
if(socketId && (index = this.sockets.indexOf(socketId)) != -1){
this.sockets.splice(index,1);
netMessage.sendLocalMessage({clientId : this.getClientId(), socketId: socketId},'Client_SocketDisconnection');
}
if(this.sockets.length == 0){
this.status = 2;
// 所有socket断开后2秒中触发client的destroy,为防止用户仅是希望刷新页面.
this.__destroyTimer = setTimeout(this.__destroy.bind(this),2000);
}
},
__verify:function(authMethod){
var me = this;
/*
* 如果没修改过,则不理.直接返回.
*
* 因为client对像的有效期是连接时间内.
*
*/
if(me.getAuthMethod && me.getAuthMethod() == authMethod){
return;
}
fw.dev('============================================= verify ================');
// 使用get方法,使每次passport被修改时,必须经过验证, 防止随意被改;
me.getAuthMethod = function(){
return authMethod;
};
// 未提供验证类型,直接认为验证失败.
if(!authMethod){
me.userInfo = null;
me.emit('verify');
return;
}
netMessage.sendLocalMessage({
clientId : me.getClientId(),
authMethod : authMethod,
cb : function(err,rs){
if(err){
fw.log( me.getClientId() , err );
if(err.code != 2001 && err.code != 1004){ // 正常的验证失败,不打trace
console.trace();
}
}
if(rs){
me.userInfo = rs;
}else{
me.userInfo = null;
}
me.emit('verify');
}
} , 'SMR_AUTH_CLIENT_VERIFY');
},
__sendGlobalMsg:function(msg,tag){
if(!msg){
return;
}
this.sockets.forEach(function(socketId){
netMessage.sendGlobalMessage(msg,tag,socketId);
});
},
__destroy:function(){
this.isDestroy = true;
delete clientMgr[this.getClientId()];
netMessage.sendLocalMessage({clientId : this.getClientId()},'Client_Disconnection');
this.emit('destroy');
this.removeAllListeners();
}
});
//==============
//用户追踪方法
//==============
/**
*
* 尝试从每次连接的clientId中查出新的clientId, 并创建一个client对像.
*
* 如果找到的是旧的clientId则直接返回旧的client对像
*
*/
var findClient = function(clientId,authMethod){
if(!clientId){
return null;
}
var rv = clientMgr[clientId] = clientMgr[clientId] || new Client(clientId);
// 如果存在authMethod,自动调用验证方法.不存在的情况表示未登陆过
if(authMethod){
process.nextTick(function(){
rv.__verify(authMethod);
});
}
return rv;
};
clientTracer.__reg('findClient',findClient);
/**
*
* 在serverRender时获取相应的auth对像
*
* @param source {Object} controller中的env对像.
*
*/
auth.__reg('create',function(source){
var clientId = source.clientId;
if(clientId){
var real = findClient(clientId);
// 这里必须是一个假对像,因为有一些重名的方法在客户端与server端是不同的实现.比如emmit对像上的方法.
var rv = {
getUserInfo:real.getUserInfo.bind(real),
getStatus:real.getStatus.bind(real),
getToken:real.getToken.bind(real),
isLogin:real.isLogin.bind(real),
'on':function(type,listener){
if(type == 'statusChange' && 'function' == typeof listener){
listener.call(this,null,this.getStatus(),this.getUserInfo());
}
},
'once':function(){
this.on.apply(this,arguments);
},
'addEventListener':function(){
this.on.apply(this,arguments);
},
'removeListener':nope,
'removeAllListeners':nope,
'login':nope,
'logout':nope,
'modifyUserInfo':nope,
'modifyPassword':nope,
'registerValidate':nope,
'register':nope
};
return rv;
}else{
return {
'on':function(type,listener){
if(type == 'statusChange' && 'function' == typeof listener){
listener.call(this,null,'not_login',null);
}
},
'once':function(){
this.on.apply(this,arguments);
},
'addEventListener':function(){
this.on.apply(this,arguments);
},
'login':nope,
'logout':nope,
'modifyUserInfo':nope,
'modifyPassword':nope,
'registerValidate':nope,
'register':nope,
getUserInfo:function(){
return null;
},
getStatus:function(){
return 'not_login';
},
getToken:function(){
return null;
},
isLogin:function(){
return false;
}
};
}
});
clientTracer.__reg('onSocketConnection',function(clientId,socketId,conn){
//debugger;
if(!socketId || !clientId || !conn){
return;
}
socket_count ++;
var client = findClient(clientId);
// 获取来源ip.
var ip = conn.remoteAddress;
// 处理有代理的情况下的ip来源
if(conn.headers['x-forwarded-for']){
ip = conn.headers['x-forwarded-for'].split(', ')[0];
}
// 检查来源ip是否与client创建时一至,防止cookie,session的伪造
if(client.__safeIP(ip)){
client.__socketConnection(socketId);
}else{
console.warn('unsafe ip address : ' , conn.remoteAddress);
conn.end('access denied');
}
});
clientTracer.__reg('onSocketDisconnection',function(clientId,socketId){
if(!socketId){
return;
}
socket_count --;
var client;
if(clientId){
client = findClient(clientId);
client.__socketDisconnection(socketId);
fw.dev("socket disconnection, client " + clientId + ", socket disconnection :" + socketId , "Active Socket reg :" + socket_count , "Active Client : " + Object.keys(clientMgr).length);
return;
}else{
/*
* 如果未提供clientId,则应是由pushUpdateOfModel时,未找到socketId对应的socket引发.
* 此时的清理,是清理非正常断开的socket.正在常的情况下,不应走这个else.
*/
fw.dev("trying to clear the socketId:", socketId , ", that does not have a corresponding clientId");
// 如果没有 clientId,则需要遍历所有的client并找到对应的socket并断开
for(var clientId in clientMgr){
client = clientMgr[clientId];
if(client.__haveSocket(socketId)){
client.__socketDisconnection(socketId);
};
}
}
});
clientTracer.__reg("socketCount",function(clientId){
return clientMgr[clientId] ? clientMgr[clientId].length : 0;
});
/**
* 根据一组clientId发送GlobalMessage.
*/
clientTracer.__reg("SendGlobalMessageByClientId",function(msg,tag,clientId){
if(!msg || !tag || !clientId){
return;
}
client = clientMgr[clientId];
client.__sendGlobalMsg(msg,tag);
});
// 接收并处理低层事件消息
netMessage.setReceiver({
onLocalMessage:{
target:'SMR_AUTH_CLIENT_LOGIN_OK',
handle: function(msg,target){
var clientId = msg.clientId;
var userInfo = msg.userInfo;
if(!clientId || !userInfo){
fw.log('SMR_AUTH_CLIENT_LOGIN_OK, missing params');
return;
}
var client = findClient(clientId);
client.userInfo = userInfo;
client.emit('verify');
}
}
});
netMessage.setReceiver({
onLocalMessage:{
target:'SMR_AUTH_CLIENT_LOGOUT_OK',
handle: function(msg,target){
var clientId = msg.clientId;
if(!clientId){
fw.log('SMR_AUTH_CLIENT_LOGOUT_OK, missing params');
return;
}
var client = findClient(clientId);
client.userInfo = null;
client.emit('verify');
}
}
});