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.
Files changed (55) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
  2. package/.github/workflows/test.yml +15 -2
  3. package/CHANGELOG.md +12 -1
  4. package/Makefile +3 -4
  5. package/README.md +1 -1
  6. package/bin/generate-defs.js +5 -5
  7. package/examples/direct_reply_to_client.js +22 -0
  8. package/examples/direct_reply_to_server.js +25 -0
  9. package/examples/headers.js +29 -31
  10. package/examples/ssl.js +16 -10
  11. package/examples/stream_queues/README.md +40 -0
  12. package/examples/stream_queues/package.json +14 -0
  13. package/examples/stream_queues/receive_stream.js +55 -0
  14. package/examples/stream_queues/send_stream.js +38 -0
  15. package/examples/tutorials/callback_api/emit_log.js +22 -22
  16. package/examples/tutorials/callback_api/emit_log_direct.js +24 -24
  17. package/examples/tutorials/callback_api/emit_log_topic.js +23 -20
  18. package/examples/tutorials/callback_api/new_task.js +21 -20
  19. package/examples/tutorials/callback_api/receive.js +27 -21
  20. package/examples/tutorials/callback_api/receive_logs.js +32 -26
  21. package/examples/tutorials/callback_api/receive_logs_direct.js +42 -41
  22. package/examples/tutorials/callback_api/receive_logs_topic.js +43 -40
  23. package/examples/tutorials/callback_api/rpc_client.js +39 -37
  24. package/examples/tutorials/callback_api/rpc_server.js +42 -32
  25. package/examples/tutorials/callback_api/send.js +21 -22
  26. package/examples/tutorials/callback_api/worker.js +29 -25
  27. package/examples/tutorials/emit_log.js +20 -15
  28. package/examples/tutorials/emit_log_direct.js +22 -16
  29. package/examples/tutorials/emit_log_topic.js +22 -15
  30. package/examples/tutorials/new_task.js +20 -13
  31. package/examples/tutorials/receive.js +19 -14
  32. package/examples/tutorials/receive_logs.js +25 -23
  33. package/examples/tutorials/receive_logs_direct.js +29 -35
  34. package/examples/tutorials/receive_logs_topic.js +28 -32
  35. package/examples/tutorials/rpc_client.js +37 -40
  36. package/examples/tutorials/rpc_server.js +36 -29
  37. package/examples/tutorials/send.js +26 -18
  38. package/examples/tutorials/worker.js +27 -21
  39. package/examples/waitForConfirms.js +18 -19
  40. package/lib/callback_model.js +295 -290
  41. package/lib/channel.js +353 -346
  42. package/lib/channel_model.js +5 -1
  43. package/lib/codec.js +2 -2
  44. package/lib/connection.js +542 -527
  45. package/lib/credentials.js +1 -1
  46. package/lib/defs.js +2568 -2457
  47. package/lib/format.js +0 -1
  48. package/lib/frame.js +1 -1
  49. package/lib/heartbeat.js +32 -29
  50. package/lib/mux.js +96 -99
  51. package/package.json +2 -2
  52. package/test/callback_api.js +182 -174
  53. package/test/channel_api.js +455 -445
  54. package/test/codec.js +4 -4
  55. 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 inherits = require('util').inherits;
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
- function Channel(connection) {
20
- EventEmitter.call( this );
21
- this.connection = connection;
22
- // for the presently outstanding RPC
23
- this.reply = null;
24
- // for the RPCs awaiting action
25
- this.pending = [];
26
- // for unconfirmed messages
27
- this.lwm = 1; // the least, unconfirmed deliveryTag
28
- this.unconfirmed = []; // rolling window of delivery callbacks
29
- this.on('ack', this.handleConfirm.bind(this, function(cb) {
30
- if (cb) cb(null);
31
- }));
32
- this.on('nack', this.handleConfirm.bind(this, function(cb) {
33
- if (cb) cb(new Error('message nacked'));
34
- }));
35
- this.on('close', function () {
36
- var cb;
37
- while (cb = this.unconfirmed.shift()) {
38
- if (cb) cb(new Error('channel closed'));
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
- // message frame state machine
42
- this.handleMessage = acceptDeliveryOrReturn;
43
- }
44
- inherits(Channel, EventEmitter);
80
+ else {
81
+ this.pending.push({
82
+ method: method,
83
+ fields: fields,
84
+ reply: reply
85
+ });
86
+ }
87
+ }
45
88
 
46
- module.exports.Channel = Channel;
47
- module.exports.acceptMessage = acceptMessage;
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
- var C = Channel.prototype;
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
- C.allocate = function() {
52
- this.ch = this.connection.freshChannel(this);
53
- return this;
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
- // Incoming frames are either notifications of e.g., message delivery,
57
- // or replies to something we've sent. In general I deal with the
58
- // former by emitting an event, and with the latter by keeping a track
59
- // of what's expecting a reply.
60
- //
61
- // The AMQP specification implies that RPCs can't be pipelined; that
62
- // is, you can have only one outstanding RPC on a channel at a
63
- // time. Certainly that's what RabbitMQ and its clients assume. For
64
- // this reason, I buffer RPCs if the channel is already waiting for a
65
- // reply.
66
-
67
- // Just send the damn frame.
68
- C.sendImmediately = function(method, fields) {
69
- return this.connection.sendMethod(this.ch, method, fields);
70
- };
71
-
72
- // Invariant: !this.reply -> pending.length == 0. That is, whenever we
73
- // clear a reply, we must send another RPC (and thereby fill
74
- // this.reply) if there is one waiting. The invariant relevant here
75
- // and in `accept`.
76
- C.sendOrEnqueue = function(method, fields, reply) {
77
- if (!this.reply) { // if no reply waiting, we can go
78
- assert(this.pending.length === 0);
79
- this.reply = reply;
80
- this.sendImmediately(method, fields);
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
- else {
83
- this.pending.push({method: method,
84
- fields: fields,
85
- reply: reply});
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
- function reply(err, f) {
103
- if (err === null) {
104
- if (f.id === expect) {
105
- return cb(null, f);
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
- // 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);
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
- // An error will be given if, for example, this is waiting to be
121
- // sent and the connection closes
122
- else if (err instanceof Error) return cb(err);
123
- // A close frame will be given if this is the RPC awaiting reply
124
- // and the channel is closed by the server
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
- // otherwise, it's a close frame
127
- var closeReason =
128
- (err.fields.classId << 16) + err.fields.methodId;
129
- var e = (method === closeReason)
130
- ? fmt("Operation failed: %s; %s",
131
- methodName(method), closeMsg(err))
132
- : fmt("Channel closed by server: %s", closeMsg(err));
133
- var closeFrameError = new Error(e);
134
- closeFrameError.code = err.fields.replyCode;
135
- closeFrameError.classId = err.fields.classId;
136
- closeFrameError.methodId = err.fields.methodId;
137
- return cb(closeFrameError);
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
- this.sendOrEnqueue(method, fields, reply);
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
- C.handleConfirm = function(handle, f) {
351
- var tag = f.deliveryTag;
352
- var multi = f.multiple;
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
- if (multi) {
355
- var confirmed = this.unconfirmed.splice(0, tag - this.lwm + 1);
356
- this.lwm = tag + 1;
357
- confirmed.forEach(handle);
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
- else {
360
- var c;
361
- if (tag === this.lwm) {
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
- C.pushConfirmCallback = function(cb) {
382
- // `null` is used specifically for marking already confirmed slots,
383
- // so I coerce `undefined` and `null` to false; functions are never
384
- // falsey.
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
- var emsg = "Channel closed by server: " + closeMsg(f);
419
- this.sendImmediately(defs.ChannelCloseOk, {});
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
- // This adds just a bit more stuff useful for the APIs, but not
460
- // low-level machinery.
461
- function BaseChannel(connection) {
462
- Channel.call(this, connection);
463
- this.consumers = new Map();
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;