diff rlgwebd.js @ 16:ef6127ed6da3

RLGWeb: switch to JSON protocol. Port the JSON communication from WebTTY to RLGWeb. Fixing out-of-order messages is still not implemented on the server side. Terminal size is still hard-coded. Unused code is still lying around.
author John "Elwin" Edwards <elwin@sdf.org>
date Thu, 17 May 2012 09:32:19 -0700
parents ad0a31e52007
children d3e3d6b4016b
line wrap: on
line diff
--- a/rlgwebd.js	Tue May 15 16:26:28 2012 -0700
+++ b/rlgwebd.js	Thu May 17 09:32:19 2012 -0700
@@ -43,7 +43,7 @@
  * adds itself to the sessions dict. It currently assumes the user has
  * been authenticated.
  */
-function TermSession(game, user, files) {
+function TermSession(game, user, files, dims) {
   /* First make sure starting the game will work. */
   if (!(game in games)) {
     // TODO: throw an exception instead
@@ -57,15 +57,33 @@
   }
   /* Grab a spot in the sessions table. */
   sessions[this.sessid] = this;
+  /* State for messaging. */
+  this.nsend = 0;
+  this.nrecv = 0;
+  this.msgQ = []
+  /* Set up the sizes. */
+  this.w = Math.floor(Number(dims[1]));
+  if (!(this.w > 0 && this.w < 256))
+    this.w = 80;
+  this.h = Math.floor(Number(dims[0]));
+  if (!(this.h > 0 && this.h < 256))
+    this.h = 24;
+  /* Environment. */
+  var childenv = {};
+  for (var key in process.env) {
+    childenv[key] = process.env[key];
+  }
+  childenv["PTYHELPER"] = String(this.h) + "x" + String(this.w);
   /* TODO handle tty-opening errors */
   /* TODO make argument-finding into a method */
   args = [games[game].path, "-n", user.toString()];
-  this.child = child_process.spawn("/bin/ptyhelper", args);
+  this.child = child_process.spawn("/bin/ptyhelper", args, {"env": childenv});
   var ss = this;
   this.alive = true;
   this.data = [];
   this.lock = files[0];
-  fs.writeFile(this.lock, this.child.pid.toString() + '\n80\n24\n', "utf8"); 
+  fs.writeFile(this.lock, this.child.pid.toString() + '\n' + this.w + '\n' +
+               this.h + '\n', "utf8"); 
   this.record = fs.createWriteStream(files[1], { mode: 0664 });
   /* END setup */
   function ttyrec_chunk(buf) {
@@ -206,6 +224,22 @@
   return data;
 }
 
+function getMsg(posttext) {
+  var jsonobj;
+  if (!posttext)
+    return {};
+  try {
+    jsonobj = JSON.parse(posttext);
+  }
+  catch (e) {
+    if (e instanceof SyntaxError)
+      return {};
+  }
+  if (typeof(jsonobj) != "object")
+    return {};
+  return jsonobj;
+}
+
 function auth(username, password) {
   // Real authentication not implemented
   return true;
@@ -224,9 +258,10 @@
     sendError(res, 2, "Password not given.");
     return;
   }
-  var username = formdata["name"][0];
-  var password = formdata["pw"][0];
-  var gname = formdata["game"][0];
+  var username = formdata["name"];
+  var password = formdata["pw"];
+  var gname = formdata["game"];
+  var dims = [formdata["h"], formdata["w"]];
   if (!(gname in games)) {
     sendError(res, 2, "No such game: " + gname);
     console.log("Request for nonexistant game \"" + gname + "\"");
@@ -239,13 +274,15 @@
     var ts = timestamp();
     var lockfile = path.join(progressdir, username + ":node:" + ts + ".ttyrec");
     var ttyrec = path.join("/dgldir/ttyrec", username, gname, ts + ".ttyrec");
-    var nsession = new TermSession(gname, username, [lockfile, ttyrec]);
+    var nsession = new TermSession(gname, username, [lockfile, ttyrec], dims);
     if (nsession) {
       /* Technically there's a race condition for the "lock"file, but since 
        * it requires the user deliberately starting two games at similar times, 
        * it's not too serious. We can't get O_EXCL in Node anyway. */
       res.writeHead(200, {'Content-Type': 'text/plain'});
-      res.write("l1\n" + nsession.sessid + "\n");
+      var reply = {"t": "l", "id": nsession.sessid, "w": nsession.w, "h": 
+                   nsession.h};
+      res.write(JSON.stringify(reply));
       res.end();
       console.log("%s playing %s (key %s, pid %d)", username, gname, 
                   nsession.sessid, nsession.child.pid);
@@ -309,14 +346,16 @@
   cterm.close();
   var resheaders = {'Content-Type': 'text/plain'};
   res.writeHead(200, resheaders);
-  res.write("q1\n\n");
+  res.write(JSON.stringify({"t": "q"}));
   res.end();
   return;
 }
 
 function findTermSession(formdata) {
+  if (typeof(formdata) != "object")
+    return null;
   if ("id" in formdata) {
-    var sessid = formdata["id"][0];
+    var sessid = formdata["id"];
     if (sessid in sessions) {
       return sessions[sessid];
     }
@@ -370,22 +409,24 @@
 
 function readFeed(res, term) {
   if (term) {
+    var reply = {};
     var result = term.read();
+    if (result == null) {
+      if (term.alive)
+        reply.t = "n";
+      else
+        reply.t = "q";
+    }
+    else {
+      reply.t = "d";
+      reply.n = term.nsend++;
+      reply.d = result.toString("hex");
+    }
     res.writeHead(200, { "Content-Type": "text/plain" });
-    if (result == null)
-      resultstr = "";
-    else
-      resultstr = result.toString("hex");
-    if (result == null && !term.alive) {
-      /* Child has terminated and data is flushed. */
-      res.write("q1\n\n");
-    }
-    else
-      res.write("d" + resultstr.length.toString() + "\n" + resultstr + "\n");
+    res.write(JSON.stringify(reply));
     res.end();
   }
   else {
-    //console.log("Where's the term?");
     sendError(res, 1, null);
   }
 }
@@ -395,14 +436,14 @@
 
 function sendError(res, ecode, msg) {
   res.writeHead(200, { "Content-Type": "text/plain" });
-  if (ecode < errorcodes.length && ecode > 0) {
-    var emsg = errorcodes[ecode];
-    if (msg)
-      emsg += ": " + msg;
-    res.write("E" + ecode + '\n' + emsg + '\n');
-  }
-  else
-    res.write("E0\nGeneric Error\n");
+  var edict = {"t": "E"};
+  if (!(ecode < errorcodes.length && ecode > 0))
+    ecode = 0;
+  edict["c"] = ecode;
+  edict["s"] = errorcodes[ecode];
+  if (msg)
+    edict["s"] += ": " + msg;
+  res.write(JSON.stringify(edict));
   res.end();
 }
 
@@ -421,7 +462,8 @@
 
   /* This will send the response once the whole request is here. */
   function respond() {
-    formdata = getFormValues(reqbody);
+    //formdata = getFormValues(reqbody);
+    formdata = getMsg(reqbody);
     var target = url.parse(req.url).pathname;
     var cterm = findTermSession(formdata);
     /* First figure out if the client is POSTing to a command interface. */
@@ -431,18 +473,19 @@
           sendError(res, 1, null);
           return;
         }
-        if ("quit" in formdata) {
+        if (formdata.t == "q") {
           /* The client wants to terminate the process. */
           logout(cterm, res);
         }
-        else if (formdata["keys"]) {
+        else if (formdata.t == "d" && typeof(formdata.d) == "string") {
           /* process the keys */
-          hexstr = formdata["keys"][0].replace(/[^0-9a-f]/gi, "");
+          hexstr = formdata.d.replace(/[^0-9a-f]/gi, "");
           if (hexstr.length % 2 != 0) {
             sendError(res, 2, "incomplete byte");
             return;
           }
           keybuf = new Buffer(hexstr, "hex");
+          /* TODO OoO correction */
           cterm.write(keybuf);
         }
         readFeed(res, cterm);