amqplib 0.10.2 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
- package/.github/workflows/test.yml +15 -2
- package/CHANGELOG.md +12 -1
- package/Makefile +3 -4
- package/README.md +1 -1
- package/bin/generate-defs.js +5 -5
- package/examples/direct_reply_to_client.js +22 -0
- package/examples/direct_reply_to_server.js +25 -0
- package/examples/headers.js +29 -31
- package/examples/ssl.js +16 -10
- package/examples/stream_queues/README.md +40 -0
- package/examples/stream_queues/package.json +14 -0
- package/examples/stream_queues/receive_stream.js +55 -0
- package/examples/stream_queues/send_stream.js +38 -0
- package/examples/tutorials/callback_api/emit_log.js +22 -22
- package/examples/tutorials/callback_api/emit_log_direct.js +24 -24
- package/examples/tutorials/callback_api/emit_log_topic.js +23 -20
- package/examples/tutorials/callback_api/new_task.js +21 -20
- package/examples/tutorials/callback_api/receive.js +27 -21
- package/examples/tutorials/callback_api/receive_logs.js +32 -26
- package/examples/tutorials/callback_api/receive_logs_direct.js +42 -41
- package/examples/tutorials/callback_api/receive_logs_topic.js +43 -40
- package/examples/tutorials/callback_api/rpc_client.js +39 -37
- package/examples/tutorials/callback_api/rpc_server.js +42 -32
- package/examples/tutorials/callback_api/send.js +21 -22
- package/examples/tutorials/callback_api/worker.js +29 -25
- package/examples/tutorials/emit_log.js +20 -15
- package/examples/tutorials/emit_log_direct.js +22 -16
- package/examples/tutorials/emit_log_topic.js +22 -15
- package/examples/tutorials/new_task.js +20 -13
- package/examples/tutorials/receive.js +19 -14
- package/examples/tutorials/receive_logs.js +25 -23
- package/examples/tutorials/receive_logs_direct.js +29 -35
- package/examples/tutorials/receive_logs_topic.js +28 -32
- package/examples/tutorials/rpc_client.js +37 -40
- package/examples/tutorials/rpc_server.js +36 -29
- package/examples/tutorials/send.js +26 -18
- package/examples/tutorials/worker.js +27 -21
- package/examples/waitForConfirms.js +18 -19
- package/lib/callback_model.js +295 -290
- package/lib/channel.js +353 -346
- package/lib/channel_model.js +5 -1
- package/lib/codec.js +2 -2
- package/lib/connection.js +542 -527
- package/lib/credentials.js +1 -1
- package/lib/defs.js +2568 -2457
- package/lib/format.js +0 -1
- package/lib/frame.js +1 -1
- package/lib/heartbeat.js +32 -29
- package/lib/mux.js +96 -99
- package/package.json +2 -2
- package/test/callback_api.js +182 -174
- package/test/channel_api.js +455 -445
- package/test/codec.js +4 -4
- package/test/frame.js +5 -5
package/lib/channel.js
CHANGED
|
@@ -11,135 +11,345 @@ var closeMsg = require('./format').closeMessage;
|
|
|
11
11
|
var inspect = require('./format').inspect;
|
|
12
12
|
var methodName = require('./format').methodName;
|
|
13
13
|
var assert = require('assert');
|
|
14
|
-
var
|
|
15
|
-
var EventEmitter = require('events').EventEmitter;
|
|
14
|
+
var EventEmitter = require('events');
|
|
16
15
|
var fmt = require('util').format;
|
|
17
16
|
var IllegalOperationError = require('./error').IllegalOperationError;
|
|
18
17
|
var stackCapture = require('./error').stackCapture;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
18
|
+
|
|
19
|
+
class Channel extends EventEmitter {
|
|
20
|
+
constructor (connection) {
|
|
21
|
+
super();
|
|
22
|
+
|
|
23
|
+
this.connection = connection;
|
|
24
|
+
// for the presently outstanding RPC
|
|
25
|
+
this.reply = null;
|
|
26
|
+
// for the RPCs awaiting action
|
|
27
|
+
this.pending = [];
|
|
28
|
+
// for unconfirmed messages
|
|
29
|
+
this.lwm = 1; // the least, unconfirmed deliveryTag
|
|
30
|
+
this.unconfirmed = []; // rolling window of delivery callbacks
|
|
31
|
+
this.on('ack', this.handleConfirm.bind(this, function (cb) {
|
|
32
|
+
if (cb)
|
|
33
|
+
cb(null);
|
|
34
|
+
}));
|
|
35
|
+
this.on('nack', this.handleConfirm.bind(this, function (cb) {
|
|
36
|
+
if (cb)
|
|
37
|
+
cb(new Error('message nacked'));
|
|
38
|
+
}));
|
|
39
|
+
this.on('close', function () {
|
|
40
|
+
var cb;
|
|
41
|
+
while (cb = this.unconfirmed.shift()) {
|
|
42
|
+
if (cb)
|
|
43
|
+
cb(new Error('channel closed'));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
// message frame state machine
|
|
47
|
+
this.handleMessage = acceptDeliveryOrReturn;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
allocate () {
|
|
51
|
+
this.ch = this.connection.freshChannel(this);
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Incoming frames are either notifications of e.g., message delivery,
|
|
56
|
+
// or replies to something we've sent. In general I deal with the
|
|
57
|
+
// former by emitting an event, and with the latter by keeping a track
|
|
58
|
+
// of what's expecting a reply.
|
|
59
|
+
//
|
|
60
|
+
// The AMQP specification implies that RPCs can't be pipelined; that
|
|
61
|
+
// is, you can have only one outstanding RPC on a channel at a
|
|
62
|
+
// time. Certainly that's what RabbitMQ and its clients assume. For
|
|
63
|
+
// this reason, I buffer RPCs if the channel is already waiting for a
|
|
64
|
+
// reply.
|
|
65
|
+
// Just send the damn frame.
|
|
66
|
+
sendImmediately (method, fields) {
|
|
67
|
+
return this.connection.sendMethod(this.ch, method, fields);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Invariant: !this.reply -> pending.length == 0. That is, whenever we
|
|
71
|
+
// clear a reply, we must send another RPC (and thereby fill
|
|
72
|
+
// this.reply) if there is one waiting. The invariant relevant here
|
|
73
|
+
// and in `accept`.
|
|
74
|
+
sendOrEnqueue (method, fields, reply) {
|
|
75
|
+
if (!this.reply) { // if no reply waiting, we can go
|
|
76
|
+
assert(this.pending.length === 0);
|
|
77
|
+
this.reply = reply;
|
|
78
|
+
this.sendImmediately(method, fields);
|
|
39
79
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
80
|
+
else {
|
|
81
|
+
this.pending.push({
|
|
82
|
+
method: method,
|
|
83
|
+
fields: fields,
|
|
84
|
+
reply: reply
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
45
88
|
|
|
46
|
-
|
|
47
|
-
|
|
89
|
+
sendMessage (fields, properties, content) {
|
|
90
|
+
return this.connection.sendMessage(
|
|
91
|
+
this.ch,
|
|
92
|
+
defs.BasicPublish, fields,
|
|
93
|
+
defs.BasicProperties, properties,
|
|
94
|
+
content);
|
|
95
|
+
}
|
|
48
96
|
|
|
49
|
-
|
|
97
|
+
// Internal, synchronously resolved RPC; the return value is resolved
|
|
98
|
+
// with the whole frame.
|
|
99
|
+
_rpc (method, fields, expect, cb) {
|
|
100
|
+
var self = this;
|
|
50
101
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
102
|
+
function reply (err, f) {
|
|
103
|
+
if (err === null) {
|
|
104
|
+
if (f.id === expect) {
|
|
105
|
+
return cb(null, f);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// We have detected a problem, so it's up to us to close the
|
|
109
|
+
// channel
|
|
110
|
+
var expectedName = methodName(expect);
|
|
111
|
+
|
|
112
|
+
var e = new Error(fmt("Expected %s; got %s",
|
|
113
|
+
expectedName, inspect(f, false)));
|
|
114
|
+
self.closeWithError(f.id, fmt('Expected %s; got %s',
|
|
115
|
+
expectedName, methodName(f.id)),
|
|
116
|
+
defs.constants.UNEXPECTED_FRAME, e);
|
|
117
|
+
return cb(e);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
55
120
|
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
this.
|
|
121
|
+
|
|
122
|
+
// An error will be given if, for example, this is waiting to be
|
|
123
|
+
// sent and the connection closes
|
|
124
|
+
else if (err instanceof Error)
|
|
125
|
+
return cb(err);
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
// A close frame will be given if this is the RPC awaiting reply
|
|
129
|
+
// and the channel is closed by the server
|
|
130
|
+
else {
|
|
131
|
+
// otherwise, it's a close frame
|
|
132
|
+
var closeReason = (err.fields.classId << 16) + err.fields.methodId;
|
|
133
|
+
var e = (method === closeReason)
|
|
134
|
+
? fmt("Operation failed: %s; %s",
|
|
135
|
+
methodName(method), closeMsg(err))
|
|
136
|
+
: fmt("Channel closed by server: %s", closeMsg(err));
|
|
137
|
+
var closeFrameError = new Error(e);
|
|
138
|
+
closeFrameError.code = err.fields.replyCode;
|
|
139
|
+
closeFrameError.classId = err.fields.classId;
|
|
140
|
+
closeFrameError.methodId = err.fields.methodId;
|
|
141
|
+
return cb(closeFrameError);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.sendOrEnqueue(method, fields, reply);
|
|
81
146
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
147
|
+
|
|
148
|
+
// Move to entirely closed state.
|
|
149
|
+
toClosed (capturedStack) {
|
|
150
|
+
this._rejectPending();
|
|
151
|
+
invalidateSend(this, 'Channel closed', capturedStack);
|
|
152
|
+
this.accept = invalidOp('Channel closed', capturedStack);
|
|
153
|
+
this.connection.releaseChannel(this.ch);
|
|
154
|
+
this.emit('close');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Stop being able to send and receive methods and content. Used when
|
|
158
|
+
// we close the channel. Invokes the continuation once the server has
|
|
159
|
+
// acknowledged the close, but before the channel is moved to the
|
|
160
|
+
// closed state.
|
|
161
|
+
toClosing (capturedStack, k) {
|
|
162
|
+
var send = this.sendImmediately.bind(this);
|
|
163
|
+
invalidateSend(this, 'Channel closing', capturedStack);
|
|
164
|
+
|
|
165
|
+
this.accept = function (f) {
|
|
166
|
+
if (f.id === defs.ChannelCloseOk) {
|
|
167
|
+
if (k)
|
|
168
|
+
k();
|
|
169
|
+
var s = stackCapture('ChannelCloseOk frame received');
|
|
170
|
+
this.toClosed(s);
|
|
171
|
+
}
|
|
172
|
+
else if (f.id === defs.ChannelClose) {
|
|
173
|
+
send(defs.ChannelCloseOk, {});
|
|
174
|
+
}
|
|
175
|
+
// else ignore frame
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
_rejectPending () {
|
|
180
|
+
function rej (r) {
|
|
181
|
+
r(new Error("Channel ended, no reply will be forthcoming"));
|
|
182
|
+
}
|
|
183
|
+
if (this.reply !== null)
|
|
184
|
+
rej(this.reply);
|
|
185
|
+
this.reply = null;
|
|
186
|
+
|
|
187
|
+
var discard;
|
|
188
|
+
while (discard = this.pending.shift())
|
|
189
|
+
rej(discard.reply);
|
|
190
|
+
this.pending = null; // so pushes will break
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
closeBecause (reason, code, k) {
|
|
194
|
+
this.sendImmediately(defs.ChannelClose, {
|
|
195
|
+
replyText: reason,
|
|
196
|
+
replyCode: code,
|
|
197
|
+
methodId: 0, classId: 0
|
|
198
|
+
});
|
|
199
|
+
var s = stackCapture('closeBecause called: ' + reason);
|
|
200
|
+
this.toClosing(s, k);
|
|
86
201
|
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
C.sendMessage = function(fields, properties, content) {
|
|
90
|
-
return this.connection.sendMessage(
|
|
91
|
-
this.ch,
|
|
92
|
-
defs.BasicPublish, fields,
|
|
93
|
-
defs.BasicProperties, properties,
|
|
94
|
-
content);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Internal, synchronously resolved RPC; the return value is resolved
|
|
98
|
-
// with the whole frame.
|
|
99
|
-
C._rpc = function(method, fields, expect, cb) {
|
|
100
|
-
var self = this;
|
|
101
202
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
203
|
+
// If we close because there's been an error, we need to distinguish
|
|
204
|
+
// between what we tell the server (`reason`) and what we report as
|
|
205
|
+
// the cause in the client (`error`).
|
|
206
|
+
closeWithError (id, reason, code, error) {
|
|
207
|
+
var self = this;
|
|
208
|
+
this.closeBecause(reason, code, function () {
|
|
209
|
+
error.code = code;
|
|
210
|
+
// content frames and consumer errors do not provide a method a class/method ID
|
|
211
|
+
if (id) {
|
|
212
|
+
error.classId = defs.info(id).classId;
|
|
213
|
+
error.methodId = defs.info(id).methodId;
|
|
214
|
+
}
|
|
215
|
+
self.emit('error', error);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// A trampolining state machine for message frames on a channel. A
|
|
220
|
+
// message arrives in at least two frames: first, a method announcing
|
|
221
|
+
// the message (either a BasicDeliver or BasicGetOk); then, a message
|
|
222
|
+
// header with the message properties; then, zero or more content
|
|
223
|
+
// frames.
|
|
224
|
+
// Keep the try/catch localised, in an attempt to avoid disabling
|
|
225
|
+
// optimisation
|
|
226
|
+
acceptMessageFrame (f) {
|
|
227
|
+
try {
|
|
228
|
+
this.handleMessage = this.handleMessage(f);
|
|
229
|
+
}
|
|
230
|
+
catch (msg) {
|
|
231
|
+
if (typeof msg === 'string') {
|
|
232
|
+
this.closeWithError(f.id, msg, defs.constants.UNEXPECTED_FRAME,
|
|
233
|
+
new Error(msg));
|
|
234
|
+
}
|
|
235
|
+
else if (msg instanceof Error) {
|
|
236
|
+
this.closeWithError(f.id, 'Error while processing message',
|
|
237
|
+
defs.constants.INTERNAL_ERROR, msg);
|
|
106
238
|
}
|
|
107
239
|
else {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
var e = new Error(fmt("Expected %s; got %s",
|
|
113
|
-
expectedName, inspect(f, false)));
|
|
114
|
-
self.closeWithError(f.id, fmt('Expected %s; got %s',
|
|
115
|
-
expectedName, methodName(f.id)),
|
|
116
|
-
defs.constants.UNEXPECTED_FRAME, e);
|
|
117
|
-
return cb(e);
|
|
240
|
+
this.closeWithError(f.id, 'Internal error while processing message',
|
|
241
|
+
defs.constants.INTERNAL_ERROR,
|
|
242
|
+
new Error(msg.toString()));
|
|
118
243
|
}
|
|
119
244
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
handleConfirm (handle, f) {
|
|
248
|
+
var tag = f.deliveryTag;
|
|
249
|
+
var multi = f.multiple;
|
|
250
|
+
|
|
251
|
+
if (multi) {
|
|
252
|
+
var confirmed = this.unconfirmed.splice(0, tag - this.lwm + 1);
|
|
253
|
+
this.lwm = tag + 1;
|
|
254
|
+
confirmed.forEach(handle);
|
|
255
|
+
}
|
|
125
256
|
else {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
257
|
+
var c;
|
|
258
|
+
if (tag === this.lwm) {
|
|
259
|
+
c = this.unconfirmed.shift();
|
|
260
|
+
this.lwm++;
|
|
261
|
+
// Advance the LWM and the window to the next non-gap, or
|
|
262
|
+
// possibly to the end
|
|
263
|
+
while (this.unconfirmed[0] === null) {
|
|
264
|
+
this.unconfirmed.shift();
|
|
265
|
+
this.lwm++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
c = this.unconfirmed[tag - this.lwm];
|
|
270
|
+
this.unconfirmed[tag - this.lwm] = null;
|
|
271
|
+
}
|
|
272
|
+
// Technically, in the single-deliveryTag case, I should report a
|
|
273
|
+
// protocol breach if it's already been confirmed.
|
|
274
|
+
handle(c);
|
|
138
275
|
}
|
|
139
276
|
}
|
|
140
277
|
|
|
141
|
-
|
|
142
|
-
|
|
278
|
+
pushConfirmCallback (cb) {
|
|
279
|
+
// `null` is used specifically for marking already confirmed slots,
|
|
280
|
+
// so I coerce `undefined` and `null` to false; functions are never
|
|
281
|
+
// falsey.
|
|
282
|
+
this.unconfirmed.push(cb || false);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
onBufferDrain () {
|
|
286
|
+
this.emit('drain');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
accept(f) {
|
|
290
|
+
|
|
291
|
+
switch (f.id) {
|
|
292
|
+
|
|
293
|
+
// Message frames
|
|
294
|
+
case undefined: // content frame!
|
|
295
|
+
case defs.BasicDeliver:
|
|
296
|
+
case defs.BasicReturn:
|
|
297
|
+
case defs.BasicProperties:
|
|
298
|
+
return this.acceptMessageFrame(f);
|
|
299
|
+
|
|
300
|
+
// confirmations, need to do confirm.select first
|
|
301
|
+
case defs.BasicAck:
|
|
302
|
+
return this.emit('ack', f.fields);
|
|
303
|
+
case defs.BasicNack:
|
|
304
|
+
return this.emit('nack', f.fields);
|
|
305
|
+
case defs.BasicCancel:
|
|
306
|
+
// The broker can send this if e.g., the queue is deleted.
|
|
307
|
+
return this.emit('cancel', f.fields);
|
|
308
|
+
|
|
309
|
+
case defs.ChannelClose:
|
|
310
|
+
// Any remote closure is an error to us. Reject the pending reply
|
|
311
|
+
// with the close frame, so it can see whether it was that
|
|
312
|
+
// operation that caused it to close.
|
|
313
|
+
if (this.reply) {
|
|
314
|
+
var reply = this.reply; this.reply = null;
|
|
315
|
+
reply(f);
|
|
316
|
+
}
|
|
317
|
+
var emsg = "Channel closed by server: " + closeMsg(f);
|
|
318
|
+
this.sendImmediately(defs.ChannelCloseOk, {});
|
|
319
|
+
|
|
320
|
+
var error = new Error(emsg);
|
|
321
|
+
error.code = f.fields.replyCode;
|
|
322
|
+
error.classId = f.fields.classId;
|
|
323
|
+
error.methodId = f.fields.methodId;
|
|
324
|
+
this.emit('error', error);
|
|
325
|
+
|
|
326
|
+
var s = stackCapture(emsg);
|
|
327
|
+
this.toClosed(s);
|
|
328
|
+
return;
|
|
329
|
+
|
|
330
|
+
case defs.BasicFlow:
|
|
331
|
+
// RabbitMQ doesn't send this, it just blocks the TCP socket
|
|
332
|
+
return this.closeWithError(f.id, "Flow not implemented",
|
|
333
|
+
defs.constants.NOT_IMPLEMENTED,
|
|
334
|
+
new Error('Flow not implemented'));
|
|
335
|
+
|
|
336
|
+
default: // assume all other things are replies
|
|
337
|
+
// Resolving the reply may lead to another RPC; to make sure we
|
|
338
|
+
// don't hold that up, clear this.reply
|
|
339
|
+
var reply = this.reply; this.reply = null;
|
|
340
|
+
// however, maybe there's an RPC waiting to go? If so, that'll
|
|
341
|
+
// fill this.reply again, restoring the invariant. This does rely
|
|
342
|
+
// on any response being recv'ed after resolving the promise,
|
|
343
|
+
// below; hence, I use synchronous defer.
|
|
344
|
+
if (this.pending.length > 0) {
|
|
345
|
+
var send = this.pending.shift();
|
|
346
|
+
this.reply = send.reply;
|
|
347
|
+
this.sendImmediately(send.method, send.fields);
|
|
348
|
+
}
|
|
349
|
+
return reply(null, f);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
143
353
|
|
|
144
354
|
// Shutdown protocol. There's three scenarios:
|
|
145
355
|
//
|
|
@@ -165,103 +375,6 @@ function invalidateSend(ch, msg, stack) {
|
|
|
165
375
|
invalidOp(msg, stack);
|
|
166
376
|
}
|
|
167
377
|
|
|
168
|
-
// Move to entirely closed state.
|
|
169
|
-
C.toClosed = function(capturedStack) {
|
|
170
|
-
this._rejectPending();
|
|
171
|
-
invalidateSend(this, 'Channel closed', capturedStack);
|
|
172
|
-
this.accept = invalidOp('Channel closed', capturedStack);
|
|
173
|
-
this.connection.releaseChannel(this.ch);
|
|
174
|
-
this.emit('close');
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
// Stop being able to send and receive methods and content. Used when
|
|
178
|
-
// we close the channel. Invokes the continuation once the server has
|
|
179
|
-
// acknowledged the close, but before the channel is moved to the
|
|
180
|
-
// closed state.
|
|
181
|
-
C.toClosing = function(capturedStack, k) {
|
|
182
|
-
var send = this.sendImmediately.bind(this);
|
|
183
|
-
invalidateSend(this, 'Channel closing', capturedStack);
|
|
184
|
-
|
|
185
|
-
this.accept = function(f) {
|
|
186
|
-
if (f.id === defs.ChannelCloseOk) {
|
|
187
|
-
if (k) k();
|
|
188
|
-
var s = stackCapture('ChannelCloseOk frame received');
|
|
189
|
-
this.toClosed(s);
|
|
190
|
-
}
|
|
191
|
-
else if (f.id === defs.ChannelClose) {
|
|
192
|
-
send(defs.ChannelCloseOk, {});
|
|
193
|
-
}
|
|
194
|
-
// else ignore frame
|
|
195
|
-
};
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
C._rejectPending = function() {
|
|
199
|
-
function rej(r) {
|
|
200
|
-
r(new Error("Channel ended, no reply will be forthcoming"));
|
|
201
|
-
}
|
|
202
|
-
if (this.reply !== null) rej(this.reply);
|
|
203
|
-
this.reply = null;
|
|
204
|
-
|
|
205
|
-
var discard;
|
|
206
|
-
while (discard = this.pending.shift()) rej(discard.reply);
|
|
207
|
-
this.pending = null; // so pushes will break
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
C.closeBecause = function(reason, code, k) {
|
|
211
|
-
this.sendImmediately(defs.ChannelClose, {
|
|
212
|
-
replyText: reason,
|
|
213
|
-
replyCode: code,
|
|
214
|
-
methodId:0, classId: 0
|
|
215
|
-
});
|
|
216
|
-
var s = stackCapture('closeBecause called: ' + reason);
|
|
217
|
-
this.toClosing(s, k);
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
// If we close because there's been an error, we need to distinguish
|
|
221
|
-
// between what we tell the server (`reason`) and what we report as
|
|
222
|
-
// the cause in the client (`error`).
|
|
223
|
-
C.closeWithError = function(id, reason, code, error) {
|
|
224
|
-
var self = this;
|
|
225
|
-
this.closeBecause(reason, code, function() {
|
|
226
|
-
error.code = code;
|
|
227
|
-
// content frames and consumer errors do not provide a method a class/method ID
|
|
228
|
-
if (id) {
|
|
229
|
-
error.classId = defs.info(id).classId;
|
|
230
|
-
error.methodId = defs.info(id).methodId;
|
|
231
|
-
}
|
|
232
|
-
self.emit('error', error);
|
|
233
|
-
});
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// A trampolining state machine for message frames on a channel. A
|
|
237
|
-
// message arrives in at least two frames: first, a method announcing
|
|
238
|
-
// the message (either a BasicDeliver or BasicGetOk); then, a message
|
|
239
|
-
// header with the message properties; then, zero or more content
|
|
240
|
-
// frames.
|
|
241
|
-
|
|
242
|
-
// Keep the try/catch localised, in an attempt to avoid disabling
|
|
243
|
-
// optimisation
|
|
244
|
-
C.acceptMessageFrame = function(f) {
|
|
245
|
-
try {
|
|
246
|
-
this.handleMessage = this.handleMessage(f);
|
|
247
|
-
}
|
|
248
|
-
catch (msg) {
|
|
249
|
-
if (typeof msg === 'string') {
|
|
250
|
-
this.closeWithError(f.id, msg, defs.constants.UNEXPECTED_FRAME,
|
|
251
|
-
new Error(msg));
|
|
252
|
-
}
|
|
253
|
-
else if (msg instanceof Error) {
|
|
254
|
-
this.closeWithError(f.id, 'Error while processing message',
|
|
255
|
-
defs.constants.INTERNAL_ERROR, msg);
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
this.closeWithError(f.id, 'Internal error while processing message',
|
|
259
|
-
defs.constants.INTERNAL_ERROR,
|
|
260
|
-
new Error(msg.toString()));
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
};
|
|
264
|
-
|
|
265
378
|
// Kick off a message delivery given a BasicDeliver or BasicReturn
|
|
266
379
|
// frame (BasicGet uses the RPC mechanism)
|
|
267
380
|
function acceptDeliveryOrReturn(f) {
|
|
@@ -347,153 +460,47 @@ function acceptMessage(continuation) {
|
|
|
347
460
|
}
|
|
348
461
|
}
|
|
349
462
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
463
|
+
// This adds just a bit more stuff useful for the APIs, but not
|
|
464
|
+
// low-level machinery.
|
|
465
|
+
class BaseChannel extends Channel {
|
|
466
|
+
constructor (connection) {
|
|
467
|
+
super(connection);
|
|
468
|
+
this.consumers = new Map();
|
|
469
|
+
}
|
|
353
470
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
471
|
+
// Not sure I like the ff, it's going to be changing hidden classes
|
|
472
|
+
// all over the place. On the other hand, whaddya do.
|
|
473
|
+
registerConsumer (tag, callback) {
|
|
474
|
+
this.consumers.set(tag, callback);
|
|
358
475
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
c = this.unconfirmed.shift();
|
|
363
|
-
this.lwm++;
|
|
364
|
-
// Advance the LWM and the window to the next non-gap, or
|
|
365
|
-
// possibly to the end
|
|
366
|
-
while (this.unconfirmed[0] === null) {
|
|
367
|
-
this.unconfirmed.shift();
|
|
368
|
-
this.lwm++;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
c = this.unconfirmed[tag - this.lwm];
|
|
373
|
-
this.unconfirmed[tag - this.lwm] = null;
|
|
374
|
-
}
|
|
375
|
-
// Technically, in the single-deliveryTag case, I should report a
|
|
376
|
-
// protocol breach if it's already been confirmed.
|
|
377
|
-
handle(c);
|
|
476
|
+
|
|
477
|
+
unregisterConsumer (tag) {
|
|
478
|
+
this.consumers.delete(tag);
|
|
378
479
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
this.unconfirmed.push(cb || false);
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
// Interface for connection to use
|
|
389
|
-
|
|
390
|
-
C.accept = function(f) {
|
|
391
|
-
|
|
392
|
-
switch (f.id) {
|
|
393
|
-
|
|
394
|
-
// Message frames
|
|
395
|
-
case undefined: // content frame!
|
|
396
|
-
case defs.BasicDeliver:
|
|
397
|
-
case defs.BasicReturn:
|
|
398
|
-
case defs.BasicProperties:
|
|
399
|
-
return this.acceptMessageFrame(f);
|
|
400
|
-
|
|
401
|
-
// confirmations, need to do confirm.select first
|
|
402
|
-
case defs.BasicAck:
|
|
403
|
-
return this.emit('ack', f.fields);
|
|
404
|
-
case defs.BasicNack:
|
|
405
|
-
return this.emit('nack', f.fields);
|
|
406
|
-
case defs.BasicCancel:
|
|
407
|
-
// The broker can send this if e.g., the queue is deleted.
|
|
408
|
-
return this.emit('cancel', f.fields);
|
|
409
|
-
|
|
410
|
-
case defs.ChannelClose:
|
|
411
|
-
// Any remote closure is an error to us. Reject the pending reply
|
|
412
|
-
// with the close frame, so it can see whether it was that
|
|
413
|
-
// operation that caused it to close.
|
|
414
|
-
if (this.reply) {
|
|
415
|
-
var reply = this.reply; this.reply = null;
|
|
416
|
-
reply(f);
|
|
480
|
+
|
|
481
|
+
dispatchMessage (fields, message) {
|
|
482
|
+
var consumerTag = fields.consumerTag;
|
|
483
|
+
var consumer = this.consumers.get(consumerTag);
|
|
484
|
+
if (consumer) {
|
|
485
|
+
return consumer(message);
|
|
417
486
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
var error = new Error(emsg);
|
|
422
|
-
error.code = f.fields.replyCode;
|
|
423
|
-
error.classId = f.fields.classId;
|
|
424
|
-
error.methodId = f.fields.methodId;
|
|
425
|
-
this.emit('error', error);
|
|
426
|
-
|
|
427
|
-
var s = stackCapture(emsg);
|
|
428
|
-
this.toClosed(s);
|
|
429
|
-
return;
|
|
430
|
-
|
|
431
|
-
case defs.BasicFlow:
|
|
432
|
-
// RabbitMQ doesn't send this, it just blocks the TCP socket
|
|
433
|
-
return this.closeWithError(f.id, "Flow not implemented",
|
|
434
|
-
defs.constants.NOT_IMPLEMENTED,
|
|
435
|
-
new Error('Flow not implemented'));
|
|
436
|
-
|
|
437
|
-
default: // assume all other things are replies
|
|
438
|
-
// Resolving the reply may lead to another RPC; to make sure we
|
|
439
|
-
// don't hold that up, clear this.reply
|
|
440
|
-
var reply = this.reply; this.reply = null;
|
|
441
|
-
// however, maybe there's an RPC waiting to go? If so, that'll
|
|
442
|
-
// fill this.reply again, restoring the invariant. This does rely
|
|
443
|
-
// on any response being recv'ed after resolving the promise,
|
|
444
|
-
// below; hence, I use synchronous defer.
|
|
445
|
-
if (this.pending.length > 0) {
|
|
446
|
-
var send = this.pending.shift();
|
|
447
|
-
this.reply = send.reply;
|
|
448
|
-
this.sendImmediately(send.method, send.fields);
|
|
487
|
+
else {
|
|
488
|
+
// %%% Surely a race here
|
|
489
|
+
throw new Error("Unknown consumer: " + consumerTag);
|
|
449
490
|
}
|
|
450
|
-
return reply(null, f);
|
|
451
491
|
}
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
C.onBufferDrain = function() {
|
|
455
|
-
this.emit('drain');
|
|
456
|
-
};
|
|
457
492
|
|
|
493
|
+
handleDelivery (message) {
|
|
494
|
+
return this.dispatchMessage(message.fields, message);
|
|
495
|
+
}
|
|
458
496
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
497
|
+
handleCancel (fields) {
|
|
498
|
+
var result = this.dispatchMessage(fields, null);
|
|
499
|
+
this.unregisterConsumer(fields.consumerTag);
|
|
500
|
+
return result;
|
|
501
|
+
}
|
|
464
502
|
}
|
|
465
|
-
inherits(BaseChannel, Channel);
|
|
466
503
|
|
|
504
|
+
module.exports.acceptMessage = acceptMessage;
|
|
467
505
|
module.exports.BaseChannel = BaseChannel;
|
|
468
|
-
|
|
469
|
-
// Not sure I like the ff, it's going to be changing hidden classes
|
|
470
|
-
// all over the place. On the other hand, whaddya do.
|
|
471
|
-
BaseChannel.prototype.registerConsumer = function(tag, callback) {
|
|
472
|
-
this.consumers.set(tag, callback);
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
BaseChannel.prototype.unregisterConsumer = function(tag) {
|
|
476
|
-
this.consumers.delete(tag);
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
BaseChannel.prototype.dispatchMessage = function(fields, message) {
|
|
480
|
-
var consumerTag = fields.consumerTag;
|
|
481
|
-
var consumer = this.consumers.get(consumerTag);
|
|
482
|
-
if (consumer) {
|
|
483
|
-
return consumer(message);
|
|
484
|
-
}
|
|
485
|
-
else {
|
|
486
|
-
// %%% Surely a race here
|
|
487
|
-
throw new Error("Unknown consumer: " + consumerTag);
|
|
488
|
-
}
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
BaseChannel.prototype.handleDelivery = function(message) {
|
|
492
|
-
return this.dispatchMessage(message.fields, message);
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
BaseChannel.prototype.handleCancel = function(fields) {
|
|
496
|
-
var result = this.dispatchMessage(fields, null);
|
|
497
|
-
this.unregisterConsumer(fields.consumerTag);
|
|
498
|
-
return result;
|
|
499
|
-
};
|
|
506
|
+
module.exports.Channel = Channel;
|