jetstream-microservice
Version:
Jetstream Node Package for easy implementation of Jetstream microservices
263 lines (222 loc) • 9.54 kB
JavaScript
var Guid = require('guid');
var amqp = require('amqplib/callback_api');
// Rabbit MQ flavoured events
if (global.queueEvents == undefined) global.queueEvents = [];
if (global.queueConnection == undefined) global.queueConnection = null;
if (global.queueChannel == undefined) global.queueChannel = null;
function pub(m, callback, error) {
var q = global.jetstreamConfig.name;
var msg = process.argv.slice(2).join(' ') || JSON.stringify(m);
global.queueChannel.publish(q, '', new Buffer(msg));
if (callback) callback();
}
module.exports = {
connect: function () {
var tryConnection = function () {
amqp.connect(global.jetstreamConfig.amqpHost, function (err, conn) {
if (err) {
global.dependencies.amqp = false;
console.log("AMQP connection failed, retrying in 5 seconds...");
setTimeout(function () {
tryConnection();
}, 5000)
} else {
global.dependencies.amqp = true;
global.queueConnection = conn;
conn.createChannel(function (err, ch) {
global.queueChannel = ch;
var wq = global.jetstreamConfig.name + ".queue";
ch.assertQueue(wq, { durable: true });
console.log(" [*] Waiting for Work messages in %s.", wq);
ch.consume(wq, function (msg) {
var e = JSON.parse(msg.content.toString());
global.queueEvents.forEach(function (event) {
if (event.name === e.name) {
if (!event.distribute) {
event.callback(e.payload);
} else {
pub(e);
}
}
})
}, { noAck: true });
var iq = global.jetstreamConfig.name + "." + Guid.raw().substring(0, 8);
ch.assertExchange(global.jetstreamConfig.name, 'fanout', { durable: false });
ch.assertQueue(iq, { exclusive: true }, function (err, q) {
console.log(" [*] Waiting for pub/sub messages in %s.", q.queue);
ch.bindQueue(q.queue, global.jetstreamConfig.name, '');
ch.consume(q.queue, function (msg) {
var e = JSON.parse(msg.content.toString());
global.queueEvents.forEach(function (event) {
if (event.name === e.name) {
event.callback(e.payload);
}
})
}, { noAck: true });
});
});
}
});
}
tryConnection();
},
on: function (event_name, callback, distribute) {
global.queueEvents.push({ name: event_name, callback: callback, distribute: distribute || false })
},
emit: function (event_name, payload, callback, error) {
var q = "service-registry.queue"
// put the message in to the correct format
var m = {
"trace_id": Guid.raw(),
"source": global.jetstreamConfig.name,
"name": event_name,
"timestamp": new Date(),
"payload": payload
}
var msg = process.argv.slice(2).join(' ') || JSON.stringify(m);
global.queueChannel.assertQueue(q, { durable: true });
global.queueChannel.sendToQueue(q, new Buffer(msg), { persistent: true });
if (callback) callback(m);
},
publish: function (event_name, payload, callback, error) {
// put the message in to the correct format
var m = {
"trace_id": Guid.raw(),
"source": global.jetstreamConfig.name,
"name": event_name,
"timestamp": new Date(),
"payload": payload
}
pub(m, callback, error);
}
}
var Guid = require('guid');
var errorHandler = function(err, req, res, next) {
var id = Guid.raw();
console.error('Something went wrong: ' + id);
var error = {
"code": id,
"type": "Unhandled exception",
"message": err,
"details": [req.url, req.body]
}
res.status(500).send(error)
}
module.exports = errorHandler;
var sign = require('../security');
module.exports = {
registerMe: function() {
var request = require("../request");
var postData = {
method: 'POST',
uri: jetstreamConfig.registry_url + "/registry",
body: {
name: jetstreamConfig.name,
location: jetstreamConfig.location,
documentation: jetstreamConfig.documentation,
health_endpoint: "/health"
},
json: true
}
console.log("Registering with Jetstream service registry...");
request(postData, function(err, resp, body) {
if (!err && resp.statusCode == 200) {
console.log("Registered with Jetstream service registry successfully, your service can now be accessed via " + jetstreamConfig.registry_url + "/services/" + jetstreamConfig.name);
global.dependencies.registry = true;
} else {
console.log(err);
console.log("Failed to register with Jetstream service registry, your service is not visible for Jetstream");
global.dependencies.registry = false;
}
});
// renew every 60 seconds
setInterval(function() {
request(postData, function(err, resp, body) {
if (!err && resp.statusCode == 200) {
console.log("Renewed registration with service registry");
global.dependencies.registry = true;
} else {
console.log(err);
console.log("Failed to renew registration with service registry");
global.dependencies.registry = false;
}
});
}, 60000)
}
}
var func = function (req, res) {
// here we need to check if there is a global dependency object
// if there is it should be an object of dependencies and statuses { dependency_one_name : boolean, dependency_two_name: boolean }
// if any of them are false then we should return a 503 (service unavailable)
var valid = true;
if (global.dependencies) {
Object.keys(dependencies).forEach(function (key) {
if (dependencies[key] === false) {
valid = false;
}
});
}
var result = {
"status": valid ? "ok" : "missing dependency",
"dependencies": dependencies
}
res.status(valid ? 200 : 503).send(result)
};
module.exports = func;
var jwt = require('jsonwebtoken');
module.exports = {
inbound: function(req, res, next) {
self = this;
// first check for a header
if (req.headers['x-jetstream-jwt']) {
// now we need to check the jwt is legit
var token = req.headers['x-jetstream-jwt'];
jwt.verify(token, jetstreamConfig.jwt_secret, { issuer: jetstreamConfig.registry_url, audience: jetstreamConfig.location }, function(err, decoded) {
if (decoded) {
next();
} else {
res.status(401).send("Unauthorized")
console.log("Error: " + err);
console.log("Token: " + token);
console.log("Expected registry: " + jetstreamConfig.registry_url);
console.log("Expected audience: " + jetstreamConfig.location);
}
});
} else {
res.status(401).send("Unauthorized")
console.log("Unauthorized: No header");
}
},
outbound: function() {
return jwt.sign({},
jetstreamConfig.jwt_secret, {
issuer: jetstreamConfig.location, // iss - issuer
audience: jetstreamConfig.registry_url, // aud - audience
expiresIn: 5, // exp - expires after
noTimestamp: false // iat - issued at
});
}
}
/*
***********************************************************************************************
Description: A wrapper for npm request which will add security headers to all outbound requests
Created by: David Dyke
Created on: 16th June 2017
***********************************************************************************************
*/
;
const request = require("request");
const security = require("../security");
var updateOptions = function (options) {
// check for headers object and create it if it doesn't exist
options.headers || (options.headers = []);
// add security
options.headers['x-jetstream-jwt'] = security.outbound();
// add a user-agent for easy source tracking
options.headers['User-Agent'] = jetstreamConfig.name;
return options;
};
const ret = function(options, next){
request(updateOptions(options), next);
};
module.exports = ret;