comparison 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
comparison
equal deleted inserted replaced
15:7466927c17a5 16:ef6127ed6da3
41 41
42 /* Constructor for TermSessions. Note that it opens the terminal and 42 /* Constructor for TermSessions. Note that it opens the terminal and
43 * adds itself to the sessions dict. It currently assumes the user has 43 * adds itself to the sessions dict. It currently assumes the user has
44 * been authenticated. 44 * been authenticated.
45 */ 45 */
46 function TermSession(game, user, files) { 46 function TermSession(game, user, files, dims) {
47 /* First make sure starting the game will work. */ 47 /* First make sure starting the game will work. */
48 if (!(game in games)) { 48 if (!(game in games)) {
49 // TODO: throw an exception instead 49 // TODO: throw an exception instead
50 return null; 50 return null;
51 } 51 }
55 while (this.sessid in sessions) { 55 while (this.sessid in sessions) {
56 this.sessid = randkey(); 56 this.sessid = randkey();
57 } 57 }
58 /* Grab a spot in the sessions table. */ 58 /* Grab a spot in the sessions table. */
59 sessions[this.sessid] = this; 59 sessions[this.sessid] = this;
60 /* State for messaging. */
61 this.nsend = 0;
62 this.nrecv = 0;
63 this.msgQ = []
64 /* Set up the sizes. */
65 this.w = Math.floor(Number(dims[1]));
66 if (!(this.w > 0 && this.w < 256))
67 this.w = 80;
68 this.h = Math.floor(Number(dims[0]));
69 if (!(this.h > 0 && this.h < 256))
70 this.h = 24;
71 /* Environment. */
72 var childenv = {};
73 for (var key in process.env) {
74 childenv[key] = process.env[key];
75 }
76 childenv["PTYHELPER"] = String(this.h) + "x" + String(this.w);
60 /* TODO handle tty-opening errors */ 77 /* TODO handle tty-opening errors */
61 /* TODO make argument-finding into a method */ 78 /* TODO make argument-finding into a method */
62 args = [games[game].path, "-n", user.toString()]; 79 args = [games[game].path, "-n", user.toString()];
63 this.child = child_process.spawn("/bin/ptyhelper", args); 80 this.child = child_process.spawn("/bin/ptyhelper", args, {"env": childenv});
64 var ss = this; 81 var ss = this;
65 this.alive = true; 82 this.alive = true;
66 this.data = []; 83 this.data = [];
67 this.lock = files[0]; 84 this.lock = files[0];
68 fs.writeFile(this.lock, this.child.pid.toString() + '\n80\n24\n', "utf8"); 85 fs.writeFile(this.lock, this.child.pid.toString() + '\n' + this.w + '\n' +
86 this.h + '\n', "utf8");
69 this.record = fs.createWriteStream(files[1], { mode: 0664 }); 87 this.record = fs.createWriteStream(files[1], { mode: 0664 });
70 /* END setup */ 88 /* END setup */
71 function ttyrec_chunk(buf) { 89 function ttyrec_chunk(buf) {
72 var ts = new Date(); 90 var ts = new Date();
73 var chunk = new Buffer(buf.length + 12); 91 var chunk = new Buffer(buf.length + 12);
204 } 222 }
205 } 223 }
206 return data; 224 return data;
207 } 225 }
208 226
227 function getMsg(posttext) {
228 var jsonobj;
229 if (!posttext)
230 return {};
231 try {
232 jsonobj = JSON.parse(posttext);
233 }
234 catch (e) {
235 if (e instanceof SyntaxError)
236 return {};
237 }
238 if (typeof(jsonobj) != "object")
239 return {};
240 return jsonobj;
241 }
242
209 function auth(username, password) { 243 function auth(username, password) {
210 // Real authentication not implemented 244 // Real authentication not implemented
211 return true; 245 return true;
212 } 246 }
213 247
222 } 256 }
223 else if (!("pw" in formdata)) { 257 else if (!("pw" in formdata)) {
224 sendError(res, 2, "Password not given."); 258 sendError(res, 2, "Password not given.");
225 return; 259 return;
226 } 260 }
227 var username = formdata["name"][0]; 261 var username = formdata["name"];
228 var password = formdata["pw"][0]; 262 var password = formdata["pw"];
229 var gname = formdata["game"][0]; 263 var gname = formdata["game"];
264 var dims = [formdata["h"], formdata["w"]];
230 if (!(gname in games)) { 265 if (!(gname in games)) {
231 sendError(res, 2, "No such game: " + gname); 266 sendError(res, 2, "No such game: " + gname);
232 console.log("Request for nonexistant game \"" + gname + "\""); 267 console.log("Request for nonexistant game \"" + gname + "\"");
233 return; 268 return;
234 } 269 }
237 // This sets up the game once starting is approved. 272 // This sets up the game once starting is approved.
238 function startgame() { 273 function startgame() {
239 var ts = timestamp(); 274 var ts = timestamp();
240 var lockfile = path.join(progressdir, username + ":node:" + ts + ".ttyrec"); 275 var lockfile = path.join(progressdir, username + ":node:" + ts + ".ttyrec");
241 var ttyrec = path.join("/dgldir/ttyrec", username, gname, ts + ".ttyrec"); 276 var ttyrec = path.join("/dgldir/ttyrec", username, gname, ts + ".ttyrec");
242 var nsession = new TermSession(gname, username, [lockfile, ttyrec]); 277 var nsession = new TermSession(gname, username, [lockfile, ttyrec], dims);
243 if (nsession) { 278 if (nsession) {
244 /* Technically there's a race condition for the "lock"file, but since 279 /* Technically there's a race condition for the "lock"file, but since
245 * it requires the user deliberately starting two games at similar times, 280 * it requires the user deliberately starting two games at similar times,
246 * it's not too serious. We can't get O_EXCL in Node anyway. */ 281 * it's not too serious. We can't get O_EXCL in Node anyway. */
247 res.writeHead(200, {'Content-Type': 'text/plain'}); 282 res.writeHead(200, {'Content-Type': 'text/plain'});
248 res.write("l1\n" + nsession.sessid + "\n"); 283 var reply = {"t": "l", "id": nsession.sessid, "w": nsession.w, "h":
284 nsession.h};
285 res.write(JSON.stringify(reply));
249 res.end(); 286 res.end();
250 console.log("%s playing %s (key %s, pid %d)", username, gname, 287 console.log("%s playing %s (key %s, pid %d)", username, gname,
251 nsession.sessid, nsession.child.pid); 288 nsession.sessid, nsession.child.pid);
252 } 289 }
253 else { 290 else {
307 return; 344 return;
308 } 345 }
309 cterm.close(); 346 cterm.close();
310 var resheaders = {'Content-Type': 'text/plain'}; 347 var resheaders = {'Content-Type': 'text/plain'};
311 res.writeHead(200, resheaders); 348 res.writeHead(200, resheaders);
312 res.write("q1\n\n"); 349 res.write(JSON.stringify({"t": "q"}));
313 res.end(); 350 res.end();
314 return; 351 return;
315 } 352 }
316 353
317 function findTermSession(formdata) { 354 function findTermSession(formdata) {
355 if (typeof(formdata) != "object")
356 return null;
318 if ("id" in formdata) { 357 if ("id" in formdata) {
319 var sessid = formdata["id"][0]; 358 var sessid = formdata["id"];
320 if (sessid in sessions) { 359 if (sessid in sessions) {
321 return sessions[sessid]; 360 return sessions[sessid];
322 } 361 }
323 } 362 }
324 return null; 363 return null;
368 return; 407 return;
369 } 408 }
370 409
371 function readFeed(res, term) { 410 function readFeed(res, term) {
372 if (term) { 411 if (term) {
412 var reply = {};
373 var result = term.read(); 413 var result = term.read();
414 if (result == null) {
415 if (term.alive)
416 reply.t = "n";
417 else
418 reply.t = "q";
419 }
420 else {
421 reply.t = "d";
422 reply.n = term.nsend++;
423 reply.d = result.toString("hex");
424 }
374 res.writeHead(200, { "Content-Type": "text/plain" }); 425 res.writeHead(200, { "Content-Type": "text/plain" });
375 if (result == null) 426 res.write(JSON.stringify(reply));
376 resultstr = "";
377 else
378 resultstr = result.toString("hex");
379 if (result == null && !term.alive) {
380 /* Child has terminated and data is flushed. */
381 res.write("q1\n\n");
382 }
383 else
384 res.write("d" + resultstr.length.toString() + "\n" + resultstr + "\n");
385 res.end(); 427 res.end();
386 } 428 }
387 else { 429 else {
388 //console.log("Where's the term?");
389 sendError(res, 1, null); 430 sendError(res, 1, null);
390 } 431 }
391 } 432 }
392 433
393 var errorcodes = [ "Generic Error", "Not logged in", "Invalid data", 434 var errorcodes = [ "Generic Error", "Not logged in", "Invalid data",
394 "Login failed", "Already playing", "Game launch failed" ]; 435 "Login failed", "Already playing", "Game launch failed" ];
395 436
396 function sendError(res, ecode, msg) { 437 function sendError(res, ecode, msg) {
397 res.writeHead(200, { "Content-Type": "text/plain" }); 438 res.writeHead(200, { "Content-Type": "text/plain" });
398 if (ecode < errorcodes.length && ecode > 0) { 439 var edict = {"t": "E"};
399 var emsg = errorcodes[ecode]; 440 if (!(ecode < errorcodes.length && ecode > 0))
400 if (msg) 441 ecode = 0;
401 emsg += ": " + msg; 442 edict["c"] = ecode;
402 res.write("E" + ecode + '\n' + emsg + '\n'); 443 edict["s"] = errorcodes[ecode];
403 } 444 if (msg)
404 else 445 edict["s"] += ": " + msg;
405 res.write("E0\nGeneric Error\n"); 446 res.write(JSON.stringify(edict));
406 res.end(); 447 res.end();
407 } 448 }
408 449
409 function handler(req, res) { 450 function handler(req, res) {
410 /* default headers for the response */ 451 /* default headers for the response */
419 } 460 }
420 req.on('data', moredata); 461 req.on('data', moredata);
421 462
422 /* This will send the response once the whole request is here. */ 463 /* This will send the response once the whole request is here. */
423 function respond() { 464 function respond() {
424 formdata = getFormValues(reqbody); 465 //formdata = getFormValues(reqbody);
466 formdata = getMsg(reqbody);
425 var target = url.parse(req.url).pathname; 467 var target = url.parse(req.url).pathname;
426 var cterm = findTermSession(formdata); 468 var cterm = findTermSession(formdata);
427 /* First figure out if the client is POSTing to a command interface. */ 469 /* First figure out if the client is POSTing to a command interface. */
428 if (req.method == 'POST') { 470 if (req.method == 'POST') {
429 if (target == '/feed') { 471 if (target == '/feed') {
430 if (!cterm) { 472 if (!cterm) {
431 sendError(res, 1, null); 473 sendError(res, 1, null);
432 return; 474 return;
433 } 475 }
434 if ("quit" in formdata) { 476 if (formdata.t == "q") {
435 /* The client wants to terminate the process. */ 477 /* The client wants to terminate the process. */
436 logout(cterm, res); 478 logout(cterm, res);
437 } 479 }
438 else if (formdata["keys"]) { 480 else if (formdata.t == "d" && typeof(formdata.d) == "string") {
439 /* process the keys */ 481 /* process the keys */
440 hexstr = formdata["keys"][0].replace(/[^0-9a-f]/gi, ""); 482 hexstr = formdata.d.replace(/[^0-9a-f]/gi, "");
441 if (hexstr.length % 2 != 0) { 483 if (hexstr.length % 2 != 0) {
442 sendError(res, 2, "incomplete byte"); 484 sendError(res, 2, "incomplete byte");
443 return; 485 return;
444 } 486 }
445 keybuf = new Buffer(hexstr, "hex"); 487 keybuf = new Buffer(hexstr, "hex");
488 /* TODO OoO correction */
446 cterm.write(keybuf); 489 cterm.write(keybuf);
447 } 490 }
448 readFeed(res, cterm); 491 readFeed(res, cterm);
449 } 492 }
450 else if (target == "/login") { 493 else if (target == "/login") {