Changeset View
Changeset View
Standalone View
Standalone View
support/aphlict/server/aphlict_server.js
/** | |||||
* Notification server. Launch with: | |||||
* | |||||
* sudo node aphlict_server.js --user=aphlict | |||||
* | |||||
* You can also specify `port`, `admin`, `host` and `log`. | |||||
*/ | |||||
var JX = require('./lib/javelin').JX; | var JX = require('./lib/javelin').JX; | ||||
var http = require('http'); | |||||
var https = require('https'); | |||||
var util = require('util'); | |||||
var fs = require('fs'); | |||||
joshuaspence: Maybe we should `try`/`catch` this and provide installation instructions if it is missing. | |||||
JX.require('lib/AphlictFlashPolicyServer', __dirname); | |||||
JX.require('lib/AphlictListenerList', __dirname); | JX.require('lib/AphlictListenerList', __dirname); | ||||
JX.require('lib/AphlictLog', __dirname); | JX.require('lib/AphlictLog', __dirname); | ||||
function parse_command_line_arguments(argv) { | function parse_command_line_arguments(argv) { | ||||
var config = { | var config = { | ||||
port: 22280, | port: 22280, | ||||
admin: 22281, | admin: 22281, | ||||
host: '127.0.0.1', | host: '127.0.0.1', | ||||
user: null, | log: '/var/log/aphlict.log', | ||||
log: '/var/log/aphlict.log' | 'ssl-key': null, | ||||
'ssl-certificate': null, | |||||
Not Done Inline ActionsSort of ugly, maybe just cert and key would suffice. joshuaspence: Sort of ugly, maybe just `cert` and `key` would suffice. | |||||
test: false | |||||
}; | }; | ||||
for (var ii = 2; ii < argv.length; ii++) { | for (var ii = 2; ii < argv.length; ii++) { | ||||
var arg = argv[ii]; | var arg = argv[ii]; | ||||
var matches = arg.match(/^--([^=]+)=(.*)$/); | var matches = arg.match(/^--([^=]+)=(.*)$/); | ||||
if (!matches) { | if (!matches) { | ||||
throw new Error("Unknown argument '" + arg + "'!"); | throw new Error("Unknown argument '" + arg + "'!"); | ||||
} | } | ||||
if (!(matches[1] in config)) { | if (!(matches[1] in config)) { | ||||
throw new Error("Unknown argument '" + matches[1] + "'!"); | throw new Error("Unknown argument '" + matches[1] + "'!"); | ||||
} | } | ||||
config[matches[1]] = matches[2]; | config[matches[1]] = matches[2]; | ||||
} | } | ||||
config.port = parseInt(config.port, 10); | config.port = parseInt(config.port, 10); | ||||
config.admin = parseInt(config.admin, 10); | config.admin = parseInt(config.admin, 10); | ||||
return config; | return config; | ||||
} | } | ||||
var debug = new JX.AphlictLog() | var debug = new JX.AphlictLog() | ||||
.addConsole(console); | .addConsole(console); | ||||
var clients = new JX.AphlictListenerList(); | |||||
var config = parse_command_line_arguments(process.argv); | var config = parse_command_line_arguments(process.argv); | ||||
if (config.logfile) { | |||||
debug.addLogfile(config.logfile); | |||||
} | |||||
if (process.getuid() !== 0) { | |||||
console.log( | |||||
"ERROR: " + | |||||
"This server must be run as root because it needs to bind to privileged " + | |||||
"port 843 to start a Flash policy server. It will downgrade to run as a " + | |||||
"less-privileged user after binding if you pass a user in the command " + | |||||
"line arguments with '--user=alincoln'."); | |||||
process.exit(1); | |||||
} | |||||
var net = require('net'); | |||||
var http = require('http'); | |||||
process.on('uncaughtException', function(err) { | process.on('uncaughtException', function(err) { | ||||
debug.log('\n<<< UNCAUGHT EXCEPTION! >>>\n' + err.stack); | debug.log('\n<<< UNCAUGHT EXCEPTION! >>>\n' + err.stack); | ||||
process.exit(1); | process.exit(1); | ||||
}); | }); | ||||
new JX.AphlictFlashPolicyServer() | var WebSocket; | ||||
.setDebugLog(debug) | try { | ||||
.setAccessPort(config.port) | WebSocket = require('ws'); | ||||
.start(); | } catch (ex) { | ||||
throw new Error( | |||||
'You need to install the Node.js "ws" module for websocket support. ' + | |||||
'Usually, you can do this with `npm install -g ws`. ' + ex.toString()); | |||||
} | |||||
var ssl_config = { | |||||
enabled: (config['ssl-key'] || config['ssl-cert']) | |||||
}; | |||||
net.createServer(function(socket) { | // Load the SSL certificates (if any were provided) now, so that runs with | ||||
var listener = clients.addListener(socket); | // `--test` will see any errors. | ||||
if (ssl_config.enabled) { | |||||
ssl_config.key = fs.readFileSync(config['ssl-key']); | |||||
ssl_config.cert = fs.readFileSync(config['ssl-cert']); | |||||
} | |||||
debug.log('<%s> Connected from %s', | // Add the logfile so we'll fail if we can't write to it. | ||||
listener.getDescription(), | if (config.logfile) { | ||||
socket.remoteAddress); | debug.addLogfile(config.logfile); | ||||
} | |||||
var buffer = new Buffer([]); | // If we're just doing a configuration test, exit here before starting any | ||||
var length = 0; | // servers. | ||||
if (config.test) { | |||||
debug.log('Configuration test OK.'); | |||||
process.exit(0); | |||||
} | |||||
var start_time = new Date().getTime(); | |||||
var messages_out = 0; | |||||
var messages_in = 0; | |||||
socket.on('data', function(data) { | var clients = new JX.AphlictListenerList(); | ||||
buffer = Buffer.concat([buffer, new Buffer(data)]); | |||||
while (buffer.length) { | function https_discard_handler(req, res) { | ||||
if (!length) { | res.writeHead(501); | ||||
length = buffer.readUInt16BE(0); | res.end('HTTP/501 Use Websockets\n'); | ||||
buffer = buffer.slice(2); | |||||
} | } | ||||
if (buffer.length < length) { | var ws; | ||||
// We need to wait for the rest of the data. | if (ssl_config.enabled) { | ||||
return; | var https_server = https.createServer({ | ||||
key: ssl_config.key, | |||||
cert: ssl_config.cert | |||||
}, https_discard_handler).listen(config.port); | |||||
ws = new WebSocket.Server({server: https_server}); | |||||
} else { | |||||
ws = new WebSocket.Server({port: config.port}); | |||||
} | |||||
Not Done Inline ActionsMaybe include ws._socket.remotePort as well? joshuaspence: Maybe include `ws._socket.remotePort` as well? | |||||
ws.on('connection', function(ws) { | |||||
var listener = clients.addListener(ws); | |||||
function log() { | |||||
debug.log( | |||||
util.format('<%s>', listener.getDescription()) + | |||||
' ' + | |||||
util.format.apply(null, arguments)); | |||||
} | } | ||||
log('Connected from %s.', ws._socket.remoteAddress); | |||||
ws.on('message', function(data) { | |||||
log('Received message: %s', data); | |||||
var message; | var message; | ||||
try { | try { | ||||
message = JSON.parse(buffer.toString('utf8', 0, length)); | message = JSON.parse(data); | ||||
} catch (err) { | } catch (err) { | ||||
debug.log('<%s> Received invalid data.', listener.getDescription()); | log('Message is invalid: %s', err.message); | ||||
continue; | return; | ||||
} finally { | |||||
buffer = buffer.slice(length); | |||||
length = 0; | |||||
} | } | ||||
debug.log('<%s> Received data: %s', | |||||
listener.getDescription(), | |||||
JSON.stringify(message)); | |||||
switch (message.command) { | switch (message.command) { | ||||
case 'subscribe': | case 'subscribe': | ||||
debug.log( | log( | ||||
'<%s> Subscribed to: %s', | 'Subscribed to: %s', | ||||
listener.getDescription(), | |||||
JSON.stringify(message.data)); | JSON.stringify(message.data)); | ||||
listener.subscribe(message.data); | listener.subscribe(message.data); | ||||
break; | break; | ||||
case 'unsubscribe': | case 'unsubscribe': | ||||
debug.log( | log( | ||||
'<%s> Unsubscribed from: %s', | 'Unsubscribed from: %s', | ||||
listener.getDescription(), | |||||
JSON.stringify(message.data)); | JSON.stringify(message.data)); | ||||
listener.unsubscribe(message.data); | listener.unsubscribe(message.data); | ||||
break; | break; | ||||
default: | default: | ||||
debug.log('<s> Unrecognized command.', listener.getDescription()); | log('Unrecognized command "%s".', message.command || '<undefined>'); | ||||
} | |||||
} | } | ||||
}); | }); | ||||
socket.on('close', function() { | ws.on('close', function() { | ||||
clients.removeListener(listener); | clients.removeListener(listener); | ||||
debug.log('<%s> Disconnected', listener.getDescription()); | log('Disconnected.'); | ||||
}); | }); | ||||
socket.on('timeout', function() { | ws.on('error', function(err) { | ||||
debug.log('<%s> Timed Out', listener.getDescription()); | log('Error: %s', err.message); | ||||
}); | }); | ||||
socket.on('end', function() { | |||||
debug.log('<%s> Ended Connection', listener.getDescription()); | |||||
}); | }); | ||||
socket.on('error', function(e) { | |||||
debug.log('<%s> Error: %s', listener.getDescription(), e); | |||||
}); | |||||
}).listen(config.port); | |||||
var messages_out = 0; | |||||
var messages_in = 0; | |||||
var start_time = new Date().getTime(); | |||||
function transmit(msg) { | function transmit(msg) { | ||||
var listeners = clients.getListeners().filter(function(client) { | var listeners = clients.getListeners().filter(function(client) { | ||||
return client.isSubscribedToAny(msg.subscribers); | return client.isSubscribedToAny(msg.subscribers); | ||||
}); | }); | ||||
for (var i = 0; i < listeners.length; i++) { | for (var i = 0; i < listeners.length; i++) { | ||||
var listener = listeners[i]; | var listener = listeners[i]; | ||||
Show All 18 Lines | if (request.method == 'POST') { | ||||
request.on('data', function(data) { | request.on('data', function(data) { | ||||
body += data; | body += data; | ||||
}); | }); | ||||
request.on('end', function() { | request.on('end', function() { | ||||
try { | try { | ||||
var msg = JSON.parse(body); | var msg = JSON.parse(body); | ||||
debug.log('notification: ' + JSON.stringify(msg)); | debug.log('Received notification: ' + JSON.stringify(msg)); | ||||
++messages_in; | ++messages_in; | ||||
try { | try { | ||||
transmit(msg); | transmit(msg); | ||||
response.writeHead(200, {'Content-Type': 'text/plain'}); | response.writeHead(200, {'Content-Type': 'text/plain'}); | ||||
} catch (err) { | } catch (err) { | ||||
debug.log( | debug.log( | ||||
'<%s> Internal Server Error! %s', | '<%s> Internal Server Error! %s', | ||||
Show All 30 Lines | if (request.url == '/') { | ||||
response.write(JSON.stringify(status)); | response.write(JSON.stringify(status)); | ||||
response.end(); | response.end(); | ||||
} else { | } else { | ||||
response.writeHead(404, 'Not Found'); | response.writeHead(404, 'Not Found'); | ||||
response.end(); | response.end(); | ||||
} | } | ||||
}).listen(config.admin, config.host); | }).listen(config.admin, config.host); | ||||
// If we're configured to drop permissions, get rid of them now that we've | |||||
// bound to the ports we need and opened logfiles. | |||||
if (config.user) { | |||||
process.setuid(config.user); | |||||
} | |||||
debug.log('Started Server (PID %d)', process.pid); | debug.log('Started Server (PID %d)', process.pid); |
Maybe we should try/catch this and provide installation instructions if it is missing. Otherwise, users who blindly update may not understand why Aphlict stopped working.