-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from iansuny/master
web server version
- Loading branch information
Showing
59 changed files
with
9,452 additions
and
220 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,239 +1,85 @@ | ||
var agent = require('./agent'), | ||
nats = require('nats').connect(), | ||
fs = require('fs'), | ||
path = require("path"), | ||
respawn = require('respawn'), | ||
bunyan = require("bunyan"), | ||
log = bunyan.createLogger({name: 'Sense'}), | ||
async = require('async'), | ||
config = require('./config'); | ||
/* Module dependencies */ | ||
|
||
var modules = {}; | ||
var statusCheckHandle; | ||
var web = require('./web'); | ||
var debug = require('debug')('humix-sense-web:server'); | ||
var http = require('http'); | ||
var fs = require('fs'); | ||
var sense = require('./sense'); | ||
|
||
/* Get port from environment and store in Express. */ | ||
|
||
/* Constants */ | ||
var port = normalizePort(process.env.PORT || '3000'); | ||
web.set('port', port); | ||
|
||
var STATUS_CHECK_INTERVAL = 3000; | ||
var STATUS_CHECK_TIMEOUT = 5000; | ||
/* Create HTTP server */ | ||
|
||
var server = http.createServer(web); | ||
|
||
process.on('SIGTERM', function() { | ||
if (agent.getState() === 'RUNNING') { | ||
agent.stop(); | ||
} | ||
}); | ||
/* Listen on provided port, on all network interfaces */ | ||
|
||
var senseId = process.argv[2] || undefined; | ||
if (!senseId) { | ||
senseId = config.senseId || 'humix'; | ||
} | ||
|
||
humixSenseInit(); | ||
|
||
try { | ||
agent.init(config.thinkURL, senseId, {autoreconnect: true, logger: log}); | ||
agent.start(); | ||
|
||
} catch (e) { | ||
log.error('Error: '+e); | ||
} | ||
|
||
function humixSenseInit(){ | ||
if (config.log.file) { | ||
var level = config.log.level || 'info'; | ||
log.addStream({path: config.log.file, level: level}); | ||
} | ||
|
||
log.info("Init Humix Sense"); | ||
|
||
// starting core modules | ||
|
||
var coreModulePath = './modules/core/'; | ||
|
||
fs.readdir(coreModulePath,function(err, coreModules){ | ||
|
||
if(err){ | ||
|
||
log.error('Failed to read core modules. Error:'+err); | ||
return; | ||
} | ||
|
||
coreModules.map(function (m) { | ||
return path.join(coreModulePath, m); | ||
|
||
}).filter(function (m) { | ||
return fs.statSync(m).isDirectory(); | ||
server.listen(port); | ||
server.on('error', onError); | ||
server.on('listening', onListening); | ||
|
||
}).forEach(function (m) { | ||
log.info("module : %s", m); | ||
/* Normalize a port into a number, string, or false */ | ||
|
||
var p = respawn(['npm','start'],{cwd:m}); | ||
function normalizePort(val) { | ||
var port = parseInt(val, 10); | ||
|
||
p.on('stdout', function(data) { | ||
// sometimes sense module emit multiple JSON logs to this, | ||
// so need split each JSON and output to file/console | ||
data.toString().split('\n').forEach(function(e,i,a) { | ||
try { | ||
// quick quess if data is JSON object received from bunyan logging style | ||
var m = JSON.parse(e); | ||
log.info(m.msg); | ||
} catch (err) { | ||
if (e.trim().length > 0) { | ||
log.info(e); | ||
} | ||
} | ||
}); | ||
if (isNaN(port)) { | ||
// named pipe | ||
return val; | ||
} | ||
|
||
}); | ||
if (port >= 0) { | ||
// port number | ||
return port; | ||
} | ||
|
||
p.on('stderr', function(data) { | ||
data.toString().split('\n').forEach(function(e,i,a) { | ||
try { | ||
var m = JSON.parse(e); | ||
log.error(m.msg); | ||
} catch (err) { | ||
if (e.trim().length > 0) { | ||
log.error(e); | ||
} | ||
} | ||
}); | ||
}); | ||
|
||
p.on('spawn', function () { | ||
log.info('process spawned') | ||
}); | ||
|
||
p.on('exit', function (code, signal) { | ||
log.error({msg: 'process exited, code: ' + code + ' signal: ' + signal}); | ||
|
||
}); | ||
p.start(); | ||
}); | ||
|
||
}); | ||
statusCheckHandle = startStatusCheck(); | ||
return false; | ||
} | ||
|
||
|
||
function startStatusCheck() { | ||
|
||
return setInterval(function () { | ||
|
||
var moduleStatus = []; | ||
|
||
var myPromise = function (ms, module, callback) { | ||
return new Promise(function(resolve, reject) { | ||
callback(resolve, reject); | ||
setTimeout(function() { | ||
reject('Status Check promise timed out after ' + ms + ' ms'); | ||
}, ms); | ||
}); | ||
} | ||
|
||
|
||
async.eachSeries(Object.keys(modules), function (module, cb) { | ||
|
||
log.info('checking status of ' + module); | ||
|
||
myPromise(STATUS_CHECK_TIMEOUT, module, function (resolve, reject) { | ||
var t = 'humix.sense.mgmt.' + module + ".ping"; | ||
nats.request(t, null, { 'max': 1 }, function (res) { | ||
resolve('success'); | ||
|
||
}) | ||
|
||
}).then(function (result) { | ||
log.info('connection with module ' + module + " succeed"); | ||
moduleStatus.push({ moduleId: module, status: 'connected' }); | ||
cb(null); | ||
}).catch(function () { | ||
log.info('connection with module ' + module + " failed"); | ||
moduleStatus.push({ moduleId: module, status: 'disconnected' }); | ||
cb(null); | ||
}) | ||
|
||
}, function (err) { | ||
agent.publish('humix-think', 'module.status', moduleStatus); | ||
}); | ||
|
||
},STATUS_CHECK_INTERVAL); | ||
|
||
|
||
/* Event listener for HTTP server "error" event */ | ||
|
||
function onError(error) { | ||
if (error.syscall !== 'listen') { | ||
throw error; | ||
} | ||
|
||
var bind = typeof port === 'string' | ||
? 'Pipe ' + port | ||
: 'Port ' + port; | ||
|
||
// handle specific listen errors with friendly messages | ||
switch (error.code) { | ||
case 'EACCES': | ||
console.error(bind + ' requires elevated privileges'); | ||
process.exit(1); | ||
break; | ||
case 'EADDRINUSE': | ||
console.error(bind + ' is already in use'); | ||
process.exit(1); | ||
break; | ||
default: | ||
throw error; | ||
} | ||
} | ||
|
||
agent.events.on('module.command', function(data) { | ||
|
||
log.info('Command: '+JSON.stringify(data)); | ||
|
||
var module = data.commandType; | ||
var command = data.commandName; | ||
var topic = 'humix.sense.'+module+'.command.'+command; | ||
|
||
log.info('topic: '+topic + ', data: '+JSON.stringify(data.commandData)); | ||
|
||
if(modules.hasOwnProperty(module) && modules[module].commands.indexOf(command) != -1 ){ | ||
|
||
if (data.syncCmdId) { | ||
|
||
// TODO: handle timeout here | ||
data.commandData.syncCmdId = data.syncCmdId; | ||
nats.request(topic, JSON.stringify(data.commandData), { 'max': 1 }, function (res) { | ||
agent.publish_syncResult(data.syncCmdId, res); | ||
|
||
}) | ||
} else { | ||
nats.publish(topic, JSON.stringify(data.commandData)); | ||
} | ||
|
||
log.debug('publish command'); | ||
|
||
}else{ | ||
|
||
log.info('skip command'); | ||
} | ||
}); | ||
/* Event listener for HTTP server "listening" event */ | ||
|
||
// handle module registration | ||
nats.subscribe('humix.sense.mgmt.register', function(request, replyto){ | ||
log.info("Receive registration :"+ request); | ||
|
||
var requestModule = JSON.parse(request); | ||
|
||
if(modules.hasOwnProperty(requestModule.moduleName)){ | ||
log.info('Module [' + requestModule.moduleName + '] already register. Skip'); | ||
nats.publish(replyto,'module already registered'); | ||
return; | ||
} | ||
|
||
modules[requestModule.moduleName] = requestModule; | ||
|
||
|
||
var eventPrefix = 'humix.sense.'+requestModule.moduleName+".event"; | ||
|
||
for ( var i in requestModule.events){ | ||
|
||
var event = requestModule.events[i]; | ||
var module = requestModule.moduleName; | ||
var topic = eventPrefix + "." + event; | ||
|
||
log.debug("subscribing topic:"+ topic); | ||
|
||
(function(topic,module,event){ | ||
|
||
nats.subscribe(topic, function(data){ | ||
log.debug('about to publish topic:'+topic+", data:"+data); | ||
agent.publish(module, event, data); | ||
}); | ||
})(topic,module,event); | ||
|
||
} | ||
|
||
// register the module to humix-think | ||
function onListening() { | ||
var addr = server.address(); | ||
var bind = typeof addr === 'string' | ||
? 'pipe ' + addr | ||
: 'port ' + addr.port; | ||
debug('Listening on ' + bind); | ||
} | ||
|
||
agent.publish('humix-think', 'registerModule', requestModule); | ||
console.log('##### Web Server Started #####'); | ||
|
||
log.debug('current modules:'+JSON.stringify(modules)); | ||
nats.publish(replyto,'module registration succeed'); | ||
// determine whether config is already set or not | ||
var init = JSON.parse(fs.readFileSync('config.json', 'utf8')).init; | ||
if (!init){ | ||
sense.humixSenseStart(); | ||
} | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"init": true, | ||
"thinkURL": "https://todo-bot.mybluemix.net", | ||
"senseId": "alpha", | ||
"log": { | ||
"file": "./sense.log", | ||
"level": "debug" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* Type */ | ||
|
||
body, input, select, textarea { | ||
color: #ffffff; | ||
} | ||
|
||
/* Button */ | ||
|
||
input[type="submit"], | ||
input[type="reset"], | ||
input[type="button"], | ||
button, | ||
.button { | ||
position: relative; | ||
} | ||
|
||
input[type="submit"]:after, | ||
input[type="reset"]:after, | ||
input[type="button"]:after, | ||
button:after, | ||
.button:after { | ||
display: none; | ||
} | ||
|
||
/* Features */ | ||
|
||
.features { | ||
border: solid 1px; | ||
} | ||
|
||
/* Form */ | ||
|
||
input[type="text"], | ||
input[type="password"], | ||
input[type="email"], | ||
input[type="tel"], | ||
select, | ||
textarea { | ||
background: transparent; | ||
border: solid 1px; | ||
} | ||
|
||
/* Split */ | ||
|
||
.split.style1 > :first-child { | ||
padding-right: 2em; | ||
width: 70%; | ||
} |
Oops, something went wrong.