diff webtty.js @ 140:789c094675f4

WebTTY: use WebSockets when possible.
author John "Elwin" Edwards
date Mon, 22 Jul 2013 07:51:53 -0700
parents f14e92f6d955
children c4a32007d2dc
line wrap: on
line diff
--- a/webtty.js	Sat Jul 20 12:23:53 2013 -0700
+++ b/webtty.js	Mon Jul 22 07:51:53 2013 -0700
@@ -6,15 +6,83 @@
 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 @@
   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');