Skip to content

Instantly share code, notes, and snippets.

@kokobd
Created February 23, 2018 14:12
Show Gist options
  • Save kokobd/6a64b0f01e19ebadfb21a40d81ad4d88 to your computer and use it in GitHub Desktop.
Save kokobd/6a64b0f01e19ebadfb21a40d81ad4d88 to your computer and use it in GitHub Desktop.
Custom irunner.js that enables static file serving for GHCJSi
/*
GHCJSi communication
reads messages from stdin, sends over stderr
*/
var h$GHCJSiRecord = // true ||
!!process.env['GHCJS_RECORD_GHCJSI'];
var h$GHCJSiPort = process.env['GHCJSI_PORT'] || 6400;
/* An optional static directory to serve at root */
var h$GHCJSiStaticDir = process.env['GHCJSI_STATIC_DIR'];
var h$GHCJSiReplay = process.argv.length > 0 &&
process.argv[process.argv.length-1] === 'replay';
var h$GHCJSi = { data: null
, loadedSymbols: {}
, current: null
, sendMessage: h$sendMessage
, done: h$GHCJSiDone
, clientHtml: ''
, clientJS: ''
, socket: null
};
global.h$GHCJSi = h$GHCJSi;
global.require = require;
global.module = module;
var fs = require('fs');
var server = require('http').createServer(h$handleHTTP);
var url = require('url');
var io = null;
try {
io = require('socket.io')(server);
} catch (e) { }
if (h$GHCJSiStaticDir) {
try {
/* Try to set up a static file server for GHCJSi */
var finalhandler = require('finalhandler');
var serveStatic = require('serve-static');
var serveStaticDir = serveStatic(h$GHCJSiStaticDir);
} catch (e) {
console.log("\ncan't set up static file server for " + h$GHCJSiStaticDir);
console.log("\nmake sure you have finalhandler and serve-static installed:");
console.log("\n\n npm install finalhandler serve-static\n");
}
}
// start listening
function h$initGHCJSi() {
process.stdin.setEncoding('utf8');
process.stderr.setEncoding('binary');
process.on('uncaughtException', function(err) {
console.log(err);
console.log(err.stack);
});
if(h$GHCJSiReplay) {
h$replayMessage(1);
} else {
h$startHTTPServer();
process.stdin.on('readable', function() {
while(true) {
var str = process.stdin.read();
if(str) {
var buf = new Buffer(str, 'hex');
h$GHCJSi.data = h$GHCJSi.data ? Buffer.concat([h$GHCJSi.data, buf]) : buf;
h$processInput();
} else {
return;
}
}
});
process.stdin.on('close', function() { process.exit(0); });
}
}
function h$replayMessage(n) {
try {
var buffer = fs.readFileSync("ghcjsimessage." + n + ".dat");
var msgType = buffer.readUInt32BE(0);
h$processMessage(msgType, buffer.slice(4));
setTimeout(function() { h$replayMessage(n+1); }, 1500);
} catch(e) { }
}
function h$startHTTPServer() {
if(!io) {
console.log("\nsocket.io not found, browser session not available");
return;
} else {
console.log("\nsocket.io found, browser session available at http://localhost:" + h$GHCJSiPort);
if (typeof serveStaticDir !== 'undefined') {
console.log("\nserving static files from " + h$GHCJSiStaticDir);
}
}
io.on('connection', function(socket) {
console.log("\nbrowser connected, code runs in browser from now on");
h$GHCJSi.socket = io;
socket.on('msg', function(msg) {
h$GHCJSi.sendMessage(msg.type, msg.payload);
});
socket.on('out', function(data) {
process.stdout.write(data);
});
socket.on('disconnect', function() {
// console.log('browser disconnected');
});
});
h$GHCJSi.clientHtml = fs.readFileSync(h$GHCJSiStaticDir + '/ghcjsiClient.html');
h$GHCJSi.clientJs = fs.readFileSync(__dirname + '/ghcjsiClient.js');
server.listen(h$GHCJSiPort);
}
function h$handleHTTP(req, res) {
var u = url.parse(req.url);
if(u.pathname === '/' || u.pathname === '/index.html') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(h$GHCJSi.clientHtml);
} else if(u.pathname === '/client.js') {
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end(h$GHCJSi.clientJs);
} else if (typeof serveStaticDir !== 'undefined') {
serveStaticDir(req, res, finalhandler(req, res));
} else {
res.statusCode = 404;
res.statusMessage = 'not found';
res.end();
}
}
var h$GHCJSiMessageN = 0;
function h$processInput() {
while(h$GHCJSi.data && h$GHCJSi.data.length >= 8) {
var msgLength = h$GHCJSi.data.readUInt32BE(0);
var msgType = h$GHCJSi.data.readUInt32BE(4);
if(h$GHCJSi.data.length >= msgLength + 8) {
if(h$GHCJSiRecord && !h$GHCJSiReplay) {
fs.writeFileSync("ghcjsimessage." + (++h$GHCJSiMessageN) + ".dat"
,h$GHCJSi.data.slice(4, msgLength+8));
}
var msgPayload = h$GHCJSi.data.slice(8, msgLength + 8);
h$GHCJSi.data = h$GHCJSi.data.slice(msgLength + 8);
if(h$GHCJSi.socket) {
h$GHCJSi.socket.emit('msg', { type: msgType, payload: msgPayload });
} else {
h$processMessage(msgType, msgPayload);
}
} else return;
}
}
function h$processMessage(msgType, msgPayload) {
switch(msgType) {
case 0: // load initial code/rts and init
h$loadInitialCode(msgPayload.toString('utf8'));
h$sendMessage(0);
break;
case 1: // load code
h$loadCodeStr(msgPayload.toString('utf8'));
h$sendMessage(0);
break;
case 2: // run action
var symb = msgPayload.toString('utf8');
h$GHCJSi.current = h$main(h$GHCJSi.loadedSymbols[msgPayload.toString('utf8')]);
break;
case 3: // abort
if(h$GHCJSi.current)
h$killThread( h$GHCJSi.current
, h$baseZCControlziExceptionziBasezinonTermination);
break;
default:
throw new Error("unknown message type: " + msgType);
}
}
function h$GHCJSiDone(thread) {
h$sendMessage(0);
h$GHCJSi.current = null;
}
function h$sendMessage(msgType, msg, c) {
var hdr = new Buffer(8);
hdr.writeUInt32BE(msg ? msg.length : 0, 0);
hdr.writeUInt32BE(msgType, 4);
process.stderr.write( msg ? Buffer.concat([hdr, msg]) : hdr, 'binary'
, function() { if(c) c(); });
}
// load the RTS and set up the standard streams
function h$loadInitialCode(code) {
h$loadCodeStr(code, true);
// don't allow Haskell to read from stdin (fixme!)
h$base_stdin_fd.read = function(fd, fdo, buf, buf_offset, n, c) { c(0); }
// redirect Haskell's stderr to stdout since we use stderr to communicate (fixme!)
h$base_stderr_fd.write = h$base_stdout_fd.write;
}
function h$loadCodeStr(str) {
eval.call(null, str);
}
h$initGHCJSi();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment