Mercurial > hg > rlgwebd
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") { |
