/* * Storoid worker. * * Configure in storoid.config.json. */ // global includes var express = require('express'), cluster = require('cluster'), http = require('http'), fs = require('fs'), child_process = require('child_process'), request = require('request'), querystring = require('querystring'); var config; // Get the config try { config = JSON.parse(fs.readFileSync('./mathoid.config.json', 'utf8')); } catch ( e ) { // Build a skeleton localSettings to prevent errors later. console.error("Please set up your mathoid.config.json from the example " + "storoid.config.json.example"); process.exit(1); } /** * The name of this instance. * @property {string} */ var instanceName = cluster.isWorker ? 'worker(' + process.pid + ')' : 'master'; console.log( ' - ' + instanceName + ' loading...' ); /* * Backend setup */ var restarts = 10; var backend, backendStarting = false, backendPort, requestQueue = []; // forward declaration var handleRequests; var backendCB = function () { backendStarting = false; handleRequests(); }; var startBackend = function (cb) { if (backend) { backend.removeAllListeners(); backend.kill('SIGKILL'); } backendPort = Math.floor(9000 + Math.random() * 50000); console.error(instanceName + ': Starting backend on port ' + backendPort); backend = child_process.spawn('phantomjs', ['main.js', backendPort]); backend.stdout.pipe(process.stderr); backend.stderr.pipe(process.stderr); backend.on('close', startBackend); backendStarting = true; // give backend 1 seconds to start up setTimeout(backendCB, 1000); }; startBackend(); /* -------------------- Web service --------------------- */ var app = express.createServer(); // Increase the form field size limit from the 2M default. app.use(express.bodyParser({maxFieldsSize: 25 * 1024 * 1024})); app.use( express.limit( '25mb' ) ); app.get('/', function(req, res){ res.write('\n'); res.write('Welcome to Mathoid. POST to / with var tex'); res.write('
'); res.end(''); }); // robots.txt: no indexing. app.get(/^\/robots.txt$/, function ( req, res ) { res.end( "User-agent: *\nDisallow: /\n" ); }); function handleRequest(req, res, tex) { // do the backend request var reqbody = new Buffer(querystring.stringify({tex: tex})), options = { method: 'POST', uri: 'http://localhost:' + backendPort.toString() + '/', body: reqbody, // Work around https://github.com/ariya/phantomjs/issues/11421 by // setting explicit upper-case headers (request sends them lowercase // by default) and manually encoding the body. headers: { 'Content-Length': reqbody.length, 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: 2000 }; request(options, function (err, response, body) { body = new Buffer(body); if (err || response.statusCode !== 200) { var errBuf; if (err) { errBuf = new Buffer(JSON.stringify({ tex: tex, log: err.toString(), success: false })); } else { errBuf = body; } res.writeHead(500, { 'Content-Type': 'application/json', 'Content-Length': errBuf.length }); res.end(errBuf); // don't retry the request requestQueue.shift(); startBackend(); return handleRequests(); } res.writeHead(200, { 'Content-Type': 'application/json', 'Content-length': body.length }); res.end(body); requestQueue.shift(); handleRequests(); }); } handleRequests = function() { // Call the next request on the queue if (!backendStarting && requestQueue.length) { requestQueue[0](); } }; app.post(/^\/$/, function ( req, res ) { // First some rudimentary input validation if (!req.body.tex) { res.writeHead(400); return res.end(JSON.stringify({error: "'tex' post parameter is missing!"})); } var tex = req.body.tex; requestQueue.push(handleRequest.bind(null, req, res, tex)); // phantomjs only handles one request at a time. Enforce this. if (requestQueue.length === 1) { // Start this process handleRequests(); } }); console.log( ' - ' + instanceName + ' ready' ); module.exports = app;