Permalink
Browse files

Add server-side socket support

-Refactor into REST (node.js) and socket (node_app_socket.js)
-File node_apps.js now only deals with app management
  • Loading branch information...
1 parent 2e964c7 commit 5ef6b76add222e16038180ef695a60356190978b @mamacdon committed Dec 11, 2012
Showing with 351 additions and 178 deletions.
  1. +23 −142 lib/node.js
  2. +89 −0 lib/node_app_socket.js
  3. +214 −33 lib/node_apps.js
  4. +25 −3 server.js
View
@@ -6,181 +6,62 @@ var url = require('url');
var api = require('./api'), write = api.write, writeError = api.writeError;
var fileUtil = require('./fileUtil');
var resource = require('./resource');
-var node_apps = require('./node_apps'), App = node_apps.App, AppTable = node_apps.AppTable;
-
-var PATH_TO_NODE = process.execPath;
-var INSPECT_PORT = 8900;
+var node_apps = require('./node_apps');
function printError(e) {
console.log('err' + e);
}
-function spawnNode(modulePath, args) {
- var child = child_process.spawn(PATH_TO_NODE, [modulePath].concat(Array.isArray(args) ? args : []), {
- cwd: path.dirname(modulePath) // weird/maybe wrong: cwd is module dir, not the Orion Shell's cwd
+function getDecoratedAppJson(req, nodeRoot, appContext, app) {
+ var json = app.toJson();
+ var requestUrl = url.parse(req.url);
+ json.Location = url.format({
+ host: req.headers.host,
+ pathname: requestUrl.pathname + '/' + app.pid
});
- child.on('error', printError);
- return child;
+ return json;
}
module.exports = function(options) {
var nodeRoot = options.root;
- var fileRoot = options.fileRoot;
- var workspaceDir = options.workspaceDir;
- if (!nodeRoot) { throw 'options.root path required'; }
- var appTable = new AppTable();
- var inspector = null;
-
- function safeModulePath(p) {
- var filePath = api.rest(fileRoot, p);
- return fileUtil.safeFilePath(workspaceDir, filePath);
- }
-
- function startInspectorApp(req, modulePath, args, restartOnExit){
- var child, app;
- try {
- child = spawnNode(modulePath, args);
- var location = url.resolve(url.format({
- protocol: req.protocol, // TODO this may be bad
- host: req.headers.host,
- pathname: 'node-inspector' + '/'
- }), './' + child.pid);
- app = new App(child.pid, location, child);
- appTable.put(child.pid, app);
- child.on('exit', function() {
- console.log('exit # ' + child.pid);
- appTable.remove(child.pid);
- if(restartOnExit){
- startInspectorApp(req, modulePath, args, restartOnExit);
- }
- });
- return app;
- } catch (e) {
- console.log(e.stack || e);
- appTable.remove(child.pid);
- app.stop();
- return null;
- }
- }
-
- function startApp(req, res, modulePath, args, debugInfo) {
- var child, app;
- try {
- child = spawnNode(modulePath, args);
- var location = url.resolve(url.format({
- protocol: req.protocol, // TODO this may be bad
- host: req.headers.host,
- pathname: nodeRoot + '/'
- }), './' + child.pid);
-
- app = new App(child.pid, location, child, debugInfo);
- appTable.put(child.pid, app);
- child.on('exit', function() {
- console.log('exit # ' + child.pid);
- appTable.remove(child.pid);
- });
- write(201, res, { Location: location }, app.toJson());
- return app;
- } catch (e) {
- console.log(e.stack || e);
- appTable.remove(child.pid);
- app.stop();
- return null;
- }
- }
+ var appContext = options.appContext;
+ if (!nodeRoot || !appContext) { throw 'Missing "nodeRoot" or "appContext" parameter'; }
- /**
- * @param {HttpRequest} req
- * @param {HttpResponse} res
- * @param {Function} next
- * @param {String} rest
- */
return resource(nodeRoot, {
+ /**
+ * @param {HttpRequest} req
+ * @param {HttpResponse} res
+ * @param {Function} next
+ * @param {String} rest
+ */
GET: function(req, res, next, rest) {
var pid = rest;
if (pid === '') {
write(200, res, null, {
- Apps: appTable.apps().map(function(app) {
- return app.toJson();
+ Apps: appContext.appTable.apps().map(function(app) {
+ return getDecoratedAppJson(req, nodeRoot, appContext, app);
})
});
} else {
- var app = appTable.get(pid);
+ var app = appContext.appTable.get(pid);
if (!app) {
writeError(404, res);
return;
}
- write(200, res, null, app.toJson());
- }
- },
- POST: function(req, res, next, rest) {
- function checkPath(modulePath) {
- if (typeof modulePath !== 'string') {
- writeError(400, res, 'Missing parameter "modulePath"');
- return false;
- }
- return true;
- }
- function checkArgs(args) {
- if (args && !Array.isArray(args)) {
- writeError(400, res, 'Parameter "args" must be an array, or omitted');
- return false;
- }
- return true;
- }
- function checkPort(port) {
- if (typeof port !== 'number') {
- writeError(400, res, 'Parameter "port" must be a number');
- return false;
- }
- return true;
- }
- var params = req.body, modulePath = params.modulePath, args = params.args, port;
- if (rest === 'start') {
- if (checkPath(modulePath) && checkArgs(args)) {
- startApp(req, res, safeModulePath(modulePath), args);
- }
- } else if (rest === 'debug') {
- port = params.port;
- if (checkPath(modulePath) && checkPort(port)) {
- modulePath = safeModulePath(modulePath);
- var parsedOrigin = url.parse(req.headers.origin);
- var debugInfo = url.resolve(url.format({
- protocol: parsedOrigin.protocol,
- hostname: parsedOrigin.hostname,
- port: INSPECT_PORT + '/'
- }), './' + "debug?port=" + port);
-
- var app = startApp(req, res, modulePath, ["--debug-brk=" + port].concat(modulePath), debugInfo);
- //Lazy spawn the node inspector procees for the first time when user wants to debug an app.
- if(app && !inspector){
- modulePath = require.resolve('node-inspector/bin/inspector');
- var inspectorArg = [modulePath].concat("--web-port=" + INSPECT_PORT);
- inspector = startInspectorApp(req, modulePath, inspectorArg, true);
- }
- }
- } else if (rest === 'debug_inspect') {
- port = params.port;
- if (checkPort(port) && !inspector) {
- modulePath = require.resolve('node-inspector/bin/inspector');
- var inspectorArg = [modulePath].concat("--web-port=" + port);
- inspector = startApp(req, res, modulePath, inspectorArg, true);
- }
- } else {
- write(400, res);
+ write(200, res, null, getDecoratedAppJson(req, nodeRoot, appContext, app));
}
},
+ // POST: No POST for apps -- starting apps is handled by a Web Socket connection
DELETE: function(req, res, next, rest) {
if (rest === '') {
writeError(400, res);
return;
}
- var pid = rest;
- var app = appTable.remove(pid);
+ var pid = rest, app = appContext.appTable.get(pid);
if (!app) {
writeError(404, res);
} else {
- app.stop();
+ appContext.stopApp(app);
write(204, res);
}
}
View
@@ -0,0 +1,89 @@
+/*global Buffer exports module require*/
+/*jslint devel:true*/
+var api = require('./api');
+
+function emitError(socket, error) {
+ socket.emit('error', error && error.stack);
+}
+
+/**
+ * Forwards events:
+ * app 'stdout' -> socket 'stdout'
+ * app 'stderr' -> socket 'stderr'
+ */
+function pipeStreams(app, socket) {
+ var proc = app.process;
+ // TODO: This forces proc output to be interpreted as UTF-8. Should send binary data to client and let them deal with it
+ proc.stdout.setEncoding('utf8');
+ proc.stderr.setEncoding('utf8');
+ var streamHandler = function(type, data) {
+ data = Buffer.isBuffer(data) ? data.toString('base64') : data;
+ socket.emit(type, data);
+ };
+ var stdoutListener = streamHandler.bind(null, 'stdout');
+ var stderrListener = streamHandler.bind(null, 'stderr');
+ app.on('stdout', stdoutListener);
+ app.on('stderr', stderrListener);
+ app.on('exit', function() {
+ app.removeListener('stdout', stdoutListener);
+ app.removeListener('stderr', stderrListener);
+ });
+}
+
+function checkParamType(args, name, type) {
+ if (typeof args[name] !== type) {
+ throw new Error('Missing parameter "' + name + '"');
+ }
+}
+
+function checkPort(port) {
+ if (typeof port !== 'number') {
+ throw new Error('Parameter "port" must be a number');
+ }
+ return true;
+}
+
+exports.install = function(options) {
+ var io = options.io, appContext = options.appContext;
+ if (!io || !appContext) {
+ throw new Error('Missing "io" or "appContext"');
+ }
+ io.sockets.on('connection', function(socket) {
+ var handshakeData = socket.handshake;
+ socket.on('start', function(data) {
+ try {
+ checkParamType(data, 'modulePath', 'string');
+ var app = appContext.startApp(data.modulePath, data.args);
+ pipeStreams(app, socket);
+ app.on('exit', function(c) {
+ socket.emit('stopped', app.toJson());
+ });
+ socket.emit('started', app.toJson());
+ } catch (error) {
+ console.log(error && error.stack);
+ emitError(socket, error);
+ }
+ });
+ socket.on('debug', function(data) {
+ try {
+ checkParamType(data, 'modulePath', 'string');
+ checkParamType(data, 'port', 'number');
+ var app = appContext.debugApp(data.modulePath, data.port, handshakeData.headers, handshakeData.url);
+ pipeStreams(app, socket);
+ app.on('exit', function(c) {
+ socket.emit('stopped', app.toJson());
+ });
+ socket.emit('started', app.toJson());
+ } catch (error) {
+ console.log(error && error.stack);
+ emitError(socket, error);
+ }
+ });
+ socket.on('disconnect', function() {
+ // stop piping?
+ });
+ });
+};
+
+exports.uninstall = function() {
+};
Oops, something went wrong.

0 comments on commit 5ef6b76

Please sign in to comment.