WebTTY: use WebSockets when possible.

This commit is contained in:
John "Elwin" Edwards 2013-07-22 07:51:53 -07:00
parent fabaea6849
commit d7df88f3cf
2 changed files with 183 additions and 18 deletions

105
webtty.js
View file

@ -6,15 +6,83 @@ var url = require('url');
var path = require('path');
var fs = require('fs');
var pty = require(path.join(localModules, "pty.js"));
var child_process = require("child_process");
var webSocketServer = require(path.join(localModules, "websocket")).server;
var serveStaticRoot = fs.realpathSync(".");
var sessions = {};
var sessionsWS = {};
var env_dontuse = {"TMUX": true, "TMUX_PANE": true};
/* Constructor for TermSessions. Note that it opens the terminal and
* adds itself to the sessions dict.
*/
function TermSessionWS(conn, h, w) {
var ss = this;
/* Set up the sizes. */
w = Math.floor(Number(w));
if (!(w > 0 && w < 256))
w = 80;
this.w = w;
h = Math.floor(Number(h));
if (!(h > 0 && h < 256))
h = 25;
this.h = h;
this.conn = conn;
/* Customize the environment. */
var childenv = {};
for (var key in process.env) {
if (!(key in env_dontuse))
childenv[key] = process.env[key];
}
var spawnopts = {"env": childenv, "cwd": process.env["HOME"],
"rows": this.h, "cols": this.w};
this.term = pty.spawn("bash", [], spawnopts);
this.alive = true;
this.term.on("data", function (datastr) {
var buf = new Buffer(datastr);
if (ss.conn.connected)
ss.conn.sendUTF(JSON.stringify({"t": "d", "d": buf.toString("hex")}));
});
this.term.on("exit", function () {
ss.alive = false;
/* Wait for all the data to get collected */
setTimeout(ss.cleanup, 1000);
});
this.conn.on("message", function (msg) {
try {
var msgObj = JSON.parse(msg.utf8Data);
}
catch (e) {
return;
}
if (msgObj.t == "d") {
var hexstr = msgObj["d"].replace(/[^0-9a-f]/gi, "");
if (hexstr.length % 2 != 0) {
return;
}
var keybuf = new Buffer(hexstr, "hex");
ss.term.write(keybuf);
}
});
this.conn.on("close", function (msg) {
if (ss.alive)
ss.term.kill('SIGHUP');
console.log("WebSocket connection closed.");
});
this.cleanup = function () {
/* Call this when the child is dead. */
if (ss.alive)
return;
if (ss.conn.connected) {
ss.conn.sendUTF(JSON.stringify({"t": "q"}));
}
};
this.conn.sendUTF(JSON.stringify({"t": "l", "w": w, "h": h}));
console.log("New WebSocket connection.");
}
function TermSession(sessid, h, w) {
/* Set up the sizes. */
w = Math.floor(Number(w));
@ -355,6 +423,41 @@ process.on("exit", function () {
return;
});
function wsRespond(req) {
var w, h, conn;
if (req.resourceURL.pathname == "/sock") {
w = parseInt(req.resourceURL.query.w);
if (isNaN(w) || w <= 0 || w > 256)
w = 80;
h = parseInt(req.resourceURL.query.h);
if (isNaN(h) || h <= 0 || h > 256)
h = 25;
conn = req.accept(null, req.origin);
new TermSessionWS(conn, h, w);
}
else {
req.reject(404, "No such resource.");
}
}
/* The pty.js module doesn't wait for the processes it spawns, so they
* become zombies, which leads to unpleasantness when the system runs
* out of process table entries. But if the child_process module is
* initialized and a child spawned, node will continue waiting for any
* children.
* Someday, some developer will get the bright idea of tracking how many
* processes the child_process module has spawned, and not waiting if
* it's zero. Until then, the following useless line will protect us
* from the zombie hordes.
* Figuring this out was almost as interesting as the Rogue bug where
* printf debugging altered whether the high score list was checked.
*/
child_process.spawn("/bin/true");
process.env["TERM"] = "xterm-256color";
http.createServer(handler).listen(8080, "127.0.0.1");
var webServer = http.createServer(handler);
webServer.listen(8080, "127.0.0.1");
console.log('Server running at http://127.0.0.1:8080/');
var wsServer = new webSocketServer({"httpServer": webServer});
wsServer.on("request", wsRespond);
console.log('WebSockets online');