primer cambio
This commit is contained in:
314
node_modules/amqplib/lib/api_args.js
generated
vendored
Normal file
314
node_modules/amqplib/lib/api_args.js
generated
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
The channel (promise) and callback APIs have similar signatures, and
|
||||
in particular, both need AMQP fields prepared from the same arguments
|
||||
and options. The arguments marshalling is done here. Each of the
|
||||
procedures below takes arguments and options (the latter in an object)
|
||||
particular to the operation it represents, and returns an object with
|
||||
fields for handing to the encoder.
|
||||
*/
|
||||
|
||||
// A number of AMQP methods have a table-typed field called
|
||||
// `arguments`, that is intended to carry extension-specific
|
||||
// values. RabbitMQ uses this in a number of places; e.g., to specify
|
||||
// an 'alternate exchange'.
|
||||
//
|
||||
// Many of the methods in this API have an `options` argument, from
|
||||
// which I take both values that have a default in AMQP (e.g.,
|
||||
// autoDelete in QueueDeclare) *and* values that are specific to
|
||||
// RabbitMQ (e.g., 'alternate-exchange'), which would normally be
|
||||
// supplied in `arguments`. So that extensions I don't support yet can
|
||||
// be used, I include `arguments` itself among the options.
|
||||
//
|
||||
// The upshot of this is that I often need to prepare an `arguments`
|
||||
// value that has any values passed in `options.arguments` as well as
|
||||
// any I've promoted to being options themselves. Since I don't want
|
||||
// to mutate anything passed in, the general pattern is to create a
|
||||
// fresh object with the `arguments` value given as its prototype; all
|
||||
// fields in the supplied value will be serialised, as well as any I
|
||||
// set on the fresh object. What I don't want to do, however, is set a
|
||||
// field to undefined by copying possibly missing field values,
|
||||
// because that will mask a value in the prototype.
|
||||
//
|
||||
// NB the `arguments` field already has a default value of `{}`, so
|
||||
// there's no need to explicitly default it unless I'm setting values.
|
||||
function setIfDefined(obj, prop, value) {
|
||||
if (value != undefined) obj[prop] = value;
|
||||
}
|
||||
|
||||
var EMPTY_OPTIONS = Object.freeze({});
|
||||
|
||||
var Args = {};
|
||||
|
||||
Args.assertQueue = function(queue, options) {
|
||||
queue = queue || '';
|
||||
options = options || EMPTY_OPTIONS;
|
||||
|
||||
var argt = Object.create(options.arguments || null);
|
||||
setIfDefined(argt, 'x-expires', options.expires);
|
||||
setIfDefined(argt, 'x-message-ttl', options.messageTtl);
|
||||
setIfDefined(argt, 'x-dead-letter-exchange',
|
||||
options.deadLetterExchange);
|
||||
setIfDefined(argt, 'x-dead-letter-routing-key',
|
||||
options.deadLetterRoutingKey);
|
||||
setIfDefined(argt, 'x-max-length', options.maxLength);
|
||||
setIfDefined(argt, 'x-max-priority', options.maxPriority);
|
||||
setIfDefined(argt, 'x-overflow', options.overflow);
|
||||
setIfDefined(argt, 'x-queue-mode', options.queueMode);
|
||||
|
||||
return {
|
||||
queue: queue,
|
||||
exclusive: !!options.exclusive,
|
||||
durable: (options.durable === undefined) ? true : options.durable,
|
||||
autoDelete: !!options.autoDelete,
|
||||
arguments: argt,
|
||||
passive: false,
|
||||
// deprecated but we have to include it
|
||||
ticket: 0,
|
||||
nowait: false
|
||||
};
|
||||
};
|
||||
|
||||
Args.checkQueue = function(queue) {
|
||||
return {
|
||||
queue: queue,
|
||||
passive: true, // switch to "completely different" mode
|
||||
nowait: false,
|
||||
durable: true, autoDelete: false, exclusive: false, // ignored
|
||||
ticket: 0,
|
||||
};
|
||||
};
|
||||
|
||||
Args.deleteQueue = function(queue, options) {
|
||||
options = options || EMPTY_OPTIONS;
|
||||
return {
|
||||
queue: queue,
|
||||
ifUnused: !!options.ifUnused,
|
||||
ifEmpty: !!options.ifEmpty,
|
||||
ticket: 0, nowait: false
|
||||
};
|
||||
};
|
||||
|
||||
Args.purgeQueue = function(queue) {
|
||||
return {
|
||||
queue: queue,
|
||||
ticket: 0, nowait: false
|
||||
};
|
||||
};
|
||||
|
||||
Args.bindQueue = function(queue, source, pattern, argt) {
|
||||
return {
|
||||
queue: queue,
|
||||
exchange: source,
|
||||
routingKey: pattern,
|
||||
arguments: argt,
|
||||
ticket: 0, nowait: false
|
||||
};
|
||||
};
|
||||
|
||||
Args.unbindQueue = function(queue, source, pattern, argt) {
|
||||
return {
|
||||
queue: queue,
|
||||
exchange: source,
|
||||
routingKey: pattern,
|
||||
arguments: argt,
|
||||
ticket: 0, nowait: false
|
||||
};
|
||||
};
|
||||
|
||||
Args.assertExchange = function(exchange, type, options) {
|
||||
options = options || EMPTY_OPTIONS;
|
||||
var argt = Object.create(options.arguments || null);
|
||||
setIfDefined(argt, 'alternate-exchange', options.alternateExchange);
|
||||
return {
|
||||
exchange: exchange,
|
||||
ticket: 0,
|
||||
type: type,
|
||||
passive: false,
|
||||
durable: (options.durable === undefined) ? true : options.durable,
|
||||
autoDelete: !!options.autoDelete,
|
||||
internal: !!options.internal,
|
||||
nowait: false,
|
||||
arguments: argt
|
||||
};
|
||||
};
|
||||
|
||||
Args.checkExchange = function(exchange) {
|
||||
return {
|
||||
exchange: exchange,
|
||||
passive: true, // switch to 'may as well be another method' mode
|
||||
nowait: false,
|
||||
// ff are ignored
|
||||
durable: true, internal: false, type: '', autoDelete: false,
|
||||
ticket: 0
|
||||
};
|
||||
};
|
||||
|
||||
Args.deleteExchange = function(exchange, options) {
|
||||
options = options || EMPTY_OPTIONS;
|
||||
return {
|
||||
exchange: exchange,
|
||||
ifUnused: !!options.ifUnused,
|
||||
ticket: 0, nowait: false
|
||||
};
|
||||
};
|
||||
|
||||
Args.bindExchange = function(dest, source, pattern, argt) {
|
||||
return {
|
||||
source: source,
|
||||
destination: dest,
|
||||
routingKey: pattern,
|
||||
arguments: argt,
|
||||
ticket: 0, nowait: false
|
||||
};
|
||||
};
|
||||
|
||||
Args.unbindExchange = function(dest, source, pattern, argt) {
|
||||
return {
|
||||
source: source,
|
||||
destination: dest,
|
||||
routingKey: pattern,
|
||||
arguments: argt,
|
||||
ticket: 0, nowait: false
|
||||
};
|
||||
};
|
||||
|
||||
// It's convenient to construct the properties and the method fields
|
||||
// at the same time, since in the APIs, values for both can appear in
|
||||
// `options`. Since the property or mthod field names don't overlap, I
|
||||
// just return one big object that can be used for both purposes, and
|
||||
// the encoder will pick out what it wants.
|
||||
Args.publish = function(exchange, routingKey, options) {
|
||||
options = options || EMPTY_OPTIONS;
|
||||
|
||||
// The CC and BCC fields expect an array of "longstr", which would
|
||||
// normally be buffer values in JavaScript; however, since a field
|
||||
// array (or table) cannot have shortstr values, the codec will
|
||||
// encode all strings as longstrs anyway.
|
||||
function convertCC(cc) {
|
||||
if (cc === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
else if (Array.isArray(cc)) {
|
||||
return cc.map(String);
|
||||
}
|
||||
else return [String(cc)];
|
||||
}
|
||||
|
||||
var headers = Object.create(options.headers || null);
|
||||
setIfDefined(headers, 'CC', convertCC(options.CC));
|
||||
setIfDefined(headers, 'BCC', convertCC(options.BCC));
|
||||
|
||||
var deliveryMode; // undefined will default to 1 (non-persistent)
|
||||
|
||||
// Previously I overloaded deliveryMode be a boolean meaning
|
||||
// 'persistent or not'; better is to name this option for what it
|
||||
// is, but I need to have backwards compatibility for applications
|
||||
// that either supply a numeric or boolean value.
|
||||
if (options.persistent !== undefined)
|
||||
deliveryMode = (options.persistent) ? 2 : 1;
|
||||
else if (typeof options.deliveryMode === 'number')
|
||||
deliveryMode = options.deliveryMode;
|
||||
else if (options.deliveryMode) // is supplied and truthy
|
||||
deliveryMode = 2;
|
||||
|
||||
var expiration = options.expiration;
|
||||
if (expiration !== undefined) expiration = expiration.toString();
|
||||
|
||||
return {
|
||||
// method fields
|
||||
exchange: exchange,
|
||||
routingKey: routingKey,
|
||||
mandatory: !!options.mandatory,
|
||||
immediate: false, // RabbitMQ doesn't implement this any more
|
||||
ticket: undefined,
|
||||
// properties
|
||||
contentType: options.contentType,
|
||||
contentEncoding: options.contentEncoding,
|
||||
headers: headers,
|
||||
deliveryMode: deliveryMode,
|
||||
priority: options.priority,
|
||||
correlationId: options.correlationId,
|
||||
replyTo: options.replyTo,
|
||||
expiration: expiration,
|
||||
messageId: options.messageId,
|
||||
timestamp: options.timestamp,
|
||||
type: options.type,
|
||||
userId: options.userId,
|
||||
appId: options.appId,
|
||||
clusterId: undefined
|
||||
};
|
||||
};
|
||||
|
||||
Args.consume = function(queue, options) {
|
||||
options = options || EMPTY_OPTIONS;
|
||||
var argt = Object.create(options.arguments || null);
|
||||
setIfDefined(argt, 'x-priority', options.priority);
|
||||
return {
|
||||
ticket: 0,
|
||||
queue: queue,
|
||||
consumerTag: options.consumerTag || '',
|
||||
noLocal: !!options.noLocal,
|
||||
noAck: !!options.noAck,
|
||||
exclusive: !!options.exclusive,
|
||||
nowait: false,
|
||||
arguments: argt
|
||||
};
|
||||
};
|
||||
|
||||
Args.cancel = function(consumerTag) {
|
||||
return {
|
||||
consumerTag: consumerTag,
|
||||
nowait: false
|
||||
};
|
||||
};
|
||||
|
||||
Args.get = function(queue, options) {
|
||||
options = options || EMPTY_OPTIONS;
|
||||
return {
|
||||
ticket: 0,
|
||||
queue: queue,
|
||||
noAck: !!options.noAck
|
||||
};
|
||||
};
|
||||
|
||||
Args.ack = function(tag, allUpTo) {
|
||||
return {
|
||||
deliveryTag: tag,
|
||||
multiple: !!allUpTo
|
||||
};
|
||||
};
|
||||
|
||||
Args.nack = function(tag, allUpTo, requeue) {
|
||||
return {
|
||||
deliveryTag: tag,
|
||||
multiple: !!allUpTo,
|
||||
requeue: (requeue === undefined) ? true : requeue
|
||||
};
|
||||
};
|
||||
|
||||
Args.reject = function(tag, requeue) {
|
||||
return {
|
||||
deliveryTag: tag,
|
||||
requeue: (requeue === undefined) ? true : requeue
|
||||
};
|
||||
};
|
||||
|
||||
Args.prefetch = function(count, global) {
|
||||
return {
|
||||
prefetchCount: count || 0,
|
||||
prefetchSize: 0,
|
||||
global: !!global
|
||||
};
|
||||
};
|
||||
|
||||
Args.recover = function() {
|
||||
return {requeue: true};
|
||||
};
|
||||
|
||||
module.exports = Object.freeze(Args);
|
||||
130
node_modules/amqplib/lib/bitset.js
generated
vendored
Normal file
130
node_modules/amqplib/lib/bitset.js
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A bitset implementation, after that in java.util. Yes there
|
||||
* already exist such things, but none implement next{Clear|Set}Bit or
|
||||
* equivalent, and none involved me tooling about for an evening.
|
||||
*/
|
||||
class BitSet {
|
||||
/**
|
||||
* @param {number} [size]
|
||||
*/
|
||||
constructor(size) {
|
||||
if (size) {
|
||||
const numWords = Math.ceil(size / 32);
|
||||
this.words = new Array(numWords);
|
||||
}
|
||||
else {
|
||||
this.words = [];
|
||||
}
|
||||
this.wordsInUse = 0; // = number, not index
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} numWords
|
||||
*/
|
||||
ensureSize(numWords) {
|
||||
const wordsPresent = this.words.length;
|
||||
if (wordsPresent < numWords) {
|
||||
this.words = this.words.concat(new Array(numWords - wordsPresent));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} bitIndex
|
||||
*/
|
||||
set(bitIndex) {
|
||||
const w = wordIndex(bitIndex);
|
||||
if (w >= this.wordsInUse) {
|
||||
this.ensureSize(w + 1);
|
||||
this.wordsInUse = w + 1;
|
||||
}
|
||||
const bit = 1 << bitIndex;
|
||||
this.words[w] |= bit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} bitIndex
|
||||
*/
|
||||
clear(bitIndex) {
|
||||
const w = wordIndex(bitIndex);
|
||||
if (w >= this.wordsInUse) return;
|
||||
const mask = ~(1 << bitIndex);
|
||||
this.words[w] &= mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} bitIndex
|
||||
*/
|
||||
get(bitIndex) {
|
||||
const w = wordIndex(bitIndex);
|
||||
if (w >= this.wordsInUse) return false; // >= since index vs size
|
||||
const bit = 1 << bitIndex;
|
||||
return !!(this.words[w] & bit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the next bit that is set on or after fromIndex, or -1 if no such bit
|
||||
*
|
||||
* @param {number} fromIndex
|
||||
*/
|
||||
nextSetBit(fromIndex) {
|
||||
let w = wordIndex(fromIndex);
|
||||
if (w >= this.wordsInUse) return -1;
|
||||
|
||||
// the right-hand side is shifted to only test the bits of the first
|
||||
// word that are > fromIndex
|
||||
let word = this.words[w] & (0xffffffff << fromIndex);
|
||||
while (true) {
|
||||
if (word) return (w * 32) + trailingZeros(word);
|
||||
w++;
|
||||
if (w === this.wordsInUse) return -1;
|
||||
word = this.words[w];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} fromIndex
|
||||
*/
|
||||
nextClearBit(fromIndex) {
|
||||
let w = wordIndex(fromIndex);
|
||||
if (w >= this.wordsInUse) return fromIndex;
|
||||
|
||||
let word = ~(this.words[w]) & (0xffffffff << fromIndex);
|
||||
while (true) {
|
||||
if (word) return (w * 32) + trailingZeros(word);
|
||||
w++;
|
||||
if (w == this.wordsInUse) return w * 32;
|
||||
word = ~(this.words[w]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} bitIndex
|
||||
*/
|
||||
function wordIndex(bitIndex) {
|
||||
return Math.floor(bitIndex / 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} i
|
||||
*/
|
||||
function trailingZeros(i) {
|
||||
// From Hacker's Delight, via JDK. Probably far less effective here,
|
||||
// since bit ops are not necessarily the quick way to do things in
|
||||
// JS.
|
||||
if (i === 0) return 32;
|
||||
let y, n = 31;
|
||||
y = i << 16; if (y != 0) { n = n -16; i = y; }
|
||||
y = i << 8; if (y != 0) { n = n - 8; i = y; }
|
||||
y = i << 4; if (y != 0) { n = n - 4; i = y; }
|
||||
y = i << 2; if (y != 0) { n = n - 2; i = y; }
|
||||
return n - ((i << 1) >>> 31);
|
||||
}
|
||||
|
||||
module.exports.BitSet = BitSet;
|
||||
342
node_modules/amqplib/lib/callback_model.js
generated
vendored
Normal file
342
node_modules/amqplib/lib/callback_model.js
generated
vendored
Normal file
@@ -0,0 +1,342 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
'use strict';
|
||||
|
||||
var defs = require('./defs');
|
||||
var EventEmitter = require('events');
|
||||
var BaseChannel = require('./channel').BaseChannel;
|
||||
var acceptMessage = require('./channel').acceptMessage;
|
||||
var Args = require('./api_args');
|
||||
|
||||
class CallbackModel extends EventEmitter {
|
||||
constructor (connection) {
|
||||
super();
|
||||
this.connection = connection;
|
||||
var self = this;
|
||||
['error', 'close', 'blocked', 'unblocked'].forEach(function (ev) {
|
||||
connection.on(ev, self.emit.bind(self, ev));
|
||||
});
|
||||
}
|
||||
|
||||
close (cb) {
|
||||
this.connection.close(cb);
|
||||
}
|
||||
|
||||
updateSecret(newSecret, reason, cb) {
|
||||
this.connection._updateSecret(newSecret, reason, cb);
|
||||
}
|
||||
|
||||
createChannel (options, cb) {
|
||||
if (arguments.length === 1) {
|
||||
cb = options;
|
||||
options = undefined;
|
||||
}
|
||||
var ch = new Channel(this.connection);
|
||||
ch.setOptions(options);
|
||||
ch.open(function (err, ok) {
|
||||
if (err === null)
|
||||
cb && cb(null, ch);
|
||||
else
|
||||
cb && cb(err);
|
||||
});
|
||||
return ch;
|
||||
}
|
||||
|
||||
createConfirmChannel (options, cb) {
|
||||
if (arguments.length === 1) {
|
||||
cb = options;
|
||||
options = undefined;
|
||||
}
|
||||
var ch = new ConfirmChannel(this.connection);
|
||||
ch.setOptions(options);
|
||||
ch.open(function (err) {
|
||||
if (err !== null)
|
||||
return cb && cb(err);
|
||||
else {
|
||||
ch.rpc(defs.ConfirmSelect, { nowait: false },
|
||||
defs.ConfirmSelectOk, function (err, _ok) {
|
||||
if (err !== null)
|
||||
return cb && cb(err);
|
||||
else
|
||||
cb && cb(null, ch);
|
||||
});
|
||||
}
|
||||
});
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
|
||||
class Channel extends BaseChannel {
|
||||
constructor (connection) {
|
||||
super(connection);
|
||||
this.on('delivery', this.handleDelivery.bind(this));
|
||||
this.on('cancel', this.handleCancel.bind(this));
|
||||
}
|
||||
|
||||
// This encodes straight-forward RPC: no side-effects and return the
|
||||
// fields from the server response. It wraps the callback given it, so
|
||||
// the calling method argument can be passed as-is. For anything that
|
||||
// needs to have side-effects, or needs to change the server response,
|
||||
// use `#_rpc(...)` and remember to dereference `.fields` of the
|
||||
// server response.
|
||||
rpc (method, fields, expect, cb0) {
|
||||
var cb = callbackWrapper(this, cb0);
|
||||
this._rpc(method, fields, expect, function (err, ok) {
|
||||
cb(err, ok && ok.fields); // in case of an error, ok will be
|
||||
|
||||
// undefined
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
// === Public API ===
|
||||
open (cb) {
|
||||
try { this.allocate(); }
|
||||
catch (e) { return cb(e); }
|
||||
|
||||
return this.rpc(defs.ChannelOpen, { outOfBand: "" },
|
||||
defs.ChannelOpenOk, cb);
|
||||
}
|
||||
|
||||
close (cb) {
|
||||
return this.closeBecause("Goodbye", defs.constants.REPLY_SUCCESS,
|
||||
function () { cb && cb(null); });
|
||||
}
|
||||
|
||||
assertQueue (queue, options, cb) {
|
||||
return this.rpc(defs.QueueDeclare,
|
||||
Args.assertQueue(queue, options),
|
||||
defs.QueueDeclareOk, cb);
|
||||
}
|
||||
|
||||
checkQueue (queue, cb) {
|
||||
return this.rpc(defs.QueueDeclare,
|
||||
Args.checkQueue(queue),
|
||||
defs.QueueDeclareOk, cb);
|
||||
}
|
||||
|
||||
deleteQueue (queue, options, cb) {
|
||||
return this.rpc(defs.QueueDelete,
|
||||
Args.deleteQueue(queue, options),
|
||||
defs.QueueDeleteOk, cb);
|
||||
}
|
||||
|
||||
purgeQueue (queue, cb) {
|
||||
return this.rpc(defs.QueuePurge,
|
||||
Args.purgeQueue(queue),
|
||||
defs.QueuePurgeOk, cb);
|
||||
}
|
||||
|
||||
bindQueue (queue, source, pattern, argt, cb) {
|
||||
return this.rpc(defs.QueueBind,
|
||||
Args.bindQueue(queue, source, pattern, argt),
|
||||
defs.QueueBindOk, cb);
|
||||
}
|
||||
|
||||
unbindQueue (queue, source, pattern, argt, cb) {
|
||||
return this.rpc(defs.QueueUnbind,
|
||||
Args.unbindQueue(queue, source, pattern, argt),
|
||||
defs.QueueUnbindOk, cb);
|
||||
}
|
||||
|
||||
assertExchange (ex, type, options, cb0) {
|
||||
var cb = callbackWrapper(this, cb0);
|
||||
this._rpc(defs.ExchangeDeclare,
|
||||
Args.assertExchange(ex, type, options),
|
||||
defs.ExchangeDeclareOk,
|
||||
function (e, _) { cb(e, { exchange: ex }); });
|
||||
return this;
|
||||
}
|
||||
|
||||
checkExchange (exchange, cb) {
|
||||
return this.rpc(defs.ExchangeDeclare,
|
||||
Args.checkExchange(exchange),
|
||||
defs.ExchangeDeclareOk, cb);
|
||||
}
|
||||
|
||||
deleteExchange (exchange, options, cb) {
|
||||
return this.rpc(defs.ExchangeDelete,
|
||||
Args.deleteExchange(exchange, options),
|
||||
defs.ExchangeDeleteOk, cb);
|
||||
}
|
||||
|
||||
bindExchange (dest, source, pattern, argt, cb) {
|
||||
return this.rpc(defs.ExchangeBind,
|
||||
Args.bindExchange(dest, source, pattern, argt),
|
||||
defs.ExchangeBindOk, cb);
|
||||
}
|
||||
|
||||
unbindExchange (dest, source, pattern, argt, cb) {
|
||||
return this.rpc(defs.ExchangeUnbind,
|
||||
Args.unbindExchange(dest, source, pattern, argt),
|
||||
defs.ExchangeUnbindOk, cb);
|
||||
}
|
||||
|
||||
publish (exchange, routingKey, content, options) {
|
||||
var fieldsAndProps = Args.publish(exchange, routingKey, options);
|
||||
return this.sendMessage(fieldsAndProps, fieldsAndProps, content);
|
||||
}
|
||||
|
||||
sendToQueue (queue, content, options) {
|
||||
return this.publish('', queue, content, options);
|
||||
}
|
||||
|
||||
consume (queue, callback, options, cb0) {
|
||||
var cb = callbackWrapper(this, cb0);
|
||||
var fields = Args.consume(queue, options);
|
||||
var self = this;
|
||||
this._rpc(
|
||||
defs.BasicConsume, fields, defs.BasicConsumeOk,
|
||||
function (err, ok) {
|
||||
if (err === null) {
|
||||
self.registerConsumer(ok.fields.consumerTag, callback);
|
||||
cb(null, ok.fields);
|
||||
}
|
||||
else
|
||||
cb(err);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
cancel (consumerTag, cb0) {
|
||||
var cb = callbackWrapper(this, cb0);
|
||||
var self = this;
|
||||
this._rpc(
|
||||
defs.BasicCancel, Args.cancel(consumerTag), defs.BasicCancelOk,
|
||||
function (err, ok) {
|
||||
if (err === null) {
|
||||
self.unregisterConsumer(consumerTag);
|
||||
cb(null, ok.fields);
|
||||
}
|
||||
else
|
||||
cb(err);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
get (queue, options, cb0) {
|
||||
var self = this;
|
||||
var fields = Args.get(queue, options);
|
||||
var cb = callbackWrapper(this, cb0);
|
||||
this.sendOrEnqueue(defs.BasicGet, fields, function (err, f) {
|
||||
if (err === null) {
|
||||
if (f.id === defs.BasicGetEmpty) {
|
||||
cb(null, false);
|
||||
}
|
||||
else if (f.id === defs.BasicGetOk) {
|
||||
self.handleMessage = acceptMessage(function (m) {
|
||||
m.fields = f.fields;
|
||||
cb(null, m);
|
||||
});
|
||||
}
|
||||
else {
|
||||
cb(new Error("Unexpected response to BasicGet: " +
|
||||
inspect(f)));
|
||||
}
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
ack (message, allUpTo) {
|
||||
this.sendImmediately(
|
||||
defs.BasicAck, Args.ack(message.fields.deliveryTag, allUpTo));
|
||||
return this;
|
||||
}
|
||||
|
||||
ackAll () {
|
||||
this.sendImmediately(defs.BasicAck, Args.ack(0, true));
|
||||
return this;
|
||||
}
|
||||
|
||||
nack (message, allUpTo, requeue) {
|
||||
this.sendImmediately(
|
||||
defs.BasicNack,
|
||||
Args.nack(message.fields.deliveryTag, allUpTo, requeue));
|
||||
return this;
|
||||
}
|
||||
|
||||
nackAll (requeue) {
|
||||
this.sendImmediately(
|
||||
defs.BasicNack, Args.nack(0, true, requeue));
|
||||
return this;
|
||||
}
|
||||
|
||||
reject (message, requeue) {
|
||||
this.sendImmediately(
|
||||
defs.BasicReject,
|
||||
Args.reject(message.fields.deliveryTag, requeue));
|
||||
return this;
|
||||
}
|
||||
|
||||
prefetch (count, global, cb) {
|
||||
return this.rpc(defs.BasicQos,
|
||||
Args.prefetch(count, global),
|
||||
defs.BasicQosOk, cb);
|
||||
}
|
||||
|
||||
recover (cb) {
|
||||
return this.rpc(defs.BasicRecover,
|
||||
Args.recover(),
|
||||
defs.BasicRecoverOk, cb);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Wrap an RPC callback to make sure the callback is invoked with
|
||||
// either `(null, value)` or `(error)`, i.e., never two non-null
|
||||
// values. Also substitutes a stub if the callback is `undefined` or
|
||||
// otherwise falsey, for convenience in methods for which the callback
|
||||
// is optional (that is, most of them).
|
||||
function callbackWrapper(ch, cb) {
|
||||
return (cb) ? function(err, ok) {
|
||||
if (err === null) {
|
||||
cb(null, ok);
|
||||
}
|
||||
else cb(err);
|
||||
} : function() {};
|
||||
}
|
||||
|
||||
class ConfirmChannel extends Channel {
|
||||
publish (exchange, routingKey,
|
||||
content, options, cb) {
|
||||
this.pushConfirmCallback(cb);
|
||||
return Channel.prototype.publish.call(
|
||||
this, exchange, routingKey, content, options);
|
||||
}
|
||||
|
||||
sendToQueue (queue, content,
|
||||
options, cb) {
|
||||
return this.publish('', queue, content, options, cb);
|
||||
}
|
||||
|
||||
waitForConfirms (k) {
|
||||
var awaiting = [];
|
||||
var unconfirmed = this.unconfirmed;
|
||||
unconfirmed.forEach(function (val, index) {
|
||||
if (val === null)
|
||||
; // already confirmed
|
||||
else {
|
||||
var confirmed = new Promise(function (resolve, reject) {
|
||||
unconfirmed[index] = function (err) {
|
||||
if (val)
|
||||
val(err);
|
||||
if (err === null)
|
||||
resolve();
|
||||
else
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
awaiting.push(confirmed);
|
||||
}
|
||||
});
|
||||
return Promise.all(awaiting).then(function () { k(); },
|
||||
function (err) { k(err); });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.CallbackModel = CallbackModel;
|
||||
module.exports.Channel = Channel;
|
||||
module.exports.ConfirmChannel = ConfirmChannel;
|
||||
510
node_modules/amqplib/lib/channel.js
generated
vendored
Normal file
510
node_modules/amqplib/lib/channel.js
generated
vendored
Normal file
@@ -0,0 +1,510 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
// Channel machinery.
|
||||
|
||||
'use strict';
|
||||
|
||||
var defs = require('./defs');
|
||||
var closeMsg = require('./format').closeMessage;
|
||||
var inspect = require('./format').inspect;
|
||||
var methodName = require('./format').methodName;
|
||||
var assert = require('assert');
|
||||
var EventEmitter = require('events');
|
||||
var fmt = require('util').format;
|
||||
var IllegalOperationError = require('./error').IllegalOperationError;
|
||||
var stackCapture = require('./error').stackCapture;
|
||||
|
||||
class Channel extends EventEmitter {
|
||||
constructor (connection) {
|
||||
super();
|
||||
|
||||
this.connection = connection;
|
||||
// for the presently outstanding RPC
|
||||
this.reply = null;
|
||||
// for the RPCs awaiting action
|
||||
this.pending = [];
|
||||
// for unconfirmed messages
|
||||
this.lwm = 1; // the least, unconfirmed deliveryTag
|
||||
this.unconfirmed = []; // rolling window of delivery callbacks
|
||||
this.on('ack', this.handleConfirm.bind(this, function (cb) {
|
||||
if (cb)
|
||||
cb(null);
|
||||
}));
|
||||
this.on('nack', this.handleConfirm.bind(this, function (cb) {
|
||||
if (cb)
|
||||
cb(new Error('message nacked'));
|
||||
}));
|
||||
this.on('close', function () {
|
||||
var cb;
|
||||
while (cb = this.unconfirmed.shift()) {
|
||||
if (cb)
|
||||
cb(new Error('channel closed'));
|
||||
}
|
||||
});
|
||||
// message frame state machine
|
||||
this.handleMessage = acceptDeliveryOrReturn;
|
||||
}
|
||||
|
||||
setOptions(options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
allocate () {
|
||||
this.ch = this.connection.freshChannel(this, this.options);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Incoming frames are either notifications of e.g., message delivery,
|
||||
// or replies to something we've sent. In general I deal with the
|
||||
// former by emitting an event, and with the latter by keeping a track
|
||||
// of what's expecting a reply.
|
||||
//
|
||||
// The AMQP specification implies that RPCs can't be pipelined; that
|
||||
// is, you can have only one outstanding RPC on a channel at a
|
||||
// time. Certainly that's what RabbitMQ and its clients assume. For
|
||||
// this reason, I buffer RPCs if the channel is already waiting for a
|
||||
// reply.
|
||||
// Just send the damn frame.
|
||||
sendImmediately (method, fields) {
|
||||
return this.connection.sendMethod(this.ch, method, fields);
|
||||
}
|
||||
|
||||
// Invariant: !this.reply -> pending.length == 0. That is, whenever we
|
||||
// clear a reply, we must send another RPC (and thereby fill
|
||||
// this.reply) if there is one waiting. The invariant relevant here
|
||||
// and in `accept`.
|
||||
sendOrEnqueue (method, fields, reply) {
|
||||
if (!this.reply) { // if no reply waiting, we can go
|
||||
assert(this.pending.length === 0);
|
||||
this.reply = reply;
|
||||
this.sendImmediately(method, fields);
|
||||
}
|
||||
else {
|
||||
this.pending.push({
|
||||
method: method,
|
||||
fields: fields,
|
||||
reply: reply
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage (fields, properties, content) {
|
||||
return this.connection.sendMessage(
|
||||
this.ch,
|
||||
defs.BasicPublish, fields,
|
||||
defs.BasicProperties, properties,
|
||||
content);
|
||||
}
|
||||
|
||||
// Internal, synchronously resolved RPC; the return value is resolved
|
||||
// with the whole frame.
|
||||
_rpc (method, fields, expect, cb) {
|
||||
var self = this;
|
||||
|
||||
function reply (err, f) {
|
||||
if (err === null) {
|
||||
if (f.id === expect) {
|
||||
return cb(null, f);
|
||||
}
|
||||
else {
|
||||
// We have detected a problem, so it's up to us to close the
|
||||
// channel
|
||||
var expectedName = methodName(expect);
|
||||
|
||||
var e = new Error(fmt("Expected %s; got %s",
|
||||
expectedName, inspect(f, false)));
|
||||
self.closeWithError(f.id, fmt('Expected %s; got %s',
|
||||
expectedName, methodName(f.id)),
|
||||
defs.constants.UNEXPECTED_FRAME, e);
|
||||
return cb(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// An error will be given if, for example, this is waiting to be
|
||||
// sent and the connection closes
|
||||
else if (err instanceof Error)
|
||||
return cb(err);
|
||||
|
||||
|
||||
// A close frame will be given if this is the RPC awaiting reply
|
||||
// and the channel is closed by the server
|
||||
else {
|
||||
// otherwise, it's a close frame
|
||||
var closeReason = (err.fields.classId << 16) + err.fields.methodId;
|
||||
var e = (method === closeReason)
|
||||
? fmt("Operation failed: %s; %s",
|
||||
methodName(method), closeMsg(err))
|
||||
: fmt("Channel closed by server: %s", closeMsg(err));
|
||||
var closeFrameError = new Error(e);
|
||||
closeFrameError.code = err.fields.replyCode;
|
||||
closeFrameError.classId = err.fields.classId;
|
||||
closeFrameError.methodId = err.fields.methodId;
|
||||
return cb(closeFrameError);
|
||||
}
|
||||
}
|
||||
|
||||
this.sendOrEnqueue(method, fields, reply);
|
||||
}
|
||||
|
||||
// Move to entirely closed state.
|
||||
toClosed (capturedStack) {
|
||||
this._rejectPending();
|
||||
invalidateSend(this, 'Channel closed', capturedStack);
|
||||
this.accept = invalidOp('Channel closed', capturedStack);
|
||||
this.connection.releaseChannel(this.ch);
|
||||
this.emit('close');
|
||||
}
|
||||
|
||||
// Stop being able to send and receive methods and content. Used when
|
||||
// we close the channel. Invokes the continuation once the server has
|
||||
// acknowledged the close, but before the channel is moved to the
|
||||
// closed state.
|
||||
toClosing (capturedStack, k) {
|
||||
var send = this.sendImmediately.bind(this);
|
||||
invalidateSend(this, 'Channel closing', capturedStack);
|
||||
|
||||
this.accept = function (f) {
|
||||
if (f.id === defs.ChannelCloseOk) {
|
||||
if (k)
|
||||
k();
|
||||
var s = stackCapture('ChannelCloseOk frame received');
|
||||
this.toClosed(s);
|
||||
}
|
||||
else if (f.id === defs.ChannelClose) {
|
||||
send(defs.ChannelCloseOk, {});
|
||||
}
|
||||
// else ignore frame
|
||||
};
|
||||
}
|
||||
|
||||
_rejectPending () {
|
||||
function rej (r) {
|
||||
r(new Error("Channel ended, no reply will be forthcoming"));
|
||||
}
|
||||
if (this.reply !== null)
|
||||
rej(this.reply);
|
||||
this.reply = null;
|
||||
|
||||
var discard;
|
||||
while (discard = this.pending.shift())
|
||||
rej(discard.reply);
|
||||
this.pending = null; // so pushes will break
|
||||
}
|
||||
|
||||
closeBecause (reason, code, k) {
|
||||
this.sendImmediately(defs.ChannelClose, {
|
||||
replyText: reason,
|
||||
replyCode: code,
|
||||
methodId: 0, classId: 0
|
||||
});
|
||||
var s = stackCapture('closeBecause called: ' + reason);
|
||||
this.toClosing(s, k);
|
||||
}
|
||||
|
||||
// If we close because there's been an error, we need to distinguish
|
||||
// between what we tell the server (`reason`) and what we report as
|
||||
// the cause in the client (`error`).
|
||||
closeWithError (id, reason, code, error) {
|
||||
var self = this;
|
||||
this.closeBecause(reason, code, function () {
|
||||
error.code = code;
|
||||
// content frames and consumer errors do not provide a method a class/method ID
|
||||
if (id) {
|
||||
error.classId = defs.info(id).classId;
|
||||
error.methodId = defs.info(id).methodId;
|
||||
}
|
||||
self.emit('error', error);
|
||||
});
|
||||
}
|
||||
|
||||
// A trampolining state machine for message frames on a channel. A
|
||||
// message arrives in at least two frames: first, a method announcing
|
||||
// the message (either a BasicDeliver or BasicGetOk); then, a message
|
||||
// header with the message properties; then, zero or more content
|
||||
// frames.
|
||||
// Keep the try/catch localised, in an attempt to avoid disabling
|
||||
// optimisation
|
||||
acceptMessageFrame (f) {
|
||||
try {
|
||||
this.handleMessage = this.handleMessage(f);
|
||||
}
|
||||
catch (msg) {
|
||||
if (typeof msg === 'string') {
|
||||
this.closeWithError(f.id, msg, defs.constants.UNEXPECTED_FRAME,
|
||||
new Error(msg));
|
||||
}
|
||||
else if (msg instanceof Error) {
|
||||
this.closeWithError(f.id, 'Error while processing message',
|
||||
defs.constants.INTERNAL_ERROR, msg);
|
||||
}
|
||||
else {
|
||||
this.closeWithError(f.id, 'Internal error while processing message',
|
||||
defs.constants.INTERNAL_ERROR,
|
||||
new Error(msg.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleConfirm (handle, f) {
|
||||
var tag = f.deliveryTag;
|
||||
var multi = f.multiple;
|
||||
|
||||
if (multi) {
|
||||
var confirmed = this.unconfirmed.splice(0, tag - this.lwm + 1);
|
||||
this.lwm = tag + 1;
|
||||
confirmed.forEach(handle);
|
||||
}
|
||||
else {
|
||||
var c;
|
||||
if (tag === this.lwm) {
|
||||
c = this.unconfirmed.shift();
|
||||
this.lwm++;
|
||||
// Advance the LWM and the window to the next non-gap, or
|
||||
// possibly to the end
|
||||
while (this.unconfirmed[0] === null) {
|
||||
this.unconfirmed.shift();
|
||||
this.lwm++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
c = this.unconfirmed[tag - this.lwm];
|
||||
this.unconfirmed[tag - this.lwm] = null;
|
||||
}
|
||||
// Technically, in the single-deliveryTag case, I should report a
|
||||
// protocol breach if it's already been confirmed.
|
||||
handle(c);
|
||||
}
|
||||
}
|
||||
|
||||
pushConfirmCallback (cb) {
|
||||
// `null` is used specifically for marking already confirmed slots,
|
||||
// so I coerce `undefined` and `null` to false; functions are never
|
||||
// falsey.
|
||||
this.unconfirmed.push(cb || false);
|
||||
}
|
||||
|
||||
onBufferDrain () {
|
||||
this.emit('drain');
|
||||
}
|
||||
|
||||
accept(f) {
|
||||
|
||||
switch (f.id) {
|
||||
|
||||
// Message frames
|
||||
case undefined: // content frame!
|
||||
case defs.BasicDeliver:
|
||||
case defs.BasicReturn:
|
||||
case defs.BasicProperties:
|
||||
return this.acceptMessageFrame(f);
|
||||
|
||||
// confirmations, need to do confirm.select first
|
||||
case defs.BasicAck:
|
||||
return this.emit('ack', f.fields);
|
||||
case defs.BasicNack:
|
||||
return this.emit('nack', f.fields);
|
||||
case defs.BasicCancel:
|
||||
// The broker can send this if e.g., the queue is deleted.
|
||||
return this.emit('cancel', f.fields);
|
||||
|
||||
case defs.ChannelClose:
|
||||
// Any remote closure is an error to us. Reject the pending reply
|
||||
// with the close frame, so it can see whether it was that
|
||||
// operation that caused it to close.
|
||||
if (this.reply) {
|
||||
var reply = this.reply; this.reply = null;
|
||||
reply(f);
|
||||
}
|
||||
var emsg = "Channel closed by server: " + closeMsg(f);
|
||||
this.sendImmediately(defs.ChannelCloseOk, {});
|
||||
|
||||
var error = new Error(emsg);
|
||||
error.code = f.fields.replyCode;
|
||||
error.classId = f.fields.classId;
|
||||
error.methodId = f.fields.methodId;
|
||||
this.emit('error', error);
|
||||
|
||||
var s = stackCapture(emsg);
|
||||
this.toClosed(s);
|
||||
return;
|
||||
|
||||
case defs.BasicFlow:
|
||||
// RabbitMQ doesn't send this, it just blocks the TCP socket
|
||||
return this.closeWithError(f.id, "Flow not implemented",
|
||||
defs.constants.NOT_IMPLEMENTED,
|
||||
new Error('Flow not implemented'));
|
||||
|
||||
default: // assume all other things are replies
|
||||
// Resolving the reply may lead to another RPC; to make sure we
|
||||
// don't hold that up, clear this.reply
|
||||
var reply = this.reply; this.reply = null;
|
||||
// however, maybe there's an RPC waiting to go? If so, that'll
|
||||
// fill this.reply again, restoring the invariant. This does rely
|
||||
// on any response being recv'ed after resolving the promise,
|
||||
// below; hence, I use synchronous defer.
|
||||
if (this.pending.length > 0) {
|
||||
var send = this.pending.shift();
|
||||
this.reply = send.reply;
|
||||
this.sendImmediately(send.method, send.fields);
|
||||
}
|
||||
return reply(null, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown protocol. There's three scenarios:
|
||||
//
|
||||
// 1. The application decides to shut the channel
|
||||
// 2. The server decides to shut the channel, possibly because of
|
||||
// something the application did
|
||||
// 3. The connection is closing, so there won't be any more frames
|
||||
// going back and forth.
|
||||
//
|
||||
// 1 and 2 involve an exchange of method frames (Close and CloseOk),
|
||||
// while 3 doesn't; the connection simply says "shutdown" to the
|
||||
// channel, which then acts as if it's closing, without going through
|
||||
// the exchange.
|
||||
|
||||
function invalidOp(msg, stack) {
|
||||
return function() {
|
||||
throw new IllegalOperationError(msg, stack);
|
||||
};
|
||||
}
|
||||
|
||||
function invalidateSend(ch, msg, stack) {
|
||||
ch.sendImmediately = ch.sendOrEnqueue = ch.sendMessage =
|
||||
invalidOp(msg, stack);
|
||||
}
|
||||
|
||||
// Kick off a message delivery given a BasicDeliver or BasicReturn
|
||||
// frame (BasicGet uses the RPC mechanism)
|
||||
function acceptDeliveryOrReturn(f) {
|
||||
var event;
|
||||
if (f.id === defs.BasicDeliver) event = 'delivery';
|
||||
else if (f.id === defs.BasicReturn) event = 'return';
|
||||
else throw fmt("Expected BasicDeliver or BasicReturn; got %s",
|
||||
inspect(f));
|
||||
|
||||
var self = this;
|
||||
var fields = f.fields;
|
||||
return acceptMessage(function(message) {
|
||||
message.fields = fields;
|
||||
self.emit(event, message);
|
||||
});
|
||||
}
|
||||
|
||||
// Move to the state of waiting for message frames (headers, then
|
||||
// one or more content frames)
|
||||
function acceptMessage(continuation) {
|
||||
var totalSize = 0, remaining = 0;
|
||||
var buffers = null;
|
||||
|
||||
var message = {
|
||||
fields: null,
|
||||
properties: null,
|
||||
content: null
|
||||
};
|
||||
|
||||
return headers;
|
||||
|
||||
// expect a headers frame
|
||||
function headers(f) {
|
||||
if (f.id === defs.BasicProperties) {
|
||||
message.properties = f.fields;
|
||||
totalSize = remaining = f.size;
|
||||
|
||||
// for zero-length messages, content frames aren't required.
|
||||
if (totalSize === 0) {
|
||||
message.content = Buffer.alloc(0);
|
||||
continuation(message);
|
||||
return acceptDeliveryOrReturn;
|
||||
}
|
||||
else {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw "Expected headers frame after delivery";
|
||||
}
|
||||
}
|
||||
|
||||
// expect a content frame
|
||||
// %%% TODO cancelled messages (sent as zero-length content frame)
|
||||
function content(f) {
|
||||
if (f.content) {
|
||||
var size = f.content.length;
|
||||
remaining -= size;
|
||||
if (remaining === 0) {
|
||||
if (buffers !== null) {
|
||||
buffers.push(f.content);
|
||||
message.content = Buffer.concat(buffers);
|
||||
}
|
||||
else {
|
||||
message.content = f.content;
|
||||
}
|
||||
continuation(message);
|
||||
return acceptDeliveryOrReturn;
|
||||
}
|
||||
else if (remaining < 0) {
|
||||
throw fmt("Too much content sent! Expected %d bytes",
|
||||
totalSize);
|
||||
}
|
||||
else {
|
||||
if (buffers !== null)
|
||||
buffers.push(f.content);
|
||||
else
|
||||
buffers = [f.content];
|
||||
return content;
|
||||
}
|
||||
}
|
||||
else throw "Expected content frame after headers"
|
||||
}
|
||||
}
|
||||
|
||||
// This adds just a bit more stuff useful for the APIs, but not
|
||||
// low-level machinery.
|
||||
class BaseChannel extends Channel {
|
||||
constructor (connection) {
|
||||
super(connection);
|
||||
this.consumers = new Map();
|
||||
}
|
||||
|
||||
// Not sure I like the ff, it's going to be changing hidden classes
|
||||
// all over the place. On the other hand, whaddya do.
|
||||
registerConsumer (tag, callback) {
|
||||
this.consumers.set(tag, callback);
|
||||
}
|
||||
|
||||
unregisterConsumer (tag) {
|
||||
this.consumers.delete(tag);
|
||||
}
|
||||
|
||||
dispatchMessage (fields, message) {
|
||||
var consumerTag = fields.consumerTag;
|
||||
var consumer = this.consumers.get(consumerTag);
|
||||
if (consumer) {
|
||||
return consumer(message);
|
||||
}
|
||||
else {
|
||||
// %%% Surely a race here
|
||||
throw new Error("Unknown consumer: " + consumerTag);
|
||||
}
|
||||
}
|
||||
|
||||
handleDelivery (message) {
|
||||
return this.dispatchMessage(message.fields, message);
|
||||
}
|
||||
|
||||
handleCancel (fields) {
|
||||
var result = this.dispatchMessage(fields, null);
|
||||
this.unregisterConsumer(fields.consumerTag);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.acceptMessage = acceptMessage;
|
||||
module.exports.BaseChannel = BaseChannel;
|
||||
module.exports.Channel = Channel;
|
||||
308
node_modules/amqplib/lib/channel_model.js
generated
vendored
Normal file
308
node_modules/amqplib/lib/channel_model.js
generated
vendored
Normal file
@@ -0,0 +1,308 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const promisify = require('util').promisify;
|
||||
const defs = require('./defs');
|
||||
const {BaseChannel} = require('./channel');
|
||||
const {acceptMessage} = require('./channel');
|
||||
const Args = require('./api_args');
|
||||
const {inspect} = require('./format');
|
||||
|
||||
class ChannelModel extends EventEmitter {
|
||||
constructor(connection) {
|
||||
super();
|
||||
this.connection = connection;
|
||||
|
||||
['error', 'close', 'blocked', 'unblocked'].forEach(ev => {
|
||||
connection.on(ev, this.emit.bind(this, ev));
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
return promisify(this.connection.close.bind(this.connection))();
|
||||
}
|
||||
|
||||
updateSecret(newSecret, reason) {
|
||||
return promisify(this.connection._updateSecret.bind(this.connection))(newSecret, reason);
|
||||
}
|
||||
|
||||
async createChannel(options) {
|
||||
const channel = new Channel(this.connection);
|
||||
channel.setOptions(options);
|
||||
await channel.open();
|
||||
return channel;
|
||||
}
|
||||
|
||||
async createConfirmChannel(options) {
|
||||
const channel = new ConfirmChannel(this.connection);
|
||||
channel.setOptions(options);
|
||||
await channel.open();
|
||||
await channel.rpc(defs.ConfirmSelect, {nowait: false}, defs.ConfirmSelectOk);
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
// Channels
|
||||
|
||||
class Channel extends BaseChannel {
|
||||
constructor(connection) {
|
||||
super(connection);
|
||||
this.on('delivery', this.handleDelivery.bind(this));
|
||||
this.on('cancel', this.handleCancel.bind(this));
|
||||
}
|
||||
|
||||
// An RPC that returns a 'proper' promise, which resolves to just the
|
||||
// response's fields; this is intended to be suitable for implementing
|
||||
// API procedures.
|
||||
async rpc(method, fields, expect) {
|
||||
const f = await promisify(cb => {
|
||||
return this._rpc(method, fields, expect, cb);
|
||||
})();
|
||||
|
||||
return f.fields;
|
||||
}
|
||||
|
||||
// Do the remarkably simple channel open handshake
|
||||
async open() {
|
||||
const ch = await this.allocate.bind(this)();
|
||||
return ch.rpc(defs.ChannelOpen, {outOfBand: ""},
|
||||
defs.ChannelOpenOk);
|
||||
}
|
||||
|
||||
close() {
|
||||
return promisify(cb => {
|
||||
return this.closeBecause("Goodbye", defs.constants.REPLY_SUCCESS,
|
||||
cb);
|
||||
})();
|
||||
}
|
||||
|
||||
// === Public API, declaring queues and stuff ===
|
||||
|
||||
assertQueue(queue, options) {
|
||||
return this.rpc(defs.QueueDeclare,
|
||||
Args.assertQueue(queue, options),
|
||||
defs.QueueDeclareOk);
|
||||
}
|
||||
|
||||
checkQueue(queue) {
|
||||
return this.rpc(defs.QueueDeclare,
|
||||
Args.checkQueue(queue),
|
||||
defs.QueueDeclareOk);
|
||||
}
|
||||
|
||||
deleteQueue(queue, options) {
|
||||
return this.rpc(defs.QueueDelete,
|
||||
Args.deleteQueue(queue, options),
|
||||
defs.QueueDeleteOk);
|
||||
}
|
||||
|
||||
purgeQueue(queue) {
|
||||
return this.rpc(defs.QueuePurge,
|
||||
Args.purgeQueue(queue),
|
||||
defs.QueuePurgeOk);
|
||||
}
|
||||
|
||||
bindQueue(queue, source, pattern, argt) {
|
||||
return this.rpc(defs.QueueBind,
|
||||
Args.bindQueue(queue, source, pattern, argt),
|
||||
defs.QueueBindOk);
|
||||
}
|
||||
|
||||
unbindQueue(queue, source, pattern, argt) {
|
||||
return this.rpc(defs.QueueUnbind,
|
||||
Args.unbindQueue(queue, source, pattern, argt),
|
||||
defs.QueueUnbindOk);
|
||||
}
|
||||
|
||||
assertExchange(exchange, type, options) {
|
||||
// The server reply is an empty set of fields, but it's convenient
|
||||
// to have the exchange name handed to the continuation.
|
||||
return this.rpc(defs.ExchangeDeclare,
|
||||
Args.assertExchange(exchange, type, options),
|
||||
defs.ExchangeDeclareOk)
|
||||
.then(_ok => { return { exchange }; });
|
||||
}
|
||||
|
||||
checkExchange(exchange) {
|
||||
return this.rpc(defs.ExchangeDeclare,
|
||||
Args.checkExchange(exchange),
|
||||
defs.ExchangeDeclareOk);
|
||||
}
|
||||
|
||||
deleteExchange(name, options) {
|
||||
return this.rpc(defs.ExchangeDelete,
|
||||
Args.deleteExchange(name, options),
|
||||
defs.ExchangeDeleteOk);
|
||||
}
|
||||
|
||||
bindExchange(dest, source, pattern, argt) {
|
||||
return this.rpc(defs.ExchangeBind,
|
||||
Args.bindExchange(dest, source, pattern, argt),
|
||||
defs.ExchangeBindOk);
|
||||
}
|
||||
|
||||
unbindExchange(dest, source, pattern, argt) {
|
||||
return this.rpc(defs.ExchangeUnbind,
|
||||
Args.unbindExchange(dest, source, pattern, argt),
|
||||
defs.ExchangeUnbindOk);
|
||||
}
|
||||
|
||||
// Working with messages
|
||||
|
||||
publish(exchange, routingKey, content, options) {
|
||||
const fieldsAndProps = Args.publish(exchange, routingKey, options);
|
||||
return this.sendMessage(fieldsAndProps, fieldsAndProps, content);
|
||||
}
|
||||
|
||||
sendToQueue(queue, content, options) {
|
||||
return this.publish('', queue, content, options);
|
||||
}
|
||||
|
||||
consume(queue, callback, options) {
|
||||
// NB we want the callback to be run synchronously, so that we've
|
||||
// registered the consumerTag before any messages can arrive.
|
||||
const fields = Args.consume(queue, options);
|
||||
return new Promise((resolve, reject) => {
|
||||
this._rpc(defs.BasicConsume, fields, defs.BasicConsumeOk, (err, ok) => {
|
||||
if (err) return reject(err);
|
||||
this.registerConsumer(ok.fields.consumerTag, callback);
|
||||
resolve(ok.fields);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async cancel(consumerTag) {
|
||||
const ok = await promisify(cb => {
|
||||
this._rpc(defs.BasicCancel, Args.cancel(consumerTag),
|
||||
defs.BasicCancelOk,
|
||||
cb);
|
||||
})()
|
||||
.then(ok => {
|
||||
this.unregisterConsumer(consumerTag);
|
||||
return ok.fields;
|
||||
});
|
||||
}
|
||||
|
||||
get(queue, options) {
|
||||
const fields = Args.get(queue, options);
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendOrEnqueue(defs.BasicGet, fields, (err, f) => {
|
||||
if (err) return reject(err);
|
||||
if (f.id === defs.BasicGetEmpty) {
|
||||
return resolve(false);
|
||||
}
|
||||
else if (f.id === defs.BasicGetOk) {
|
||||
const fields = f.fields;
|
||||
this.handleMessage = acceptMessage(m => {
|
||||
m.fields = fields;
|
||||
resolve(m);
|
||||
});
|
||||
}
|
||||
else {
|
||||
reject(new Error(`Unexpected response to BasicGet: ${inspect(f)}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ack(message, allUpTo) {
|
||||
this.sendImmediately(
|
||||
defs.BasicAck,
|
||||
Args.ack(message.fields.deliveryTag, allUpTo));
|
||||
}
|
||||
|
||||
ackAll() {
|
||||
this.sendImmediately(defs.BasicAck, Args.ack(0, true));
|
||||
}
|
||||
|
||||
nack(message, allUpTo, requeue) {
|
||||
this.sendImmediately(
|
||||
defs.BasicNack,
|
||||
Args.nack(message.fields.deliveryTag, allUpTo, requeue));
|
||||
}
|
||||
|
||||
nackAll(requeue) {
|
||||
this.sendImmediately(defs.BasicNack,
|
||||
Args.nack(0, true, requeue));
|
||||
}
|
||||
|
||||
// `Basic.Nack` is not available in older RabbitMQ versions (or in the
|
||||
// AMQP specification), so you have to use the one-at-a-time
|
||||
// `Basic.Reject`. This is otherwise synonymous with
|
||||
// `#nack(message, false, requeue)`.
|
||||
reject(message, requeue) {
|
||||
this.sendImmediately(
|
||||
defs.BasicReject,
|
||||
Args.reject(message.fields.deliveryTag, requeue));
|
||||
}
|
||||
|
||||
recover() {
|
||||
return this.rpc(defs.BasicRecover,
|
||||
Args.recover(),
|
||||
defs.BasicRecoverOk);
|
||||
}
|
||||
|
||||
qos(count, global) {
|
||||
return this.rpc(defs.BasicQos,
|
||||
Args.prefetch(count, global),
|
||||
defs.BasicQosOk);
|
||||
}
|
||||
}
|
||||
|
||||
// There are more options in AMQP than exposed here; RabbitMQ only
|
||||
// implements prefetch based on message count, and only for individual
|
||||
// channels or consumers. RabbitMQ v3.3.0 and after treat prefetch
|
||||
// (without `global` set) as per-consumer (for consumers following),
|
||||
// and prefetch with `global` set as per-channel.
|
||||
Channel.prototype.prefetch = Channel.prototype.qos
|
||||
|
||||
// Confirm channel. This is a channel with confirms 'switched on',
|
||||
// meaning sent messages will provoke a responding 'ack' or 'nack'
|
||||
// from the server. The upshot of this is that `publish` and
|
||||
// `sendToQueue` both take a callback, which will be called either
|
||||
// with `null` as its argument to signify 'ack', or an exception as
|
||||
// its argument to signify 'nack'.
|
||||
|
||||
class ConfirmChannel extends Channel {
|
||||
publish(exchange, routingKey, content, options, cb) {
|
||||
this.pushConfirmCallback(cb);
|
||||
return super.publish(exchange, routingKey, content, options);
|
||||
}
|
||||
|
||||
sendToQueue(queue, content, options, cb) {
|
||||
return this.publish('', queue, content, options, cb);
|
||||
}
|
||||
|
||||
waitForConfirms() {
|
||||
const awaiting = [];
|
||||
const unconfirmed = this.unconfirmed;
|
||||
unconfirmed.forEach((val, index) => {
|
||||
if (val !== null) {
|
||||
const confirmed = new Promise((resolve, reject) => {
|
||||
unconfirmed[index] = err => {
|
||||
if (val) val(err);
|
||||
if (err === null) resolve();
|
||||
else reject(err);
|
||||
};
|
||||
});
|
||||
awaiting.push(confirmed);
|
||||
}
|
||||
});
|
||||
// Channel closed
|
||||
if (!this.pending) {
|
||||
var cb;
|
||||
while (cb = this.unconfirmed.shift()) {
|
||||
if (cb) cb(new Error('channel closed'));
|
||||
}
|
||||
}
|
||||
return Promise.all(awaiting);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.ConfirmChannel = ConfirmChannel;
|
||||
module.exports.Channel = Channel;
|
||||
module.exports.ChannelModel = ChannelModel;
|
||||
345
node_modules/amqplib/lib/codec.js
generated
vendored
Normal file
345
node_modules/amqplib/lib/codec.js
generated
vendored
Normal file
@@ -0,0 +1,345 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
/*
|
||||
|
||||
The AMQP 0-9-1 is a mess when it comes to the types that can be
|
||||
encoded on the wire.
|
||||
|
||||
There are four encoding schemes, and three overlapping sets of types:
|
||||
frames, methods, (field-)tables, and properties.
|
||||
|
||||
Each *frame type* has a set layout in which values of given types are
|
||||
concatenated along with sections of "raw binary" data.
|
||||
|
||||
In frames there are `shortstr`s, that is length-prefixed strings of
|
||||
UTF8 chars, 8 bit unsigned integers (called `octet`), unsigned 16 bit
|
||||
integers (called `short` or `short-uint`), unsigned 32 bit integers
|
||||
(called `long` or `long-uint`), unsigned 64 bit integers (called
|
||||
`longlong` or `longlong-uint`), and flags (called `bit`).
|
||||
|
||||
Methods are encoded as a frame giving a method ID and a sequence of
|
||||
arguments of known types. The encoded method argument values are
|
||||
concatenated (with some fun complications around "packing" consecutive
|
||||
bit values into bytes).
|
||||
|
||||
Along with the types given in frames, method arguments may be long
|
||||
byte strings (`longstr`, not required to be UTF8) or 64 bit unsigned
|
||||
integers to be interpreted as timestamps (yeah I don't know why
|
||||
either), or arbitrary sets of key-value pairs (called `field-table`).
|
||||
|
||||
Inside a field table the keys are `shortstr` and the values are
|
||||
prefixed with a byte tag giving the type. The types are any of the
|
||||
above except for bits (which are replaced by byte-wide `bool`), along
|
||||
with a NULL value `void`, a special fixed-precision number encoding
|
||||
(`decimal`), IEEE754 `float`s and `double`s, signed integers,
|
||||
`field-array` (a sequence of tagged values), and nested field-tables.
|
||||
|
||||
RabbitMQ and QPid use a subset of the field-table types, and different
|
||||
value tags, established before the AMQP 0-9-1 specification was
|
||||
published. So far as I know, no-one uses the types and tags as
|
||||
published. http://www.rabbitmq.com/amqp-0-9-1-errata.html gives the
|
||||
list of field-table types.
|
||||
|
||||
Lastly, there are (sets of) properties, only one of which is given in
|
||||
AMQP 0-9-1: `BasicProperties`. These are almost the same as methods,
|
||||
except that they appear in content header frames, which include a
|
||||
content size, and they carry a set of flags indicating which
|
||||
properties are present. This scheme can save ones of bytes per message
|
||||
(messages which take a minimum of three frames each to send).
|
||||
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ints = require('buffer-more-ints');
|
||||
|
||||
// JavaScript uses only doubles so what I'm testing for is whether
|
||||
// it's *better* to encode a number as a float or double. This really
|
||||
// just amounts to testing whether there's a fractional part to the
|
||||
// number, except that see below. NB I don't use bitwise operations to
|
||||
// do this 'efficiently' -- it would mask the number to 32 bits.
|
||||
//
|
||||
// At 2^50, doubles don't have sufficient precision to distinguish
|
||||
// between floating point and integer numbers (`Math.pow(2, 50) + 0.1
|
||||
// === Math.pow(2, 50)` (and, above 2^53, doubles cannot represent all
|
||||
// integers (`Math.pow(2, 53) + 1 === Math.pow(2, 53)`)). Hence
|
||||
// anything with a magnitude at or above 2^50 may as well be encoded
|
||||
// as a 64-bit integer. Except that only signed integers are supported
|
||||
// by RabbitMQ, so anything above 2^63 - 1 must be a double.
|
||||
function isFloatingPoint(n) {
|
||||
return n >= 0x8000000000000000 ||
|
||||
(Math.abs(n) < 0x4000000000000
|
||||
&& Math.floor(n) !== n);
|
||||
}
|
||||
|
||||
function encodeTable(buffer, val, offset) {
|
||||
var start = offset;
|
||||
offset += 4; // leave room for the table length
|
||||
for (var key in val) {
|
||||
if (val[key] !== undefined) {
|
||||
var len = Buffer.byteLength(key);
|
||||
buffer.writeUInt8(len, offset); offset++;
|
||||
buffer.write(key, offset, 'utf8'); offset += len;
|
||||
offset += encodeFieldValue(buffer, val[key], offset);
|
||||
}
|
||||
}
|
||||
var size = offset - start;
|
||||
buffer.writeUInt32BE(size - 4, start);
|
||||
return size;
|
||||
}
|
||||
|
||||
function encodeArray(buffer, val, offset) {
|
||||
var start = offset;
|
||||
offset += 4;
|
||||
for (var i=0, num=val.length; i < num; i++) {
|
||||
offset += encodeFieldValue(buffer, val[i], offset);
|
||||
}
|
||||
var size = offset - start;
|
||||
buffer.writeUInt32BE(size - 4, start);
|
||||
return size;
|
||||
}
|
||||
|
||||
function encodeFieldValue(buffer, value, offset) {
|
||||
var start = offset;
|
||||
var type = typeof value, val = value;
|
||||
// A trapdoor for specifying a type, e.g., timestamp
|
||||
if (value && type === 'object' && value.hasOwnProperty('!')) {
|
||||
val = value.value;
|
||||
type = value['!'];
|
||||
}
|
||||
|
||||
// If it's a JS number, we'll have to guess what type to encode it
|
||||
// as.
|
||||
if (type == 'number') {
|
||||
// Making assumptions about the kind of number (floating point
|
||||
// v integer, signed, unsigned, size) desired is dangerous in
|
||||
// general; however, in practice RabbitMQ uses only
|
||||
// longstrings and unsigned integers in its arguments, and
|
||||
// other clients generally conflate number types anyway. So
|
||||
// the only distinction we care about is floating point vs
|
||||
// integers, preferring integers since those can be promoted
|
||||
// if necessary. If floating point is required, we may as well
|
||||
// use double precision.
|
||||
if (isFloatingPoint(val)) {
|
||||
type = 'double';
|
||||
}
|
||||
else { // only signed values are used in tables by
|
||||
// RabbitMQ. It *used* to (< v3.3.0) treat the byte 'b'
|
||||
// type as unsigned, but most clients (and the spec)
|
||||
// think it's signed, and now RabbitMQ does too.
|
||||
if (val < 128 && val >= -128) {
|
||||
type = 'byte';
|
||||
}
|
||||
else if (val >= -0x8000 && val < 0x8000) {
|
||||
type = 'short'
|
||||
}
|
||||
else if (val >= -0x80000000 && val < 0x80000000) {
|
||||
type = 'int';
|
||||
}
|
||||
else {
|
||||
type = 'long';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tag(t) { buffer.write(t, offset); offset++; }
|
||||
|
||||
switch (type) {
|
||||
case 'string': // no shortstr in field tables
|
||||
var len = Buffer.byteLength(val, 'utf8');
|
||||
tag('S');
|
||||
buffer.writeUInt32BE(len, offset); offset += 4;
|
||||
buffer.write(val, offset, 'utf8'); offset += len;
|
||||
break;
|
||||
case 'object':
|
||||
if (val === null) {
|
||||
tag('V');
|
||||
}
|
||||
else if (Array.isArray(val)) {
|
||||
tag('A');
|
||||
offset += encodeArray(buffer, val, offset);
|
||||
}
|
||||
else if (Buffer.isBuffer(val)) {
|
||||
tag('x');
|
||||
buffer.writeUInt32BE(val.length, offset); offset += 4;
|
||||
val.copy(buffer, offset); offset += val.length;
|
||||
}
|
||||
else {
|
||||
tag('F');
|
||||
offset += encodeTable(buffer, val, offset);
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
tag('t');
|
||||
buffer.writeUInt8((val) ? 1 : 0, offset); offset++;
|
||||
break;
|
||||
// These are the types that are either guessed above, or
|
||||
// explicitly given using the {'!': type} notation.
|
||||
case 'double':
|
||||
case 'float64':
|
||||
tag('d');
|
||||
buffer.writeDoubleBE(val, offset);
|
||||
offset += 8;
|
||||
break;
|
||||
case 'byte':
|
||||
case 'int8':
|
||||
tag('b');
|
||||
buffer.writeInt8(val, offset); offset++;
|
||||
break;
|
||||
case 'unsignedbyte':
|
||||
case 'uint8':
|
||||
tag('B');
|
||||
buffer.writeUInt8(val, offset); offset++;
|
||||
break;
|
||||
case 'short':
|
||||
case 'int16':
|
||||
tag('s');
|
||||
buffer.writeInt16BE(val, offset); offset += 2;
|
||||
break;
|
||||
case 'unsignedshort':
|
||||
case 'uint16':
|
||||
tag('u');
|
||||
buffer.writeUInt16BE(val, offset); offset += 2;
|
||||
break;
|
||||
case 'int':
|
||||
case 'int32':
|
||||
tag('I');
|
||||
buffer.writeInt32BE(val, offset); offset += 4;
|
||||
break;
|
||||
case 'unsignedint':
|
||||
case 'uint32':
|
||||
tag('i');
|
||||
buffer.writeUInt32BE(val, offset); offset += 4;
|
||||
break;
|
||||
case 'long':
|
||||
case 'int64':
|
||||
tag('l');
|
||||
ints.writeInt64BE(buffer, val, offset); offset += 8;
|
||||
break;
|
||||
|
||||
// Now for exotic types, those can _only_ be denoted by using
|
||||
// `{'!': type, value: val}
|
||||
case 'timestamp':
|
||||
tag('T');
|
||||
ints.writeUInt64BE(buffer, val, offset); offset += 8;
|
||||
break;
|
||||
case 'float':
|
||||
tag('f');
|
||||
buffer.writeFloatBE(val, offset); offset += 4;
|
||||
break;
|
||||
case 'decimal':
|
||||
tag('D');
|
||||
if (val.hasOwnProperty('places') && val.hasOwnProperty('digits')
|
||||
&& val.places >= 0 && val.places < 256) {
|
||||
buffer[offset] = val.places; offset++;
|
||||
buffer.writeUInt32BE(val.digits, offset); offset += 4;
|
||||
}
|
||||
else throw new TypeError(
|
||||
"Decimal value must be {'places': 0..255, 'digits': uint32}, " +
|
||||
"got " + JSON.stringify(val));
|
||||
break;
|
||||
default:
|
||||
throw new TypeError('Unknown type to encode: ' + type);
|
||||
}
|
||||
return offset - start;
|
||||
}
|
||||
|
||||
// Assume we're given a slice of the buffer that contains just the
|
||||
// fields.
|
||||
function decodeFields(slice) {
|
||||
var fields = {}, offset = 0, size = slice.length;
|
||||
var len, key, val;
|
||||
|
||||
function decodeFieldValue() {
|
||||
var tag = String.fromCharCode(slice[offset]); offset++;
|
||||
switch (tag) {
|
||||
case 'b':
|
||||
val = slice.readInt8(offset); offset++;
|
||||
break;
|
||||
case 'B':
|
||||
val = slice.readUInt8(offset); offset++;
|
||||
break;
|
||||
case 'S':
|
||||
len = slice.readUInt32BE(offset); offset += 4;
|
||||
val = slice.toString('utf8', offset, offset + len);
|
||||
offset += len;
|
||||
break;
|
||||
case 'I':
|
||||
val = slice.readInt32BE(offset); offset += 4;
|
||||
break;
|
||||
case 'i':
|
||||
val = slice.readUInt32BE(offset); offset += 4;
|
||||
break;
|
||||
case 'D': // only positive decimals, apparently.
|
||||
var places = slice[offset]; offset++;
|
||||
var digits = slice.readUInt32BE(offset); offset += 4;
|
||||
val = {'!': 'decimal', value: {places: places, digits: digits}};
|
||||
break;
|
||||
case 'T':
|
||||
val = ints.readUInt64BE(slice, offset); offset += 8;
|
||||
val = {'!': 'timestamp', value: val};
|
||||
break;
|
||||
case 'F':
|
||||
len = slice.readUInt32BE(offset); offset += 4;
|
||||
val = decodeFields(slice.subarray(offset, offset + len));
|
||||
offset += len;
|
||||
break;
|
||||
case 'A':
|
||||
len = slice.readUInt32BE(offset); offset += 4;
|
||||
decodeArray(offset + len);
|
||||
// NB decodeArray will itself update offset and val
|
||||
break;
|
||||
case 'd':
|
||||
val = slice.readDoubleBE(offset); offset += 8;
|
||||
break;
|
||||
case 'f':
|
||||
val = slice.readFloatBE(offset); offset += 4;
|
||||
break;
|
||||
case 'l':
|
||||
val = ints.readInt64BE(slice, offset); offset += 8;
|
||||
break;
|
||||
case 's':
|
||||
val = slice.readInt16BE(offset); offset += 2;
|
||||
break;
|
||||
case 'u':
|
||||
val = slice.readUInt16BE(offset); offset += 2;
|
||||
break;
|
||||
case 't':
|
||||
val = slice[offset] != 0; offset++;
|
||||
break;
|
||||
case 'V':
|
||||
val = null;
|
||||
break;
|
||||
case 'x':
|
||||
len = slice.readUInt32BE(offset); offset += 4;
|
||||
val = slice.subarray(offset, offset + len);
|
||||
offset += len;
|
||||
break;
|
||||
default:
|
||||
throw new TypeError('Unexpected type tag "' + tag +'"');
|
||||
}
|
||||
}
|
||||
|
||||
function decodeArray(until) {
|
||||
var vals = [];
|
||||
while (offset < until) {
|
||||
decodeFieldValue();
|
||||
vals.push(val);
|
||||
}
|
||||
val = vals;
|
||||
}
|
||||
|
||||
while (offset < size) {
|
||||
len = slice.readUInt8(offset); offset++;
|
||||
key = slice.toString('utf8', offset, offset + len);
|
||||
offset += len;
|
||||
decodeFieldValue();
|
||||
fields[key] = val;
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
module.exports.encodeTable = encodeTable;
|
||||
module.exports.decodeFields = decodeFields;
|
||||
189
node_modules/amqplib/lib/connect.js
generated
vendored
Normal file
189
node_modules/amqplib/lib/connect.js
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
// General-purpose API for glueing everything together.
|
||||
|
||||
'use strict';
|
||||
|
||||
var URL = require('url-parse');
|
||||
var QS = require('querystring');
|
||||
var Connection = require('./connection').Connection;
|
||||
var fmt = require('util').format;
|
||||
var credentials = require('./credentials');
|
||||
|
||||
function copyInto(obj, target) {
|
||||
var keys = Object.keys(obj);
|
||||
var i = keys.length;
|
||||
while (i--) {
|
||||
var k = keys[i];
|
||||
target[k] = obj[k];
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
// Adapted from util._extend, which is too fringe to use.
|
||||
function clone(obj) {
|
||||
return copyInto(obj, {});
|
||||
}
|
||||
|
||||
var CLIENT_PROPERTIES = {
|
||||
"product": "amqplib",
|
||||
"version": require('../package.json').version,
|
||||
"platform": fmt('Node.JS %s', process.version),
|
||||
"information": "https://amqp-node.github.io/amqplib/",
|
||||
"capabilities": {
|
||||
"publisher_confirms": true,
|
||||
"exchange_exchange_bindings": true,
|
||||
"basic.nack": true,
|
||||
"consumer_cancel_notify": true,
|
||||
"connection.blocked": true,
|
||||
"authentication_failure_close": true
|
||||
}
|
||||
};
|
||||
|
||||
// Construct the main frames used in the opening handshake
|
||||
function openFrames(vhost, query, credentials, extraClientProperties) {
|
||||
if (!vhost)
|
||||
vhost = '/';
|
||||
else
|
||||
vhost = QS.unescape(vhost);
|
||||
|
||||
var query = query || {};
|
||||
|
||||
function intOrDefault(val, def) {
|
||||
return (val === undefined) ? def : parseInt(val);
|
||||
}
|
||||
|
||||
var clientProperties = Object.create(CLIENT_PROPERTIES);
|
||||
|
||||
return {
|
||||
// start-ok
|
||||
'clientProperties': copyInto(extraClientProperties, clientProperties),
|
||||
'mechanism': credentials.mechanism,
|
||||
'response': credentials.response(),
|
||||
'locale': query.locale || 'en_US',
|
||||
|
||||
// tune-ok
|
||||
'channelMax': intOrDefault(query.channelMax, 0),
|
||||
'frameMax': intOrDefault(query.frameMax, 131072),
|
||||
'heartbeat': intOrDefault(query.heartbeat, 0),
|
||||
|
||||
// open
|
||||
'virtualHost': vhost,
|
||||
'capabilities': '',
|
||||
'insist': 0
|
||||
};
|
||||
}
|
||||
|
||||
// Decide on credentials based on what we're supplied.
|
||||
function credentialsFromUrl(parts) {
|
||||
var user = 'guest', passwd = 'guest';
|
||||
if (parts.username != '' || parts.password != '') {
|
||||
user = (parts.username) ? unescape(parts.username) : '';
|
||||
passwd = (parts.password) ? unescape(parts.password) : '';
|
||||
}
|
||||
return credentials.plain(user, passwd);
|
||||
}
|
||||
|
||||
function connect(url, socketOptions, openCallback) {
|
||||
// tls.connect uses `util._extend()` on the options given it, which
|
||||
// copies only properties mentioned in `Object.keys()`, when
|
||||
// processing the options. So I have to make copies too, rather
|
||||
// than using `Object.create()`.
|
||||
var sockopts = clone(socketOptions || {});
|
||||
url = url || 'amqp://localhost';
|
||||
|
||||
var noDelay = !!sockopts.noDelay;
|
||||
var timeout = sockopts.timeout;
|
||||
var keepAlive = !!sockopts.keepAlive;
|
||||
// 0 is default for node
|
||||
var keepAliveDelay = sockopts.keepAliveDelay || 0;
|
||||
|
||||
var extraClientProperties = sockopts.clientProperties || {};
|
||||
|
||||
var protocol, fields;
|
||||
if (typeof url === 'object') {
|
||||
protocol = (url.protocol || 'amqp') + ':';
|
||||
sockopts.host = url.hostname;
|
||||
sockopts.servername = sockopts.servername || url.hostname;
|
||||
sockopts.port = url.port || ((protocol === 'amqp:') ? 5672 : 5671);
|
||||
|
||||
var user, pass;
|
||||
// Only default if both are missing, to have the same behaviour as
|
||||
// the stringly URL.
|
||||
if (url.username == undefined && url.password == undefined) {
|
||||
user = 'guest'; pass = 'guest';
|
||||
} else {
|
||||
user = url.username || '';
|
||||
pass = url.password || '';
|
||||
}
|
||||
|
||||
var config = {
|
||||
locale: url.locale,
|
||||
channelMax: url.channelMax,
|
||||
frameMax: url.frameMax,
|
||||
heartbeat: url.heartbeat,
|
||||
};
|
||||
|
||||
fields = openFrames(url.vhost, config, sockopts.credentials || credentials.plain(user, pass), extraClientProperties);
|
||||
} else {
|
||||
var parts = URL(url, true); // yes, parse the query string
|
||||
protocol = parts.protocol;
|
||||
sockopts.host = parts.hostname;
|
||||
sockopts.servername = sockopts.servername || parts.hostname;
|
||||
sockopts.port = parseInt(parts.port) || ((protocol === 'amqp:') ? 5672 : 5671);
|
||||
var vhost = parts.pathname ? parts.pathname.substr(1) : null;
|
||||
fields = openFrames(vhost, parts.query, sockopts.credentials || credentialsFromUrl(parts), extraClientProperties);
|
||||
}
|
||||
|
||||
var sockok = false;
|
||||
var sock;
|
||||
|
||||
function onConnect() {
|
||||
sockok = true;
|
||||
sock.setNoDelay(noDelay);
|
||||
if (keepAlive) sock.setKeepAlive(keepAlive, keepAliveDelay);
|
||||
|
||||
var c = new Connection(sock);
|
||||
c.open(fields, function(err, ok) {
|
||||
// disable timeout once the connection is open, we don't want
|
||||
// it fouling things
|
||||
if (timeout) sock.setTimeout(0);
|
||||
if (err === null) {
|
||||
openCallback(null, c);
|
||||
} else {
|
||||
// The connection isn't closed by the server on e.g. wrong password
|
||||
sock.end();
|
||||
sock.destroy();
|
||||
openCallback(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (protocol === 'amqp:') {
|
||||
sock = require('net').connect(sockopts, onConnect);
|
||||
}
|
||||
else if (protocol === 'amqps:') {
|
||||
sock = require('tls').connect(sockopts, onConnect);
|
||||
}
|
||||
else {
|
||||
throw new Error("Expected amqp: or amqps: as the protocol; got " + protocol);
|
||||
}
|
||||
|
||||
if (timeout) {
|
||||
sock.setTimeout(timeout, function() {
|
||||
sock.end();
|
||||
sock.destroy();
|
||||
openCallback(new Error('connect ETIMEDOUT'));
|
||||
});
|
||||
}
|
||||
|
||||
sock.once('error', function(err) {
|
||||
if (!sockok) openCallback(err);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
module.exports.connect = connect;
|
||||
module.exports.credentialsFromUrl = credentialsFromUrl;
|
||||
675
node_modules/amqplib/lib/connection.js
generated
vendored
Normal file
675
node_modules/amqplib/lib/connection.js
generated
vendored
Normal file
@@ -0,0 +1,675 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
'use strict';
|
||||
|
||||
var defs = require('./defs');
|
||||
var constants = defs.constants;
|
||||
var frame = require('./frame');
|
||||
var HEARTBEAT = frame.HEARTBEAT;
|
||||
var Mux = require('./mux').Mux;
|
||||
|
||||
var Duplex = require('stream').Duplex;
|
||||
var EventEmitter = require('events');
|
||||
var Heart = require('./heartbeat').Heart;
|
||||
|
||||
var methodName = require('./format').methodName;
|
||||
var closeMsg = require('./format').closeMessage;
|
||||
var inspect = require('./format').inspect;
|
||||
|
||||
var BitSet = require('./bitset').BitSet;
|
||||
var fmt = require('util').format;
|
||||
var PassThrough = require('stream').PassThrough;
|
||||
var IllegalOperationError = require('./error').IllegalOperationError;
|
||||
var stackCapture = require('./error').stackCapture;
|
||||
|
||||
// High-water mark for channel write buffers, in 'objects' (which are
|
||||
// encoded frames as buffers).
|
||||
var DEFAULT_WRITE_HWM = 1024;
|
||||
// If all the frames of a message (method, properties, content) total
|
||||
// to less than this, copy them into a single buffer and write it all
|
||||
// at once. Note that this is less than the minimum frame size: if it
|
||||
// was greater, we might have to fragment the content.
|
||||
var SINGLE_CHUNK_THRESHOLD = 2048;
|
||||
|
||||
class Connection extends EventEmitter {
|
||||
constructor (underlying) {
|
||||
super();
|
||||
|
||||
var stream = this.stream = wrapStream(underlying);
|
||||
this.muxer = new Mux(stream);
|
||||
|
||||
// frames
|
||||
this.rest = Buffer.alloc(0);
|
||||
this.frameMax = constants.FRAME_MIN_SIZE;
|
||||
this.sentSinceLastCheck = false;
|
||||
this.recvSinceLastCheck = false;
|
||||
|
||||
this.expectSocketClose = false;
|
||||
this.freeChannels = new BitSet();
|
||||
this.channels = [{
|
||||
channel: { accept: channel0(this) },
|
||||
buffer: underlying
|
||||
}];
|
||||
}
|
||||
|
||||
// This changed between versions, as did the codec, methods, etc. AMQP
|
||||
// 0-9-1 is fairly similar to 0.8, but better, and nothing implements
|
||||
// 0.8 that doesn't implement 0-9-1. In other words, it doesn't make
|
||||
// much sense to generalise here.
|
||||
sendProtocolHeader () {
|
||||
this.sendBytes(frame.PROTOCOL_HEADER);
|
||||
}
|
||||
|
||||
/*
|
||||
The frighteningly complicated opening protocol (spec section 2.2.4):
|
||||
|
||||
Client -> Server
|
||||
|
||||
protocol header ->
|
||||
<- start
|
||||
start-ok ->
|
||||
.. next two zero or more times ..
|
||||
<- secure
|
||||
secure-ok ->
|
||||
<- tune
|
||||
tune-ok ->
|
||||
open ->
|
||||
<- open-ok
|
||||
|
||||
If I'm only supporting SASL's PLAIN mechanism (which I am for the time
|
||||
being), it gets a bit easier since the server won't in general send
|
||||
back a `secure`, it'll just send `tune` after the `start-ok`.
|
||||
(SASL PLAIN: http://tools.ietf.org/html/rfc4616)
|
||||
|
||||
*/
|
||||
open (allFields, openCallback0) {
|
||||
var self = this;
|
||||
var openCallback = openCallback0 || function () { };
|
||||
|
||||
// This is where we'll put our negotiated values
|
||||
var tunedOptions = Object.create(allFields);
|
||||
|
||||
function wait (k) {
|
||||
self.step(function (err, frame) {
|
||||
if (err !== null)
|
||||
bail(err);
|
||||
else if (frame.channel !== 0) {
|
||||
bail(new Error(
|
||||
fmt("Frame on channel != 0 during handshake: %s",
|
||||
inspect(frame, false))));
|
||||
}
|
||||
else
|
||||
k(frame);
|
||||
});
|
||||
}
|
||||
|
||||
function expect (Method, k) {
|
||||
wait(function (frame) {
|
||||
if (frame.id === Method)
|
||||
k(frame);
|
||||
else {
|
||||
bail(new Error(
|
||||
fmt("Expected %s; got %s",
|
||||
methodName(Method), inspect(frame, false))));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function bail (err) {
|
||||
openCallback(err);
|
||||
}
|
||||
|
||||
function send (Method) {
|
||||
// This can throw an exception if there's some problem with the
|
||||
// options; e.g., something is a string instead of a number.
|
||||
self.sendMethod(0, Method, tunedOptions);
|
||||
}
|
||||
|
||||
function negotiate (server, desired) {
|
||||
// We get sent values for channelMax, frameMax and heartbeat,
|
||||
// which we may accept or lower (subject to a minimum for
|
||||
// frameMax, but we'll leave that to the server to enforce). In
|
||||
// all cases, `0` really means "no limit", or rather the highest
|
||||
// value in the encoding, e.g., unsigned short for channelMax.
|
||||
if (server === 0 || desired === 0) {
|
||||
// i.e., whichever places a limit, if either
|
||||
return Math.max(server, desired);
|
||||
}
|
||||
else {
|
||||
return Math.min(server, desired);
|
||||
}
|
||||
}
|
||||
|
||||
function onStart (start) {
|
||||
var mechanisms = start.fields.mechanisms.toString().split(' ');
|
||||
if (mechanisms.indexOf(allFields.mechanism) < 0) {
|
||||
bail(new Error(fmt('SASL mechanism %s is not provided by the server',
|
||||
allFields.mechanism)));
|
||||
return;
|
||||
}
|
||||
self.serverProperties = start.fields.serverProperties;
|
||||
try {
|
||||
send(defs.ConnectionStartOk);
|
||||
} catch (err) {
|
||||
bail(err);
|
||||
return;
|
||||
}
|
||||
wait(afterStartOk);
|
||||
}
|
||||
|
||||
function afterStartOk (reply) {
|
||||
switch (reply.id) {
|
||||
case defs.ConnectionSecure:
|
||||
bail(new Error(
|
||||
"Wasn't expecting to have to go through secure"));
|
||||
break;
|
||||
case defs.ConnectionClose:
|
||||
bail(new Error(fmt("Handshake terminated by server: %s",
|
||||
closeMsg(reply))));
|
||||
break;
|
||||
case defs.ConnectionTune:
|
||||
var fields = reply.fields;
|
||||
tunedOptions.frameMax =
|
||||
negotiate(fields.frameMax, allFields.frameMax);
|
||||
tunedOptions.channelMax =
|
||||
negotiate(fields.channelMax, allFields.channelMax);
|
||||
tunedOptions.heartbeat =
|
||||
negotiate(fields.heartbeat, allFields.heartbeat);
|
||||
try {
|
||||
send(defs.ConnectionTuneOk);
|
||||
send(defs.ConnectionOpen);
|
||||
} catch (err) {
|
||||
bail(err);
|
||||
return;
|
||||
}
|
||||
expect(defs.ConnectionOpenOk, onOpenOk);
|
||||
break;
|
||||
default:
|
||||
bail(new Error(
|
||||
fmt("Expected connection.secure, connection.close, " +
|
||||
"or connection.tune during handshake; got %s",
|
||||
inspect(reply, false))));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function onOpenOk (openOk) {
|
||||
// Impose the maximum of the encoded value, if the negotiated
|
||||
// value is zero, meaning "no, no limits"
|
||||
self.channelMax = tunedOptions.channelMax || 0xffff;
|
||||
self.frameMax = tunedOptions.frameMax || 0xffffffff;
|
||||
// 0 means "no heartbeat", rather than "maximum period of
|
||||
// heartbeating"
|
||||
self.heartbeat = tunedOptions.heartbeat;
|
||||
self.heartbeater = self.startHeartbeater();
|
||||
self.accept = mainAccept;
|
||||
succeed(openOk);
|
||||
}
|
||||
|
||||
// If the server closes the connection, it's probably because of
|
||||
// something we did
|
||||
function endWhileOpening (err) {
|
||||
bail(err || new Error('Socket closed abruptly ' +
|
||||
'during opening handshake'));
|
||||
}
|
||||
|
||||
this.stream.on('end', endWhileOpening);
|
||||
this.stream.on('error', endWhileOpening);
|
||||
|
||||
function succeed (ok) {
|
||||
self.stream.removeListener('end', endWhileOpening);
|
||||
self.stream.removeListener('error', endWhileOpening);
|
||||
self.stream.on('error', self.onSocketError.bind(self));
|
||||
self.stream.on('end', self.onSocketError.bind(
|
||||
self, new Error('Unexpected close')));
|
||||
self.on('frameError', self.onSocketError.bind(self));
|
||||
self.acceptLoop();
|
||||
openCallback(null, ok);
|
||||
}
|
||||
|
||||
// Now kick off the handshake by prompting the server
|
||||
this.sendProtocolHeader();
|
||||
expect(defs.ConnectionStart, onStart);
|
||||
}
|
||||
|
||||
// Closing things: AMQP has a closing handshake that applies to
|
||||
// closing both connects and channels. As the initiating party, I send
|
||||
// Close, then ignore all frames until I see either CloseOK --
|
||||
// which signifies that the other party has seen the Close and shut
|
||||
// the connection or channel down, so it's fine to free resources; or
|
||||
// Close, which means the other party also wanted to close the
|
||||
// whatever, and I should send CloseOk so it can free resources,
|
||||
// then go back to waiting for the CloseOk. If I receive a Close
|
||||
// out of the blue, I should throw away any unsent frames (they will
|
||||
// be ignored anyway) and send CloseOk, then clean up resources. In
|
||||
// general, Close out of the blue signals an error (or a forced
|
||||
// closure, which may as well be an error).
|
||||
//
|
||||
// RUNNING [1] --- send Close ---> Closing [2] ---> recv Close --+
|
||||
// | | [3]
|
||||
// | +------ send CloseOk ------+
|
||||
// recv Close recv CloseOk
|
||||
// | |
|
||||
// V V
|
||||
// Ended [4] ---- send CloseOk ---> Closed [5]
|
||||
//
|
||||
// [1] All frames accepted; getting a Close frame from the server
|
||||
// moves to Ended; client may initiate a close by sending Close
|
||||
// itself.
|
||||
// [2] Client has initiated a close; only CloseOk or (simulataneously
|
||||
// sent) Close is accepted.
|
||||
// [3] Simultaneous close
|
||||
// [4] Server won't send any more frames; accept no more frames, send
|
||||
// CloseOk.
|
||||
// [5] Fully closed, client will send no more, server will send no
|
||||
// more. Signal 'close' or 'error'.
|
||||
//
|
||||
// There are two signalling mechanisms used in the API. The first is
|
||||
// that calling `close` will return a promise, that will either
|
||||
// resolve once the connection or channel is cleanly shut down, or
|
||||
// will reject if the shutdown times out.
|
||||
//
|
||||
// The second is the 'close' and 'error' events. These are
|
||||
// emitted as above. The events will fire *before* promises are
|
||||
// resolved.
|
||||
// Close the connection without even giving a reason. Typical.
|
||||
close (closeCallback) {
|
||||
var k = closeCallback && function () { closeCallback(null); };
|
||||
this.closeBecause("Cheers, thanks", constants.REPLY_SUCCESS, k);
|
||||
}
|
||||
|
||||
// Close with a reason and a 'code'. I'm pretty sure RabbitMQ totally
|
||||
// ignores these; maybe it logs them. The continuation will be invoked
|
||||
// when the CloseOk has been received, and before the 'close' event.
|
||||
closeBecause (reason, code, k) {
|
||||
this.sendMethod(0, defs.ConnectionClose, {
|
||||
replyText: reason,
|
||||
replyCode: code,
|
||||
methodId: 0, classId: 0
|
||||
});
|
||||
var s = stackCapture('closeBecause called: ' + reason);
|
||||
this.toClosing(s, k);
|
||||
}
|
||||
|
||||
closeWithError (reason, code, error) {
|
||||
this.emit('error', error);
|
||||
this.closeBecause(reason, code);
|
||||
}
|
||||
|
||||
onSocketError (err) {
|
||||
if (!this.expectSocketClose) {
|
||||
// forestall any more calls to onSocketError, since we're signed
|
||||
// up for `'error'` *and* `'end'`
|
||||
this.expectSocketClose = true;
|
||||
this.emit('error', err);
|
||||
var s = stackCapture('Socket error');
|
||||
this.toClosed(s, err);
|
||||
}
|
||||
}
|
||||
|
||||
// A close has been initiated. Repeat: a close has been initiated.
|
||||
// This means we should not send more frames, anyway they will be
|
||||
// ignored. We also have to shut down all the channels.
|
||||
toClosing (capturedStack, k) {
|
||||
var send = this.sendMethod.bind(this);
|
||||
|
||||
this.accept = function (f) {
|
||||
if (f.id === defs.ConnectionCloseOk) {
|
||||
if (k)
|
||||
k();
|
||||
var s = stackCapture('ConnectionCloseOk received');
|
||||
this.toClosed(s, undefined);
|
||||
}
|
||||
else if (f.id === defs.ConnectionClose) {
|
||||
send(0, defs.ConnectionCloseOk, {});
|
||||
}
|
||||
// else ignore frame
|
||||
};
|
||||
invalidateSend(this, 'Connection closing', capturedStack);
|
||||
}
|
||||
|
||||
_closeChannels (capturedStack) {
|
||||
for (var i = 1; i < this.channels.length; i++) {
|
||||
var ch = this.channels[i];
|
||||
if (ch !== null) {
|
||||
ch.channel.toClosed(capturedStack); // %%% or with an error? not clear
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A close has been confirmed. Cease all communication.
|
||||
toClosed (capturedStack, maybeErr) {
|
||||
this._closeChannels(capturedStack);
|
||||
var info = fmt('Connection closed (%s)',
|
||||
(maybeErr) ? maybeErr.toString() : 'by client');
|
||||
// Tidy up, invalidate enverything, dynamite the bridges.
|
||||
invalidateSend(this, info, capturedStack);
|
||||
this.accept = invalidOp(info, capturedStack);
|
||||
this.close = function (cb) {
|
||||
cb && cb(new IllegalOperationError(info, capturedStack));
|
||||
};
|
||||
if (this.heartbeater)
|
||||
this.heartbeater.clear();
|
||||
// This is certainly true now, if it wasn't before
|
||||
this.expectSocketClose = true;
|
||||
this.stream.end();
|
||||
this.emit('close', maybeErr);
|
||||
}
|
||||
|
||||
_updateSecret(newSecret, reason, cb) {
|
||||
this.sendMethod(0, defs.ConnectionUpdateSecret, {
|
||||
newSecret,
|
||||
reason
|
||||
});
|
||||
this.once('update-secret-ok', cb);
|
||||
}
|
||||
|
||||
// ===
|
||||
startHeartbeater () {
|
||||
if (this.heartbeat === 0)
|
||||
return null;
|
||||
else {
|
||||
var self = this;
|
||||
var hb = new Heart(this.heartbeat,
|
||||
this.checkSend.bind(this),
|
||||
this.checkRecv.bind(this));
|
||||
hb.on('timeout', function () {
|
||||
var hberr = new Error("Heartbeat timeout");
|
||||
self.emit('error', hberr);
|
||||
var s = stackCapture('Heartbeat timeout');
|
||||
self.toClosed(s, hberr);
|
||||
});
|
||||
hb.on('beat', function () {
|
||||
self.sendHeartbeat();
|
||||
});
|
||||
return hb;
|
||||
}
|
||||
}
|
||||
|
||||
// I use an array to keep track of the channels, rather than an
|
||||
// object. The channel identifiers are numbers, and allocated by the
|
||||
// connection. If I try to allocate low numbers when they are
|
||||
// available (which I do, by looking from the start of the bitset),
|
||||
// this ought to keep the array small, and out of 'sparse array
|
||||
// storage'. I also set entries to null, rather than deleting them, in
|
||||
// the expectation that the next channel allocation will fill the slot
|
||||
// again rather than growing the array. See
|
||||
// http://www.html5rocks.com/en/tutorials/speed/v8/
|
||||
freshChannel (channel, options) {
|
||||
var next = this.freeChannels.nextClearBit(1);
|
||||
if (next < 0 || next > this.channelMax)
|
||||
throw new Error("No channels left to allocate");
|
||||
this.freeChannels.set(next);
|
||||
|
||||
var hwm = (options && options.highWaterMark) || DEFAULT_WRITE_HWM;
|
||||
var writeBuffer = new PassThrough({
|
||||
objectMode: true, highWaterMark: hwm
|
||||
});
|
||||
this.channels[next] = { channel: channel, buffer: writeBuffer };
|
||||
writeBuffer.on('drain', function () {
|
||||
channel.onBufferDrain();
|
||||
});
|
||||
this.muxer.pipeFrom(writeBuffer);
|
||||
return next;
|
||||
}
|
||||
|
||||
releaseChannel (channel) {
|
||||
this.freeChannels.clear(channel);
|
||||
var buffer = this.channels[channel].buffer;
|
||||
buffer.end(); // will also cause it to be unpiped
|
||||
this.channels[channel] = null;
|
||||
}
|
||||
|
||||
acceptLoop () {
|
||||
var self = this;
|
||||
|
||||
function go () {
|
||||
try {
|
||||
var f; while (f = self.recvFrame())
|
||||
self.accept(f);
|
||||
}
|
||||
catch (e) {
|
||||
self.emit('frameError', e);
|
||||
}
|
||||
}
|
||||
self.stream.on('readable', go);
|
||||
go();
|
||||
}
|
||||
|
||||
step (cb) {
|
||||
var self = this;
|
||||
function recv () {
|
||||
var f;
|
||||
try {
|
||||
f = self.recvFrame();
|
||||
}
|
||||
catch (e) {
|
||||
cb(e, null);
|
||||
return;
|
||||
}
|
||||
if (f)
|
||||
cb(null, f);
|
||||
else
|
||||
self.stream.once('readable', recv);
|
||||
}
|
||||
recv();
|
||||
}
|
||||
|
||||
checkSend () {
|
||||
var check = this.sentSinceLastCheck;
|
||||
this.sentSinceLastCheck = false;
|
||||
return check;
|
||||
}
|
||||
|
||||
checkRecv () {
|
||||
var check = this.recvSinceLastCheck;
|
||||
this.recvSinceLastCheck = false;
|
||||
return check;
|
||||
}
|
||||
|
||||
sendBytes (bytes) {
|
||||
this.sentSinceLastCheck = true;
|
||||
this.stream.write(bytes);
|
||||
}
|
||||
|
||||
sendHeartbeat () {
|
||||
return this.sendBytes(frame.HEARTBEAT_BUF);
|
||||
}
|
||||
|
||||
sendMethod (channel, Method, fields) {
|
||||
var frame = encodeMethod(Method, channel, fields);
|
||||
this.sentSinceLastCheck = true;
|
||||
var buffer = this.channels[channel].buffer;
|
||||
return buffer.write(frame);
|
||||
}
|
||||
|
||||
sendMessage (channel, Method, fields, Properties, props, content) {
|
||||
if (!Buffer.isBuffer(content))
|
||||
throw new TypeError('content is not a buffer');
|
||||
|
||||
var mframe = encodeMethod(Method, channel, fields);
|
||||
var pframe = encodeProperties(Properties, channel,
|
||||
content.length, props);
|
||||
var buffer = this.channels[channel].buffer;
|
||||
this.sentSinceLastCheck = true;
|
||||
|
||||
var methodHeaderLen = mframe.length + pframe.length;
|
||||
var bodyLen = (content.length > 0) ?
|
||||
content.length + FRAME_OVERHEAD : 0;
|
||||
var allLen = methodHeaderLen + bodyLen;
|
||||
|
||||
if (allLen < SINGLE_CHUNK_THRESHOLD) {
|
||||
// Use `allocUnsafe` to avoid excessive allocations and CPU usage
|
||||
// from zeroing. The returned Buffer is not zeroed and so must be
|
||||
// completely filled to be used safely.
|
||||
// See https://github.com/amqp-node/amqplib/pull/695
|
||||
var all = Buffer.allocUnsafe(allLen);
|
||||
var offset = mframe.copy(all, 0);
|
||||
offset += pframe.copy(all, offset);
|
||||
|
||||
if (bodyLen > 0)
|
||||
makeBodyFrame(channel, content).copy(all, offset);
|
||||
return buffer.write(all);
|
||||
}
|
||||
else {
|
||||
if (methodHeaderLen < SINGLE_CHUNK_THRESHOLD) {
|
||||
// Use `allocUnsafe` to avoid excessive allocations and CPU usage
|
||||
// from zeroing. The returned Buffer is not zeroed and so must be
|
||||
// completely filled to be used safely.
|
||||
// See https://github.com/amqp-node/amqplib/pull/695
|
||||
var both = Buffer.allocUnsafe(methodHeaderLen);
|
||||
var offset = mframe.copy(both, 0);
|
||||
pframe.copy(both, offset);
|
||||
buffer.write(both);
|
||||
}
|
||||
else {
|
||||
buffer.write(mframe);
|
||||
buffer.write(pframe);
|
||||
}
|
||||
return this.sendContent(channel, content);
|
||||
}
|
||||
}
|
||||
|
||||
sendContent (channel, body) {
|
||||
if (!Buffer.isBuffer(body)) {
|
||||
throw new TypeError(fmt("Expected buffer; got %s", body));
|
||||
}
|
||||
var writeResult = true;
|
||||
var buffer = this.channels[channel].buffer;
|
||||
|
||||
var maxBody = this.frameMax - FRAME_OVERHEAD;
|
||||
|
||||
for (var offset = 0; offset < body.length; offset += maxBody) {
|
||||
var end = offset + maxBody;
|
||||
var slice = (end > body.length) ? body.subarray(offset) : body.subarray(offset, end);
|
||||
var bodyFrame = makeBodyFrame(channel, slice);
|
||||
writeResult = buffer.write(bodyFrame);
|
||||
}
|
||||
this.sentSinceLastCheck = true;
|
||||
return writeResult;
|
||||
}
|
||||
|
||||
recvFrame () {
|
||||
// %%% identifying invariants might help here?
|
||||
var frame = parseFrame(this.rest);
|
||||
|
||||
if (!frame) {
|
||||
var incoming = this.stream.read();
|
||||
if (incoming === null) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
this.recvSinceLastCheck = true;
|
||||
this.rest = Buffer.concat([this.rest, incoming]);
|
||||
return this.recvFrame();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.rest = frame.rest;
|
||||
return decodeFrame(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usual frame accept mode
|
||||
function mainAccept(frame) {
|
||||
var rec = this.channels[frame.channel];
|
||||
if (rec) { return rec.channel.accept(frame); }
|
||||
// NB CHANNEL_ERROR may not be right, but I don't know what is ..
|
||||
else
|
||||
this.closeWithError(
|
||||
fmt('Frame on unknown channel %d', frame.channel),
|
||||
constants.CHANNEL_ERROR,
|
||||
new Error(fmt("Frame on unknown channel: %s",
|
||||
inspect(frame, false))));
|
||||
}
|
||||
|
||||
// Handle anything that comes through on channel 0, that's the
|
||||
// connection control channel. This is only used once mainAccept is
|
||||
// installed as the frame handler, after the opening handshake.
|
||||
function channel0(connection) {
|
||||
return function(f) {
|
||||
// Once we get a 'close', we know 1. we'll get no more frames, and
|
||||
// 2. anything we send except close, or close-ok, will be
|
||||
// ignored. If we already sent 'close', this won't be invoked since
|
||||
// we're already in closing mode; if we didn't well we're not going
|
||||
// to send it now are we.
|
||||
if (f === HEARTBEAT); // ignore; it's already counted as activity
|
||||
// on the socket, which is its purpose
|
||||
else if (f.id === defs.ConnectionClose) {
|
||||
// Oh. OK. I guess we're done here then.
|
||||
connection.sendMethod(0, defs.ConnectionCloseOk, {});
|
||||
var emsg = fmt('Connection closed: %s', closeMsg(f));
|
||||
var s = stackCapture(emsg);
|
||||
var e = new Error(emsg);
|
||||
e.code = f.fields.replyCode;
|
||||
if (isFatalError(e)) {
|
||||
connection.emit('error', e);
|
||||
}
|
||||
connection.toClosed(s, e);
|
||||
}
|
||||
else if (f.id === defs.ConnectionBlocked) {
|
||||
connection.emit('blocked', f.fields.reason);
|
||||
}
|
||||
else if (f.id === defs.ConnectionUnblocked) {
|
||||
connection.emit('unblocked');
|
||||
}
|
||||
else if (f.id === defs.ConnectionUpdateSecretOk) {
|
||||
connection.emit('update-secret-ok');
|
||||
}
|
||||
else {
|
||||
connection.closeWithError(
|
||||
fmt("Unexpected frame on channel 0"),
|
||||
constants.UNEXPECTED_FRAME,
|
||||
new Error(fmt("Unexpected frame on channel 0: %s",
|
||||
inspect(f, false))));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function invalidOp(msg, stack) {
|
||||
return function() {
|
||||
throw new IllegalOperationError(msg, stack);
|
||||
};
|
||||
}
|
||||
|
||||
function invalidateSend(conn, msg, stack) {
|
||||
conn.sendMethod = conn.sendContent = conn.sendMessage =
|
||||
invalidOp(msg, stack);
|
||||
}
|
||||
|
||||
var encodeMethod = defs.encodeMethod;
|
||||
var encodeProperties = defs.encodeProperties;
|
||||
|
||||
var FRAME_OVERHEAD = defs.FRAME_OVERHEAD;
|
||||
var makeBodyFrame = frame.makeBodyFrame;
|
||||
|
||||
var parseFrame = frame.parseFrame;
|
||||
var decodeFrame = frame.decodeFrame;
|
||||
|
||||
function wrapStream(s) {
|
||||
if (s instanceof Duplex) return s;
|
||||
else {
|
||||
var ws = new Duplex();
|
||||
ws.wrap(s); //wraps the readable side of things
|
||||
ws._write = function(chunk, encoding, callback) {
|
||||
return s.write(chunk, encoding, callback);
|
||||
};
|
||||
return ws;
|
||||
}
|
||||
}
|
||||
|
||||
function isFatalError(error) {
|
||||
switch (error && error.code) {
|
||||
case defs.constants.CONNECTION_FORCED:
|
||||
case defs.constants.REPLY_SUCCESS:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.Connection = Connection;
|
||||
module.exports.isFatalError = isFatalError;
|
||||
42
node_modules/amqplib/lib/credentials.js
generated
vendored
Normal file
42
node_modules/amqplib/lib/credentials.js
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
// Different kind of credentials that can be supplied when opening a
|
||||
// connection, corresponding to SASL mechanisms There's only two
|
||||
// useful mechanisms that RabbitMQ implements:
|
||||
// * PLAIN (send username and password in the plain)
|
||||
// * EXTERNAL (assume the server will figure out who you are from
|
||||
// context, i.e., your SSL certificate)
|
||||
var codec = require('./codec')
|
||||
|
||||
module.exports.plain = function(user, passwd) {
|
||||
return {
|
||||
mechanism: 'PLAIN',
|
||||
response: function() {
|
||||
return Buffer.from(['', user, passwd].join(String.fromCharCode(0)))
|
||||
},
|
||||
username: user,
|
||||
password: passwd
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.amqplain = function(user, passwd) {
|
||||
return {
|
||||
mechanism: 'AMQPLAIN',
|
||||
response: function() {
|
||||
const buffer = Buffer.alloc(16384);
|
||||
const size = codec.encodeTable(buffer, { LOGIN: user, PASSWORD: passwd}, 0);
|
||||
return buffer.subarray(4, size);
|
||||
},
|
||||
username: user,
|
||||
password: passwd
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.external = function() {
|
||||
return {
|
||||
mechanism: 'EXTERNAL',
|
||||
response: function() { return Buffer.from(''); }
|
||||
}
|
||||
}
|
||||
5077
node_modules/amqplib/lib/defs.js
generated
vendored
Normal file
5077
node_modules/amqplib/lib/defs.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
24
node_modules/amqplib/lib/error.js
generated
vendored
Normal file
24
node_modules/amqplib/lib/error.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
var inherits = require('util').inherits;
|
||||
|
||||
function trimStack(stack, num) {
|
||||
return stack && stack.split('\n').slice(num).join('\n');
|
||||
}
|
||||
|
||||
function IllegalOperationError(msg, stack) {
|
||||
var tmp = new Error();
|
||||
this.message = msg;
|
||||
this.stack = this.toString() + '\n' + trimStack(tmp.stack, 2);
|
||||
this.stackAtStateChange = stack;
|
||||
}
|
||||
inherits(IllegalOperationError, Error);
|
||||
|
||||
IllegalOperationError.prototype.name = 'IllegalOperationError';
|
||||
|
||||
function stackCapture(reason) {
|
||||
var e = new Error();
|
||||
return 'Stack capture: ' + reason + '\n' +
|
||||
trimStack(e.stack, 2);
|
||||
}
|
||||
|
||||
module.exports.IllegalOperationError = IllegalOperationError;
|
||||
module.exports.stackCapture = stackCapture;
|
||||
39
node_modules/amqplib/lib/format.js
generated
vendored
Normal file
39
node_modules/amqplib/lib/format.js
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
// Stringifying various things
|
||||
|
||||
'use strict';
|
||||
|
||||
var defs = require('./defs');
|
||||
var format = require('util').format;
|
||||
var HEARTBEAT = require('./frame').HEARTBEAT;
|
||||
|
||||
module.exports.closeMessage = function(close) {
|
||||
var code = close.fields.replyCode;
|
||||
return format('%d (%s) with message "%s"',
|
||||
code, defs.constant_strs[code],
|
||||
close.fields.replyText);
|
||||
}
|
||||
|
||||
module.exports.methodName = function(id) {
|
||||
return defs.info(id).name;
|
||||
};
|
||||
|
||||
module.exports.inspect = function(frame, showFields) {
|
||||
if (frame === HEARTBEAT) {
|
||||
return '<Heartbeat>';
|
||||
}
|
||||
else if (!frame.id) {
|
||||
return format('<Content channel:%d size:%d>',
|
||||
frame.channel, frame.size);
|
||||
}
|
||||
else {
|
||||
var info = defs.info(frame.id);
|
||||
return format('<%s channel:%d%s>', info.name, frame.channel,
|
||||
(showFields)
|
||||
? ' ' + JSON.stringify(frame.fields, undefined, 2)
|
||||
: '');
|
||||
}
|
||||
}
|
||||
175
node_modules/amqplib/lib/frame.js
generated
vendored
Normal file
175
node_modules/amqplib/lib/frame.js
generated
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
// The river sweeps through
|
||||
// Silt and twigs, gravel and leaves
|
||||
// Driving the wheel on
|
||||
|
||||
'use strict';
|
||||
|
||||
const ints = require('buffer-more-ints')
|
||||
var defs = require('./defs');
|
||||
var constants = defs.constants;
|
||||
var decode = defs.decode;
|
||||
|
||||
module.exports.PROTOCOL_HEADER = "AMQP" + String.fromCharCode(0, 0, 9, 1);
|
||||
|
||||
/*
|
||||
Frame format:
|
||||
|
||||
0 1 3 7 size+7 size+8
|
||||
+------+---------+-------------+ +------------+ +-----------+
|
||||
| type | channel | size | | payload | | frame-end |
|
||||
+------+---------+-------------+ +------------+ +-----------+
|
||||
octet short long size octets octet
|
||||
|
||||
In general I want to know those first three things straight away, so I
|
||||
can discard frames early.
|
||||
|
||||
*/
|
||||
|
||||
// framing constants
|
||||
var FRAME_METHOD = constants.FRAME_METHOD,
|
||||
FRAME_HEARTBEAT = constants.FRAME_HEARTBEAT,
|
||||
FRAME_HEADER = constants.FRAME_HEADER,
|
||||
FRAME_BODY = constants.FRAME_BODY,
|
||||
FRAME_END = constants.FRAME_END;
|
||||
|
||||
// expected byte sizes for frame parts
|
||||
const TYPE_BYTES = 1
|
||||
const CHANNEL_BYTES = 2
|
||||
const SIZE_BYTES = 4
|
||||
const FRAME_HEADER_BYTES = TYPE_BYTES + CHANNEL_BYTES + SIZE_BYTES
|
||||
const FRAME_END_BYTES = 1
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* type: number,
|
||||
* channel: number,
|
||||
* size: number,
|
||||
* payload: Buffer,
|
||||
* rest: Buffer
|
||||
* }} FrameStructure
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a polyfill which will read a big int 64 bit as a number.
|
||||
* @arg { Buffer } buffer
|
||||
* @arg { number } offset
|
||||
* @returns { number }
|
||||
*/
|
||||
function readInt64BE(buffer, offset) {
|
||||
/**
|
||||
* We try to use native implementation if available here because
|
||||
* buffer-more-ints does not
|
||||
*/
|
||||
if (typeof Buffer.prototype.readBigInt64BE === 'function') {
|
||||
return Number(buffer.readBigInt64BE(offset))
|
||||
}
|
||||
|
||||
return ints.readInt64BE(buffer, offset)
|
||||
}
|
||||
|
||||
// %%% TESTME possibly better to cons the first bit and write the
|
||||
// second directly, in the absence of IO lists
|
||||
/**
|
||||
* Make a frame header
|
||||
* @arg { number } channel
|
||||
* @arg { Buffer } payload
|
||||
*/
|
||||
module.exports.makeBodyFrame = function (channel, payload) {
|
||||
const frameSize = FRAME_HEADER_BYTES + payload.length + FRAME_END_BYTES
|
||||
|
||||
const frame = Buffer.alloc(frameSize)
|
||||
|
||||
let offset = 0
|
||||
|
||||
offset = frame.writeUInt8(FRAME_BODY, offset)
|
||||
offset = frame.writeUInt16BE(channel, offset)
|
||||
offset = frame.writeInt32BE(payload.length, offset)
|
||||
|
||||
payload.copy(frame, offset)
|
||||
offset += payload.length
|
||||
|
||||
frame.writeUInt8(FRAME_END, offset)
|
||||
|
||||
return frame
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse an AMQP frame
|
||||
* @arg { Buffer } bin
|
||||
* @arg { number } max
|
||||
* @returns { FrameStructure | boolean }
|
||||
*/
|
||||
function parseFrame(bin) {
|
||||
if (bin.length < FRAME_HEADER_BYTES) {
|
||||
return false
|
||||
}
|
||||
|
||||
const type = bin.readUInt8(0)
|
||||
const channel = bin.readUInt16BE(1)
|
||||
const size = bin.readUInt32BE(3)
|
||||
|
||||
const totalSize = FRAME_HEADER_BYTES + size + FRAME_END_BYTES
|
||||
|
||||
if (bin.length < totalSize) {
|
||||
return false
|
||||
}
|
||||
|
||||
const frameEnd = bin.readUInt8(FRAME_HEADER_BYTES + size)
|
||||
|
||||
if (frameEnd !== FRAME_END) {
|
||||
throw new Error('Invalid frame')
|
||||
}
|
||||
|
||||
return {
|
||||
type,
|
||||
channel,
|
||||
size,
|
||||
payload: bin.subarray(FRAME_HEADER_BYTES, FRAME_HEADER_BYTES + size),
|
||||
rest: bin.subarray(totalSize)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.parseFrame = parseFrame;
|
||||
|
||||
var HEARTBEAT = {channel: 0};
|
||||
|
||||
/**
|
||||
* Decode AMQP frame into JS object
|
||||
* @param { FrameStructure } frame
|
||||
* @returns
|
||||
*/
|
||||
module.exports.decodeFrame = (frame) => {
|
||||
const payload = frame.payload
|
||||
const channel = frame.channel
|
||||
|
||||
switch (frame.type) {
|
||||
case FRAME_METHOD: {
|
||||
const id = payload.readUInt32BE(0)
|
||||
const args = payload.subarray(4)
|
||||
const fields = decode(id, args)
|
||||
return { id, channel, fields }
|
||||
}
|
||||
case FRAME_HEADER: {
|
||||
const id = payload.readUInt16BE(0)
|
||||
// const weight = payload.readUInt16BE(2)
|
||||
const size = readInt64BE(payload, 4)
|
||||
const flagsAndfields = payload.subarray(12)
|
||||
const fields = decode(id, flagsAndfields)
|
||||
return { id, channel, size, fields }
|
||||
}
|
||||
case FRAME_BODY:
|
||||
return { channel, content: payload }
|
||||
case FRAME_HEARTBEAT:
|
||||
return HEARTBEAT
|
||||
default:
|
||||
throw new Error('Unknown frame type ' + frame.type)
|
||||
}
|
||||
}
|
||||
|
||||
// encoded heartbeat
|
||||
module.exports.HEARTBEAT_BUF = Buffer.from([constants.FRAME_HEARTBEAT,
|
||||
0, 0, 0, 0, // size = 0
|
||||
0, 0, // channel = 0
|
||||
constants.FRAME_END]);
|
||||
|
||||
module.exports.HEARTBEAT = HEARTBEAT;
|
||||
92
node_modules/amqplib/lib/heartbeat.js
generated
vendored
Normal file
92
node_modules/amqplib/lib/heartbeat.js
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
// Heartbeats. In AMQP both clients and servers may expect a heartbeat
|
||||
// frame if there is no activity on the connection for a negotiated
|
||||
// period of time. If there's no activity for two such intervals, the
|
||||
// server or client is allowed to close the connection on the
|
||||
// presumption that the other party is dead.
|
||||
//
|
||||
// The client has two jobs here: the first is to send a heartbeat
|
||||
// frame if it's not sent any frames for a while, so that the server
|
||||
// doesn't think it's dead; the second is to check periodically that
|
||||
// it's seen activity from the server, and to advise if there doesn't
|
||||
// appear to have been any for over two intervals.
|
||||
//
|
||||
// Node.JS timers are a bit unreliable, in that they endeavour only to
|
||||
// fire at some indeterminate point *after* the given time (rather
|
||||
// gives the lie to 'realtime', dunnit). Because the scheduler is just
|
||||
// an event loop, it's quite easy to delay timers indefinitely by
|
||||
// reacting to some I/O with a lot of computation.
|
||||
//
|
||||
// To mitigate this I need a bit of creative interpretation:
|
||||
//
|
||||
// - I'll schedule a server activity check for every `interval`, and
|
||||
// check just how much time has passed. It will overshoot by at
|
||||
// least a small margin; modulo missing timer deadlines, it'll
|
||||
// notice between two and three intervals after activity actually
|
||||
// stops (otherwise, at some point after two intervals).
|
||||
//
|
||||
// - Every `interval / 2` I'll check that we've sent something since
|
||||
// the last check, and if not, send a heartbeat frame. If we're
|
||||
// really too busy to even run the check for two whole heartbeat
|
||||
// intervals, there must be a lot of I (but not O, at least not on
|
||||
// the connection), or computation, in which case perhaps it's best
|
||||
// the server cuts us off anyway. Why `interval / 2`? Because the
|
||||
// edge case is that the client sent a frame just after a
|
||||
// heartbeat, which would mean I only send one after almost two
|
||||
// intervals. (NB a heartbeat counts as a send, so it'll be checked
|
||||
// at least twice before sending another)
|
||||
//
|
||||
// This design is based largely on RabbitMQ's heartbeating:
|
||||
// https://github.com/rabbitmq/rabbitmq-common/blob/master/src/rabbit_heartbeat.erl
|
||||
|
||||
// %% Yes, I could apply the same 'actually passage of time' thing to
|
||||
// %% send as well as to recv.
|
||||
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('events');
|
||||
|
||||
// Exported so that we can mess with it in tests
|
||||
module.exports.UNITS_TO_MS = 1000;
|
||||
|
||||
class Heart extends EventEmitter {
|
||||
constructor (interval, checkSend, checkRecv) {
|
||||
super();
|
||||
|
||||
this.interval = interval;
|
||||
|
||||
var intervalMs = interval * module.exports.UNITS_TO_MS;
|
||||
// Function#bind is my new best friend
|
||||
var beat = this.emit.bind(this, 'beat');
|
||||
var timeout = this.emit.bind(this, 'timeout');
|
||||
|
||||
this.sendTimer = setInterval(
|
||||
this.runHeartbeat.bind(this, checkSend, beat), intervalMs / 2);
|
||||
|
||||
// A timeout occurs if I see nothing for *two consecutive* intervals
|
||||
var recvMissed = 0;
|
||||
function missedTwo () {
|
||||
if (!checkRecv())
|
||||
return (++recvMissed < 2);
|
||||
else { recvMissed = 0; return true; }
|
||||
}
|
||||
this.recvTimer = setInterval(
|
||||
this.runHeartbeat.bind(this, missedTwo, timeout), intervalMs);
|
||||
}
|
||||
|
||||
clear () {
|
||||
clearInterval(this.sendTimer);
|
||||
clearInterval(this.recvTimer);
|
||||
}
|
||||
|
||||
runHeartbeat (check, fail) {
|
||||
// Have we seen activity?
|
||||
if (!check())
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.Heart = Heart;
|
||||
126
node_modules/amqplib/lib/mux.js
generated
vendored
Normal file
126
node_modules/amqplib/lib/mux.js
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
'use strict';
|
||||
|
||||
// A Mux is an object into which other readable streams may be piped;
|
||||
// it then writes 'packets' from the upstreams to the given
|
||||
// downstream.
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var schedule = (typeof setImmediate === 'function') ?
|
||||
setImmediate : process.nextTick;
|
||||
|
||||
class Mux {
|
||||
constructor (downstream) {
|
||||
this.newStreams = [];
|
||||
this.oldStreams = [];
|
||||
this.blocked = false;
|
||||
this.scheduledRead = false;
|
||||
|
||||
this.out = downstream;
|
||||
var self = this;
|
||||
downstream.on('drain', function () {
|
||||
self.blocked = false;
|
||||
self._readIncoming();
|
||||
});
|
||||
}
|
||||
|
||||
// There are 2 states we can be in:
|
||||
// - waiting for outbound capacity, which will be signalled by a
|
||||
// - 'drain' event on the downstream; or,
|
||||
// - no packets to send, waiting for an inbound buffer to have
|
||||
// packets, which will be signalled by a 'readable' event
|
||||
// If we write all packets available whenever there is outbound
|
||||
// capacity, we will either run out of outbound capacity (`#write`
|
||||
// returns false), or run out of packets (all calls to an
|
||||
// `inbound.read()` have returned null).
|
||||
_readIncoming () {
|
||||
|
||||
// We may be sent here speculatively, if an incoming stream has
|
||||
// become readable
|
||||
if (this.blocked) return;
|
||||
|
||||
var accepting = true;
|
||||
var out = this.out;
|
||||
|
||||
// Try to read a chunk from each stream in turn, until all streams
|
||||
// are empty, or we exhaust our ability to accept chunks.
|
||||
function roundrobin (streams) {
|
||||
var s;
|
||||
while (accepting && (s = streams.shift())) {
|
||||
var chunk = s.read();
|
||||
if (chunk !== null) {
|
||||
accepting = out.write(chunk);
|
||||
streams.push(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
roundrobin(this.newStreams);
|
||||
|
||||
// Either we exhausted the new queues, or we ran out of capacity. If
|
||||
// we ran out of capacity, all the remaining new streams (i.e.,
|
||||
// those with packets left) become old streams. This effectively
|
||||
// prioritises streams that keep their buffers close to empty over
|
||||
// those that are constantly near full.
|
||||
if (accepting) { // all new queues are exhausted, write as many as
|
||||
// we can from the old streams
|
||||
assert.equal(0, this.newStreams.length);
|
||||
roundrobin(this.oldStreams);
|
||||
}
|
||||
else { // ran out of room
|
||||
assert(this.newStreams.length > 0, "Expect some new streams to remain");
|
||||
Array.prototype.push.apply(this.oldStreams, this.newStreams);
|
||||
this.newStreams = [];
|
||||
}
|
||||
// We may have exhausted all the old queues, or run out of room;
|
||||
// either way, all we need to do is record whether we have capacity
|
||||
// or not, so any speculative reads will know
|
||||
this.blocked = !accepting;
|
||||
}
|
||||
|
||||
_scheduleRead () {
|
||||
var self = this;
|
||||
|
||||
if (!self.scheduledRead) {
|
||||
schedule(function () {
|
||||
self.scheduledRead = false;
|
||||
self._readIncoming();
|
||||
});
|
||||
self.scheduledRead = true;
|
||||
}
|
||||
}
|
||||
|
||||
pipeFrom (readable) {
|
||||
var self = this;
|
||||
|
||||
function enqueue () {
|
||||
self.newStreams.push(readable);
|
||||
self._scheduleRead();
|
||||
}
|
||||
|
||||
function cleanup () {
|
||||
readable.removeListener('readable', enqueue);
|
||||
readable.removeListener('error', cleanup);
|
||||
readable.removeListener('end', cleanup);
|
||||
readable.removeListener('unpipeFrom', cleanupIfMe);
|
||||
}
|
||||
function cleanupIfMe (dest) {
|
||||
if (dest === self) cleanup();
|
||||
}
|
||||
|
||||
readable.on('unpipeFrom', cleanupIfMe);
|
||||
readable.on('end', cleanup);
|
||||
readable.on('error', cleanup);
|
||||
readable.on('readable', enqueue);
|
||||
}
|
||||
|
||||
unpipeFrom (readable) {
|
||||
readable.emit('unpipeFrom', this);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.Mux = Mux;
|
||||
Reference in New Issue
Block a user