UNPKG

restbus

Version:

RESTful JSON API for the NextBus Inc. public XML feed.

196 lines (167 loc) 5.09 kB
var zlib = require('zlib'); var js4xml = require('xml2js').parseString; // @see https://retro.umoiq.com/xmlFeedDocs/NextBusXMLFeed.pdf var options = { host: 'retro.umoiq.com', hostname: 'retro.umoiq.com', headers: { 'accept-encoding': 'gzip,deflate' } }; var utils = {}; utils.c = { NEXTBUS_XMLFEED: '/service/publicXMLFeed', MTJSON: 'application/json', RTE: 'route', VEH: 'vehicle', PRED: 'prediction', AGY: 'agency' }; utils.errors = (function () { var error = {}; return { get: function (status, msg) { switch (parseInt(status, 10)) { case 400: this.set(400, 'Bad Request', msg); break; case 404: this.set(404, 'Not Found', msg); break; case 405: this.set(405, 'Method Not Allowed', msg); break; case 500: this.set(500, 'Internal Server Error', msg); break; case 503: this.set(503, 'Service Unavailable', msg); break; default: this.set( 500, 'Internal Server Error', 'The server encountered an unexpected condition which prevented it from fulfilling the request.' ); break; } return error; }, set: function (status, desc, msg) { error.status = status; error.statusDesc = desc; error.message = msg || 'HTTP Error.'; } }; })(); utils.getJsFromXml = function (res, callback) { var encoding = res.headers['content-encoding'], stream = res, xml = ''; if (encoding === 'gzip' || encoding === 'deflate') { // Set the stream to zlib.Unzip object to handle both encodings. stream = zlib.createUnzip(); // Pipe the compressed response from NextBus to the unzip stream. res.pipe(stream); } stream.on('data', function (d) { if (d) xml += d.toString(); }); stream.on('end', function () { try { js4xml(xml, function (err, js) { if (err) callback(err, { errtype: 'parse' }); else if (!js || typeof js.body === 'undefined') { console.error(JSON.stringify(js)); callback( { message: 'No "body" element in NextBus XML document' }, { errtype: 'parse' } ); } else callback(null, js); }); } catch (err) { callback(err, { errtype: 'parse' }); } }); stream.on('error', function (e) { console.error(e.stack); callback(e, { errtype: 'stream' }); }); }; utils.getOptionsWithPath = function (path) { options.path = path; return options; }; utils.nbRequestError = function (e, res) { var host = options.hostname; console.error(e.stack); res .status(500) .json( this.errors.get(500, 'Unable to fulfill request to ' + host + '. ' + e.message) ); }; utils.streamOrParseError = function (e, js, res) { if (js.errtype === 'parse') { res .status(500) .json(this.errors.get(500, 'Unable to parse XML into JavaScript. ' + e.message)); } else { res .status(500) .json(this.errors.get(500, 'An unexpected stream error has occured. ' + e.message)); } }; /** * Sends appropriate HTTP error response for a NextBus XML error. This is usually a 404, but if * the XML Error element's shouldRetry attribute == true, then its a 503 error. * * Currently deals with all the inconsistent ways NextBus embeds error messages * into the XML content (instead of with HTTP headers, etc.). Also trys to * replace query string terminology with RESTful jargon. * * TODO: Find a more efficient/sophisticated way to do string replacement with regex. * * @param {Object:e} The NextBus XML error parsed from xml2js into a JS object. * @param {Object:res} The HTTP response stream (http.ServerResponse). */ utils.nbXmlError = function (e, res) { var emsg = e._; if (e.$.shouldRetry === 'false') { emsg = emsg .replace('a=', '') .replace('parameter', 'id') .replace('tag', 'id') .replace('tags', 'ids'); emsg = emsg .replace('agency=', 'agency id ') .replace('s=', '') .replace('route r=', 'route ') .replace('r=', 'route '); emsg = emsg.replace('stopId=', 'stop code id ').replace('vehicle=', 'vehicle id '); res.status(404).json(this.errors.get(404, emsg)); } else { res.status(503).json(this.errors.get(503, emsg)); } }; utils.addOptionalSlash = function (str) { var s = str; if (str.substr(-1) !== '/') s += '/'; return s; }; utils.removeOptionalSlash = function (str) { var s = str; if (str.substr(-1) === '/') s = str.substr(0, str.length - 1); return s; }; utils.getOpenPath = function (req) { var host = req.get('host'), path = [req.protocol, '://', host, req.originalUrl].join(''); if (path.substr(-1) === '/') path = path.substr(0, path.length - 1); return path; }; utils.getClosedPath = function (req) { var host = req.get('host'), path = [req.protocol, '://', host, req.originalUrl].join(''); if (path.substr(-1) !== '/') path += '/'; return path; }; module.exports = utils;