UNPKG

cloudq

Version:

Cloud Message Queue Server (using CouchDb) NewRelic enabled

225 lines (185 loc) 5.13 kB
var nr = require('newrelic'); var _ = require('underscore'); var moment = require('moment'); var express = require('express'); var log = require('./logger'); var TIMEOUT = process.env.TIMEOUT || 500; var SUCCESS = 200; var ERROR = 500; var conn = process.env.COUCH || 'http://localhost:5984'; // Basic Auth - for now, in v3 implement user/queue based auth var auth = require('./lib/auth')(process.env.TOKEN, process.env.SECRET, process.env.SECRET2); var agentkeepalive = require('agentkeepalive'); var myagent = new agentkeepalive({ maxSockets: 50, maxKeepAliveRequests: 0, maxKeepAliveTime: 30000 }); var nano = require('nano')(conn); var db = nano.use(process.env.DB || 'cloudq'); var app = express(); var workers = {}; // TODO: User API app.configure('development', function () { app.use(logger()); }); app.configure('production', function () { app.use(logger()); }); app.configure(function () { app.use(express.json()); app.use(app.router); app.use(express.static(__dirname + '/public')); }); // TODO: User API // Cloudq API // stats app.get('/stats', function (req, res) { db.view('queues', 'all', { group: true, reduce: true }, function (err, body) { if (err) { log.error(err); return res.send(500, err.message); } var stats = statify(body.rows); res.send(SUCCESS, stats); }); }); // publish job app.post('/:queue', auth, publish); app.put('/:queue', auth, publish); // consume job app.get('/:queue', auth, function (req, res) { db.view('queue', 'next', { startkey: [req.params.queue, 1], endkey: [req.params.queue, 100], limit: 1 }, function (err, body) { if (err) { log.error(err); return res.send(500, err); } // return 0 rows if (!body.rows.length) { // queue worker instead of returning response if (!workers[req.params.queue]) workers[req.params.queue] = []; workers[req.params.queue].push(res); function dequeueResponse () { workers[req.params.queue] = _(workers[req.params.queue]).without(res); } var responseTimeoutId = setTimeout(function () { log.info({req: req}, 'Queue request timeout'); dequeueResponse(); res.send(SUCCESS, {status: 'empty'}); }, TIMEOUT); res.once('close', function () { log.info({req: req}, 'Queue request terminated'); clearTimeout(responseTimeoutId); dequeueResponse(); }); return; } // have jobs so pass first one to resp worker... var doc = body.rows[0]; db.atomic('dequeue', 'id', doc.id, function (err) { if (err) { log.error(err); return res.send(500, err.message); } doc.value.id = doc.id; doc.value.ok = true; res.send(SUCCESS, doc.value); }); }); }); // delete job - set state to complete app.del('/:queue/:id', auth, function (req, res) { db.atomic('complete', 'id', req.params.id, function (err, body) { if (err) { log.error(err); return res.send(ERROR, err.message); } res.send({status: body}); }); }); module.exports = app; //app.listen(process.env.PORT || 3000); // lib function logger () { return function (req, res, next) { var _start = new Date(); function logRequest () { log.info({req: req, res: res}); log.info('Exec Time', (new Date()) - _start, 'ms'); } res.once('finish', logRequest); res.once('close', logRequest); next(); }; } function publish (req, res) { if (!req.body) { log.error('could not find body'); return res.send(ERROR, {error: 'must submit a job'}); } var o = req.body; if (!o.job) { log.error('could not find job'); return res.send(ERROR, {error: 'job not found!'}); } _.extend(o, { type: req.params.queue, state: 'published', publishedAt: new Date(), expires_in: moment().add('days', 2), priority: o.priority || 100 }); db.insert(o, function (err, body) { if (err) { log.error(err); return res.send(500, err); } res.send(SUCCESS, body); o._id = body.id; // could emit event for job added if changes queue doesn't work notify(o); }); } function statify (rows) { return _(rows).chain().filter(function (row) { return _(row.key).has('state'); }).map(function (row) { return { type: row.key.type, state: row.key.state, value: row.value }; }) .groupBy('type') .map(function (v, k) { var _value = {}; _(v).each(function (r) { _value[r.state] = r.value; }); return {key: k, value: _value}; }) .value(); } // if worker is listening - notify.. function notify (doc) { // find queue, find worker... if (_.isArray(workers[doc.type]) && !_.isEmpty(workers[doc.type])) { var wkr = workers[doc.type].shift(); // update doc as processing db.atomic('dequeue', 'id', doc._id, function (err, body) { if (err) { log.error(err); return wkr.send(ERROR, err); } var job = _.extend(doc.job, { id: doc._id, ok: true }); wkr.send(SUCCESS, job); }); } }