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/connection.js CHANGED
@@ -13,7 +13,7 @@ var Mux = require('./mux').Mux;
13
13
  var Duplex =
14
14
  require('stream').Duplex ||
15
15
  require('readable-stream/duplex');
16
- var EventEmitter = require('events').EventEmitter;
16
+ var EventEmitter = require('events');
17
17
  var Heart = require('./heartbeat').Heart;
18
18
 
19
19
  var methodName = require('./format').methodName;
@@ -21,7 +21,6 @@ var closeMsg = require('./format').closeMessage;
21
21
  var inspect = require('./format').inspect;
22
22
 
23
23
  var BitSet = require('./bitset').BitSet;
24
- var inherits = require('util').inherits;
25
24
  var fmt = require('util').format;
26
25
  var PassThrough = require('stream').PassThrough ||
27
26
  require('readable-stream/passthrough');
@@ -37,25 +36,545 @@ var DEFAULT_WRITE_HWM = 1024;
37
36
  // was greater, we might have to fragment the content.
38
37
  var SINGLE_CHUNK_THRESHOLD = 2048;
39
38
 
40
- function Connection(underlying) {
41
- EventEmitter.call( this );
42
- var stream = this.stream = wrapStream(underlying);
43
- this.muxer = new Mux(stream);
44
-
45
- // frames
46
- this.rest = Buffer.alloc(0);
47
- this.frameMax = constants.FRAME_MIN_SIZE;
48
- this.sentSinceLastCheck = false;
49
- this.recvSinceLastCheck = false;
50
-
51
- this.expectSocketClose = false;
52
- this.freeChannels = new BitSet();
53
- this.channels = [{channel: {accept: channel0(this)},
54
- buffer: underlying}];
55
- }
56
- inherits(Connection, EventEmitter);
39
+ class Connection extends EventEmitter {
40
+ constructor (underlying) {
41
+ super();
42
+
43
+ var stream = this.stream = wrapStream(underlying);
44
+ this.muxer = new Mux(stream);
45
+
46
+ // frames
47
+ this.rest = Buffer.alloc(0);
48
+ this.frameMax = constants.FRAME_MIN_SIZE;
49
+ this.sentSinceLastCheck = false;
50
+ this.recvSinceLastCheck = false;
51
+
52
+ this.expectSocketClose = false;
53
+ this.freeChannels = new BitSet();
54
+ this.channels = [{
55
+ channel: { accept: channel0(this) },
56
+ buffer: underlying
57
+ }];
58
+ }
59
+
60
+ // This changed between versions, as did the codec, methods, etc. AMQP
61
+ // 0-9-1 is fairly similar to 0.8, but better, and nothing implements
62
+ // 0.8 that doesn't implement 0-9-1. In other words, it doesn't make
63
+ // much sense to generalise here.
64
+ sendProtocolHeader () {
65
+ this.sendBytes(frame.PROTOCOL_HEADER);
66
+ }
67
+
68
+ /*
69
+ The frighteningly complicated opening protocol (spec section 2.2.4):
70
+
71
+ Client -> Server
72
+
73
+ protocol header ->
74
+ <- start
75
+ start-ok ->
76
+ .. next two zero or more times ..
77
+ <- secure
78
+ secure-ok ->
79
+ <- tune
80
+ tune-ok ->
81
+ open ->
82
+ <- open-ok
83
+
84
+ If I'm only supporting SASL's PLAIN mechanism (which I am for the time
85
+ being), it gets a bit easier since the server won't in general send
86
+ back a `secure`, it'll just send `tune` after the `start-ok`.
87
+ (SASL PLAIN: http://tools.ietf.org/html/rfc4616)
88
+
89
+ */
90
+ open (allFields, openCallback0) {
91
+ var self = this;
92
+ var openCallback = openCallback0 || function () { };
93
+
94
+ // This is where we'll put our negotiated values
95
+ var tunedOptions = Object.create(allFields);
96
+
97
+ function wait (k) {
98
+ self.step(function (err, frame) {
99
+ if (err !== null)
100
+ bail(err);
101
+ else if (frame.channel !== 0) {
102
+ bail(new Error(
103
+ fmt("Frame on channel != 0 during handshake: %s",
104
+ inspect(frame, false))));
105
+ }
106
+ else
107
+ k(frame);
108
+ });
109
+ }
110
+
111
+ function expect (Method, k) {
112
+ wait(function (frame) {
113
+ if (frame.id === Method)
114
+ k(frame);
115
+ else {
116
+ bail(new Error(
117
+ fmt("Expected %s; got %s",
118
+ methodName(Method), inspect(frame, false))));
119
+ }
120
+ });
121
+ }
122
+
123
+ function bail (err) {
124
+ openCallback(err);
125
+ }
126
+
127
+ function send (Method) {
128
+ // This can throw an exception if there's some problem with the
129
+ // options; e.g., something is a string instead of a number.
130
+ self.sendMethod(0, Method, tunedOptions);
131
+ }
132
+
133
+ function negotiate (server, desired) {
134
+ // We get sent values for channelMax, frameMax and heartbeat,
135
+ // which we may accept or lower (subject to a minimum for
136
+ // frameMax, but we'll leave that to the server to enforce). In
137
+ // all cases, `0` really means "no limit", or rather the highest
138
+ // value in the encoding, e.g., unsigned short for channelMax.
139
+ if (server === 0 || desired === 0) {
140
+ // i.e., whichever places a limit, if either
141
+ return Math.max(server, desired);
142
+ }
143
+ else {
144
+ return Math.min(server, desired);
145
+ }
146
+ }
147
+
148
+ function onStart (start) {
149
+ var mechanisms = start.fields.mechanisms.toString().split(' ');
150
+ if (mechanisms.indexOf(allFields.mechanism) < 0) {
151
+ bail(new Error(fmt('SASL mechanism %s is not provided by the server',
152
+ allFields.mechanism)));
153
+ return;
154
+ }
155
+ self.serverProperties = start.fields.serverProperties;
156
+ try {
157
+ send(defs.ConnectionStartOk);
158
+ } catch (err) {
159
+ bail(err);
160
+ return;
161
+ }
162
+ wait(afterStartOk);
163
+ }
164
+
165
+ function afterStartOk (reply) {
166
+ switch (reply.id) {
167
+ case defs.ConnectionSecure:
168
+ bail(new Error(
169
+ "Wasn't expecting to have to go through secure"));
170
+ break;
171
+ case defs.ConnectionClose:
172
+ bail(new Error(fmt("Handshake terminated by server: %s",
173
+ closeMsg(reply))));
174
+ break;
175
+ case defs.ConnectionTune:
176
+ var fields = reply.fields;
177
+ tunedOptions.frameMax =
178
+ negotiate(fields.frameMax, allFields.frameMax);
179
+ tunedOptions.channelMax =
180
+ negotiate(fields.channelMax, allFields.channelMax);
181
+ tunedOptions.heartbeat =
182
+ negotiate(fields.heartbeat, allFields.heartbeat);
183
+ try {
184
+ send(defs.ConnectionTuneOk);
185
+ send(defs.ConnectionOpen);
186
+ } catch (err) {
187
+ bail(err);
188
+ return;
189
+ }
190
+ expect(defs.ConnectionOpenOk, onOpenOk);
191
+ break;
192
+ default:
193
+ bail(new Error(
194
+ fmt("Expected connection.secure, connection.close, " +
195
+ "or connection.tune during handshake; got %s",
196
+ inspect(reply, false))));
197
+ break;
198
+ }
199
+ }
200
+
201
+ function onOpenOk (openOk) {
202
+ // Impose the maximum of the encoded value, if the negotiated
203
+ // value is zero, meaning "no, no limits"
204
+ self.channelMax = tunedOptions.channelMax || 0xffff;
205
+ self.frameMax = tunedOptions.frameMax || 0xffffffff;
206
+ // 0 means "no heartbeat", rather than "maximum period of
207
+ // heartbeating"
208
+ self.heartbeat = tunedOptions.heartbeat;
209
+ self.heartbeater = self.startHeartbeater();
210
+ self.accept = mainAccept;
211
+ succeed(openOk);
212
+ }
213
+
214
+ // If the server closes the connection, it's probably because of
215
+ // something we did
216
+ function endWhileOpening (err) {
217
+ bail(err || new Error('Socket closed abruptly ' +
218
+ 'during opening handshake'));
219
+ }
220
+
221
+ this.stream.on('end', endWhileOpening);
222
+ this.stream.on('error', endWhileOpening);
223
+
224
+ function succeed (ok) {
225
+ self.stream.removeListener('end', endWhileOpening);
226
+ self.stream.removeListener('error', endWhileOpening);
227
+ self.stream.on('error', self.onSocketError.bind(self));
228
+ self.stream.on('end', self.onSocketError.bind(
229
+ self, new Error('Unexpected close')));
230
+ self.on('frameError', self.onSocketError.bind(self));
231
+ self.acceptLoop();
232
+ openCallback(null, ok);
233
+ }
234
+
235
+ // Now kick off the handshake by prompting the server
236
+ this.sendProtocolHeader();
237
+ expect(defs.ConnectionStart, onStart);
238
+ }
239
+
240
+ // Closing things: AMQP has a closing handshake that applies to
241
+ // closing both connects and channels. As the initiating party, I send
242
+ // Close, then ignore all frames until I see either CloseOK --
243
+ // which signifies that the other party has seen the Close and shut
244
+ // the connection or channel down, so it's fine to free resources; or
245
+ // Close, which means the other party also wanted to close the
246
+ // whatever, and I should send CloseOk so it can free resources,
247
+ // then go back to waiting for the CloseOk. If I receive a Close
248
+ // out of the blue, I should throw away any unsent frames (they will
249
+ // be ignored anyway) and send CloseOk, then clean up resources. In
250
+ // general, Close out of the blue signals an error (or a forced
251
+ // closure, which may as well be an error).
252
+ //
253
+ // RUNNING [1] --- send Close ---> Closing [2] ---> recv Close --+
254
+ // | | [3]
255
+ // | +------ send CloseOk ------+
256
+ // recv Close recv CloseOk
257
+ // | |
258
+ // V V
259
+ // Ended [4] ---- send CloseOk ---> Closed [5]
260
+ //
261
+ // [1] All frames accepted; getting a Close frame from the server
262
+ // moves to Ended; client may initiate a close by sending Close
263
+ // itself.
264
+ // [2] Client has initiated a close; only CloseOk or (simulataneously
265
+ // sent) Close is accepted.
266
+ // [3] Simultaneous close
267
+ // [4] Server won't send any more frames; accept no more frames, send
268
+ // CloseOk.
269
+ // [5] Fully closed, client will send no more, server will send no
270
+ // more. Signal 'close' or 'error'.
271
+ //
272
+ // There are two signalling mechanisms used in the API. The first is
273
+ // that calling `close` will return a promise, that will either
274
+ // resolve once the connection or channel is cleanly shut down, or
275
+ // will reject if the shutdown times out.
276
+ //
277
+ // The second is the 'close' and 'error' events. These are
278
+ // emitted as above. The events will fire *before* promises are
279
+ // resolved.
280
+ // Close the connection without even giving a reason. Typical.
281
+ close (closeCallback) {
282
+ var k = closeCallback && function () { closeCallback(null); };
283
+ this.closeBecause("Cheers, thanks", constants.REPLY_SUCCESS, k);
284
+ }
285
+
286
+ // Close with a reason and a 'code'. I'm pretty sure RabbitMQ totally
287
+ // ignores these; maybe it logs them. The continuation will be invoked
288
+ // when the CloseOk has been received, and before the 'close' event.
289
+ closeBecause (reason, code, k) {
290
+ this.sendMethod(0, defs.ConnectionClose, {
291
+ replyText: reason,
292
+ replyCode: code,
293
+ methodId: 0, classId: 0
294
+ });
295
+ var s = stackCapture('closeBecause called: ' + reason);
296
+ this.toClosing(s, k);
297
+ }
298
+
299
+ closeWithError (reason, code, error) {
300
+ this.emit('error', error);
301
+ this.closeBecause(reason, code);
302
+ }
303
+
304
+ onSocketError (err) {
305
+ if (!this.expectSocketClose) {
306
+ // forestall any more calls to onSocketError, since we're signed
307
+ // up for `'error'` *and* `'end'`
308
+ this.expectSocketClose = true;
309
+ this.emit('error', err);
310
+ var s = stackCapture('Socket error');
311
+ this.toClosed(s, err);
312
+ }
313
+ }
314
+
315
+ // A close has been initiated. Repeat: a close has been initiated.
316
+ // This means we should not send more frames, anyway they will be
317
+ // ignored. We also have to shut down all the channels.
318
+ toClosing (capturedStack, k) {
319
+ var send = this.sendMethod.bind(this);
320
+
321
+ this.accept = function (f) {
322
+ if (f.id === defs.ConnectionCloseOk) {
323
+ if (k)
324
+ k();
325
+ var s = stackCapture('ConnectionCloseOk received');
326
+ this.toClosed(s, undefined);
327
+ }
328
+ else if (f.id === defs.ConnectionClose) {
329
+ send(0, defs.ConnectionCloseOk, {});
330
+ }
331
+ // else ignore frame
332
+ };
333
+ invalidateSend(this, 'Connection closing', capturedStack);
334
+ }
335
+
336
+ _closeChannels (capturedStack) {
337
+ for (var i = 1; i < this.channels.length; i++) {
338
+ var ch = this.channels[i];
339
+ if (ch !== null) {
340
+ ch.channel.toClosed(capturedStack); // %%% or with an error? not clear
341
+ }
342
+ }
343
+ }
344
+
345
+ // A close has been confirmed. Cease all communication.
346
+ toClosed (capturedStack, maybeErr) {
347
+ this._closeChannels(capturedStack);
348
+ var info = fmt('Connection closed (%s)',
349
+ (maybeErr) ? maybeErr.toString() : 'by client');
350
+ // Tidy up, invalidate enverything, dynamite the bridges.
351
+ invalidateSend(this, info, capturedStack);
352
+ this.accept = invalidOp(info, capturedStack);
353
+ this.close = function (cb) {
354
+ cb && cb(new IllegalOperationError(info, capturedStack));
355
+ };
356
+ if (this.heartbeater)
357
+ this.heartbeater.clear();
358
+ // This is certainly true now, if it wasn't before
359
+ this.expectSocketClose = true;
360
+ this.stream.end();
361
+ this.emit('close', maybeErr);
362
+ }
363
+
364
+ _updateSecret(newSecret, reason, cb) {
365
+ this.sendMethod(0, defs.ConnectionUpdateSecret, {
366
+ newSecret,
367
+ reason
368
+ });
369
+ this.once('update-secret-ok', cb);
370
+ }
371
+
372
+ // ===
373
+ startHeartbeater () {
374
+ if (this.heartbeat === 0)
375
+ return null;
376
+ else {
377
+ var self = this;
378
+ var hb = new Heart(this.heartbeat,
379
+ this.checkSend.bind(this),
380
+ this.checkRecv.bind(this));
381
+ hb.on('timeout', function () {
382
+ var hberr = new Error("Heartbeat timeout");
383
+ self.emit('error', hberr);
384
+ var s = stackCapture('Heartbeat timeout');
385
+ self.toClosed(s, hberr);
386
+ });
387
+ hb.on('beat', function () {
388
+ self.sendHeartbeat();
389
+ });
390
+ return hb;
391
+ }
392
+ }
393
+
394
+ // I use an array to keep track of the channels, rather than an
395
+ // object. The channel identifiers are numbers, and allocated by the
396
+ // connection. If I try to allocate low numbers when they are
397
+ // available (which I do, by looking from the start of the bitset),
398
+ // this ought to keep the array small, and out of 'sparse array
399
+ // storage'. I also set entries to null, rather than deleting them, in
400
+ // the expectation that the next channel allocation will fill the slot
401
+ // again rather than growing the array. See
402
+ // http://www.html5rocks.com/en/tutorials/speed/v8/
403
+ freshChannel (channel, options) {
404
+ var next = this.freeChannels.nextClearBit(1);
405
+ if (next < 0 || next > this.channelMax)
406
+ throw new Error("No channels left to allocate");
407
+ this.freeChannels.set(next);
408
+
409
+ var hwm = (options && options.highWaterMark) || DEFAULT_WRITE_HWM;
410
+ var writeBuffer = new PassThrough({
411
+ objectMode: true, highWaterMark: hwm
412
+ });
413
+ this.channels[next] = { channel: channel, buffer: writeBuffer };
414
+ writeBuffer.on('drain', function () {
415
+ channel.onBufferDrain();
416
+ });
417
+ this.muxer.pipeFrom(writeBuffer);
418
+ return next;
419
+ }
420
+
421
+ releaseChannel (channel) {
422
+ this.freeChannels.clear(channel);
423
+ var buffer = this.channels[channel].buffer;
424
+ buffer.end(); // will also cause it to be unpiped
425
+ this.channels[channel] = null;
426
+ }
427
+
428
+ acceptLoop () {
429
+ var self = this;
430
+
431
+ function go () {
432
+ try {
433
+ var f; while (f = self.recvFrame())
434
+ self.accept(f);
435
+ }
436
+ catch (e) {
437
+ self.emit('frameError', e);
438
+ }
439
+ }
440
+ self.stream.on('readable', go);
441
+ go();
442
+ }
443
+
444
+ step (cb) {
445
+ var self = this;
446
+ function recv () {
447
+ var f;
448
+ try {
449
+ f = self.recvFrame();
450
+ }
451
+ catch (e) {
452
+ cb(e, null);
453
+ return;
454
+ }
455
+ if (f)
456
+ cb(null, f);
457
+ else
458
+ self.stream.once('readable', recv);
459
+ }
460
+ recv();
461
+ }
462
+
463
+ checkSend () {
464
+ var check = this.sentSinceLastCheck;
465
+ this.sentSinceLastCheck = false;
466
+ return check;
467
+ }
468
+
469
+ checkRecv () {
470
+ var check = this.recvSinceLastCheck;
471
+ this.recvSinceLastCheck = false;
472
+ return check;
473
+ }
474
+
475
+ sendBytes (bytes) {
476
+ this.sentSinceLastCheck = true;
477
+ this.stream.write(bytes);
478
+ }
479
+
480
+ sendHeartbeat () {
481
+ return this.sendBytes(frame.HEARTBEAT_BUF);
482
+ }
483
+
484
+ sendMethod (channel, Method, fields) {
485
+ var frame = encodeMethod(Method, channel, fields);
486
+ this.sentSinceLastCheck = true;
487
+ var buffer = this.channels[channel].buffer;
488
+ return buffer.write(frame);
489
+ }
490
+
491
+ sendMessage (channel, Method, fields, Properties, props, content) {
492
+ if (!Buffer.isBuffer(content))
493
+ throw new TypeError('content is not a buffer');
494
+
495
+ var mframe = encodeMethod(Method, channel, fields);
496
+ var pframe = encodeProperties(Properties, channel,
497
+ content.length, props);
498
+ var buffer = this.channels[channel].buffer;
499
+ this.sentSinceLastCheck = true;
500
+
501
+ var methodHeaderLen = mframe.length + pframe.length;
502
+ var bodyLen = (content.length > 0) ?
503
+ content.length + FRAME_OVERHEAD : 0;
504
+ var allLen = methodHeaderLen + bodyLen;
505
+
506
+ if (allLen < SINGLE_CHUNK_THRESHOLD) {
507
+ // Use `allocUnsafe` to avoid excessive allocations and CPU usage
508
+ // from zeroing. The returned Buffer is not zeroed and so must be
509
+ // completely filled to be used safely.
510
+ // See https://github.com/amqp-node/amqplib/pull/695
511
+ var all = Buffer.allocUnsafe(allLen);
512
+ var offset = mframe.copy(all, 0);
513
+ offset += pframe.copy(all, offset);
514
+
515
+ if (bodyLen > 0)
516
+ makeBodyFrame(channel, content).copy(all, offset);
517
+ return buffer.write(all);
518
+ }
519
+ else {
520
+ if (methodHeaderLen < SINGLE_CHUNK_THRESHOLD) {
521
+ // Use `allocUnsafe` to avoid excessive allocations and CPU usage
522
+ // from zeroing. The returned Buffer is not zeroed and so must be
523
+ // completely filled to be used safely.
524
+ // See https://github.com/amqp-node/amqplib/pull/695
525
+ var both = Buffer.allocUnsafe(methodHeaderLen);
526
+ var offset = mframe.copy(both, 0);
527
+ pframe.copy(both, offset);
528
+ buffer.write(both);
529
+ }
530
+ else {
531
+ buffer.write(mframe);
532
+ buffer.write(pframe);
533
+ }
534
+ return this.sendContent(channel, content);
535
+ }
536
+ }
537
+
538
+ sendContent (channel, body) {
539
+ if (!Buffer.isBuffer(body)) {
540
+ throw new TypeError(fmt("Expected buffer; got %s", body));
541
+ }
542
+ var writeResult = true;
543
+ var buffer = this.channels[channel].buffer;
544
+
545
+ var maxBody = this.frameMax - FRAME_OVERHEAD;
57
546
 
58
- var C = Connection.prototype;
547
+ for (var offset = 0; offset < body.length; offset += maxBody) {
548
+ var end = offset + maxBody;
549
+ var slice = (end > body.length) ? body.subarray(offset) : body.subarray(offset, end);
550
+ var bodyFrame = makeBodyFrame(channel, slice);
551
+ writeResult = buffer.write(bodyFrame);
552
+ }
553
+ this.sentSinceLastCheck = true;
554
+ return writeResult;
555
+ }
556
+
557
+ recvFrame () {
558
+ // %%% identifying invariants might help here?
559
+ var frame = parseFrame(this.rest, this.frameMax);
560
+
561
+ if (!frame) {
562
+ var incoming = this.stream.read();
563
+ if (incoming === null) {
564
+ return false;
565
+ }
566
+ else {
567
+ this.recvSinceLastCheck = true;
568
+ this.rest = Buffer.concat([this.rest, incoming]);
569
+ return this.recvFrame();
570
+ }
571
+ }
572
+ else {
573
+ this.rest = frame.rest;
574
+ return decodeFrame(frame);
575
+ }
576
+ }
577
+ }
59
578
 
60
579
  // Usual frame accept mode
61
580
  function mainAccept(frame) {
@@ -100,6 +619,9 @@ function channel0(connection) {
100
619
  else if (f.id === defs.ConnectionUnblocked) {
101
620
  connection.emit('unblocked');
102
621
  }
622
+ else if (f.id === defs.ConnectionUpdateSecretOk) {
623
+ connection.emit('update-secret-ok');
624
+ }
103
625
  else {
104
626
  connection.closeWithError(
105
627
  fmt("Unexpected frame on channel 0"),
@@ -110,260 +632,6 @@ function channel0(connection) {
110
632
  };
111
633
  }
112
634
 
113
- // This changed between versions, as did the codec, methods, etc. AMQP
114
- // 0-9-1 is fairly similar to 0.8, but better, and nothing implements
115
- // 0.8 that doesn't implement 0-9-1. In other words, it doesn't make
116
- // much sense to generalise here.
117
- C.sendProtocolHeader = function() {
118
- this.sendBytes(frame.PROTOCOL_HEADER);
119
- };
120
-
121
- /*
122
- The frighteningly complicated opening protocol (spec section 2.2.4):
123
-
124
- Client -> Server
125
-
126
- protocol header ->
127
- <- start
128
- start-ok ->
129
- .. next two zero or more times ..
130
- <- secure
131
- secure-ok ->
132
- <- tune
133
- tune-ok ->
134
- open ->
135
- <- open-ok
136
-
137
- If I'm only supporting SASL's PLAIN mechanism (which I am for the time
138
- being), it gets a bit easier since the server won't in general send
139
- back a `secure`, it'll just send `tune` after the `start-ok`.
140
- (SASL PLAIN: http://tools.ietf.org/html/rfc4616)
141
-
142
- */
143
-
144
- C.open = function(allFields, openCallback0) {
145
- var self = this;
146
- var openCallback = openCallback0 || function() {};
147
-
148
- // This is where we'll put our negotiated values
149
- var tunedOptions = Object.create(allFields);
150
-
151
- function wait(k) {
152
- self.step(function(err, frame) {
153
- if (err !== null) bail(err);
154
- else if (frame.channel !== 0) {
155
- bail(new Error(
156
- fmt("Frame on channel != 0 during handshake: %s",
157
- inspect(frame, false))));
158
- }
159
- else k(frame);
160
- });
161
- }
162
-
163
- function expect(Method, k) {
164
- wait(function(frame) {
165
- if (frame.id === Method) k(frame);
166
- else {
167
- bail(new Error(
168
- fmt("Expected %s; got %s",
169
- methodName(Method), inspect(frame, false))));
170
- }
171
- });
172
- }
173
-
174
- function bail(err) {
175
- openCallback(err);
176
- }
177
-
178
- function send(Method) {
179
- // This can throw an exception if there's some problem with the
180
- // options; e.g., something is a string instead of a number.
181
- self.sendMethod(0, Method, tunedOptions);
182
- }
183
-
184
- function negotiate(server, desired) {
185
- // We get sent values for channelMax, frameMax and heartbeat,
186
- // which we may accept or lower (subject to a minimum for
187
- // frameMax, but we'll leave that to the server to enforce). In
188
- // all cases, `0` really means "no limit", or rather the highest
189
- // value in the encoding, e.g., unsigned short for channelMax.
190
- if (server === 0 || desired === 0) {
191
- // i.e., whichever places a limit, if either
192
- return Math.max(server, desired);
193
- }
194
- else {
195
- return Math.min(server, desired);
196
- }
197
- }
198
-
199
- function onStart(start) {
200
- var mechanisms = start.fields.mechanisms.toString().split(' ');
201
- if (mechanisms.indexOf(allFields.mechanism) < 0) {
202
- bail(new Error(fmt('SASL mechanism %s is not provided by the server',
203
- allFields.mechanism)));
204
- return;
205
- }
206
- self.serverProperties = start.fields.serverProperties;
207
- try {
208
- send(defs.ConnectionStartOk);
209
- } catch (err) {
210
- bail(err);
211
- return;
212
- }
213
- wait(afterStartOk);
214
- }
215
-
216
- function afterStartOk(reply) {
217
- switch (reply.id) {
218
- case defs.ConnectionSecure:
219
- bail(new Error(
220
- "Wasn't expecting to have to go through secure"));
221
- break;
222
- case defs.ConnectionClose:
223
- bail(new Error(fmt("Handshake terminated by server: %s",
224
- closeMsg(reply))));
225
- break;
226
- case defs.ConnectionTune:
227
- var fields = reply.fields;
228
- tunedOptions.frameMax =
229
- negotiate(fields.frameMax, allFields.frameMax);
230
- tunedOptions.channelMax =
231
- negotiate(fields.channelMax, allFields.channelMax);
232
- tunedOptions.heartbeat =
233
- negotiate(fields.heartbeat, allFields.heartbeat);
234
- try {
235
- send(defs.ConnectionTuneOk);
236
- send(defs.ConnectionOpen);
237
- } catch (err) {
238
- bail(err);
239
- return;
240
- }
241
- expect(defs.ConnectionOpenOk, onOpenOk);
242
- break;
243
- default:
244
- bail(new Error(
245
- fmt("Expected connection.secure, connection.close, " +
246
- "or connection.tune during handshake; got %s",
247
- inspect(reply, false))));
248
- break;
249
- }
250
- }
251
-
252
- function onOpenOk(openOk) {
253
- // Impose the maximum of the encoded value, if the negotiated
254
- // value is zero, meaning "no, no limits"
255
- self.channelMax = tunedOptions.channelMax || 0xffff;
256
- self.frameMax = tunedOptions.frameMax || 0xffffffff;
257
- // 0 means "no heartbeat", rather than "maximum period of
258
- // heartbeating"
259
- self.heartbeat = tunedOptions.heartbeat;
260
- self.heartbeater = self.startHeartbeater();
261
- self.accept = mainAccept;
262
- succeed(openOk);
263
- }
264
-
265
- // If the server closes the connection, it's probably because of
266
- // something we did
267
- function endWhileOpening(err) {
268
- bail(err || new Error('Socket closed abruptly ' +
269
- 'during opening handshake'));
270
- }
271
-
272
- this.stream.on('end', endWhileOpening);
273
- this.stream.on('error', endWhileOpening);
274
-
275
- function succeed(ok) {
276
- self.stream.removeListener('end', endWhileOpening);
277
- self.stream.removeListener('error', endWhileOpening);
278
- self.stream.on('error', self.onSocketError.bind(self));
279
- self.stream.on('end', self.onSocketError.bind(
280
- self, new Error('Unexpected close')));
281
- self.on('frameError', self.onSocketError.bind(self));
282
- self.acceptLoop();
283
- openCallback(null, ok);
284
- }
285
-
286
- // Now kick off the handshake by prompting the server
287
- this.sendProtocolHeader();
288
- expect(defs.ConnectionStart, onStart);
289
- };
290
-
291
- // Closing things: AMQP has a closing handshake that applies to
292
- // closing both connects and channels. As the initiating party, I send
293
- // Close, then ignore all frames until I see either CloseOK --
294
- // which signifies that the other party has seen the Close and shut
295
- // the connection or channel down, so it's fine to free resources; or
296
- // Close, which means the other party also wanted to close the
297
- // whatever, and I should send CloseOk so it can free resources,
298
- // then go back to waiting for the CloseOk. If I receive a Close
299
- // out of the blue, I should throw away any unsent frames (they will
300
- // be ignored anyway) and send CloseOk, then clean up resources. In
301
- // general, Close out of the blue signals an error (or a forced
302
- // closure, which may as well be an error).
303
- //
304
- // RUNNING [1] --- send Close ---> Closing [2] ---> recv Close --+
305
- // | | [3]
306
- // | +------ send CloseOk ------+
307
- // recv Close recv CloseOk
308
- // | |
309
- // V V
310
- // Ended [4] ---- send CloseOk ---> Closed [5]
311
- //
312
- // [1] All frames accepted; getting a Close frame from the server
313
- // moves to Ended; client may initiate a close by sending Close
314
- // itself.
315
- // [2] Client has initiated a close; only CloseOk or (simulataneously
316
- // sent) Close is accepted.
317
- // [3] Simultaneous close
318
- // [4] Server won't send any more frames; accept no more frames, send
319
- // CloseOk.
320
- // [5] Fully closed, client will send no more, server will send no
321
- // more. Signal 'close' or 'error'.
322
- //
323
- // There are two signalling mechanisms used in the API. The first is
324
- // that calling `close` will return a promise, that will either
325
- // resolve once the connection or channel is cleanly shut down, or
326
- // will reject if the shutdown times out.
327
- //
328
- // The second is the 'close' and 'error' events. These are
329
- // emitted as above. The events will fire *before* promises are
330
- // resolved.
331
-
332
- // Close the connection without even giving a reason. Typical.
333
- C.close = function(closeCallback) {
334
- var k = closeCallback && function() { closeCallback(null); };
335
- this.closeBecause("Cheers, thanks", constants.REPLY_SUCCESS, k);
336
- };
337
-
338
- // Close with a reason and a 'code'. I'm pretty sure RabbitMQ totally
339
- // ignores these; maybe it logs them. The continuation will be invoked
340
- // when the CloseOk has been received, and before the 'close' event.
341
- C.closeBecause = function(reason, code, k) {
342
- this.sendMethod(0, defs.ConnectionClose, {
343
- replyText: reason,
344
- replyCode: code,
345
- methodId: 0, classId: 0
346
- });
347
- var s = stackCapture('closeBecause called: ' + reason);
348
- this.toClosing(s, k);
349
- };
350
-
351
- C.closeWithError = function(reason, code, error) {
352
- this.emit('error', error);
353
- this.closeBecause(reason, code);
354
- };
355
-
356
- C.onSocketError = function(err) {
357
- if (!this.expectSocketClose) {
358
- // forestall any more calls to onSocketError, since we're signed
359
- // up for `'error'` *and* `'end'`
360
- this.expectSocketClose = true;
361
- this.emit('error', err);
362
- var s = stackCapture('Socket error');
363
- this.toClosed(s, err);
364
- }
365
- };
366
-
367
635
  function invalidOp(msg, stack) {
368
636
  return function() {
369
637
  throw new IllegalOperationError(msg, stack);
@@ -375,268 +643,15 @@ function invalidateSend(conn, msg, stack) {
375
643
  invalidOp(msg, stack);
376
644
  }
377
645
 
378
- // A close has been initiated. Repeat: a close has been initiated.
379
- // This means we should not send more frames, anyway they will be
380
- // ignored. We also have to shut down all the channels.
381
- C.toClosing = function(capturedStack, k) {
382
- var send = this.sendMethod.bind(this);
383
-
384
- this.accept = function(f) {
385
- if (f.id === defs.ConnectionCloseOk) {
386
- if (k) k();
387
- var s = stackCapture('ConnectionCloseOk received');
388
- this.toClosed(s, undefined);
389
- }
390
- else if (f.id === defs.ConnectionClose) {
391
- send(0, defs.ConnectionCloseOk, {});
392
- }
393
- // else ignore frame
394
- };
395
- invalidateSend(this, 'Connection closing', capturedStack);
396
- };
397
-
398
- C._closeChannels = function(capturedStack) {
399
- for (var i = 1; i < this.channels.length; i++) {
400
- var ch = this.channels[i];
401
- if (ch !== null) {
402
- ch.channel.toClosed(capturedStack); // %%% or with an error? not clear
403
- }
404
- }
405
- };
406
-
407
- // A close has been confirmed. Cease all communication.
408
- C.toClosed = function(capturedStack, maybeErr) {
409
- this._closeChannels(capturedStack);
410
- var info = fmt('Connection closed (%s)',
411
- (maybeErr) ? maybeErr.toString() : 'by client');
412
- // Tidy up, invalidate enverything, dynamite the bridges.
413
- invalidateSend(this, info, capturedStack);
414
- this.accept = invalidOp(info, capturedStack);
415
- this.close = function(cb) {
416
- cb && cb(new IllegalOperationError(info, capturedStack));
417
- };
418
- if (this.heartbeater) this.heartbeater.clear();
419
- // This is certainly true now, if it wasn't before
420
- this.expectSocketClose = true;
421
- this.stream.end();
422
- this.emit('close', maybeErr);
423
- };
424
-
425
- // ===
426
-
427
- C.startHeartbeater = function() {
428
- if (this.heartbeat === 0) return null;
429
- else {
430
- var self = this;
431
- var hb = new Heart(this.heartbeat,
432
- this.checkSend.bind(this),
433
- this.checkRecv.bind(this));
434
- hb.on('timeout', function() {
435
- var hberr = new Error("Heartbeat timeout");
436
- self.emit('error', hberr);
437
- var s = stackCapture('Heartbeat timeout');
438
- self.toClosed(s, hberr);
439
- });
440
- hb.on('beat', function() {
441
- self.sendHeartbeat();
442
- });
443
- return hb;
444
- }
445
- };
446
-
447
- // I use an array to keep track of the channels, rather than an
448
- // object. The channel identifiers are numbers, and allocated by the
449
- // connection. If I try to allocate low numbers when they are
450
- // available (which I do, by looking from the start of the bitset),
451
- // this ought to keep the array small, and out of 'sparse array
452
- // storage'. I also set entries to null, rather than deleting them, in
453
- // the expectation that the next channel allocation will fill the slot
454
- // again rather than growing the array. See
455
- // http://www.html5rocks.com/en/tutorials/speed/v8/
456
- C.freshChannel = function(channel, options) {
457
- var next = this.freeChannels.nextClearBit(1);
458
- if (next < 0 || next > this.channelMax)
459
- throw new Error("No channels left to allocate");
460
- this.freeChannels.set(next);
461
-
462
- var hwm = (options && options.highWaterMark) || DEFAULT_WRITE_HWM;
463
- var writeBuffer = new PassThrough({
464
- objectMode: true, highWaterMark: hwm
465
- });
466
- this.channels[next] = {channel: channel, buffer: writeBuffer};
467
- writeBuffer.on('drain', function() {
468
- channel.onBufferDrain();
469
- });
470
- this.muxer.pipeFrom(writeBuffer);
471
- return next;
472
- };
473
-
474
- C.releaseChannel = function(channel) {
475
- this.freeChannels.clear(channel);
476
- var buffer = this.channels[channel].buffer;
477
- buffer.end(); // will also cause it to be unpiped
478
- this.channels[channel] = null;
479
- };
480
-
481
- C.acceptLoop = function() {
482
- var self = this;
483
-
484
- function go() {
485
- try {
486
- var f; while (f = self.recvFrame()) self.accept(f);
487
- }
488
- catch (e) {
489
- self.emit('frameError', e);
490
- }
491
- }
492
- self.stream.on('readable', go);
493
- go();
494
- };
495
-
496
- C.step = function(cb) {
497
- var self = this;
498
- function recv() {
499
- var f;
500
- try {
501
- f = self.recvFrame();
502
- }
503
- catch (e) {
504
- cb(e, null);
505
- return;
506
- }
507
- if (f) cb(null, f);
508
- else self.stream.once('readable', recv);
509
- }
510
- recv();
511
- };
512
-
513
- C.checkSend = function() {
514
- var check = this.sentSinceLastCheck;
515
- this.sentSinceLastCheck = false;
516
- return check;
517
- }
518
-
519
- C.checkRecv = function() {
520
- var check = this.recvSinceLastCheck;
521
- this.recvSinceLastCheck = false;
522
- return check;
523
- }
524
-
525
- C.sendBytes = function(bytes) {
526
- this.sentSinceLastCheck = true;
527
- this.stream.write(bytes);
528
- };
529
-
530
- C.sendHeartbeat = function() {
531
- return this.sendBytes(frame.HEARTBEAT_BUF);
532
- };
533
-
534
646
  var encodeMethod = defs.encodeMethod;
535
647
  var encodeProperties = defs.encodeProperties;
536
648
 
537
- C.sendMethod = function(channel, Method, fields) {
538
- var frame = encodeMethod(Method, channel, fields);
539
- this.sentSinceLastCheck = true;
540
- var buffer = this.channels[channel].buffer;
541
- return buffer.write(frame);
542
- };
543
-
544
- C.sendMessage = function(channel,
545
- Method, fields,
546
- Properties, props,
547
- content) {
548
- if (!Buffer.isBuffer(content))
549
- throw new TypeError('content is not a buffer');
550
-
551
- var mframe = encodeMethod(Method, channel, fields);
552
- var pframe = encodeProperties(Properties, channel,
553
- content.length, props);
554
- var buffer = this.channels[channel].buffer;
555
- this.sentSinceLastCheck = true;
556
-
557
- var methodHeaderLen = mframe.length + pframe.length;
558
- var bodyLen = (content.length > 0) ?
559
- content.length + FRAME_OVERHEAD : 0;
560
- var allLen = methodHeaderLen + bodyLen;
561
-
562
- if (allLen < SINGLE_CHUNK_THRESHOLD) {
563
- // Use `allocUnsafe` to avoid excessive allocations and CPU usage
564
- // from zeroing. The returned Buffer is not zeroed and so must be
565
- // completely filled to be used safely.
566
- // See https://github.com/amqp-node/amqplib/pull/695
567
- var all = Buffer.allocUnsafe(allLen);
568
- var offset = mframe.copy(all, 0);
569
- offset += pframe.copy(all, offset);
570
-
571
- if (bodyLen > 0)
572
- makeBodyFrame(channel, content).copy(all, offset);
573
- return buffer.write(all);
574
- }
575
- else {
576
- if (methodHeaderLen < SINGLE_CHUNK_THRESHOLD) {
577
- // Use `allocUnsafe` to avoid excessive allocations and CPU usage
578
- // from zeroing. The returned Buffer is not zeroed and so must be
579
- // completely filled to be used safely.
580
- // See https://github.com/amqp-node/amqplib/pull/695
581
- var both = Buffer.allocUnsafe(methodHeaderLen);
582
- var offset = mframe.copy(both, 0);
583
- pframe.copy(both, offset);
584
- buffer.write(both);
585
- }
586
- else {
587
- buffer.write(mframe);
588
- buffer.write(pframe);
589
- }
590
- return this.sendContent(channel, content);
591
- }
592
- };
593
-
594
649
  var FRAME_OVERHEAD = defs.FRAME_OVERHEAD;
595
650
  var makeBodyFrame = frame.makeBodyFrame;
596
651
 
597
- C.sendContent = function(channel, body) {
598
- if (!Buffer.isBuffer(body)) {
599
- throw new TypeError(fmt("Expected buffer; got %s", body));
600
- }
601
- var writeResult = true;
602
- var buffer = this.channels[channel].buffer;
603
-
604
- var maxBody = this.frameMax - FRAME_OVERHEAD;
605
-
606
- for (var offset = 0; offset < body.length; offset += maxBody) {
607
- var end = offset + maxBody;
608
- var slice = (end > body.length) ? body.slice(offset) : body.slice(offset, end);
609
- var bodyFrame = makeBodyFrame(channel, slice);
610
- writeResult = buffer.write(bodyFrame);
611
- }
612
- this.sentSinceLastCheck = true;
613
- return writeResult;
614
- };
615
-
616
652
  var parseFrame = frame.parseFrame;
617
653
  var decodeFrame = frame.decodeFrame;
618
654
 
619
- C.recvFrame = function() {
620
- // %%% identifying invariants might help here?
621
- var frame = parseFrame(this.rest, this.frameMax);
622
-
623
- if (!frame) {
624
- var incoming = this.stream.read();
625
- if (incoming === null) {
626
- return false;
627
- }
628
- else {
629
- this.recvSinceLastCheck = true;
630
- this.rest = Buffer.concat([this.rest, incoming]);
631
- return this.recvFrame();
632
- }
633
- }
634
- else {
635
- this.rest = frame.rest;
636
- return decodeFrame(frame);
637
- }
638
- };
639
-
640
655
  function wrapStream(s) {
641
656
  if (s instanceof Duplex) return s;
642
657
  else {