iamqp
Version:
RabbitMQ wrapper for NodeJS with several produce(reply)/consume scenarios
452 lines (389 loc) • 22.4 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Home - Documentation</title>
<script src="scripts/prettify/prettify.js"></script>
<script src="scripts/prettify/lang-css.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<nav>
<li class="nav-link nav-home-link"><a href="index.html">Home</a></li><li class="nav-heading">Classes</li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Common.html">Common</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Common.html#_commonPublishPreparations">_commonPublishPreparations</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Common.html#_emitAndDispatchError">_emitAndDispatchError</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Common.html#_logData">_logData</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Common.html#_messageDispatcherWrapper">_messageDispatcherWrapper</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Common.html#_parseConsumedMessage">_parseConsumedMessage</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Common.html#_preparePublishingMessage">_preparePublishingMessage</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Common.html#closeConnection">closeConnection</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Common.html#getEventer">getEventer</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Common.html#isReady">isReady</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Common.html#openConnection">openConnection</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Common.html#toString">toString</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="ConsoleLogger.html">ConsoleLogger</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="ConsoleLogger.html#error">error</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="ConsoleLogger.html#info">info</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="ConsoleLogger.html#log">log</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="ConsoleLogger.html#warn">warn</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="FanoutConsumer.html">FanoutConsumer</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="FanoutProducer.html">FanoutProducer</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="FanoutProducer.html#fanout">fanout</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="PlainConsumer.html">PlainConsumer</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="PlainProducer.html">PlainProducer</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="PlainProducer.html#publish">publish</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="RpcClient.html">RpcClient</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="RpcClient.html#callRemote">callRemote</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="RpcServer.html">RpcServer</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="RpcServer.html#respond">respond</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="UUIDv4.html">UUIDv4</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="UUIDv4.html#generate">generate</a></span></li><li class="nav-heading"><a href="global.html">Globals</a></li>
</nav>
<div id="main">
<section class="readme">
<article><h1>iamqp.js</h1>
<p><img src="https://snyk.io/test/npm/iamqp/badge.svg" alt="Known Vulnerabilities">
<img src="https://img.shields.io/bitbucket/pipelines/larsonvonh/iamqp.js.svg" alt="Build"></p>
<p>A simple AMQP (Advanced Message Queuing Protocol) wrapper written based on <a href="https://www.npmjs.com/package/amqplib">amqplib</a>.
Has several AMQP flavors (wrappers):</p>
<ul>
<li>basic producer with the class <code>PlainPublisher</code></li>
<li>basic consumer with the class <code>PlainConsumer</code></li>
<li>fanout exchange producer with the class <code>FanoutProducer</code></li>
<li>fanout exchange consumer with the class <code>FanoutConsumer</code></li>
<li><a href="http://www.rabbitmq.com/tutorials/tutorial-six-python.html">RPC</a> client with the class <code>RPCClient</code></li>
<li><a href="http://www.rabbitmq.com/tutorials/tutorial-six-python.html">RPC</a> server with the class <code>RPCServer</code></li>
</ul>
<p>Example usage of these is (fast) communication between:</p>
<ul>
<li>processes</li>
<li>instances in distributed systems</li>
<li>web, worker communication</li>
</ul>
<p>Each flavour has two sides; producer and a consumer.
Producer (worker) publishes/distributes data/information to consumers.
Special flavour are RPC instances where a client/server relation exists.</p>
<h2>Prerequisites</h2>
<p>A guide to installing <a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-manage-rabbitmq">RabbitMQ</a>
and some introduction:</p>
<ul>
<li><a href="http://www.rabbitmq.com/getstarted.html">rabbitmq.com</a></li>
<li><a href="https://github.com/squaremo/amqp.node/tree/master/examples/tutorials">node tutorials</a></li>
</ul>
<p>Some AMQP service providers:</p>
<ul>
<li><a href="https://www.cloudamqp.com/">cloud AMQP</a></li>
<li><a href="http://www.iron.io/mq">iron MQ</a></li>
</ul>
<h2>WoW</h2>
<p>Install with:</p>
<pre class="prettyprint source"><code>npm install iamqp --save
</code></pre>
<pre class="prettyprint source"><code>yarn add iamqp
</code></pre>
<h3>Common</h3>
<p>All instances have:</p>
<ul>
<li><code>openConnection()</code> as to establish the connection to the RabbitMQ instance</li>
<li><code>closeConnection()</code> as to close the established connection</li>
<li><code>isReady()</code> as to get the info if the connection is ready</li>
<li><code>getEventer()</code> as to get an instance of a <a href="https://nodejs.org/api/events.html#events_class_eventemitter">Event Emitter</a>
that the code uses to signal messages and information.</li>
</ul>
<p>Several things can be listened to on the eventer that are common to all instances:</p>
<ul>
<li><code>error</code> - you listen on this one when an error occurs - <strong>if you don't, the error bubbles and crashes your execution</strong>:</li>
</ul>
<pre class="prettyprint source lang-javascript"><code>producer.getEventer().on('error', (err) => {
console.log('ERROR ' + err.message);
});
</code></pre>
<ul>
<li><code>connected</code> - you can listen for this one as to know when the instance connects to the server:</li>
</ul>
<pre class="prettyprint source lang-javascript"><code>producer.getEventer().on('connected', () => {
console.log('... connected');
});
</code></pre>
<ul>
<li><code>closed</code> - you can listen for this one as to know if the connection closes:</li>
</ul>
<pre class="prettyprint source lang-javascript"><code>producer.getEventer().on('closed', () => {
console.log('... closed');
});
</code></pre>
<p>Individual instances then have their own events for data manipulation.
However, take care, as it might happen that you set to <a href="https://nodejs.org/api/events.html#events_emitter_setmaxlisteners_n">many listeners</a>
on a given event which can be a leak.</p>
<p>The connection needs to be established via the mentioned <code>openConnection()</code> method but there is an automatic reconnect
trigger if the connection fails. This trigger is however not triggered if you call the mentioned <code>closeConnection()</code>
method.</p>
<h4>Init/configuration</h4>
<p>All instances require at least two parameters on initialization:</p>
<ul>
<li>URI of the AMQP</li>
<li>name of the channel</li>
</ul>
<p>The same URI and channel name is to be set on both instances that are communicating:</p>
<ul>
<li><code>PlainConsumer</code> <-> <code>PlainProducer</code></li>
<li><code>FanoutConsumer</code> <-> <code>FanoutProducer</code></li>
<li><code>RPCClient</code> <-> <code>RPCServer</code></li>
</ul>
<pre class="prettyprint source lang-javascript"><code>let instance = new iamqp['...'](amqpUri, channelName);
</code></pre>
<p>All instances accept an optional third argument which configures several things:</p>
<ul>
<li><code>printMessages</code> - if true, the messages will be printed when the logging is enabled. Defaults to <code>true</code>.</li>
<li><code>maxMessageLengthToPrint</code> - f the messages are to printed, this is the max
number of bytes that a message can be to be printed. Defaults to <code>257</code>.</li>
<li><code>queueArguments</code> - is to configure pairs:
<ul>
<li><code>PlainConsumer</code> <-> <code>PlainProducer</code></li>
<li><code>RPCClient</code> <-> <code>RPCServer</code></li>
</ul>
</li>
<li><code>connectionOptions</code></li>
</ul>
<h5>QueueArguments</h5>
<p><code>queueArguments</code> are related to the configuration of the Queue (over which they are communicating):</p>
<ul>
<li><code>messageTtl</code> - <code>0 <= n < 2^32</code> expires messages arriving in the queue after n milliseconds</li>
<li><code>expires</code> - <code>0 < n < 2^32</code> the queue will be destroyed after n milliseconds of disuse,
where use means having consumers, being declared</li>
<li><code>maxLength</code> - sets a maximum number of messages the queue will hold</li>
</ul>
<p>Default options for all <code>queueArguments</code> are to be considered to be infinity.</p>
<p>When setting any of <code>queueArguments</code> on one instance the same value is to be set on the sister instance.
This is so because they both try to assert an Queue on the same channel and if one has already asserted the Queue,
the other will fail if it provides different options.</p>
<p><strong>Note on this</strong></p>
<p>In general, if your Queues are persistent, before changing any of the options related to them,
you would need to manually delete to Queue in question as to allow the code to re-assert it.</p>
<h5>ConnectionOptions</h5>
<p>Configuration of the connection:</p>
<ul>
<li><code>heartbeat</code> - period of the connection heartbeat, in seconds. Defaults to <code>60</code>.</li>
</ul>
<h5>Configuration options</h5>
<pre class="prettyprint source lang-javascript"><code>const iamqp = require('iamqp');
const instance = new iamqp['...']('...', '...', {
printMessages: true,
maxMessageLengthToPrint: 512,
queueArguments: {
maxLength: 100, // max number of queue entries
messageTtl: 10000, // message TTL
expires: 60000 // remove queue after dis-use
},
connectionOptions: {
heartbeat: 60
}
});
</code></pre>
<h3>Plain</h3>
<p>Plain producer/consumer pair is when we need one or multiple producers
(on the same channel) publishing to one consumer.
Example could be multiple web instances that periodically publish some data to a central aggregator.</p>
<p>For detailed documentation check the classes:</p>
<ul>
<li><code>PlainConsumer</code></li>
<li><code>PlainProducer</code></li>
</ul>
<h4>Plain Consumer</h4>
<pre class="prettyprint source lang-javascript"><code>const iamqp = require('iamqp');
const amqpUri = 'amqp://localhost';
const channel = 'amqp-channel-b';
let plainConsumer = new iamqp.PlainConsumer(amqpUri, channel);
// This needs to be done or the errors will bubble up.
plainConsumer.getEventer().on('error', (err) => {
console.log('consumer error:' + err.message);
});
// For when the connection is established.
plainConsumer.getEventer().on('connected', () => {
console.log('consumer connected');
});
// Get messages.
plainConsumer.getEventer().on('message', (message) => {
console.log('consumer message:' + message);
});
// Open the connection - this takes some time and is not sync
plainConsumer.openConnection();
// ...
// Close the connection
if (plainConsumer.closeConnection()) {
console.log('consumer connection closing ...');
}
</code></pre>
<h4>Plain Producer</h4>
<pre class="prettyprint source lang-javascript"><code>const iamqp = require('iamqp');
const amqpUri = 'amqp://localhost';
const channel = 'amqp-channel-b';
let plainProducer = new iamqp.PlainProducer(amqpUri, channel);
// This needs to be done or the errors will bubble up.
plainProducer.getEventer().on('error', (err) => {
console.log('producer error:' + err.message);
});
// For when the connection is established.
plainProducer.getEventer().on('connected', () => {
console.log('producer connected');
});
// Open the connection - this takes some time and is not sync
plainProducer.openConnection();
// Publish a single message.
let isPublishing = plainProducer.publish({
'a': 3
});
// Close the connection
if (plainProducer.closeConnection()) {
console.log('producer connection closing ...');
}
</code></pre>
<h3>Fanout</h3>
<p>Fanout is when each producer (there can be many on the same channel) publishes a
message that gets "faned-out" to all the fanout consumer on the same channel.
Every consumer on the channel gets a copy of the message.</p>
<p>Multiple producers thus publish their messages to multiple consumers.
Example could be a worker instance (or many) that publishes information to many web instances.</p>
<p>For detailed documentation check the classes:</p>
<ul>
<li><code>FanoutConsumer</code></li>
<li><code>FanoutProducer</code></li>
</ul>
<h4>Fanout Consumer</h4>
<pre class="prettyprint source lang-javascript"><code>const iamqp = require('iamqp');
const amqpUri = 'amqp://localhost';
const fanoutChannel = 'fanout-channel-a';
let fanoutConsumer = new iamqp.FanoutConsumer(amqpUri, fanoutChannel);
// This needs to be done or the errors will bubble up.
fanoutConsumer.getEventer().on('error', (err) => {
console.log('fanout consumer error:' + err.message);
});
// For when the connection is established.
fanoutConsumer.getEventer().on('connected', () => {
console.log('fanout consumer connected');
});
// Get messages as fanout by the producer.
fanoutConsumer.getEventer().on('message', (message) => {
console.log('fanout consumer message:' + message);
});
// Open the connection - this takes some time and is not sync
fanoutConsumer.openConnection();
// ...
// Close the connection
if (fanoutConsumer.closeConnection()) {
console.log('fanout consumer connection closing ...');
}
</code></pre>
<h4>Fanout Producer</h4>
<pre class="prettyprint source lang-javascript"><code>const iamqp = require('iamqp');
const amqpUri = 'amqp://localhost';
const fanoutChannel = 'fanout-channel-a';
let fanoutProducer = new iamqp.FanoutProducer(amqpUri, fanoutChannel);
// This needs to be done or the errors will bubble up.
fanoutProducer.getEventer().on('error', (err) => {
console.log('fanout producer error:' + err.message);
});
// For when the connection is established.
fanoutProducer.getEventer().on('connected', () => {
console.log('fanout producer connected');
});
// Open the connection - this takes some time and is not sync
fanoutProducer.openConnection();
// ...
// Fanout a single message.
let isFanout = fanoutProducer.fanout({
'a': 3
});
// ...
// Close the connection
if (fanoutProducer.closeConnection()) {
console.log('fanout producer connection closing ...');
}
</code></pre>
<h3>RPC</h3>
<p><em>RemoteProcedureCall</em> is when communication of type "ask -> reply" happens.
The RPC client calls the RPC server which (after some time) replies to the message.
Each call (on each client) produces an unique ID string called <code>correlation ID</code> that the user also gets on a reply as to be able to
distinguish what reply is for which question.</p>
<p>Multiple RPC client can thus perform multiple calls to the server.
An example of this usage is if the server queries a DB on behalf of the clients.
One must take care as to not overload the server instance.</p>
<p>An example; create an RPC client:</p>
<pre class="prettyprint source lang-javascript"><code>const iamqp = require('iamqp');
const amqpUri = 'amqp://localhost';
const rpcChannel = 'rpc-channel-a';
let rpcClient = new iamqp.RPCClient(amqpUri, rpcChannel);
</code></pre>
<p>... setup listeners:</p>
<pre class="prettyprint source lang-javascript"><code>rpcClient.getEventer().on('error', (err) => {
console.log('RPC client error:' + err.message);
});
rpcClient.getEventer().on('connected', () => {
console.log('RPC client connected');
});
rpcClient.getEventer().on('reply', (reply, correlationId) => {
console.log('RPC client got a reply (' + correlationId + '): ' + reply);
});
rpcClient.openConnection();
</code></pre>
<p>Each reply gives the <code>correlation ID</code> - the same one that we get when making a call.
Each call produces it's own unique <code>correlation ID</code>.</p>
<pre class="prettyprint source lang-javascript"><code>let correlationIdA = rpcClient.callRemote({'a': 5});
</code></pre>
<p>On the RPC server side we then have:</p>
<pre class="prettyprint source lang-javascript"><code>const iamqp = require('iamqp');
const amqpUri = 'amqp://localhost';
const rpcChannel = 'rpc-channel-a';
let rpcServer = new iamqp.RPCServer(amqpUri, rpcChannel);
</code></pre>
<p>... setup listeners:</p>
<pre class="prettyprint source lang-javascript"><code>rpcServer.getEventer().on('error', (err) => {
console.log('RPC server error:' + err.message);
});
rpcServer.getEventer().on('connected', () => {
console.log('RPC server connected');
});
rpcServer.getEventer().on('call', (callData, pCorrelationId) => {
console.log('RPC server call (' + pCorrelationId + '): ' + callData);
callData.a += 1;
let isServerResponding = rpcServer.respond(callData, pCorrelationId);
});
rpcServer.openConnection();
</code></pre>
<p>For detailed documentation check the classes:</p>
<ul>
<li><code>RPCClient</code></li>
<li><code>RPCServer</code></li>
</ul>
<h2>Logging</h2>
<p>The module has the capability to log to:</p>
<ul>
<li>the terminal</li>
<li>the Linux system log</li>
</ul>
<h3>Terminal</h3>
<p>The terminal logging is to be enabled with a environmental variable:</p>
<pre class="prettyprint source"><code>DBTRC_TERM_F=OK
</code></pre>
<h3>Syslog</h3>
<p>The Linux system log logging is to be enabled with a environmental variable:</p>
<pre class="prettyprint source"><code>DBTRC_SYSLOG_F=OK
</code></pre>
<p>To enable this, open <code>/etc/rsyslog.conf</code> and enable <code>UDP</code> on port <code>514</code>.
This is usually just commented and needs uncommenting:</p>
<pre class="prettyprint source"><code># provides UDP syslog reception
module(load="imudp")
input(type="imudp" port="514")
</code></pre>
<p>... and restart the service with <code>./etc/init.d/rsyslog restart</code>.
The logs should appear in <code>/var/log/syslog</code>.
This was tested on <code>Ubuntu 16.04</code>.</p>
<h2>Documentation</h2>
<p>JSDoc generated documentation is in <code>artifacts/docs/index.html</code>.</p>
<h2>Source</h2>
<p>The repository is located on <a href="https://bitbucket.org/larsonvonh/iamqp.js">bitbucket</a>.</p>
<h2>Test</h2>
<p>Snyk test badge included; report available on <a href="https://snyk.io/test/npm/iamqp">snyk</a></p>
<p>Integration tests:</p>
<pre class="prettyprint source lang-bash"><code>yarn run test
</code></pre>
<pre class="prettyprint source lang-bash"><code>DBTRC_TERM_F=OK yarn run test
</code></pre>
<p>Lint testing via:</p>
<pre class="prettyprint source lang-bash"><code>yarn run test:lint
</code></pre>
<h2>License</h2>
<p>The MIT License (MIT) (look for the LICENSE file in the root of the module).</p></article>
</section>
</div>
<br class="clear">
<footer>
Generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.2</a> on Sun Aug 04 2019 22:10:47 GMT+0200 (Central European Summer Time) using the Minami theme.
</footer>
<script>prettyPrint();</script>
<script src="scripts/linenumber.js"></script>
</body>
</html>