├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── History.md ├── LICENSE-MIT ├── Makefile ├── README.md ├── amqp-0-9-1-rabbit.xml ├── amqp-0-9-1.xml ├── amqp.js ├── index.js ├── jspack.js ├── lib ├── amqp-definitions-0-9-1.js ├── channel.js ├── connection.js ├── constants.js ├── debug.js ├── definitions.js ├── exchange.js ├── message.js ├── parser.js ├── promise.js ├── queue.js └── serializer.js ├── package.json ├── qparser.rb ├── runTests.sh ├── test ├── .jshintrc ├── harness.js ├── proxy.js ├── test-auto-delete-queue.js ├── test-basic-return.js ├── test-buffer.js ├── test-channel-overflow.js ├── test-connection-array-preference.js ├── test-connection-array.js ├── test-connection-blocked.js ├── test-connection-callbacks.js ├── test-connection-connect.js ├── test-connection-disconnect.js ├── test-connection-timeout.js ├── test-consumer-cancel-notify.js ├── test-consumer-tag.js ├── test-default-exchange.js ├── test-destroy-close-delete.js ├── test-ex-and-q-deletions.js ├── test-exchange-bind.js ├── test-exchange-bind_headers.js ├── test-exchange-callbacks.js ├── test-exchange-no-declare-and-no-confirm.js ├── test-exchange-publish-closed.js ├── test-exchange-unbind.js ├── test-flow.js ├── test-headers.js ├── test-heartbeat-shutdown.js ├── test-heartbeat.js ├── test-json.js ├── test-large-body.js ├── test-large-multiframe-body.js ├── test-parser.js ├── test-properties.js ├── test-publish-confirms-callback.js ├── test-publish-confirms-callbacks-not-hanging-after-recovery.js ├── test-publish-confirms-emitter.js ├── test-purge.js ├── test-queue-args.js ├── test-queue-bind-callbacks-cascaded.js ├── test-queue-bind-callbacks-sequential.js ├── test-queue-bind-callbacks-single.js ├── test-queue-bind-headers.js ├── test-queue-creation.js ├── test-queue-declare-error.js ├── test-queue-subscribe-event.js ├── test-queue-unbind-headers.js ├── test-receive-empty-messages.js ├── test-reconnection-server-named-queue.js ├── test-reconnection.js ├── test-reject.js ├── test-shift.js ├── test-simple.js ├── test-type-and-headers.js ├── test-unbind-unknown-exchange.js ├── test-unbind.js ├── test-unsubscribe.js └── test-volume.js ├── test2 ├── federation.js └── firehose.js ├── test2src ├── Cakefile ├── federation.coffee └── firehose.coffee └── util ├── delete-exchange.js └── delete-queue.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib/amqp/constants-generated.js 2 | node_modules/ 3 | .DS_Store 4 | .idea -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailing" : false, 3 | "sub" : true, 4 | "laxcomma" : true, 5 | "bitwise" : true, 6 | "nonew" : true, 7 | "undef" : true, 8 | "node" : true, 9 | "eqnull" : true, 10 | "jquery" : true, 11 | "strict" : false, 12 | "quotmark" : false, 13 | "browser" : true, 14 | "devel": true, 15 | "expr": true 16 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | - "0.10" 5 | - "0.8" 6 | - "0.6" 7 | 8 | services: rabbitmq 9 | 10 | notifications: 11 | email: false 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | Thank you for your interest in contributing to this project! 3 | 4 | ## New Issues 5 | 6 | If you are opening an issue, please include: 7 | 8 | - a detailed description of the problem 9 | - a description of the expected outcome 10 | - detailed steps to reproduce the issue 11 | - the version of node.js, this module, and RabbitMQ 12 | 13 | ## Pull Requests 14 | 15 | If you are opening a pull request (*thanks!*), please include: 16 | 17 | - a detailed description of the fix/improvement 18 | - test(s) that prove the code works 19 | - documentation updates _if necessary_ 20 | 21 | All tests must pass before the PR will be accepted. 22 | 23 | ## Running the tests 24 | 25 | The test suite expects a rabbit server running on the local machine. 26 | 27 | To run the test suite: 28 | 29 | make test 30 | 31 | If you'd like to run a specific test alone: 32 | 33 | node test/test-simple.js 34 | 35 | If you'd like to use a server other than `localhost:5762`: 36 | 37 | make test SERVER=otherserver:port 38 | 39 | or 40 | 41 | node test/test-simple.js otherserver:port 42 | 43 | The `NODE_DEBUG_AMQP=1` environment variable can also be useful for debugging. 44 | 45 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | ... 2 | ================== 3 | * Change the default json content type to application/json 4 | * add a basic-return event for exchanges to catch simple 5 | basicReturn messages. 6 | * cleaner and more manageable handling of server channel closes 7 | from Glen Mailer. 8 | 9 | 0.1.0 / 2011-07-30 10 | ================== 11 | 12 | * BC BREAK: Changed the default exchange name from amq.topic to '' 13 | (the empty string). This can be changed by using the new 14 | implementation options argument (second arg) to createConnection. 15 | Add { defaultExchangeName: 'amq.topic' } as second arg to 16 | createConnection for old behavior. See docs. 17 | * Added url support to connection credentials. (squaremo) 18 | * Support for pristine messages and all AMQP properties. 19 | * Added this history file. 20 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2010 Xavier Shay and Joyent, Inc., Ryan Dahl, Stephane Alnet 2 | 3 | All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to 7 | deal in the Software without restriction, including without limitation the 8 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | sell copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: test 2 | 3 | test: 4 | ./runTests.sh 5 | 6 | .PHONY: test 7 | -------------------------------------------------------------------------------- /amqp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Connection = require('./lib/connection'); 3 | 4 | module.exports = { 5 | Connection: Connection, 6 | createConnection: function (options, implOptions, readyCallback) { 7 | var c = new Connection(options, implOptions, readyCallback); 8 | c.connect(); 9 | return c; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | amqp.js -------------------------------------------------------------------------------- /jspack.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2008, Fair Oaks Labs, Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without modification, are 5 | // permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright notice, this list 8 | // of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright notice, this 10 | // list of conditions and the following disclaimer in the documentation and/or other 11 | // materials provided with the distribution. 12 | // * Neither the name of Fair Oaks Labs, Inc. nor the names of its contributors may be 13 | // used to endorse or promote products derived from this software without specific 14 | // prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 17 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 19 | // THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 | // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 23 | // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 24 | // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | // Modified from original JSPack 26 | exports.jspack = function (bigEndian) { 27 | this.bigEndian = bigEndian; 28 | } 29 | 30 | exports.jspack.prototype._DeArray = function (a, p, l) { 31 | return [a.slice(p, p + l)]; 32 | }; 33 | 34 | exports.jspack.prototype._EnArray = function (a, p, l, v) { 35 | for (var i = 0; i < l; ++i) { 36 | a[p + i] = v[i] ? v[i] : 0; 37 | } 38 | }; 39 | 40 | exports.jspack.prototype._DeChar = function (a, p) { 41 | return String.fromCharCode(a[p]); 42 | }; 43 | 44 | exports.jspack.prototype._EnChar = function (a, p, v) { 45 | a[p] = v.charCodeAt(0); 46 | }; 47 | 48 | exports.jspack.prototype._DeInt = function (a, p) { 49 | var lsb = bigEndian ? format.len - 1 : 0; 50 | var nsb = bigEndian ? -1 : 1; 51 | var stp = lsb + nsb * format.len, 52 | rv; 53 | var ret = 0; 54 | 55 | var i = lsb; 56 | var f = 1; 57 | while (i != stp) { 58 | rv += a[p + i] * f; 59 | i += nsb; 60 | f *= 256; 61 | } 62 | 63 | if (format.signed) { 64 | if ((rv & Math.pow(2, format.len * 8 - 1)) != 0) { 65 | rv -= Math.pow(2, format.len * 8); 66 | } 67 | } 68 | 69 | return rv; 70 | }; 71 | 72 | exports.jspack.prototype._EnInt = function (a, p, v) { 73 | var lsb = bigEndian ? format.len - 1 : 0; 74 | var nsb = bigEndian ? -1 : 1; 75 | var stp = lsb + nsb * format.len; 76 | 77 | v = v < format.min ? format.min : ((v > format.max) ? format.max : v); 78 | 79 | var i = lsb; 80 | while (i != stp) { 81 | a[p + i] = v & 0xff; 82 | i += nsb; 83 | v >>= 8; 84 | } 85 | }; 86 | 87 | exports.jspack.prototype._DeString = function (a, p, l) { 88 | var rv = new Array(1); 89 | 90 | for (i = 0; i < l; i++) { 91 | rv[i] = String.fromCharCode(a[p + i]); 92 | } 93 | 94 | return rv.join(''); 95 | }; 96 | 97 | exports.jspack.prototype._EnString = function (a, p, l, v) { 98 | for (var t, i = 0; i < l; ++i) { 99 | t = v.charCodeAt(i); 100 | if (!t) t = 0; 101 | 102 | a[p + i] = t; 103 | } 104 | }; 105 | 106 | exports.jspack.prototype._De754 = function (a, p) { 107 | var s, e, m, i, d, bits, bit, len, bias, max; 108 | 109 | bit = format.bit; 110 | len = format.len * 8 - format.bit - 1; 111 | max = (1 << len) - 1; 112 | bias = max >> 1; 113 | 114 | i = bigEndian ? 0 : format.len - 1; 115 | d = bigEndian ? 1 : -1;; 116 | s = a[p + i]; 117 | i = i + d; 118 | 119 | bits = -7; 120 | 121 | e = s & ((1 << -bits) - 1); 122 | s >>= -bits; 123 | 124 | for (bits += len; bits > 0; bits -= 8) { 125 | e = e * 256 + a[p + i]; 126 | i += d; 127 | } 128 | 129 | m = e & ((1 << -bits) - 1); 130 | e >>= -bits; 131 | 132 | for (bits += bit; bits > 0; bits -= 8) { 133 | m = m * 256 + a[p + i]; 134 | i += d; 135 | } 136 | 137 | switch (e) { 138 | case 0: 139 | // Zero, or denormalized number 140 | e = 1 - bias; 141 | break; 142 | 143 | case max: 144 | // NaN, or +/-Infinity 145 | return m ? NaN : ((s ? -1 : 1) * Infinity); 146 | 147 | default: 148 | // Normalized number 149 | m = m + Math.pow(2, bit); 150 | e = e - bias; 151 | break; 152 | } 153 | 154 | return (s ? -1 : 1) * m * Math.pow(2, e - bit); 155 | }; 156 | 157 | exports.jspack.prototype._En754 = function (a, p, v) { 158 | var s, e, m, i, d, c, bit, len, bias, max; 159 | 160 | bit = format.bit; 161 | len = format.len * 8 - format.bit - 1; 162 | max = (1 << len) - 1; 163 | bias = max >> 1; 164 | 165 | s = v < 0 ? 1 : 0; 166 | v = Math.abs(v); 167 | 168 | if (isNaN(v) || (v == Infinity)) { 169 | m = isNaN(v) ? 1 : 0; 170 | e = max; 171 | } else { 172 | e = Math.floor(Math.log(v) / Math.LN2); // Calculate log2 of the value 173 | c = Math.pow(2, -e); 174 | if (v * c < 1) { 175 | e--; 176 | c = c * 2; 177 | } 178 | 179 | // Round by adding 1/2 the significand's LSD 180 | if (e + bias >= 1) { 181 | v += format.rt / c; // Normalized: bit significand digits 182 | } else { 183 | v += format.rt * Math.pow(2, 1 - bias); // Denormalized: <= bit significand digits 184 | } 185 | if (v * c >= 2) { 186 | e++; 187 | c = c / 2; // Rounding can increment the exponent 188 | } 189 | 190 | if (e + bias >= max) { // overflow 191 | m = 0; 192 | e = max; 193 | } else if (e + bias >= 1) { // normalized 194 | m = (v * c - 1) * Math.pow(2, bit); // do not reorder this expression 195 | e = e + bias; 196 | } else { 197 | // Denormalized - also catches the '0' case, somewhat by chance 198 | m = v * Math.pow(2, bias - 1) * Math.pow(2, bit); 199 | e = 0; 200 | } 201 | } 202 | 203 | i = bigEndian ? format.len - 1 : 0; 204 | d = bigEndian ? -1 : 1;; 205 | 206 | while (bit >= 8) { 207 | a[p + i] = m & 0xff; 208 | i += d; 209 | m /= 256; 210 | bit -= 8; 211 | } 212 | 213 | e = (e << bit) | m; 214 | for (len += bit; len > 0; len -= 8) { 215 | a[p + i] = e & 0xff; 216 | i += d; 217 | e /= 256; 218 | } 219 | 220 | a[p + i - d] |= s * 128; 221 | }; 222 | 223 | // Unpack a series of n formatements of size s from array a at offset p with fxn 224 | exports.jspack.prototype._UnpackSeries = function (n, s, a, p) { 225 | var fxn = format.de; 226 | 227 | var ret = []; 228 | for (var i = 0; i < n; i++) { 229 | ret.push(fxn(a, p + i * s)); 230 | } 231 | 232 | return ret; 233 | }; 234 | 235 | // Pack a series of n formatements of size s from array v at offset i to array a at offset p with fxn 236 | exports.jspack.prototype._PackSeries = function (n, s, a, p, v, i) { 237 | var fxn = format.en; 238 | 239 | for (o = 0; o < n; o++) { 240 | fxn(a, p + o * s, v[i + o]); 241 | } 242 | }; 243 | 244 | // Unpack the octet array a, beginning at offset p, according to the fmt string 245 | exports.jspack.prototype.Unpack = function (fmt, a, p) { 246 | bigEndian = fmt.charAt(0) != '<'; 247 | 248 | if (p == undefined || p == null) p = 0; 249 | 250 | var re = new RegExp(this._sPattern, 'g'); 251 | 252 | var ret = []; 253 | 254 | for (var m; m = re.exec(fmt); /* */ ) { 255 | var n; 256 | if (m[1] == undefined || m[1] == '') n = 1; 257 | else n = parseInt(m[1]); 258 | 259 | var s = this._lenLut[m[2]]; 260 | 261 | if ((p + n * s) > a.length) return undefined; 262 | 263 | switch (m[2]) { 264 | case 'A': 265 | case 's': 266 | rv.push(this._formatLut[m[2]].de(a, p, n)); 267 | break; 268 | case 'c': 269 | case 'b': 270 | case 'B': 271 | case 'h': 272 | case 'H': 273 | case 'i': 274 | case 'I': 275 | case 'l': 276 | case 'L': 277 | case 'f': 278 | case 'd': 279 | format = this._formatLut[m[2]]; 280 | ret.push(this._UnpackSeries(n, s, a, p)); 281 | break; 282 | } 283 | 284 | p += n * s; 285 | } 286 | 287 | return Array.prototype.concat.apply([], ret); 288 | }; 289 | 290 | // Pack the supplied values into the octet array a, beginning at offset p, according to the fmt string 291 | exports.jspack.prototype.PackTo = function (fmt, a, p, values) { 292 | bigEndian = (fmt.charAt(0) != '<'); 293 | 294 | var re = new RegExp(this._sPattern, 'g'); 295 | 296 | for (var m, i = 0; m = re.exec(fmt); /* */ ) { 297 | var n; 298 | if (m[1] == undefined || m[1] == '') n = 1; 299 | else n = parseInt(m[1]); 300 | 301 | var s = this._lenLut[m[2]]; 302 | 303 | if ((p + n * s) > a.length) return false; 304 | 305 | switch (m[2]) { 306 | case 'A': 307 | case 's': 308 | if ((i + 1) > values.length) return false; 309 | 310 | this._formatLut[m[2]].en(a, p, n, values[i]); 311 | 312 | i += 1; 313 | break; 314 | 315 | case 'c': 316 | case 'b': 317 | case 'B': 318 | case 'h': 319 | case 'H': 320 | case 'i': 321 | case 'I': 322 | case 'l': 323 | case 'L': 324 | case 'f': 325 | case 'd': 326 | format = this._formatLut[m[2]]; 327 | 328 | if (i + n > values.length) return false; 329 | 330 | this._PackSeries(n, s, a, p, values, i); 331 | 332 | i += n; 333 | break; 334 | 335 | case 'x': 336 | for (var j = 0; j < n; j++) { 337 | a[p + j] = 0; 338 | } 339 | break; 340 | } 341 | 342 | p += n * s; 343 | } 344 | 345 | return a; 346 | }; 347 | 348 | // Pack the supplied values into a new octet array, according to the fmt string 349 | exports.jspack.prototype.Pack = function (fmt, values) { 350 | return this.PackTo(fmt, new Array(this.CalcLength(fmt)), 0, values); 351 | }; 352 | 353 | // Determine the number of bytes represented by the format string 354 | exports.jspack.prototype.CalcLength = function (fmt) { 355 | var re = new RegExp(this._sPattern, 'g'); 356 | var sz = 0; 357 | 358 | while (match = re.exec(fmt)) { 359 | var n; 360 | if (match[1] == undefined || match[1] == '') n = 1; 361 | else n = parseInt(match[1]); 362 | 363 | sz += n * this._lenLut[match[2]]; 364 | } 365 | 366 | return sz; 367 | }; 368 | 369 | // Regular expression for counting digits 370 | exports.jspack.prototype._sPattern = '(\\d+)?([AxcbBhHsfdiIlL])'; 371 | 372 | // Byte widths for associated formats 373 | exports.jspack.prototype._lenLut = { 374 | 'A': 1, 375 | 'x': 1, 376 | 'c': 1, 377 | 'b': 1, 378 | 'B': 1, 379 | 'h': 2, 380 | 'H': 2, 381 | 's': 1, 382 | 'f': 4, 383 | 'd': 8, 384 | 'i': 4, 385 | 'I': 4, 386 | 'l': 4, 387 | 'L': 4 388 | }; 389 | 390 | exports.jspack.prototype._formatLut = { 391 | 'A': { 392 | en: exports.jspack.prototype._EnArray, 393 | de: exports.jspack.prototype._DeArray 394 | }, 395 | 's': { 396 | en: exports.jspack.prototype._EnString, 397 | de: exports.jspack.prototype._DeString 398 | }, 399 | 'c': { 400 | en: exports.jspack.prototype._EnChar, 401 | de: exports.jspack.prototype._DeChar 402 | }, 403 | 'b': { 404 | en: exports.jspack.prototype._EnInt, 405 | de: exports.jspack.prototype._DeInt, 406 | len: 1, 407 | signed: true, 408 | min: -Math.pow(2, 7), 409 | max: Math.pow(2, 7) - 1 410 | }, 411 | 'B': { 412 | en: exports.jspack.prototype._EnInt, 413 | de: exports.jspack.prototype._DeInt, 414 | len: 1, 415 | signed: false, 416 | min: 0, 417 | max: Math.pow(2, 8) - 1 418 | }, 419 | 'h': { 420 | en: exports.jspack.prototype._EnInt, 421 | de: exports.jspack.prototype._DeInt, 422 | len: 2, 423 | signed: true, 424 | min: -Math.pow(2, 15), 425 | max: Math.pow(2, 15) - 1 426 | }, 427 | 'H': { 428 | en: exports.jspack.prototype._EnInt, 429 | de: exports.jspack.prototype._DeInt, 430 | len: 2, 431 | signed: false, 432 | min: 0, 433 | max: Math.pow(2, 16) - 1 434 | }, 435 | 'i': { 436 | en: exports.jspack.prototype._EnInt, 437 | de: exports.jspack.prototype._DeInt, 438 | len: 4, 439 | signed: true, 440 | min: -Math.pow(2, 31), 441 | max: Math.pow(2, 31) - 1 442 | }, 443 | 'I': { 444 | en: exports.jspack.prototype._EnInt, 445 | de: exports.jspack.prototype._DeInt, 446 | len: 4, 447 | signed: false, 448 | min: 0, 449 | max: Math.pow(2, 32) - 1 450 | }, 451 | 'l': { 452 | en: exports.jspack.prototype._EnInt, 453 | de: exports.jspack.prototype._DeInt, 454 | len: 4, 455 | signed: true, 456 | min: -Math.pow(2, 31), 457 | max: Math.pow(2, 31) - 1 458 | }, 459 | 'L': { 460 | en: exports.jspack.prototype._EnInt, 461 | de: exports.jspack.prototype._DeInt, 462 | len: 4, 463 | signed: false, 464 | min: 0, 465 | max: Math.pow(2, 32) - 1 466 | }, 467 | 'f': { 468 | en: exports.jspack.prototype._En754, 469 | de: exports.jspack.prototype._De754, 470 | len: 4, 471 | bit: 23, 472 | rt: Math.pow(2, -24) - Math.pow(2, -77) 473 | }, 474 | 'd': { 475 | en: exports.jspack.prototype._En754, 476 | de: exports.jspack.prototype._De754, 477 | len: 8, 478 | bit: 52, 479 | rt: 0 480 | } 481 | }; 482 | -------------------------------------------------------------------------------- /lib/amqp-definitions-0-9-1.js: -------------------------------------------------------------------------------- 1 | exports.constants = [ 2 | [1, "frameMethod"], 3 | [2, "frameHeader"], 4 | [3, "frameBody"], 5 | [8, "frameHeartbeat"], 6 | [200, "replySuccess"], 7 | [206, "frameEnd"], 8 | [311, "contentTooLarge"], 9 | [313, "noConsumers"], 10 | [320, "connectionForced"], 11 | [402, "invalidPath"], 12 | [403, "accessRefused"], 13 | [404, "notFound"], 14 | [405, "resourceLocked"], 15 | [406, "preconditionFailed"], 16 | [501, "frameError"], 17 | [502, "syntaxError"], 18 | [503, "commandInvalid"], 19 | [504, "channelError"], 20 | [505, "unexpectedFrame"], 21 | [506, "resourceError"], 22 | [530, "notAllowed"], 23 | [540, "notImplemented"], 24 | [541, "internalError"], 25 | [4096, "frameMinSize"] 26 | ]; 27 | exports.classes = [{ 28 | "name": "connection", 29 | "index": 10, 30 | "fields": [], 31 | "methods": [{ 32 | "name": "start", 33 | "index": 10, 34 | "fields": [{ 35 | "name": "versionMajor", 36 | "domain": "octet" 37 | }, { 38 | "name": "versionMinor", 39 | "domain": "octet" 40 | }, { 41 | "name": "serverProperties", 42 | "domain": "table" 43 | }, { 44 | "name": "mechanisms", 45 | "domain": "longstr" 46 | }, { 47 | "name": "locales", 48 | "domain": "longstr" 49 | }] 50 | }, { 51 | "name": "startOk", 52 | "index": 11, 53 | "fields": [{ 54 | "name": "clientProperties", 55 | "domain": "table" 56 | }, { 57 | "name": "mechanism", 58 | "domain": "shortstr" 59 | }, { 60 | "name": "response", 61 | "domain": "longstr" 62 | }, { 63 | "name": "locale", 64 | "domain": "shortstr" 65 | }] 66 | }, { 67 | "name": "secure", 68 | "index": 20, 69 | "fields": [{ 70 | "name": "challenge", 71 | "domain": "longstr" 72 | }] 73 | }, { 74 | "name": "secureOk", 75 | "index": 21, 76 | "fields": [{ 77 | "name": "response", 78 | "domain": "longstr" 79 | }] 80 | }, { 81 | "name": "tune", 82 | "index": 30, 83 | "fields": [{ 84 | "name": "channelMax", 85 | "domain": "short" 86 | }, { 87 | "name": "frameMax", 88 | "domain": "long" 89 | }, { 90 | "name": "heartbeat", 91 | "domain": "short" 92 | }] 93 | }, { 94 | "name": "tuneOk", 95 | "index": 31, 96 | "fields": [{ 97 | "name": "channelMax", 98 | "domain": "short" 99 | }, { 100 | "name": "frameMax", 101 | "domain": "long" 102 | }, { 103 | "name": "heartbeat", 104 | "domain": "short" 105 | }] 106 | }, { 107 | "name": "open", 108 | "index": 40, 109 | "fields": [{ 110 | "name": "virtualHost", 111 | "domain": "shortstr" 112 | }, { 113 | "name": "reserved1", 114 | "domain": "shortstr" 115 | }, { 116 | "name": "reserved2", 117 | "domain": "bit" 118 | }] 119 | }, { 120 | "name": "openOk", 121 | "index": 41, 122 | "fields": [{ 123 | "name": "reserved1", 124 | "domain": "shortstr" 125 | }] 126 | }, { 127 | "name": "close", 128 | "index": 50, 129 | "fields": [{ 130 | "name": "replyCode", 131 | "domain": "short" 132 | }, { 133 | "name": "replyText", 134 | "domain": "shortstr" 135 | }, { 136 | "name": "classId", 137 | "domain": "short" 138 | }, { 139 | "name": "methodId", 140 | "domain": "short" 141 | }] 142 | }, { 143 | "name": "closeOk", 144 | "index": 51, 145 | "fields": [] 146 | }, { 147 | "name": "blocked", 148 | "index": 60, 149 | "fields": [{ 150 | "name": "reason", 151 | "domain": "shortstr" 152 | }] 153 | }, { 154 | "name": "unblocked", 155 | "index": 61, 156 | "fields": [] 157 | }] 158 | }, { 159 | "name": "channel", 160 | "index": 20, 161 | "fields": [], 162 | "methods": [{ 163 | "name": "open", 164 | "index": 10, 165 | "fields": [{ 166 | "name": "reserved1", 167 | "domain": "shortstr" 168 | }] 169 | }, { 170 | "name": "openOk", 171 | "index": 11, 172 | "fields": [{ 173 | "name": "reserved1", 174 | "domain": "longstr" 175 | }] 176 | }, { 177 | "name": "flow", 178 | "index": 20, 179 | "fields": [{ 180 | "name": "active", 181 | "domain": "bit" 182 | }] 183 | }, { 184 | "name": "flowOk", 185 | "index": 21, 186 | "fields": [{ 187 | "name": "active", 188 | "domain": "bit" 189 | }] 190 | }, { 191 | "name": "close", 192 | "index": 40, 193 | "fields": [{ 194 | "name": "replyCode", 195 | "domain": "short" 196 | }, { 197 | "name": "replyText", 198 | "domain": "shortstr" 199 | }, { 200 | "name": "classId", 201 | "domain": "short" 202 | }, { 203 | "name": "methodId", 204 | "domain": "short" 205 | }] 206 | }, { 207 | "name": "closeOk", 208 | "index": 41, 209 | "fields": [] 210 | }] 211 | }, { 212 | "name": "exchange", 213 | "index": 40, 214 | "fields": [], 215 | "methods": [{ 216 | "name": "declare", 217 | "index": 10, 218 | "fields": [{ 219 | "name": "reserved1", 220 | "domain": "short" 221 | }, { 222 | "name": "exchange", 223 | "domain": "shortstr" 224 | }, { 225 | "name": "type", 226 | "domain": "shortstr" 227 | }, { 228 | "name": "passive", 229 | "domain": "bit" 230 | }, { 231 | "name": "durable", 232 | "domain": "bit" 233 | }, { 234 | "name": "autoDelete", 235 | "domain": "bit" 236 | }, { 237 | "name": "reserved2", 238 | "domain": "bit" 239 | }, { 240 | "name": "reserved3", 241 | "domain": "bit" 242 | }, { 243 | "name": "noWait", 244 | "domain": "bit" 245 | }, { 246 | "name": "arguments", 247 | "domain": "table" 248 | }] 249 | }, { 250 | "name": "declareOk", 251 | "index": 11, 252 | "fields": [] 253 | }, { 254 | "name": "delete", 255 | "index": 20, 256 | "fields": [{ 257 | "name": "reserved1", 258 | "domain": "short" 259 | }, { 260 | "name": "exchange", 261 | "domain": "shortstr" 262 | }, { 263 | "name": "ifUnused", 264 | "domain": "bit" 265 | }, { 266 | "name": "noWait", 267 | "domain": "bit" 268 | }] 269 | }, { 270 | "name": "deleteOk", 271 | "index": 21, 272 | "fields": [] 273 | }, { 274 | "name": "bind", 275 | "index": 30, 276 | "fields": [{ 277 | "name": "reserved1", 278 | "domain": "short" 279 | }, { 280 | "name": "destination", 281 | "domain": "shortstr" 282 | }, { 283 | "name": "source", 284 | "domain": "shortstr" 285 | }, { 286 | "name": "routingKey", 287 | "domain": "shortstr" 288 | }, { 289 | "name": "noWait", 290 | "domain": "bit" 291 | }, { 292 | "name": "arguments", 293 | "domain": "table" 294 | }] 295 | }, { 296 | "name": "bindOk", 297 | "index": 31, 298 | "fields": [] 299 | }, { 300 | "name": "unbind", 301 | "index": 40, 302 | "fields": [{ 303 | "name": "reserved1", 304 | "domain": "short" 305 | }, { 306 | "name": "destination", 307 | "domain": "shortstr" 308 | }, { 309 | "name": "source", 310 | "domain": "shortstr" 311 | }, { 312 | "name": "routingKey", 313 | "domain": "shortstr" 314 | }, { 315 | "name": "noWait", 316 | "domain": "bit" 317 | }, { 318 | "name": "arguments", 319 | "domain": "table" 320 | }] 321 | }, { 322 | "name": "unbindOk", 323 | "index": 51, 324 | "fields": [] 325 | }] 326 | }, { 327 | "name": "queue", 328 | "index": 50, 329 | "fields": [], 330 | "methods": [{ 331 | "name": "declare", 332 | "index": 10, 333 | "fields": [{ 334 | "name": "reserved1", 335 | "domain": "short" 336 | }, { 337 | "name": "queue", 338 | "domain": "shortstr" 339 | }, { 340 | "name": "passive", 341 | "domain": "bit" 342 | }, { 343 | "name": "durable", 344 | "domain": "bit" 345 | }, { 346 | "name": "exclusive", 347 | "domain": "bit" 348 | }, { 349 | "name": "autoDelete", 350 | "domain": "bit" 351 | }, { 352 | "name": "noWait", 353 | "domain": "bit" 354 | }, { 355 | "name": "arguments", 356 | "domain": "table" 357 | }] 358 | }, { 359 | "name": "declareOk", 360 | "index": 11, 361 | "fields": [{ 362 | "name": "queue", 363 | "domain": "shortstr" 364 | }, { 365 | "name": "messageCount", 366 | "domain": "long" 367 | }, { 368 | "name": "consumerCount", 369 | "domain": "long" 370 | }] 371 | }, { 372 | "name": "bind", 373 | "index": 20, 374 | "fields": [{ 375 | "name": "reserved1", 376 | "domain": "short" 377 | }, { 378 | "name": "queue", 379 | "domain": "shortstr" 380 | }, { 381 | "name": "exchange", 382 | "domain": "shortstr" 383 | }, { 384 | "name": "routingKey", 385 | "domain": "shortstr" 386 | }, { 387 | "name": "noWait", 388 | "domain": "bit" 389 | }, { 390 | "name": "arguments", 391 | "domain": "table" 392 | }] 393 | }, { 394 | "name": "bindOk", 395 | "index": 21, 396 | "fields": [] 397 | }, { 398 | "name": "unbind", 399 | "index": 50, 400 | "fields": [{ 401 | "name": "reserved1", 402 | "domain": "short" 403 | }, { 404 | "name": "queue", 405 | "domain": "shortstr" 406 | }, { 407 | "name": "exchange", 408 | "domain": "shortstr" 409 | }, { 410 | "name": "routingKey", 411 | "domain": "shortstr" 412 | }, { 413 | "name": "arguments", 414 | "domain": "table" 415 | }] 416 | }, { 417 | "name": "unbindOk", 418 | "index": 51, 419 | "fields": [] 420 | }, { 421 | "name": "purge", 422 | "index": 30, 423 | "fields": [{ 424 | "name": "reserved1", 425 | "domain": "short" 426 | }, { 427 | "name": "queue", 428 | "domain": "shortstr" 429 | }, { 430 | "name": "noWait", 431 | "domain": "bit" 432 | }] 433 | }, { 434 | "name": "purgeOk", 435 | "index": 31, 436 | "fields": [{ 437 | "name": "messageCount", 438 | "domain": "long" 439 | }] 440 | }, { 441 | "name": "delete", 442 | "index": 40, 443 | "fields": [{ 444 | "name": "reserved1", 445 | "domain": "short" 446 | }, { 447 | "name": "queue", 448 | "domain": "shortstr" 449 | }, { 450 | "name": "ifUnused", 451 | "domain": "bit" 452 | }, { 453 | "name": "ifEmpty", 454 | "domain": "bit" 455 | }, { 456 | "name": "noWait", 457 | "domain": "bit" 458 | }] 459 | }, { 460 | "name": "deleteOk", 461 | "index": 41, 462 | "fields": [{ 463 | "name": "messageCount", 464 | "domain": "long" 465 | }] 466 | }] 467 | }, { 468 | "name": "basic", 469 | "index": 60, 470 | "fields": [{ 471 | "name": "contentType", 472 | "domain": "shortstr" 473 | }, { 474 | "name": "contentEncoding", 475 | "domain": "shortstr" 476 | }, { 477 | "name": "headers", 478 | "domain": "table" 479 | }, { 480 | "name": "deliveryMode", 481 | "domain": "octet" 482 | }, { 483 | "name": "priority", 484 | "domain": "octet" 485 | }, { 486 | "name": "correlationId", 487 | "domain": "shortstr" 488 | }, { 489 | "name": "replyTo", 490 | "domain": "shortstr" 491 | }, { 492 | "name": "expiration", 493 | "domain": "shortstr" 494 | }, { 495 | "name": "messageId", 496 | "domain": "shortstr" 497 | }, { 498 | "name": "timestamp", 499 | "domain": "timestamp" 500 | }, { 501 | "name": "type", 502 | "domain": "shortstr" 503 | }, { 504 | "name": "userId", 505 | "domain": "shortstr" 506 | }, { 507 | "name": "appId", 508 | "domain": "shortstr" 509 | }, { 510 | "name": "reserved", 511 | "domain": "shortstr" 512 | }], 513 | "methods": [{ 514 | "name": "qos", 515 | "index": 10, 516 | "fields": [{ 517 | "name": "prefetchSize", 518 | "domain": "long" 519 | }, { 520 | "name": "prefetchCount", 521 | "domain": "short" 522 | }, { 523 | "name": "global", 524 | "domain": "bit" 525 | }] 526 | }, { 527 | "name": "qosOk", 528 | "index": 11, 529 | "fields": [] 530 | }, { 531 | "name": "consume", 532 | "index": 20, 533 | "fields": [{ 534 | "name": "reserved1", 535 | "domain": "short" 536 | }, { 537 | "name": "queue", 538 | "domain": "shortstr" 539 | }, { 540 | "name": "consumerTag", 541 | "domain": "shortstr" 542 | }, { 543 | "name": "noLocal", 544 | "domain": "bit" 545 | }, { 546 | "name": "noAck", 547 | "domain": "bit" 548 | }, { 549 | "name": "exclusive", 550 | "domain": "bit" 551 | }, { 552 | "name": "noWait", 553 | "domain": "bit" 554 | }, { 555 | "name": "arguments", 556 | "domain": "table" 557 | }] 558 | }, { 559 | "name": "consumeOk", 560 | "index": 21, 561 | "fields": [{ 562 | "name": "consumerTag", 563 | "domain": "shortstr" 564 | }] 565 | }, { 566 | "name": "cancel", 567 | "index": 30, 568 | "fields": [{ 569 | "name": "consumerTag", 570 | "domain": "shortstr" 571 | }, { 572 | "name": "noWait", 573 | "domain": "bit" 574 | }] 575 | }, { 576 | "name": "cancelOk", 577 | "index": 31, 578 | "fields": [{ 579 | "name": "consumerTag", 580 | "domain": "shortstr" 581 | }] 582 | }, { 583 | "name": "publish", 584 | "index": 40, 585 | "fields": [{ 586 | "name": "reserved1", 587 | "domain": "short" 588 | }, { 589 | "name": "exchange", 590 | "domain": "shortstr" 591 | }, { 592 | "name": "routingKey", 593 | "domain": "shortstr" 594 | }, { 595 | "name": "mandatory", 596 | "domain": "bit" 597 | }, { 598 | "name": "immediate", 599 | "domain": "bit" 600 | }] 601 | }, { 602 | "name": "return", 603 | "index": 50, 604 | "fields": [{ 605 | "name": "replyCode", 606 | "domain": "short" 607 | }, { 608 | "name": "replyText", 609 | "domain": "shortstr" 610 | }, { 611 | "name": "exchange", 612 | "domain": "shortstr" 613 | }, { 614 | "name": "routingKey", 615 | "domain": "shortstr" 616 | }] 617 | }, { 618 | "name": "deliver", 619 | "index": 60, 620 | "fields": [{ 621 | "name": "consumerTag", 622 | "domain": "shortstr" 623 | }, { 624 | "name": "deliveryTag", 625 | "domain": "longlong" 626 | }, { 627 | "name": "redelivered", 628 | "domain": "bit" 629 | }, { 630 | "name": "exchange", 631 | "domain": "shortstr" 632 | }, { 633 | "name": "routingKey", 634 | "domain": "shortstr" 635 | }] 636 | }, { 637 | "name": "get", 638 | "index": 70, 639 | "fields": [{ 640 | "name": "reserved1", 641 | "domain": "short" 642 | }, { 643 | "name": "queue", 644 | "domain": "shortstr" 645 | }, { 646 | "name": "noAck", 647 | "domain": "bit" 648 | }] 649 | }, { 650 | "name": "getOk", 651 | "index": 71, 652 | "fields": [{ 653 | "name": "deliveryTag", 654 | "domain": "longlong" 655 | }, { 656 | "name": "redelivered", 657 | "domain": "bit" 658 | }, { 659 | "name": "exchange", 660 | "domain": "shortstr" 661 | }, { 662 | "name": "routingKey", 663 | "domain": "shortstr" 664 | }, { 665 | "name": "messageCount", 666 | "domain": "long" 667 | }] 668 | }, { 669 | "name": "getEmpty", 670 | "index": 72, 671 | "fields": [{ 672 | "name": "reserved1", 673 | "domain": "shortstr" 674 | }] 675 | }, { 676 | "name": "ack", 677 | "index": 80, 678 | "fields": [{ 679 | "name": "deliveryTag", 680 | "domain": "longlong" 681 | }, { 682 | "name": "multiple", 683 | "domain": "bit" 684 | }] 685 | }, { 686 | "name": "reject", 687 | "index": 90, 688 | "fields": [{ 689 | "name": "deliveryTag", 690 | "domain": "longlong" 691 | }, { 692 | "name": "requeue", 693 | "domain": "bit" 694 | }] 695 | }, { 696 | "name": "recoverAsync", 697 | "index": 100, 698 | "fields": [{ 699 | "name": "requeue", 700 | "domain": "bit" 701 | }] 702 | }, { 703 | "name": "recover", 704 | "index": 110, 705 | "fields": [{ 706 | "name": "requeue", 707 | "domain": "bit" 708 | }] 709 | }, { 710 | "name": "recoverOk", 711 | "index": 111, 712 | "fields": [] 713 | }] 714 | }, { 715 | "name": "tx", 716 | "index": 90, 717 | "fields": [], 718 | "methods": [{ 719 | "name": "select", 720 | "index": 10, 721 | "fields": [] 722 | }, { 723 | "name": "selectOk", 724 | "index": 11, 725 | "fields": [] 726 | }, { 727 | "name": "commit", 728 | "index": 20, 729 | "fields": [] 730 | }, { 731 | "name": "commitOk", 732 | "index": 21, 733 | "fields": [] 734 | }, { 735 | "name": "rollback", 736 | "index": 30, 737 | "fields": [] 738 | }, { 739 | "name": "rollbackOk", 740 | "index": 31, 741 | "fields": [] 742 | }] 743 | }, { 744 | "name": "confirm", 745 | "index": 85, 746 | "fields": [], 747 | "methods": [{ 748 | "name": "select", 749 | "index": 10, 750 | "fields": [{ 751 | "name": "noWait", 752 | "domain": "bit" 753 | }] 754 | }, { 755 | "name": "selectOk", 756 | "index": 11, 757 | "fields": [] 758 | }] 759 | }]; 760 | -------------------------------------------------------------------------------- /lib/channel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var events = require('events'); 3 | var util = require('util'); 4 | var fs = require('fs'); 5 | var Promise = require('./promise').Promise; 6 | var definitions = require('./definitions'); 7 | var methods = definitions.methods; 8 | 9 | // This class is not exposed to the user. Queue and Exchange are subclasses 10 | // of Channel. This just provides a task queue. 11 | var Channel = module.exports = function Channel (connection, channel) { 12 | events.EventEmitter.call(this); 13 | 14 | // Unlimited listeners. Helps when e.g. publishing high-volume messages, 15 | // 10 is far too low. 16 | this.setMaxListeners(0); 17 | 18 | this.channel = channel; 19 | this.connection = connection; 20 | this._tasks = []; 21 | 22 | this.reconnect(); 23 | }; 24 | util.inherits(Channel, events.EventEmitter); 25 | 26 | Channel.prototype.closeOK = function() { 27 | this.connection._sendMethod(this.channel, methods.channelCloseOk, {reserved1: ""}); 28 | }; 29 | 30 | Channel.prototype.reconnect = function () { 31 | this.connection._sendMethod(this.channel, methods.channelOpen, {reserved1: ""}); 32 | }; 33 | 34 | Channel.prototype._taskPush = function (reply, cb) { 35 | var promise = new Promise(); 36 | this._tasks.push({ 37 | promise: promise, 38 | reply: reply, 39 | sent: false, 40 | cb: cb 41 | }); 42 | this._tasksFlush(); 43 | return promise; 44 | }; 45 | 46 | Channel.prototype._tasksFlush = function () { 47 | if (this.state != 'open') return; 48 | 49 | for (var i = 0; i < this._tasks.length; i++) { 50 | var task = this._tasks[i]; 51 | if (task.sent) continue; 52 | task.cb(); 53 | task.sent = true; 54 | if (!task.reply) { 55 | // if we don't expect a reply, just delete it now 56 | this._tasks.splice(i, 1); 57 | i = i-1; 58 | } 59 | } 60 | }; 61 | 62 | Channel.prototype._handleTaskReply = function (channel, method, args) { 63 | var task, i; 64 | 65 | for (i = 0; i < this._tasks.length; i++) { 66 | if (this._tasks[i].reply == method) { 67 | task = this._tasks[i]; 68 | this._tasks.splice(i, 1); 69 | task.promise.emitSuccess(args); 70 | this._tasksFlush(); 71 | return true; 72 | } 73 | } 74 | 75 | return false; 76 | }; 77 | 78 | Channel.prototype._onChannelMethod = function(channel, method, args) { 79 | switch (method) { 80 | case methods.channelCloseOk: 81 | delete this.connection.channels[this.channel]; 82 | this.state = 'closed'; 83 | // TODO should this be falling through? 84 | default: 85 | this._onMethod(channel, method, args); 86 | } 87 | }; 88 | 89 | Channel.prototype.close = function(reason) { 90 | this.state = 'closing'; 91 | this.connection._sendMethod(this.channel, methods.channelClose, 92 | {'replyText': reason ? reason : 'Goodbye from node', 93 | 'replyCode': 200, 94 | 'classId': 0, 95 | 'methodId': 0}); 96 | }; 97 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | AMQPTypes: Object.freeze({ 3 | STRING: 'S'.charCodeAt(0) 4 | , INTEGER: 'I'.charCodeAt(0) 5 | , HASH: 'F'.charCodeAt(0) 6 | , TIME: 'T'.charCodeAt(0) 7 | , DECIMAL: 'D'.charCodeAt(0) 8 | , BOOLEAN: 't'.charCodeAt(0) 9 | , SIGNED_8BIT: 'b'.charCodeAt(0) 10 | , SIGNED_16BIT: 's'.charCodeAt(0) 11 | , SIGNED_64BIT: 'l'.charCodeAt(0) 12 | , _32BIT_FLOAT: 'f'.charCodeAt(0) 13 | , _64BIT_FLOAT: 'd'.charCodeAt(0) 14 | , VOID: 'V'.charCodeAt(0) 15 | , BYTE_ARRAY: 'x'.charCodeAt(0) 16 | , ARRAY: 'A'.charCodeAt(0) 17 | , TEN: '10'.charCodeAt(0) 18 | , BOOLEAN_TRUE: '\x01' 19 | , BOOLEAN_FALSE:'\x00' 20 | 21 | }) 22 | , Indicators: Object.freeze({ 23 | FRAME_END: 206 24 | }) 25 | , FrameType: Object.freeze({ 26 | METHOD: 1 27 | , HEADER: 2 28 | , BODY: 3 29 | , HEARTBEAT: 8 30 | }) 31 | } 32 | 33 | -------------------------------------------------------------------------------- /lib/debug.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var DEBUG = process.env['NODE_DEBUG_AMQP']; 4 | 5 | // only define debug function in debugging mode 6 | if (DEBUG) { 7 | module.exports = function debug () { 8 | console.error.apply(null, arguments); 9 | }; 10 | } else { 11 | module.exports = null; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /lib/definitions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var protocol = require('./amqp-definitions-0-9-1'); 4 | 5 | // a look up table for methods recieved 6 | // indexed on class id, method id 7 | var methodTable = {}; 8 | 9 | // methods keyed on their name 10 | var methods = {}; 11 | 12 | // classes keyed on their index 13 | var classes = {}; 14 | 15 | (function () { // anon scope for init 16 | //debug("initializing amqp methods..."); 17 | 18 | for (var i = 0; i < protocol.classes.length; i++) { 19 | var classInfo = protocol.classes[i]; 20 | classes[classInfo.index] = classInfo; 21 | 22 | for (var j = 0; j < classInfo.methods.length; j++) { 23 | var methodInfo = classInfo.methods[j]; 24 | 25 | var name = classInfo.name + 26 | methodInfo.name[0].toUpperCase() + 27 | methodInfo.name.slice(1); 28 | 29 | //debug(name); 30 | 31 | var method = { 32 | name: name, 33 | fields: methodInfo.fields, 34 | methodIndex: methodInfo.index, 35 | classIndex: classInfo.index 36 | }; 37 | 38 | if (!methodTable[classInfo.index]) methodTable[classInfo.index] = {}; 39 | methodTable[classInfo.index][methodInfo.index] = method; 40 | methods[name] = method; 41 | } 42 | } 43 | })(); 44 | 45 | module.exports = {methods: methods, classes: classes, methodTable: methodTable}; 46 | -------------------------------------------------------------------------------- /lib/exchange.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var events = require('events'); 3 | var util = require('util'); 4 | var net = require('net'); 5 | var tls = require('tls'); 6 | var fs = require('fs'); 7 | var _ = require('lodash'); 8 | var methods = require('./definitions').methods; 9 | var Channel = require('./channel'); 10 | var debug = require('./debug'); 11 | 12 | var Exchange = module.exports = function Exchange (connection, channel, name, options, openCallback) { 13 | Channel.call(this, connection, channel); 14 | this.name = name; 15 | this.binds = 0; // keep track of queues bound 16 | this.exchangeBinds = 0; // keep track of exchanges bound 17 | this.sourceExchanges = {}; 18 | this.options = _.defaults(options || {}, {autoDelete: true}); 19 | this._openCallback = openCallback; 20 | 21 | this._sequence = null; 22 | this._unAcked = {}; 23 | this._addedExchangeErrorHandler = false; 24 | }; 25 | util.inherits(Exchange, Channel); 26 | 27 | // creates an error handler scoped to the given `exchange` 28 | function createExchangeErrorHandlerFor (exchange) { 29 | return function (err) { 30 | if (!exchange.options.confirm) return; 31 | 32 | // should requeue instead? 33 | // https://www.rabbitmq.com/reliability.html#producer 34 | debug && debug('Exchange error handler triggered, erroring and wiping all unacked publishes'); 35 | for (var id in exchange._unAcked) { 36 | var task = exchange._unAcked[id]; 37 | task.emit('ack error', err); 38 | delete exchange._unAcked[id]; 39 | } 40 | }; 41 | } 42 | 43 | Exchange.prototype._onMethod = function (channel, method, args) { 44 | this.emit(method.name, args); 45 | 46 | if (this._handleTaskReply.apply(this, arguments)) 47 | return true; 48 | 49 | var cb; 50 | 51 | switch (method) { 52 | case methods.channelOpenOk: 53 | this._sequence = null; 54 | 55 | if (!this._addedExchangeErrorHandler) { 56 | var errorHandler = createExchangeErrorHandlerFor(this); 57 | this.connection.on('error', errorHandler); 58 | this.on('error', errorHandler); 59 | this._addedExchangeErrorHandler = true; 60 | } 61 | 62 | // Pre-baked exchanges don't need to be declared 63 | if (/^$|(amq\.)/.test(this.name)) { 64 | //If confirm mode is specified we have to set it no matter the exchange. 65 | if (this.options.confirm) { 66 | this._confirmSelect(channel); 67 | return; 68 | } 69 | 70 | this.state = 'open'; 71 | // - issue #33 fix 72 | if (this._openCallback) { 73 | this._openCallback(this); 74 | this._openCallback = null; 75 | } 76 | // -- 77 | this.emit('open'); 78 | 79 | // For if we want to delete a exchange, 80 | // we dont care if all of the options match. 81 | } else if (this.options.noDeclare) { 82 | if (this.options.confirm) { 83 | this._confirmSelect(channel); 84 | return; 85 | } 86 | 87 | this.state = 'open'; 88 | 89 | if (this._openCallback) { 90 | this._openCallback(this); 91 | this._openCallback = null; 92 | } 93 | 94 | this.emit('open'); 95 | } else { 96 | this.connection._sendMethod(channel, methods.exchangeDeclare, 97 | { reserved1: 0 98 | , reserved2: false 99 | , reserved3: false 100 | , exchange: this.name 101 | , type: this.options.type || 'topic' 102 | , passive: !!this.options.passive 103 | , durable: !!this.options.durable 104 | , autoDelete: !!this.options.autoDelete 105 | , internal: !!this.options.internal 106 | , noWait: false 107 | , "arguments":this.options.arguments || {} 108 | }); 109 | this.state = 'declaring'; 110 | } 111 | break; 112 | 113 | case methods.exchangeDeclareOk: 114 | if (this.options.confirm) { 115 | this._confirmSelect(channel); 116 | } else { 117 | 118 | this.state = 'open'; 119 | this.emit('open'); 120 | if (this._openCallback) { 121 | this._openCallback(this); 122 | this._openCallback = null; 123 | } 124 | } 125 | break; 126 | 127 | case methods.confirmSelectOk: 128 | this._sequence = 1; 129 | 130 | this.state = 'open'; 131 | this.emit('open'); 132 | if (this._openCallback) { 133 | this._openCallback(this); 134 | this._openCallback = null; 135 | } 136 | break; 137 | 138 | case methods.channelClose: 139 | this.state = "closed"; 140 | this.closeOK(); 141 | this.connection.exchangeClosed(this.name); 142 | var e = new Error(args.replyText); 143 | e.code = args.replyCode; 144 | this.emit('error', e); 145 | this.emit('close'); 146 | break; 147 | 148 | case methods.channelCloseOk: 149 | this.connection.exchangeClosed(this.name); 150 | this.emit('close'); 151 | break; 152 | 153 | 154 | case methods.basicAck: 155 | this.emit('basic-ack', args); 156 | var sequenceNumber = args.deliveryTag.readUInt32BE(4), tag; 157 | debug && debug("basic-ack, sequence: ", sequenceNumber); 158 | 159 | if (sequenceNumber === 0 && args.multiple === true) { 160 | // we must ack everything 161 | for (tag in this._unAcked) { 162 | this._unAcked[tag].emit('ack'); 163 | delete this._unAcked[tag]; 164 | } 165 | } else if (sequenceNumber !== 0 && args.multiple === true) { 166 | // we must ack everything before the delivery tag 167 | for (tag in this._unAcked) { 168 | if (tag <= sequenceNumber) { 169 | this._unAcked[tag].emit('ack'); 170 | delete this._unAcked[tag]; 171 | } 172 | } 173 | } else if (this._unAcked[sequenceNumber] && args.multiple === false) { 174 | // simple single ack 175 | this._unAcked[sequenceNumber].emit('ack'); 176 | delete this._unAcked[sequenceNumber]; 177 | } 178 | break; 179 | 180 | case methods.basicReturn: 181 | this.emit('basic-return', args); 182 | break; 183 | 184 | case methods.exchangeBindOk: 185 | if (this._bindCallback) { 186 | // setting this._bindCallback to null before calling the callback allows for a subsequent bind within the callback 187 | cb = this._bindCallback; 188 | this._bindCallback = null; 189 | cb(this); 190 | } 191 | break; 192 | 193 | case methods.exchangeUnbindOk: 194 | if (this._unbindCallback) { 195 | cb = this._unbindCallback; 196 | this._unbindCallback = null; 197 | cb(this); 198 | } 199 | break; 200 | 201 | default: 202 | throw new Error("Uncaught method '" + method.name + "' with args " + 203 | JSON.stringify(args)); 204 | } 205 | 206 | this._tasksFlush(); 207 | }; 208 | 209 | // exchange.publish('routing.key', 'body'); 210 | // 211 | // the third argument can specify additional options 212 | // - mandatory (boolean, default false) 213 | // - immediate (boolean, default false) 214 | // - contentType (default 'application/octet-stream') 215 | // - contentEncoding 216 | // - headers 217 | // - deliveryMode 218 | // - priority (0-9) 219 | // - correlationId 220 | // - replyTo 221 | // - expiration 222 | // - messageId 223 | // - timestamp 224 | // - userId 225 | // - appId 226 | // - clusterId 227 | // 228 | // the callback is optional and is only used when confirm is turned on for the exchange 229 | 230 | Exchange.prototype.publish = function (routingKey, data, options, callback) { 231 | var self = this; 232 | callback = callback || function() {}; 233 | 234 | if (this.connection._blocked) { 235 | return callback(true, new Error('Connection is blocked, server reason: ' + this.connection._blockedReason)); 236 | } 237 | 238 | if (this.state !== 'open') { 239 | this._sequence = null; 240 | return callback(true, new Error('Can not publish: exchange is not open')); 241 | } 242 | 243 | if (this.options.confirm && !this._readyToPublishWithConfirms()) { 244 | return callback(true, new Error('Not yet ready to publish with confirms')); 245 | } 246 | 247 | options = _.assignIn({}, options || {}); 248 | options.routingKey = routingKey; 249 | options.exchange = self.name; 250 | options.mandatory = options.mandatory ? true : false; 251 | options.immediate = options.immediate ? true : false; 252 | options.reserved1 = 0; 253 | 254 | var task = this._taskPush(null, function () { 255 | self.connection._sendMethod(self.channel, methods.basicPublish, options); 256 | // This interface is probably not appropriate for streaming large files. 257 | // (Of course it's arguable about whether AMQP is the appropriate 258 | // transport for large files.) The content header wants to know the size 259 | // of the data before sending it - so there's no point in trying to have a 260 | // general streaming interface - streaming messages of unknown size simply 261 | // isn't possible with AMQP. This is all to say, don't send big messages. 262 | // If you need to stream something large, chunk it yourself. 263 | self.connection._sendBody(self.channel, data, options); 264 | }); 265 | 266 | if (self.options.confirm) self._awaitConfirm(task, callback); 267 | return task; 268 | }; 269 | 270 | // registers tasks for confirms 271 | Exchange.prototype._awaitConfirm = function _awaitConfirm (task, callback) { 272 | if (!this._addedExchangeErrorHandler) { 273 | // if connection fails, we want to ack error all unacked publishes. 274 | this.connection.on('error', createExchangeErrorHandlerFor(this)); 275 | this.on('error', createExchangeErrorHandlerFor(this)); 276 | this._addedExchangeErrorHandler = true; 277 | } 278 | 279 | debug && debug('awaiting confirmation for ' + this._sequence); 280 | task.sequence = this._sequence; 281 | this._unAcked[this._sequence] = task; 282 | this._sequence++; 283 | 284 | if ('function' != typeof callback) return; 285 | 286 | task.once('ack error', function (err) { 287 | task.removeAllListeners(); 288 | callback(true, err); 289 | }); 290 | 291 | task.once('ack', function () { 292 | task.removeAllListeners(); 293 | callback(false); 294 | }); 295 | }; 296 | 297 | // do any necessary cleanups eg. after queue destruction 298 | Exchange.prototype.cleanup = function() { 299 | if (this.binds === 0) { // don't keep reference open if unused 300 | this.connection.exchangeClosed(this.name); 301 | } 302 | }; 303 | 304 | Exchange.prototype.destroy = function (ifUnused) { 305 | var self = this; 306 | return this._taskPush(methods.exchangeDeleteOk, function () { 307 | self.connection.exchangeClosed(self.name); 308 | self.connection._sendMethod(self.channel, methods.exchangeDelete, 309 | { reserved1: 0 310 | , exchange: self.name 311 | , ifUnused: ifUnused ? true : false 312 | , noWait: false 313 | }); 314 | }); 315 | }; 316 | 317 | // E2E Unbind 318 | // support RabbitMQ's exchange-to-exchange binding extension 319 | // http://www.rabbitmq.com/e2e.html 320 | Exchange.prototype.unbind = function (/* exchange, routingKey [, bindCallback] */) { 321 | var self = this; 322 | 323 | // Both arguments are required. The binding to the destination 324 | // exchange/routingKey will be unbound. 325 | 326 | var exchange = arguments[0] 327 | , routingKey = arguments[1] 328 | , callback = arguments[2] 329 | ; 330 | 331 | if (callback) this._unbindCallback = callback; 332 | 333 | return this._taskPush(methods.exchangeUnbindOk, function () { 334 | var source = exchange instanceof Exchange ? exchange.name : exchange; 335 | var destination = self.name; 336 | 337 | if (source in self.connection.exchanges) { 338 | delete self.sourceExchanges[source]; 339 | self.connection.exchanges[source].exchangeBinds--; 340 | } 341 | 342 | self.connection._sendMethod(self.channel, methods.exchangeUnbind, 343 | { reserved1: 0 344 | , destination: destination 345 | , source: source 346 | , routingKey: routingKey 347 | , noWait: false 348 | , "arguments": {} 349 | }); 350 | }); 351 | }; 352 | 353 | // E2E Bind 354 | // support RabbitMQ's exchange-to-exchange binding extension 355 | // http://www.rabbitmq.com/e2e.html 356 | Exchange.prototype.bind = function (/* exchange, routingKey [, bindCallback] */) { 357 | var self = this; 358 | 359 | // Two arguments are required. The binding to the destination 360 | // exchange/routingKey will be established. 361 | 362 | var exchange = arguments[0] 363 | , routingKey = arguments[1] 364 | , callback = arguments[2] 365 | ; 366 | 367 | if (callback) this._bindCallback = callback; 368 | 369 | var source = exchange instanceof Exchange ? exchange.name : exchange; 370 | var destination = self.name; 371 | 372 | if(source in self.connection.exchanges) { 373 | self.sourceExchanges[source] = self.connection.exchanges[source]; 374 | self.connection.exchanges[source].exchangeBinds++; 375 | } 376 | 377 | self.connection._sendMethod(self.channel, methods.exchangeBind, 378 | { reserved1: 0 379 | , destination: destination 380 | , source: source 381 | , routingKey: routingKey 382 | , noWait: false 383 | , "arguments": {} 384 | }); 385 | 386 | }; 387 | 388 | // E2E Bind 389 | // support RabbitMQ's exchange-to-exchange binding extension 390 | // http://www.rabbitmq.com/e2e.html 391 | Exchange.prototype.bind_headers = function (/* exchange, routing [, bindCallback] */) { 392 | var self = this; 393 | 394 | // Two arguments are required. The binding to the destination 395 | // exchange/routingKey will be established. 396 | 397 | var exchange = arguments[0] 398 | , routing = arguments[1] 399 | , callback = arguments[2] 400 | ; 401 | 402 | if (callback) this._bindCallback = callback; 403 | 404 | var source = exchange instanceof Exchange ? exchange.name : exchange; 405 | var destination = self.name; 406 | 407 | if (source in self.connection.exchanges) { 408 | self.sourceExchanges[source] = self.connection.exchanges[source]; 409 | self.connection.exchanges[source].exchangeBinds++; 410 | } 411 | 412 | self.connection._sendMethod(self.channel, methods.exchangeBind, 413 | { reserved1: 0 414 | , destination: destination 415 | , source: source 416 | , routingKey: '' 417 | , noWait: false 418 | , "arguments": routing 419 | }); 420 | }; 421 | 422 | Exchange.prototype._confirmSelect = function(channel) { 423 | this.connection._sendMethod(channel, methods.confirmSelect, { noWait: false }); 424 | }; 425 | 426 | Exchange.prototype._readyToPublishWithConfirms = function() { 427 | return this._sequence != null; 428 | }; 429 | -------------------------------------------------------------------------------- /lib/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var events = require('events'), 3 | util = require('util'), 4 | fs = require('fs'), 5 | protocol, 6 | definitions = require('./definitions'); 7 | 8 | // Properties: 9 | // - routingKey 10 | // - size 11 | // - deliveryTag 12 | // 13 | // - contentType (default 'application/octet-stream') 14 | // - contentEncoding 15 | // - headers 16 | // - deliveryMode 17 | // - priority (0-9) 18 | // - correlationId 19 | // - replyTo 20 | // - experation 21 | // - messageId 22 | // - timestamp 23 | // - userId 24 | // - appId 25 | // - clusterId 26 | var Message = module.exports = function Message (queue, args) { 27 | var msgProperties = definitions.classes[60].fields; 28 | 29 | events.EventEmitter.call(this); 30 | 31 | this.queue = queue; 32 | 33 | this.deliveryTag = args.deliveryTag; 34 | this.redelivered = args.redelivered; 35 | this.exchange = args.exchange; 36 | this.routingKey = args.routingKey; 37 | this.consumerTag = args.consumerTag; 38 | 39 | for (var i=0, l=msgProperties.length; i= fh.length) { 55 | fh.read = 0; 56 | frameType = fh[fh.read++]; 57 | frameChannel = parseInt(fh, 2); 58 | var frameSize = parseInt(fh, 4); 59 | fh.used = 0; // for reuse 60 | if (frameSize > self.maxFrameBuffer) { 61 | self.throwError("Oversized frame " + frameSize); 62 | } 63 | frameBuffer = new Buffer(frameSize); 64 | frameBuffer.used = 0; 65 | return frame(data.slice(needed)); 66 | } 67 | else { // need more! 68 | return header; 69 | } 70 | } 71 | 72 | function frame(data) { 73 | var fb = frameBuffer; 74 | var needed = fb.length - fb.used; 75 | var sourceEnd = (fb.length > data.length) ? data.length : fb.length; 76 | data.copy(fb, fb.used, 0, sourceEnd); 77 | fb.used += data.length; 78 | if (data.length > needed) { 79 | return frameEnd(data.slice(needed)); 80 | } 81 | else if (data.length == needed) { 82 | return frameEnd; 83 | } 84 | else { 85 | return frame; 86 | } 87 | } 88 | 89 | function frameEnd(data) { 90 | if (data.length > 0) { 91 | if (data[0] === Indicators.FRAME_END) { 92 | switch (frameType) { 93 | case FrameType.METHOD: 94 | self._parseMethodFrame(frameChannel, frameBuffer); 95 | break; 96 | case FrameType.HEADER: 97 | self._parseHeaderFrame(frameChannel, frameBuffer); 98 | break; 99 | case FrameType.BODY: 100 | if (self.onContent) { 101 | self.onContent(frameChannel, frameBuffer); 102 | } 103 | break; 104 | case FrameType.HEARTBEAT: 105 | debug && debug("heartbeat"); 106 | if (self.onHeartBeat) self.onHeartBeat(); 107 | break; 108 | default: 109 | self.throwError("Unhandled frame type " + frameType); 110 | break; 111 | } 112 | return header(data.slice(1)); 113 | } 114 | else { 115 | self.throwError("Missing frame end marker"); 116 | } 117 | } 118 | else { 119 | return frameEnd; 120 | } 121 | } 122 | 123 | self.parse = header; 124 | } 125 | 126 | // If there's an error in the parser, call the onError handler or throw 127 | AMQPParser.prototype.throwError = function (error) { 128 | if (this.onError) this.onError(error); 129 | else throw new Error(error); 130 | }; 131 | 132 | // Everytime data is recieved on the socket, pass it to this function for 133 | // parsing. 134 | AMQPParser.prototype.execute = function (data) { 135 | // This function only deals with dismantling and buffering the frames. 136 | // It delegates to other functions for parsing the frame-body. 137 | debug && debug('execute: ' + data.toString('hex')); 138 | this.parse = this.parse(data); 139 | }; 140 | 141 | /** 142 | * Set the maximum frame buffer size in bytes. The connection needs to change this 143 | * if the server responds with a connection tune event where the maxFrameBuffer 144 | * was changed in the server config. 145 | * 146 | * @param maxFrameBuffer the maximum frame buffer size in bytes 147 | */ 148 | AMQPParser.prototype.setMaxFrameBuffer = function(maxFrameBuffer) { 149 | this.maxFrameBuffer = maxFrameBuffer; 150 | }; 151 | 152 | // parse Network Byte Order integers. size can be 1,2,4,8 153 | function parseInt (buffer, size) { 154 | switch (size) { 155 | case 1: 156 | return buffer[buffer.read++]; 157 | 158 | case 2: 159 | return (buffer[buffer.read++] << 8) + buffer[buffer.read++]; 160 | 161 | case 4: 162 | return (buffer[buffer.read++] << 24) + (buffer[buffer.read++] << 16) + 163 | (buffer[buffer.read++] << 8) + buffer[buffer.read++]; 164 | 165 | case 8: 166 | return (buffer[buffer.read++] << 56) + (buffer[buffer.read++] << 48) + 167 | (buffer[buffer.read++] << 40) + (buffer[buffer.read++] << 32) + 168 | (buffer[buffer.read++] << 24) + (buffer[buffer.read++] << 16) + 169 | (buffer[buffer.read++] << 8) + buffer[buffer.read++]; 170 | 171 | default: 172 | throw new Error("cannot parse ints of that size"); 173 | } 174 | } 175 | 176 | 177 | function parseShortString (buffer) { 178 | var length = buffer[buffer.read++]; 179 | var s = buffer.toString('utf8', buffer.read, buffer.read+length); 180 | buffer.read += length; 181 | return s; 182 | } 183 | 184 | 185 | function parseLongString (buffer) { 186 | var length = parseInt(buffer, 4); 187 | var s = buffer.slice(buffer.read, buffer.read + length); 188 | buffer.read += length; 189 | return s.toString(); 190 | } 191 | 192 | 193 | function parseSignedInteger (buffer) { 194 | var int = parseInt(buffer, 4); 195 | if (int & 0x80000000) { 196 | int |= 0xEFFFFFFF; 197 | int = -int; 198 | } 199 | return int; 200 | } 201 | 202 | function parseValue (buffer) { 203 | switch (buffer[buffer.read++]) { 204 | case AMQPTypes.STRING: 205 | return parseLongString(buffer); 206 | 207 | case AMQPTypes.INTEGER: 208 | return parseInt(buffer, 4); 209 | 210 | case AMQPTypes.DECIMAL: 211 | var dec = parseInt(buffer, 1); 212 | var num = parseInt(buffer, 4); 213 | return num / (dec * 10); 214 | 215 | case AMQPTypes._64BIT_FLOAT: 216 | var b = []; 217 | for (var i = 0; i < 8; ++i) 218 | b[i] = buffer[buffer.read++]; 219 | 220 | return (new jspack(true)).Unpack('d', b); 221 | 222 | case AMQPTypes._32BIT_FLOAT: 223 | var b = []; 224 | for (var i = 0; i < 4; ++i) 225 | b[i] = buffer[buffer.read++]; 226 | 227 | return (new jspack(true)).Unpack('f', b); 228 | 229 | case AMQPTypes.TIME: 230 | var int = parseInt(buffer, 8); 231 | return (new Date()).setTime(int * 1000); 232 | 233 | case AMQPTypes.HASH: 234 | return parseTable(buffer); 235 | 236 | case AMQPTypes.SIGNED_64BIT: 237 | return parseInt(buffer, 8); 238 | 239 | case AMQPTypes.SIGNED_8BIT: 240 | return parseInt(buffer, 1); 241 | 242 | case AMQPTypes.BOOLEAN: 243 | return (parseInt(buffer, 1) > 0); 244 | 245 | case AMQPTypes.BYTE_ARRAY: 246 | var len = parseInt(buffer, 4); 247 | var buf = new Buffer(len); 248 | buffer.copy(buf, 0, buffer.read, buffer.read + len); 249 | buffer.read += len; 250 | return buf; 251 | 252 | case AMQPTypes.ARRAY: 253 | var len = parseInt(buffer, 4); 254 | var end = buffer.read + len; 255 | var arr = []; 256 | 257 | while (buffer.read < end) { 258 | arr.push(parseValue(buffer)); 259 | } 260 | 261 | return arr; 262 | 263 | case AMQPTypes.VOID: 264 | return null; 265 | 266 | default: 267 | throw new Error("Unknown field value type " + buffer[buffer.read-1]); 268 | } 269 | } 270 | 271 | function parseTable (buffer) { 272 | var length = buffer.read + parseInt(buffer, 4); 273 | var table = {}; 274 | 275 | while (buffer.read < length) { 276 | table[parseShortString(buffer)] = parseValue(buffer); 277 | } 278 | 279 | return table; 280 | } 281 | 282 | function parseFields (buffer, fields) { 283 | var args = {}; 284 | var bitIndex = 0; 285 | var value; 286 | 287 | for (var i = 0; i < fields.length; i++) { 288 | var field = fields[i]; 289 | 290 | //debug && debug("parsing field " + field.name + " of type " + field.domain); 291 | 292 | switch (field.domain) { 293 | case 'bit': 294 | // 8 bits can be packed into one octet. 295 | 296 | // XXX check if bitIndex greater than 7? 297 | 298 | value = (buffer[buffer.read] & (1 << bitIndex)) ? true : false; 299 | 300 | if (fields[i+1] && fields[i+1].domain == 'bit') { 301 | bitIndex++; 302 | } else { 303 | bitIndex = 0; 304 | buffer.read++; 305 | } 306 | break; 307 | 308 | case 'octet': 309 | value = buffer[buffer.read++]; 310 | break; 311 | 312 | case 'short': 313 | value = parseInt(buffer, 2); 314 | break; 315 | 316 | case 'long': 317 | value = parseInt(buffer, 4); 318 | break; 319 | 320 | // In a previous version this shared code with 'longlong', which caused problems when passed Date 321 | // integers. Nobody expects to pass a Buffer here, 53 bits is still 28 million years after 1970, we'll be fine. 322 | case 'timestamp': 323 | value = parseInt(buffer, 8); 324 | break; 325 | 326 | // JS doesn't support 64-bit Numbers, so we expect if you're using 'longlong' that you've 327 | // used a Buffer instead 328 | case 'longlong': 329 | value = new Buffer(8); 330 | for (var j = 0; j < 8; j++) { 331 | value[j] = buffer[buffer.read++]; 332 | } 333 | break; 334 | 335 | case 'shortstr': 336 | value = parseShortString(buffer); 337 | break; 338 | 339 | case 'longstr': 340 | value = parseLongString(buffer); 341 | break; 342 | 343 | case 'table': 344 | value = parseTable(buffer); 345 | break; 346 | 347 | default: 348 | throw new Error("Unhandled parameter type " + field.domain); 349 | } 350 | //debug && debug("got " + value); 351 | args[field.name] = value; 352 | } 353 | 354 | return args; 355 | } 356 | 357 | 358 | AMQPParser.prototype._parseMethodFrame = function (channel, buffer) { 359 | buffer.read = 0; 360 | var classId = parseInt(buffer, 2), 361 | methodId = parseInt(buffer, 2); 362 | 363 | // Make sure that this is a method that we understand. 364 | if (!methodTable[classId] || !methodTable[classId][methodId]) { 365 | this.throwError("Received unknown [classId, methodId] pair [" + 366 | classId + ", " + methodId + "]"); 367 | } 368 | 369 | var method = methodTable[classId][methodId]; 370 | 371 | if (!method) this.throwError("bad method?"); 372 | 373 | var args = parseFields(buffer, method.fields); 374 | 375 | if (this.onMethod) { 376 | debug && debug("Executing method", channel, method, args); 377 | this.onMethod(channel, method, args); 378 | } 379 | }; 380 | 381 | 382 | AMQPParser.prototype._parseHeaderFrame = function (channel, buffer) { 383 | buffer.read = 0; 384 | 385 | var classIndex = parseInt(buffer, 2); 386 | var weight = parseInt(buffer, 2); 387 | var size = parseInt(buffer, 8); 388 | 389 | var classInfo = classes[classIndex]; 390 | 391 | if (classInfo.fields.length > 15) { 392 | this.throwError("TODO: support more than 15 properties"); 393 | } 394 | 395 | var propertyFlags = parseInt(buffer, 2); 396 | 397 | var fields = []; 398 | for (var i = 0; i < classInfo.fields.length; i++) { 399 | var field = classInfo.fields[i]; 400 | // groan. 401 | if (propertyFlags & (1 << (15-i))) fields.push(field); 402 | } 403 | 404 | var properties = parseFields(buffer, fields); 405 | 406 | if (this.onContentHeader) { 407 | this.onContentHeader(channel, classInfo, weight, properties, size); 408 | } 409 | }; 410 | -------------------------------------------------------------------------------- /lib/promise.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var inherits = require('util').inherits; 3 | 4 | exports.Promise = function () { 5 | events.EventEmitter.call(this); 6 | this._blocking = false; 7 | this.hasFired = false; 8 | this.hasAcked = false; 9 | this._values = undefined; 10 | }; 11 | inherits(exports.Promise, events.EventEmitter); 12 | 13 | exports.Promise.prototype.timeout = function(timeout) { 14 | if (!timeout) { 15 | return this._timeoutDuration; 16 | } 17 | 18 | this._timeoutDuration = timeout; 19 | 20 | if (this.hasFired) return; 21 | this._clearTimeout(); 22 | 23 | var self = this; 24 | this._timer = setTimeout(function() { 25 | self._timer = null; 26 | if (self.hasFired) { 27 | return; 28 | } 29 | 30 | self.emitError(new Error('timeout')); 31 | }, timeout); 32 | 33 | return this; 34 | }; 35 | 36 | exports.Promise.prototype._clearTimeout = function() { 37 | if (!this._timer) return; 38 | 39 | clearTimeout(this._timer); 40 | this._timer = null; 41 | } 42 | 43 | exports.Promise.prototype.emitSuccess = function() { 44 | if (this.hasFired) return; 45 | this.hasFired = 'success'; 46 | this._clearTimeout(); 47 | 48 | this._values = Array.prototype.slice.call(arguments); 49 | this.emit.apply(this, ['success'].concat(this._values)); 50 | }; 51 | 52 | exports.Promise.prototype.emitError = function() { 53 | if (this.hasFired) return; 54 | this.hasFired = 'error'; 55 | this._clearTimeout(); 56 | 57 | this._values = Array.prototype.slice.call(arguments); 58 | this.emit.apply(this, ['error'].concat(this._values)); 59 | 60 | if (this.listeners('error').length == 0) { 61 | var self = this; 62 | process.nextTick(function() { 63 | if (self.listeners('error').length == 0) { 64 | throw (self._values[0] instanceof Error) 65 | ? self._values[0] 66 | : new Error('Unhandled emitError: '+JSON.stringify(self._values)); 67 | } 68 | }); 69 | } 70 | }; 71 | 72 | exports.Promise.prototype.addCallback = function (listener) { 73 | if (this.hasFired === 'success') { 74 | listener.apply(this, this._values); 75 | } 76 | 77 | return this.addListener("success", listener); 78 | }; 79 | 80 | exports.Promise.prototype.addErrback = function (listener) { 81 | if (this.hasFired === 'error') { 82 | listener.apply(this, this._values); 83 | } 84 | 85 | return this.addListener("error", listener); 86 | }; 87 | -------------------------------------------------------------------------------- /lib/serializer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var jspack = require('../jspack').jspack; 4 | 5 | var serializer = module.exports = { 6 | serializeFloat: function(b, size, value, bigEndian) { 7 | var jp = new jspack(bigEndian); 8 | 9 | switch(size) { 10 | case 4: 11 | var x = jp.Pack('f', [value]); 12 | for (var i = 0; i < x.length; ++i) 13 | b[b.used++] = x[i]; 14 | break; 15 | 16 | case 8: 17 | var x = jp.Pack('d', [value]); 18 | for (var i = 0; i < x.length; ++i) 19 | b[b.used++] = x[i]; 20 | break; 21 | 22 | default: 23 | throw new Error("Unknown floating point size"); 24 | } 25 | }, 26 | 27 | serializeInt: function (b, size, int) { 28 | if (b.used + size > b.length) { 29 | throw new Error("write out of bounds"); 30 | } 31 | 32 | // Only 4 cases - just going to be explicit instead of looping. 33 | 34 | switch (size) { 35 | // octet 36 | case 1: 37 | b[b.used++] = int; 38 | break; 39 | 40 | // short 41 | case 2: 42 | b[b.used++] = (int & 0xFF00) >> 8; 43 | b[b.used++] = (int & 0x00FF) >> 0; 44 | break; 45 | 46 | // long 47 | case 4: 48 | b[b.used++] = (int & 0xFF000000) >> 24; 49 | b[b.used++] = (int & 0x00FF0000) >> 16; 50 | b[b.used++] = (int & 0x0000FF00) >> 8; 51 | b[b.used++] = (int & 0x000000FF) >> 0; 52 | break; 53 | 54 | 55 | // long long 56 | case 8: 57 | b[b.used++] = (int & 0xFF00000000000000) >> 56; 58 | b[b.used++] = (int & 0x00FF000000000000) >> 48; 59 | b[b.used++] = (int & 0x0000FF0000000000) >> 40; 60 | b[b.used++] = (int & 0x000000FF00000000) >> 32; 61 | b[b.used++] = (int & 0x00000000FF000000) >> 24; 62 | b[b.used++] = (int & 0x0000000000FF0000) >> 16; 63 | b[b.used++] = (int & 0x000000000000FF00) >> 8; 64 | b[b.used++] = (int & 0x00000000000000FF) >> 0; 65 | break; 66 | 67 | default: 68 | throw new Error("Bad size"); 69 | } 70 | }, 71 | 72 | 73 | serializeShortString: function (b, string) { 74 | if (typeof(string) != "string") { 75 | throw new Error("param must be a string"); 76 | } 77 | var byteLength = Buffer.byteLength(string, 'utf8'); 78 | if (byteLength > 0xFF) { 79 | throw new Error("String too long for 'shortstr' parameter"); 80 | } 81 | if (1 + byteLength + b.used >= b.length) { 82 | throw new Error("Not enough space in buffer for 'shortstr'"); 83 | } 84 | b[b.used++] = byteLength; 85 | b.write(string, b.used, 'utf8'); 86 | b.used += byteLength; 87 | }, 88 | 89 | serializeLongString: function(b, string) { 90 | // we accept string, object, or buffer for this parameter. 91 | // in the case of string we serialize it to utf8. 92 | if (typeof(string) == 'string') { 93 | var byteLength = Buffer.byteLength(string, 'utf8'); 94 | serializer.serializeInt(b, 4, byteLength); 95 | b.write(string, b.used, 'utf8'); 96 | b.used += byteLength; 97 | } else if (typeof(string) == 'object') { 98 | serializer.serializeTable(b, string); 99 | } else { 100 | // data is Buffer 101 | var byteLength = string.length; 102 | serializer.serializeInt(b, 4, byteLength); 103 | b.write(string, b.used); // memcpy 104 | b.used += byteLength; 105 | } 106 | }, 107 | 108 | serializeDate: function(b, date) { 109 | serializer.serializeInt(b, 8, date.valueOf() / 1000); 110 | }, 111 | 112 | serializeBuffer: function(b, buffer) { 113 | serializer.serializeInt(b, 4, buffer.length); 114 | buffer.copy(b, b.used, 0); 115 | b.used += buffer.length; 116 | }, 117 | 118 | serializeBase64: function(b, buffer) { 119 | serializer.serializeLongString(b, buffer.toString('base64')); 120 | }, 121 | 122 | isBigInt: function(value) { 123 | return value > 0xffffffff; 124 | }, 125 | 126 | getCode: function(dec) { 127 | var hexArray = "0123456789ABCDEF".split(''); 128 | var code1 = Math.floor(dec / 16); 129 | var code2 = dec - code1 * 16; 130 | return hexArray[code2]; 131 | }, 132 | 133 | isFloat: function(value){ 134 | return value === +value && value !== (value|0); 135 | }, 136 | 137 | serializeValue: function(b, value) { 138 | switch (typeof(value)) { 139 | case 'string': 140 | b[b.used++] = 'S'.charCodeAt(0); 141 | serializer.serializeLongString(b, value); 142 | break; 143 | 144 | case 'number': 145 | if (!serializer.isFloat(value)) { 146 | if (serializer.isBigInt(value)) { 147 | // 64-bit uint 148 | b[b.used++] = 'l'.charCodeAt(0); 149 | serializer.serializeInt(b, 8, value); 150 | } else { 151 | //32-bit uint 152 | b[b.used++] = 'I'.charCodeAt(0); 153 | serializer.serializeInt(b, 4, value); 154 | } 155 | } else { 156 | //64-bit float 157 | b[b.used++] = 'd'.charCodeAt(0); 158 | serializer.serializeFloat(b, 8, value); 159 | } 160 | break; 161 | 162 | case 'boolean': 163 | b[b.used++] = 't'.charCodeAt(0); 164 | b[b.used++] = value; 165 | break; 166 | 167 | default: 168 | if (value instanceof Date) { 169 | b[b.used++] = 'T'.charCodeAt(0); 170 | serializer.serializeDate(b, value); 171 | } else if (value instanceof Buffer) { 172 | b[b.used++] = 'x'.charCodeAt(0); 173 | serializer.serializeBuffer(b, value); 174 | } else if (Array.isArray(value)) { 175 | b[b.used++] = 'A'.charCodeAt(0); 176 | serializer.serializeArray(b, value); 177 | } else if (typeof(value) === 'object') { 178 | b[b.used++] = 'F'.charCodeAt(0); 179 | serializer.serializeTable(b, value); 180 | } else { 181 | throw new Error("unsupported type in amqp table: " + typeof(value)); 182 | } 183 | } 184 | }, 185 | 186 | serializeTable: function(b, object) { 187 | if (typeof(object) != "object") { 188 | throw new Error("param must be an object"); 189 | } 190 | 191 | // Save our position so that we can go back and write the length of this table 192 | // at the beginning of the packet (once we know how many entries there are). 193 | var lengthIndex = b.used; 194 | b.used += 4; // sizeof long 195 | var startIndex = b.used; 196 | 197 | for (var key in object) { 198 | if (!object.hasOwnProperty(key)) continue; 199 | serializer.serializeShortString(b, key); 200 | serializer.serializeValue(b, object[key]); 201 | } 202 | 203 | var endIndex = b.used; 204 | b.used = lengthIndex; 205 | serializer.serializeInt(b, 4, endIndex - startIndex); 206 | b.used = endIndex; 207 | }, 208 | 209 | serializeArray: function(b, arr) { 210 | // Save our position so that we can go back and write the byte length of this array 211 | // at the beginning of the packet (once we have serialized all elements). 212 | var lengthIndex = b.used; 213 | b.used += 4; // sizeof long 214 | var startIndex = b.used; 215 | 216 | var len = arr.length; 217 | for (var i = 0; i < len; i++) { 218 | serializer.serializeValue(b, arr[i]); 219 | } 220 | 221 | var endIndex = b.used; 222 | b.used = lengthIndex; 223 | serializer.serializeInt(b, 4, endIndex - startIndex); 224 | b.used = endIndex; 225 | }, 226 | 227 | serializeFields: function(buffer, fields, args, strict) { 228 | var bitField = 0; 229 | var bitIndex = 0; 230 | for (var i = 0; i < fields.length; i++) { 231 | var field = fields[i]; 232 | var domain = field.domain; 233 | if (!(field.name in args)) { 234 | if (strict) { 235 | throw new Error("Missing field '" + field.name + "' of type '" + domain + "' while executing AMQP method '" + 236 | arguments.callee.caller.arguments[1].name + "'"); 237 | } 238 | continue; 239 | } 240 | 241 | var param = args[field.name]; 242 | 243 | //debug("domain: " + domain + " param: " + param); 244 | 245 | switch (domain) { 246 | case 'bit': 247 | if (typeof(param) != "boolean") { 248 | throw new Error("Unmatched field " + JSON.stringify(field)); 249 | } 250 | 251 | if (param) bitField |= (1 << bitIndex); 252 | bitIndex++; 253 | 254 | if (!fields[i+1] || fields[i+1].domain != 'bit') { 255 | //debug('SET bit field ' + field.name + ' 0x' + bitField.toString(16)); 256 | buffer[buffer.used++] = bitField; 257 | bitField = 0; 258 | bitIndex = 0; 259 | } 260 | break; 261 | 262 | case 'octet': 263 | if (typeof(param) != "number" || param > 0xFF) { 264 | throw new Error("Unmatched field " + JSON.stringify(field)); 265 | } 266 | buffer[buffer.used++] = param; 267 | break; 268 | 269 | case 'short': 270 | if (typeof(param) != "number" || param > 0xFFFF) { 271 | throw new Error("Unmatched field " + JSON.stringify(field)); 272 | } 273 | serializer.serializeInt(buffer, 2, param); 274 | break; 275 | 276 | case 'long': 277 | if (typeof(param) != "number" || param > 0xFFFFFFFF) { 278 | throw new Error("Unmatched field " + JSON.stringify(field)); 279 | } 280 | serializer.serializeInt(buffer, 4, param); 281 | break; 282 | 283 | // In a previous version this shared code with 'longlong', which caused problems when passed Date 284 | // integers. Nobody expects to pass a Buffer here, 53 bits is still 28 million years after 1970, we'll be fine. 285 | case 'timestamp': 286 | serializer.serializeInt(buffer, 8, param); 287 | break; 288 | 289 | case 'longlong': 290 | for (var j = 0; j < 8; j++) { 291 | buffer[buffer.used++] = param[j]; 292 | } 293 | break; 294 | 295 | case 'shortstr': 296 | if (typeof(param) != "string" || param.length > 0xFF) { 297 | throw new Error("Unmatched field " + JSON.stringify(field)); 298 | } 299 | serializer.serializeShortString(buffer, param); 300 | break; 301 | 302 | case 'longstr': 303 | serializer.serializeLongString(buffer, param); 304 | break; 305 | 306 | case 'table': 307 | if (typeof(param) != "object") { 308 | throw new Error("Unmatched field " + JSON.stringify(field)); 309 | } 310 | serializer.serializeTable(buffer, param); 311 | break; 312 | 313 | default: 314 | throw new Error("Unknown domain value type " + domain); 315 | } 316 | } 317 | } 318 | }; 319 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "name" : "amqp" 2 | , "description" : "AMQP driver for node" 3 | , "keywords" : [ "amqp" ] 4 | , "version" : "0.2.7" 5 | , "author" : { "name" : "Ryan Dahl" } 6 | , "contributors" : 7 | [ { "name" : "Vasili Sviridov" } 8 | , { "name" : "Theo Schlossnagle" } 9 | , { "name" : "Vincent Desjardins" } 10 | , { "name" : "Liang-Chi Hsieh" } 11 | , { "name" : "Tim Baga" } 12 | , { "name" : "Stéphane Alnet" } 13 | , { "name" : "Alen Mujezinovic" } 14 | , { "name" : "Michael Bridgen" } 15 | , { "name" : "Chris Bond" } 16 | , { "name" : "Andrei Vereha" } 17 | , { "name" : "Mike Bardzinski" } 18 | , { "name" : "James Carr" } 19 | , { "name" : "David Barshow" } 20 | , { "name" : "Jason Pincin" } 21 | , { "name" : "Carl Hörberg" } 22 | ] 23 | , "repository" : 24 | { "type" : "git" 25 | , "url" : "git://github.com/postwait/node-amqp.git" 26 | } 27 | , "bugs" : 28 | { "url" : "http://github.com/postwait/node-amqp/issues" 29 | } 30 | , "main" : "./amqp" 31 | , "licenses" : 32 | [ { "type" : "MIT" 33 | , "url" : "http://github.com/postwait/node-amqp/raw/master/LICENSE-MIT" 34 | } 35 | ] 36 | , "dependencies" : 37 | { 38 | "lodash": "^4.0.0" 39 | } 40 | , "devDependencies" : 41 | { 42 | "optimist": "~0.6.0" 43 | , "longjohn": "~0.2.1" 44 | } 45 | , "scripts" : 46 | { 47 | "test": "make test" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /qparser.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # USAGE: ruby qparser.rb amqp-0.8.xml > amqp-definitions-0-8.js 3 | 4 | require 'rubygems' 5 | require 'nokogiri' 6 | require 'erb' 7 | require 'pathname' 8 | require 'yaml' 9 | require 'active_support' 10 | require 'json' 11 | 12 | class InputError < StandardError; end 13 | 14 | def spec_details(doc) 15 | # AMQP spec details 16 | 17 | spec_details = {} 18 | 19 | root = doc.at('amqp') 20 | spec_details['major'] = root['major'] 21 | spec_details['minor'] = root['minor'] 22 | spec_details['revision'] = root['revision'] || '0' 23 | spec_details['port'] = root['port'] 24 | spec_details['comment'] = root['comment'] || 'No comment' 25 | 26 | spec_details 27 | end 28 | 29 | def jscase(s) 30 | t = s.gsub(/\s|-/, '_').camelcase 31 | t[0] = t[0].downcase 32 | t 33 | end 34 | 35 | def process_constants(doc) 36 | # AMQP constants 37 | 38 | constants = {} 39 | 40 | doc.xpath('//constant').each do |element| 41 | constants[element['value'].to_i] = jscase(element['name']) 42 | end 43 | 44 | constants.sort 45 | end 46 | 47 | def domain_types(doc, major, minor, revision) 48 | # AMQP domain types 49 | 50 | dt_arr = [] 51 | doc.xpath('amqp/domain').each do |element| 52 | dt_arr << element['type'] 53 | end 54 | 55 | # Add domain types for specific document 56 | add_arr = add_types(major, minor, revision) 57 | type_arr = dt_arr + add_arr 58 | 59 | # Return sorted array 60 | type_arr.uniq.sort 61 | end 62 | 63 | def classes(doc, major, minor, revision) 64 | # AMQP classes 65 | 66 | cls_arr = [] 67 | 68 | doc.xpath('amqp/class').each do |element| 69 | cls_hash = {} 70 | cls_hash[:name] = jscase(element['name']) 71 | cls_hash[:index] = element['index'].to_i 72 | # Get fields for class 73 | field_arr = fields(doc, element) 74 | cls_hash[:fields] = field_arr 75 | # Get methods for class 76 | meth_arr = class_methods(doc, element) 77 | # Add missing methods 78 | add_arr =[] 79 | add_arr = add_methods(major, minor, revision) if cls_hash[:name] == 'queue' 80 | method_arr = meth_arr + add_arr 81 | # Add array to class hash 82 | cls_hash[:methods] = method_arr 83 | cls_arr << cls_hash 84 | end 85 | 86 | # Return class information array 87 | cls_arr 88 | end 89 | 90 | def class_methods(doc, cls) 91 | meth_arr = [] 92 | 93 | # Get methods for class 94 | cls.xpath('./method').each do |method| 95 | meth_hash = {} 96 | meth_hash[:name] = jscase(method['name']) 97 | meth_hash[:index] = method['index'].to_i 98 | # Get fields for method 99 | field_arr = fields(doc, method) 100 | meth_hash[:fields] = field_arr 101 | meth_arr << meth_hash 102 | end 103 | 104 | # Return methods 105 | meth_arr 106 | end 107 | 108 | def fields(doc, element) 109 | field_arr = [] 110 | 111 | # Get fields for element 112 | element.xpath('./field').each do |field| 113 | field_hash = {} 114 | field_hash[:name] = jscase(field['name'].tr(' ', '-')) 115 | field_hash[:domain] = field['type'] || field['domain'] 116 | 117 | # Convert domain type if necessary 118 | conv_arr = convert_type(field_hash[:domain]) 119 | field_hash[:domain] = conv_arr[field_hash[:domain]] unless conv_arr.empty? 120 | 121 | field_arr << field_hash 122 | end 123 | 124 | # Return fields 125 | field_arr 126 | 127 | end 128 | 129 | def add_types(major, minor, revision) 130 | type_arr = [] 131 | type_arr = ['long', 'longstr', 'octet', 'timestamp'] if (major == '8' and minor == '0' and revision == '0') 132 | type_arr 133 | end 134 | 135 | def add_methods(major, minor, revision) 136 | meth_arr = [] 137 | 138 | if (major == '8' and minor == '0' and revision == '0') 139 | # Add Queue Unbind method 140 | meth_hash = {:name => 'unbind', 141 | :index => '50', 142 | :fields => [{:name => 'ticket', :domain => 'short'}, 143 | {:name => 'queue', :domain => 'shortstr'}, 144 | {:name => 'exchange', :domain => 'shortstr'}, 145 | {:name => 'routing_key', :domain => 'shortstr'}, 146 | {:name => 'arguments', :domain => 'table'} 147 | ] 148 | } 149 | 150 | meth_arr << meth_hash 151 | 152 | # Add Queue Unbind-ok method 153 | meth_hash = {:name => 'unbind-ok', 154 | :index => '51', 155 | :fields => [] 156 | } 157 | 158 | meth_arr << meth_hash 159 | end 160 | 161 | # Return methods 162 | meth_arr 163 | 164 | end 165 | 166 | def convert_type(name) 167 | type_arr = @type_conversion.reject {|k,v| k != name} 168 | end 169 | 170 | # Start of Main program 171 | 172 | # Read in the spec file 173 | doc = Nokogiri::XML(File.new(ARGV[0])) 174 | 175 | # Declare type conversion hash 176 | @type_conversion = {'path' => 'shortstr', 177 | 'known hosts' => 'shortstr', 178 | 'known-hosts' => 'shortstr', 179 | 'reply code' => 'short', 180 | 'reply-code' => 'short', 181 | 'reply text' => 'shortstr', 182 | 'reply-text' => 'shortstr', 183 | 'class id' => 'short', 184 | 'class-id' => 'short', 185 | 'method id' => 'short', 186 | 'method-id' => 'short', 187 | 'channel-id' => 'longstr', 188 | 'access ticket' => 'short', 189 | 'access-ticket' => 'short', 190 | 'exchange name' => 'shortstr', 191 | 'exchange-name' => 'shortstr', 192 | 'queue name' => 'shortstr', 193 | 'queue-name' => 'shortstr', 194 | 'consumer tag' => 'shortstr', 195 | 'consumer-tag' => 'shortstr', 196 | 'delivery tag' => 'longlong', 197 | 'delivery-tag' => 'longlong', 198 | 'redelivered' => 'bit', 199 | 'no ack' => 'bit', 200 | 'no-ack' => 'bit', 201 | 'no local' => 'bit', 202 | 'no-local' => 'bit', 203 | 'peer properties' => 'table', 204 | 'peer-properties' => 'table', 205 | 'destination' => 'shortstr', 206 | 'duration' => 'longlong', 207 | 'security-token' => 'longstr', 208 | 'reject-code' => 'short', 209 | 'reject-text' => 'shortstr', 210 | 'offset' => 'longlong', 211 | 'no-wait' => 'bit', 212 | 'message-count' => 'long' 213 | } 214 | 215 | # Spec details 216 | spec_info = spec_details(doc) 217 | 218 | # Constants 219 | constants = process_constants(doc) 220 | 221 | 222 | # Frame constants 223 | #frame_constants = constants[0].select {|k,v| k <= 8} 224 | #frame_footer = constants[0].select {|k,v| v == 'End'}[0][0] 225 | 226 | # Other constants 227 | other_constants = constants[1] 228 | 229 | # Domain types 230 | data_types = domain_types(doc, spec_info['major'], spec_info['minor'], spec_info['revision']) 231 | 232 | # Classes 233 | class_defs = classes(doc, spec_info['major'], spec_info['minor'], spec_info['revision']) 234 | 235 | 236 | def format_name(name) 237 | name.split('-').collect {|x| x.camelcase }.join 238 | end 239 | 240 | 241 | puts "exports.constants = " + constants.to_json + ";" 242 | puts "exports.classes = " + class_defs.to_json + ";" 243 | -------------------------------------------------------------------------------- /runTests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NODE=`which node` 4 | FILES=test/test-*.js 5 | EXITCODE=0 6 | 7 | for test in $FILES 8 | do 9 | trap "echo Exited!; exit;" SIGINT SIGTERM 10 | echo -n "$test: " 11 | if $NODE $test > /dev/null; then 12 | echo "PASS" 13 | else 14 | echo "FAIL" 15 | EXITCODE=1 16 | fi 17 | done 18 | 19 | # Exit with error if any tests didn't pass. 20 | exit $EXITCODE -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailing" : false, 3 | "sub" : true, 4 | "laxcomma" : true, 5 | "bitwise" : true, 6 | "nonew" : true, 7 | "undef" : true, 8 | "node" : true, 9 | "eqnull" : true, 10 | "jquery" : true, 11 | "strict" : false, 12 | "quotmark" : false, 13 | "predef" : [ 14 | "util", 15 | "puts", 16 | "amqp", 17 | "assert", 18 | "connection", 19 | "options", 20 | "implOpts" 21 | ], 22 | "browser" : true, 23 | "devel": true, 24 | "expr": true 25 | } -------------------------------------------------------------------------------- /test/harness.js: -------------------------------------------------------------------------------- 1 | require('longjohn'); 2 | var _ = require('lodash'); 3 | 4 | // Parse incoming options. 5 | var optimist = require('optimist') 6 | .usage('Run a test.\nUsage: $0') 7 | .alias('h', 'host') 8 | .describe('h', 'Specify a hostname to connect to.') 9 | .default('h', 'localhost') 10 | .alias('p', 'port') 11 | .describe('p', 'Specify a port to connect to.') 12 | .default('p', 5672) 13 | .alias('d', 'debug') 14 | .describe('d', 'Show debug output during test.') 15 | .default('d', false) 16 | .describe('help', 'Display this help message.'); 17 | 18 | var argv = optimist.argv; 19 | 20 | if (argv.help) { 21 | optimist.showHelp(); 22 | process.exit(0); 23 | } 24 | 25 | if (argv.debug) { 26 | process.env['NODE_DEBUG_AMQP'] = true; 27 | } 28 | 29 | global.util = require('util'); 30 | global.puts = console.log; 31 | global.assert = require('assert'); 32 | global.amqp = require('../amqp'); 33 | global.options = _.assignIn(global.options || {}, argv); 34 | 35 | 36 | global.implOpts = { 37 | defaultExchangeName: 'amq.topic' 38 | }; 39 | 40 | 41 | var harness = module.exports = { 42 | createConnection: function(opts, implOpts){ 43 | opts = _.defaults(opts || {}, global.options); 44 | implOpts = _.defaults(implOpts || {}, global.implOpts); 45 | return amqp.createConnection(options, implOpts); 46 | }, 47 | run: function(opts, implOpts) { 48 | global.connection = harness.createConnection(opts, implOpts); 49 | global.connection.addListener('error', global.errorCallback); 50 | global.connection.addListener('close', function (e) { 51 | console.log('connection closed.'); 52 | }); 53 | return global.connection; 54 | } 55 | 56 | }; 57 | 58 | global.errorCallback = function(e) { 59 | throw e; 60 | }; 61 | -------------------------------------------------------------------------------- /test/proxy.js: -------------------------------------------------------------------------------- 1 | // Stolen from Devendra Tewari 2 | // (http://delog.wordpress.com/2011/04/08/a-simple-tcp-proxy-in-node-js/) 3 | 4 | var net = require('net'); 5 | 6 | process.on("uncaughtException", function(e) { 7 | console.log(e); 8 | }); 9 | 10 | module.exports.route = function (proxyPort, servicePort, serviceHost) { 11 | var proxyRoute = this; 12 | proxyRoute.proxyPort = proxyPort || 9001; 13 | var servicePort = servicePort || 5672; 14 | var serviceHost = serviceHost || '127.0.0.1'; 15 | 16 | proxyRoute.operational = true; 17 | proxyRoute.serviceSockets = []; 18 | proxyRoute.proxySockets = []; 19 | 20 | proxyRoute.server = net.createServer(function (proxySocket) { 21 | // If we're "experiencing trouble", immediately end the connection. 22 | if (!proxyRoute.operational) { 23 | proxySocket.end(); 24 | return; 25 | } 26 | 27 | // If we're operating normally, accept the connection and begin proxying traffic. 28 | proxyRoute.proxySockets.push(proxySocket); 29 | 30 | var connected = false; 31 | var buffers = []; 32 | var serviceSocket = new net.Socket(); 33 | proxyRoute.serviceSockets.push(serviceSocket); 34 | serviceSocket.connect(parseInt(servicePort), serviceHost); 35 | serviceSocket.on('connect', function() { 36 | connected = true; 37 | for (var i in buffers) { 38 | serviceSocket.write(buffers[i]); 39 | } 40 | buffers = []; 41 | }); 42 | proxySocket.on('error', function (e) { 43 | serviceSocket.end(); 44 | }); 45 | serviceSocket.on('error', function (e) { 46 | console.log('Could not connect to service at host ' + serviceHost + ', port ' + servicePort); 47 | proxySocket.end(); 48 | }); 49 | proxySocket.on("data", function (data) { 50 | if (proxyRoute.operational) { 51 | if (connected) { 52 | serviceSocket.write(data); 53 | } else { 54 | buffers.push(data); 55 | } 56 | } 57 | }); 58 | serviceSocket.on("data", function(data) { 59 | if (proxyRoute.operational) { 60 | proxySocket.write(data); 61 | } 62 | }); 63 | proxySocket.on("close", function(had_error) { 64 | serviceSocket.end(); 65 | }); 66 | serviceSocket.on("close", function(had_error) { 67 | proxySocket.end(); 68 | }); 69 | }); 70 | proxyRoute.listen(); 71 | }; 72 | module.exports.route.prototype.listen = function () { 73 | var proxyRoute = this; 74 | proxyRoute.operational = true; 75 | proxyRoute.server.listen(proxyRoute.proxyPort); 76 | }; 77 | module.exports.route.prototype.close = function () { 78 | var proxyRoute = this; 79 | proxyRoute.operational = false; 80 | for (var index in proxyRoute.serviceSockets) { 81 | proxyRoute.serviceSockets[index].destroy(); 82 | } 83 | proxyRoute.serviceSockets = []; 84 | for (var index in proxyRoute.proxySockets) { 85 | proxyRoute.proxySockets[index].destroy(); 86 | } 87 | proxyRoute.proxySockets = []; 88 | proxyRoute.server.close(); 89 | }; 90 | module.exports.route.prototype.interrupt = function (howLong) { 91 | var proxyRoute = this; 92 | console.log('interrupting proxy connection...'); 93 | proxyRoute.close(); 94 | setTimeout(function () { 95 | proxyRoute.listen(); 96 | }, howLong || 50); 97 | }; 98 | 99 | if (!module.parent) { 100 | var proxyPort = process.argv[2]; 101 | var servicePort = process.argv[3]; 102 | var serviceHost = process.argv[4]; 103 | var proxyRoute = new module.exports.route(proxyPort, servicePort, serviceHost); 104 | // Don't exit until parent kills us. 105 | setInterval(function () { 106 | if (process.argv[5]) { 107 | proxyRoute.interrupt(); 108 | } 109 | }, parseInt(process.argv[5]) || 1000); 110 | } -------------------------------------------------------------------------------- /test/test-auto-delete-queue.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var connects = 0; 4 | 5 | connection.addListener('ready', function () { 6 | connects++; 7 | puts("connected to " + connection.serverProperties.product); 8 | 9 | var e = connection.exchange(); 10 | 11 | var q = connection.queue('node-test-autodelete', {exclusive: true}); 12 | q.once('queueDeclareOk', function (args) { 13 | puts('queue opened.'); 14 | assert.equal(0, args.messageCount); 15 | assert.equal(0, args.consumerCount); 16 | 17 | q.bind(e, "#"); 18 | 19 | q.once('queueBindOk', function () { 20 | puts('bound'); 21 | // publish message, but don't consume it. 22 | e.publish('routingKey', {hello: 'world'}); 23 | puts('message published'); 24 | puts('closing connection...'); 25 | connection.end(); 26 | }); 27 | }); 28 | 29 | }); 30 | 31 | connection.addListener('close', function () { 32 | puts('close'); 33 | if (connects < 3) connection.reconnect(); 34 | }); 35 | 36 | 37 | process.addListener('exit', function () { 38 | assert.equal(3, connects); 39 | }); 40 | -------------------------------------------------------------------------------- /test/test-basic-return.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var fire, fired = false, 4 | replyCode = null, 5 | replyText = null; 6 | 7 | connection.addListener('ready', function () { 8 | connection.exchange('node-simple-fanout', {type: 'fanout'}, 9 | function(exchange) { 10 | exchange.on('basic-return', function(args) { 11 | fired = true; 12 | replyCode = args['replyCode']; 13 | replyText = args['replyText']; 14 | clearTimeout(fire); 15 | followup(); 16 | }); 17 | exchange.publish("", "hello", { mandatory: true }); 18 | }); 19 | }); 20 | 21 | function followup() { 22 | assert.ok(fired); 23 | assert.ok(replyCode === 312); 24 | assert.ok(replyText === "NO_ROUTE"); 25 | connection.end(); 26 | } 27 | fire = setTimeout(function() { 28 | followup(); 29 | }, 5000); 30 | -------------------------------------------------------------------------------- /test/test-buffer.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var recvCount = 0; 4 | var body = new Buffer([1,99,253,255,0,1,5,6]); 5 | 6 | connection.addListener('ready', function () { 7 | puts("connected to " + connection.serverProperties.product); 8 | 9 | var exchange = connection.exchange('node-binary-fanout', {type: 'fanout'}); 10 | 11 | var q = connection.queue('node-binary-queue', function() { 12 | 13 | q.bind(exchange, "*"); 14 | 15 | q.subscribeRaw(function (m) { 16 | var data; 17 | m.addListener('data', function (d) { data = d; }); 18 | m.addListener('end', function () { 19 | recvCount++; 20 | m.acknowledge(); 21 | switch (m.routingKey) { 22 | case 'message.bin1': 23 | assert.equal(util.inspect(body), util.inspect(data)); 24 | break; 25 | 26 | default: 27 | throw new Error('unexpected routing key: ' + m.routingKey); 28 | } 29 | }); 30 | }) 31 | .addCallback(function () { 32 | puts("publishing 1 raw message"); 33 | exchange.publish('message.bin1', body); 34 | 35 | setTimeout(function () { 36 | // wait one second to receive the message, then quit 37 | connection.end(); 38 | }, 1000); 39 | }); 40 | }); 41 | }); 42 | 43 | 44 | process.addListener('exit', function () { 45 | assert.equal(1, recvCount); 46 | }); 47 | -------------------------------------------------------------------------------- /test/test-channel-overflow.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var channelMax = 65536; // 2^16 4 | 5 | connection.addListener('ready', function () { 6 | puts("connected to " + connection.serverProperties.product); 7 | 8 | // preset channel counter value near max limit 9 | connection.channelCounter = channelMax - 1; 10 | 11 | // opening a channel with channel counter up to channelMax is ok 12 | connection.exchange('amq.topic', {type: 'topic'}, function(exchange) { 13 | assert(connection.channelCounter, channelMax); 14 | 15 | // if the client tries to open a channel with counter value above channelMax, the request fails 16 | connection.exchange('amq.topic', {type: 'topic'}, function(exchange) { 17 | 18 | // if channel counter is above channelMax, this line should never be reached 19 | assert(connection.channelCounter != channelMax + 1); 20 | connection.end(); 21 | }); 22 | }); 23 | }); 24 | 25 | connection.addListener('error', function () { 26 | assert(0); 27 | connection.end(); 28 | }); -------------------------------------------------------------------------------- /test/test-connection-array-preference.js: -------------------------------------------------------------------------------- 1 | require('./harness'); 2 | 3 | options.host = [options.host, "nohost"]; 4 | 5 | implOpts.reconnect = false; 6 | 7 | //If we specify a number bigger than the array, amqp.js will just use the last element in the array 8 | for (var i = 0; i < 3; i++){ 9 | createConnection(i); 10 | } 11 | 12 | function createConnection(i){ 13 | var connection; 14 | var connectionOptions = options; 15 | connectionOptions.hostPreference = i; 16 | 17 | console.log("Connecting to host " + i + " " + options.host[i]); 18 | connection = amqp.createConnection(connectionOptions, implOpts); 19 | 20 | connection.on('error', function() { 21 | console.log('err'); 22 | }); 23 | connection.on('ready', function() { 24 | console.log('connected'); 25 | connection.destroy(); 26 | }); 27 | } 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/test-connection-array.js: -------------------------------------------------------------------------------- 1 | var harness = require('./harness'); 2 | 3 | // Test that connection handles an array of hostnames. 4 | // If given one, without an index (`this.hosti`), it will randomly pick one. 5 | options.host = [options.host,"nohost:5673"]; 6 | options.reconnect = true; 7 | 8 | 9 | for (var i = 0; i < options.host.length; i++){ 10 | test(); 11 | } 12 | 13 | function test() { 14 | var conn = harness.createConnection(); 15 | 16 | var callbackCalled = false; 17 | conn.once('ready', function() { 18 | callbackCalled = true; 19 | conn.destroy(); 20 | }); 21 | 22 | conn.once('close', function() { 23 | assert(callbackCalled); 24 | callbackCalled = false; 25 | }); 26 | 27 | conn.once('error', function(e) { 28 | // If we get an error, it should be ENOTFOUND (bad dns); 29 | assert(e.code === 'ENOTFOUND'); 30 | callbackCalled = true; 31 | }); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /test/test-connection-blocked.js: -------------------------------------------------------------------------------- 1 | var harness = require('./harness'); 2 | var sys = require('sys'); 3 | var exec = require('child_process').exec; 4 | 5 | if (typeof(options.clientProperties) === 'undefined') { 6 | options.clientProperties = {}; 7 | } 8 | if (typeof(options.clientProperties.capabilities) === 'undefined') { 9 | options.clientProperties.capabilities = {}; 10 | } 11 | options.clientProperties.capabilities['connection.blocked'] = true; 12 | 13 | var connection = harness.run(); 14 | var exchange; 15 | 16 | var blockedCnt = 0; 17 | var unblockedCnt = 0; 18 | var errorWhenBlocked = false; 19 | 20 | var finishTimeout = setTimeout(function() { 21 | //console.log('!!!fired!!!'); 22 | connection.end(); 23 | }, 10000); 24 | 25 | connection.once('ready', function() { 26 | exchange = connection.exchange('node-connection-blocked', { 27 | autoDelete: true 28 | }, function(exchange) { 29 | exec('rabbitmqctl set_vm_memory_high_watermark 0', function(err, stdout, stderr) { 30 | exchange.publish("", "hello"); 31 | }); 32 | }); 33 | }); 34 | 35 | connection.on('blocked', function() { 36 | //console.log('!blocked'); 37 | blockedCnt++; 38 | 39 | exchange.publish("", "hello", {}, function(isErr, err) { 40 | if (isErr && err) { 41 | errorWhenBlocked = true; 42 | } 43 | exec('rabbitmqctl set_vm_memory_high_watermark 0.4'); 44 | }); 45 | }); 46 | 47 | connection.on('unblocked', function() { 48 | //console.log('!unblocked'); 49 | 50 | unblockedCnt++; 51 | clearTimeout(finishTimeout); 52 | connection.end(); 53 | }); 54 | 55 | process.addListener('exit', function() { 56 | exec('rabbitmqctl set_vm_memory_high_watermark 0.4'); 57 | 58 | assert.equal(1, blockedCnt); 59 | assert.equal(1, unblockedCnt); 60 | assert.equal(true, errorWhenBlocked); 61 | }); 62 | -------------------------------------------------------------------------------- /test/test-connection-callbacks.js: -------------------------------------------------------------------------------- 1 | testLog = function(name, message) { console.log("Test case: "+name+":", message); }; 2 | assert = require('assert'); 3 | amqp = require('../amqp'); 4 | 5 | var options = global.options || {}; 6 | if (process.argv[2]) { 7 | var server = process.argv[2].split(':'); 8 | if (server[0]) options.host = server[0]; 9 | if (server[1]) options.port = parseInt(server[1]); 10 | } 11 | 12 | var implOpts = { 13 | defaultExchangeName: 'amq.topic' 14 | }; 15 | 16 | var callbackCalled = false; 17 | 18 | var testCases = [ 19 | { name: "OK readyCallback", callback: readyCallback }, 20 | { name: "no readyCallback", callback: undefined }, 21 | { name: "null readyCallback", callback: null }, 22 | { name: "non-function readyCallback", callback: "Not a function" }, 23 | 24 | ]; 25 | 26 | function cycleThroughTestCases(index) { 27 | index = index || 0; 28 | if (index >= testCases.length) { 29 | testLog("", "All done!"); 30 | return; 31 | } 32 | 33 | var testCase = testCases[index]; 34 | var connection; 35 | 36 | testLog(testCase.name,"Starting..."); 37 | 38 | callbackCalled = false; 39 | 40 | if (testCase.callback === undefined) { 41 | connection = amqp.createConnection(options, implOpts); 42 | } 43 | else { 44 | connection = amqp.createConnection(options, implOpts, testCase.callback); 45 | } 46 | connection.on('ready', function() { 47 | testLog(testCase.name,"Connection ready. Checking results..."); 48 | if (typeof testCase.callback === 'function') { 49 | assert(callbackCalled, "readyCallback was not called"); 50 | } 51 | else { 52 | assert(!callbackCalled, "readyCallback was unexpectedly called"); 53 | } 54 | testLog(testCase.name,"Success! Closing connection."); 55 | connection.destroy(); 56 | }); 57 | connection.on('error', function(e) { 58 | testLog(testCase.name,"Connection error. Abandoning test cycle."); 59 | throw(e); 60 | }); 61 | connection.on('close', function(e) { 62 | testLog(testCase.name,"Connection closed. Starting next test case."); 63 | cycleThroughTestCases(index+1); 64 | }); 65 | 66 | }; 67 | 68 | function readyCallback(connection) { 69 | callbackCalled = true; 70 | }; 71 | 72 | cycleThroughTestCases(); 73 | 74 | -------------------------------------------------------------------------------- /test/test-connection-connect.js: -------------------------------------------------------------------------------- 1 | var harness = require('./harness'); 2 | 3 | // 50% of the time, this will throw as it attempts to connect to 'nohost'. We want that, it should reconnect 4 | // to options.host the next time. 5 | var conn = new amqp.Connection(); 6 | 7 | conn.on('ready', function() { 8 | conn.destroy(); 9 | }); 10 | 11 | conn._createSocket(); 12 | conn._startHandshake(); 13 | -------------------------------------------------------------------------------- /test/test-connection-disconnect.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | var cbcnt = 0; 3 | 4 | // Only way to test if we disconnect cleanly is to look for 5 | // connectionCloseOk response from server. 6 | var oldOnMethod = connection._onMethod; 7 | connection._onMethod = function (channel, method, args) { 8 | if (method.name === 'connectionCloseOk') { 9 | cbcnt++; 10 | } 11 | oldOnMethod.apply(connection, arguments); 12 | }; 13 | 14 | // And verify that we do really call end on the socket. 15 | connection.on('end', function() { 16 | cbcnt++; 17 | }); 18 | 19 | connection.on('ready', function(){ 20 | connection.disconnect(); 21 | }); 22 | 23 | process.addListener('exit', function () { 24 | assert.equal(cbcnt, 2); 25 | }); 26 | -------------------------------------------------------------------------------- /test/test-connection-timeout.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var amqp = require('../amqp'); 3 | var exec = require('child_process').exec; 4 | 5 | var options = { 6 | host: '10.255.255.1', 7 | connectionTimeout: 1000 8 | } 9 | var implOpts = { 10 | reconnect: false, 11 | }; 12 | 13 | console.log("Trying to connect to non-routable address"); 14 | var hasTimedOut = false; 15 | var connection = amqp.createConnection(options, implOpts); 16 | connection.on('error', function (e) { 17 | if (e.name === 'TimeoutError') hasTimedOut = true; 18 | }); 19 | 20 | process.addListener('exit', function () { 21 | assert(hasTimedOut, 'connection didnt timeout'); 22 | }); 23 | 24 | -------------------------------------------------------------------------------- /test/test-consumer-cancel-notify.js: -------------------------------------------------------------------------------- 1 | var harness = require('./harness'); 2 | 3 | if (typeof(options.clientProperties) === 'undefined') { 4 | options.clientProperties = {}; 5 | } 6 | if (typeof(options.clientProperties.capabilities) === 'undefined') { 7 | options.clientProperties.capabilities = {}; 8 | } 9 | options.clientProperties.capabilities.consumer_cancel_notify = true; 10 | 11 | var connection = harness.run(); 12 | var notifyCount = 0; 13 | 14 | connection.once('ready', function() { 15 | // set a timer to close things if we're not done in one second 16 | var finisherId = setTimeout(function () { 17 | connection.end(); 18 | }, 1000); 19 | 20 | connection.queue('node-ccn-queue', function(q) { 21 | q.bind("#") 22 | q.on('queueBindOk', function() { 23 | q.on('basicCancel', function(args) { 24 | notifyCount++; 25 | }); 26 | 27 | q.on('close', function() { 28 | connection.end(); 29 | clearTimeout(finisherId); 30 | }); 31 | 32 | q.on('basicConsumeOk', function () { 33 | connection.queue('node-ccn-queue', function(q2) { 34 | q2.destroy(); 35 | }); 36 | }); 37 | 38 | q.subscribe(function (m) { 39 | // no-op 40 | }) 41 | }); 42 | }); 43 | }); 44 | 45 | 46 | process.addListener('exit', function () { 47 | assert.equal(1, notifyCount); 48 | }); 49 | -------------------------------------------------------------------------------- /test/test-consumer-tag.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var queueName = 'node-consumer-tag-test'; 4 | var consumerTag = 'testingConsumerTag'; 5 | 6 | connection.on('ready', function() { 7 | var q = connection.queue(queueName, {autoDelete: false}, function() { 8 | q.subscribe({consumerTag}); 9 | 10 | assert.ok(Object.keys(q.consumerTagOptions)[0].includes(consumerTag)); 11 | connection.end(); 12 | }); 13 | }); 14 | 15 | -------------------------------------------------------------------------------- /test/test-default-exchange.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var recvCount = 0; 4 | var body = "hello world"; 5 | 6 | connection.addListener('ready', function () { 7 | puts("connected to " + connection.serverProperties.product); 8 | 9 | var q = connection.queue('node-default-exchange', function() { 10 | q.bind("#"); // bind to queue 11 | 12 | q.on('queueBindOk', function() { // wait until queue is bound 13 | q.on('basicConsumeOk', function () { // wait until consumer is registered 14 | puts("publishing 2 msg messages"); 15 | connection.publish('message.msg1', {two:2, one:1}); 16 | connection.publish('message.msg2', {foo:'bar', hello: 'world'}); 17 | 18 | setTimeout(function () { 19 | // wait one second to receive the message, then quit 20 | connection.end(); 21 | }, 1000); 22 | }); 23 | 24 | q.subscribe({ routingKeyInPayload: true }, 25 | function (msg) { // register consumer 26 | recvCount++; 27 | switch (msg._routingKey) { 28 | case 'message.msg1': 29 | assert.equal(1, msg.one); 30 | assert.equal(2, msg.two); 31 | break; 32 | 33 | case 'message.msg2': 34 | assert.equal('world', msg.hello); 35 | assert.equal('bar', msg.foo); 36 | break; 37 | 38 | default: 39 | throw new Error('unexpected routing key: ' + msg._routingKey); 40 | } 41 | }) 42 | }); 43 | }); 44 | 45 | }); 46 | 47 | 48 | process.addListener('exit', function () { 49 | assert.equal(2, recvCount); 50 | }); 51 | -------------------------------------------------------------------------------- /test/test-destroy-close-delete.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | var assert = require('assert'); 3 | var cbcnt = 0; 4 | 5 | connection.on('ready', function(){ 6 | var exchange1 = connection.exchange('test-exchange-1'); 7 | var exchange2 = connection.exchange('test-exchange-2'); 8 | 9 | var queue = connection.queue('test-queue', function() { 10 | 11 | assert.equal(2, Object.keys(connection.exchanges).length); 12 | assert.equal(4, Object.keys(connection.channels).length); 13 | 14 | var messages = 0; 15 | 16 | exchange1.on('close', function(){ 17 | cbcnt++; 18 | assert.equal('closed', exchange1.state); 19 | assert.equal(1, Object.keys(connection.exchanges).length); 20 | assert.equal(3, Object.keys(connection.channels).length); 21 | exchange2.publish('','test3'); 22 | exchange2.destroy(); 23 | exchange2.close(); 24 | }); 25 | 26 | exchange2.on('close', function(){ 27 | cbcnt++; 28 | assert.equal('closed', exchange2.state); 29 | assert.equal(2, Object.keys(connection.channels).length); 30 | assert.equal("3", Object.keys(connection.channels)[1]); 31 | assert.equal(2, messages); 32 | connection.destroy(); 33 | }); 34 | 35 | queue.bind(exchange2, ''); 36 | 37 | queue.subscribe(function(message){ 38 | messages++; 39 | }).addCallback(function(){ 40 | exchange1.publish('','test1'); 41 | exchange2.publish('','test2'); 42 | exchange1.destroy(); 43 | exchange1.close(); 44 | assert.equal('closing', exchange1.state); 45 | }); 46 | }); 47 | }); 48 | process.addListener('exit', function () { 49 | assert.equal(cbcnt, 2); 50 | }); 51 | -------------------------------------------------------------------------------- /test/test-ex-and-q-deletions.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var recvCount = 0; 4 | 5 | connection.addListener('ready', function () { 6 | puts("connected to " + connection.serverProperties.product); 7 | 8 | var exchange1 = connection.exchange('node-conn-share1', {type: 'direct'}); 9 | var exchange2 = connection.exchange('node-conn-share2', {type: 'direct'}); 10 | 11 | assert.equal(2, Object.keys(connection.exchanges).length); 12 | 13 | exchange2.destroy(); // checked at end 14 | 15 | var q1 = connection.queue('node-q1', function() { 16 | var q2 = connection.queue('node-q2', function() { 17 | 18 | q1.bind(exchange1, "node-consumer-1"); 19 | q1.on('queueBindOk', function() { 20 | q2.bind(exchange1, "node-consumer-2"); 21 | q2.on('queueBindOk', function() { 22 | assert.equal(2, Object.keys(connection.queues).length); 23 | 24 | q1.on('basicConsumeOk', function () { 25 | exchange1.publish("node-consumer-1", 'foo'); 26 | exchange1.publish("node-consumer-1", 'foo'); 27 | exchange1.publish("node-consumer-2", 'foo'); 28 | }); 29 | 30 | q2.on('basicConsumeOk', function () { 31 | exchange1.publish("node-consumer-1", 'foo'); 32 | exchange1.publish("node-consumer-2", 'foo'); 33 | 34 | setTimeout(function () { 35 | // wait one second to receive the message, then quit 36 | q2.destroy(); 37 | connection.end(); 38 | }, 1000); 39 | }); 40 | 41 | q1.subscribe(function (m, headers, deliveryInfo) { 42 | assert.equal('node-consumer-1', deliveryInfo.routingKey); 43 | recvCount++; 44 | }); 45 | q2.subscribe(function (m, headers, deliveryInfo) { 46 | assert.equal('node-consumer-2', deliveryInfo.routingKey); 47 | recvCount++; 48 | }) 49 | }); 50 | }); 51 | }); 52 | }); 53 | }); 54 | 55 | process.addListener('exit', function () { 56 | assert.equal(1, Object.keys(connection.exchanges).length); 57 | assert.equal(1, Object.keys(connection.queues).length); 58 | assert.equal(5, recvCount); 59 | }); 60 | -------------------------------------------------------------------------------- /test/test-exchange-bind.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | var testName = __filename.replace(__dirname+'/','').replace('.js',''); 3 | var msgsReceived = 0; 4 | 5 | connection.addListener('ready', function () { 6 | puts("connected to " + connection.serverProperties.product); 7 | var callbackCalled = false; 8 | 9 | connection.exchange('node.'+testName+'.dstExchange', {type: 'topic', autoDelete: false}, function(dstExchange) { 10 | connection.exchange('node.'+testName+'.srcExchange', {type: 'topic', autoDelete: false}, function(srcExchange) { 11 | dstExchange.bind(srcExchange, '#', function () { 12 | connection.queue( 'node.'+testName+'.nestedExchangeQueue', { durable: false, autoDelete: false}, function (queue) { 13 | queue.bind(dstExchange, '#', function () { 14 | queue.subscribe(function ( msg ) { 15 | puts(msg.data.toString()); 16 | msgsReceived++; 17 | }); 18 | srcExchange.publish('node.'+testName+'.nestedExchangeTest', 19 | 'Queue received message from non-directly-bound exchange.'); 20 | 21 | setTimeout(function () { 22 | // wait one second to receive the message, then quit 23 | queue.destroy(); 24 | dstExchange.destroy(); 25 | srcExchange.destroy(); 26 | connection.end(); 27 | }, 1000); 28 | }); 29 | }); 30 | }); 31 | }); 32 | }); 33 | }); 34 | 35 | process.addListener('exit', function() { 36 | assert.equal(1, msgsReceived); 37 | }); -------------------------------------------------------------------------------- /test/test-exchange-bind_headers.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | var testName = __filename.replace(__dirname+'/','').replace('.js',''); 3 | var msgsReceived = 0; 4 | 5 | connection.addListener('ready', function () { 6 | puts("connected to " + connection.serverProperties.product); 7 | 8 | connection.exchange('node.'+testName+'.dstExchange', {type: 'headers'}, function(dstExchange) { 9 | connection.exchange('node.'+testName+'.srcExchange', {type: 'headers'}, function(srcExchange) { 10 | dstExchange.bind_headers(srcExchange, {test: 'validHeaderKey'}, function () { 11 | connection.queue( 'node.'+testName+'.nestedExchangeQueue', { durable: false, autoDelete : true }, function (queue) { 12 | queue.bind(dstExchange, '#', function () { 13 | queue.subscribe(function ( msg ) { 14 | puts(msg.data.toString()); 15 | msgsReceived++; 16 | }); 17 | srcExchange.publish('node.'+testName+'.nestedExchangeTest', 18 | 'valid Message', 19 | { 20 | headers: {test: 'validHeaderKey'} 21 | } 22 | ); 23 | srcExchange.publish('node.'+testName+'.nestedExchangeTest', 24 | 'wrong Message', 25 | { 26 | headers: {test: 'wrongKey'} 27 | } 28 | ); 29 | 30 | setTimeout(function () { 31 | // wait one second to receive the message, then quit 32 | queue.destroy(); 33 | dstExchange.destroy(); 34 | srcExchange.destroy(); 35 | connection.end(); 36 | }, 1000); 37 | }); 38 | }); 39 | }); 40 | }); 41 | }); 42 | }); 43 | 44 | process.addListener('exit', function() { 45 | assert.equal(1, msgsReceived); 46 | }); 47 | -------------------------------------------------------------------------------- /test/test-exchange-callbacks.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | 4 | 5 | connection.addListener('ready', function () { 6 | puts("connected to " + connection.serverProperties.product); 7 | 8 | var nonAmqExchangeCalledback = false; 9 | var amqExchangeCalledback = false; 10 | 11 | connection.exchange('node-simple-fanout', {type: 'fanout'}, function(exchange) { 12 | nonAmqExchangeCalledback = true; 13 | }); 14 | 15 | connection.exchange('amq.topic', {type: 'topic'}, function(exchange) { 16 | amqExchangeCalledback = true; 17 | }); 18 | setTimeout( function() { 19 | assert.ok(nonAmqExchangeCalledback, "non amq.* exchange callback method not called"); 20 | assert.ok(amqExchangeCalledback, "amq.topic exchange callback method not called"); 21 | connection.end(); 22 | connection.destroy(); 23 | }, 1000); 24 | }); 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/test-exchange-no-declare-and-no-confirm.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | connection.addListener('ready', function () { 4 | var callbackFired = false; 5 | 6 | // first call to ensure exchange exists 7 | connection.exchange('node-no-declare-and-confirm', {type: 'fanout'}, function () { 8 | 9 | connection.exchange('node-no-declare-and-confirm', { noDeclare: true, confirm: false }, function (exchange) { 10 | callbackFired = true; 11 | }); 12 | }); 13 | 14 | setTimeout( function() { 15 | assert.ok(callbackFired, "exchange not connected"); 16 | connection.end(); 17 | connection.destroy(); 18 | }, 1000); 19 | }); 20 | -------------------------------------------------------------------------------- /test/test-exchange-publish-closed.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | connection.addListener('ready', function () { 4 | puts("connected to " + connection.serverProperties.product); 5 | 6 | var error; 7 | var exchange = connection.exchange('node-simple-fanout', {type: 'fanout'}); 8 | exchange.publish('foo', 'data', {}, function(errored, err) { 9 | assert.ok(errored); 10 | error = err; 11 | 12 | setTimeout(function() { 13 | assert.equal(error.message, 'Can not publish: exchange is not open'); 14 | connection.end(); 15 | connection.destroy(); 16 | process.exit(); 17 | }, 1000); 18 | }); 19 | }); 20 | 21 | -------------------------------------------------------------------------------- /test/test-exchange-unbind.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | var testName = __filename.replace(__dirname+'/','').replace('.js',''); 3 | var msgsReceived = 0; 4 | connection.addListener('ready', function () { 5 | puts("connected to " + connection.serverProperties.product); 6 | var callbackCalled = false; 7 | 8 | connection.exchange('node.'+testName+'.dstExchange', {type: 'topic', autoDelete: false}, function(dstExchange) { 9 | connection.exchange('node.'+testName+'.srcExchange', {type: 'topic', autoDelete: false}, function(srcExchange) { 10 | dstExchange.bind(srcExchange, '#', function () { 11 | connection.queue( 'node.'+testName+'.nestedExchangeQueue', { durable: false, autoDelete : false }, function (queue) { 12 | queue.bind(dstExchange, '#', function () { 13 | queue.subscribe(function ( msg ) { 14 | puts(msg.data.toString()); 15 | msgsReceived++; 16 | }); 17 | srcExchange.publish('node.'+testName+'.nestedExchangeTest', 18 | 'Queue received message from non-directly-bound exchange.'); 19 | 20 | // Unbinding the srcExchange will delete it if autoDelete:true 21 | dstExchange.unbind(srcExchange,'#', function () { 22 | srcExchange.publish('node.'+testName+'.nestedExchangeTest', 23 | 'You should NOT see this.'); 24 | }); 25 | setTimeout(function () { 26 | // wait one second to receive the message, then quit 27 | queue.destroy(); 28 | dstExchange.destroy(); 29 | // If autoDelete:true on srcExchange, you can get a writeAfterEnd error. Comment out 30 | // connection.end() to find the real error - the srcExchange doesn't exist. 31 | // See http://www.rabbitmq.com/e2e.html 32 | srcExchange.destroy(); 33 | connection.end(); 34 | }, 1000); 35 | }); 36 | }); 37 | }); 38 | }); 39 | }); 40 | }); 41 | 42 | process.addListener('exit', function() { 43 | assert.equal(1, msgsReceived); 44 | }); -------------------------------------------------------------------------------- /test/test-flow.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var recvCount = 0; 4 | var pubCount = 0; 5 | var timeOutFired = false 6 | 7 | connection.addListener('ready', function () { 8 | puts("connected to " + connection.serverProperties.product); 9 | 10 | var e = connection.exchange('node-flow-fanout', {type: 'fanout'}); 11 | var q = connection.queue('node-flow-queue', function() { 12 | q.bind(e, "*") 13 | q.on('queueBindOk', function() { 14 | q.on('basicConsumeOk', function () { 15 | puts("publishing 1 json message"); 16 | e.publish('ackmessage.json1', { name: 'A' }); 17 | pubCount++ 18 | }); 19 | 20 | q.subscribe({ ack: true, prefetchCount: 5 }, function (json) { 21 | recvCount++; 22 | puts('Got message ' + JSON.stringify(json)); 23 | if (recvCount == 1) { 24 | puts('Got message 1.. stop flow'); 25 | var f = q.flow(false).addCallback(function(ok){ 26 | puts("puts flow turned to passive"); 27 | puts("publishing another json message"); 28 | e.publish('ackmessage.json2', { name: 'B' }); 29 | pubCount++ 30 | }); 31 | 32 | assert.equal('A', json.name); 33 | setTimeout(function () { 34 | timeOutFired = true 35 | q.flow(true).addCallback(function(ok){ 36 | puts("flow turned back to active") 37 | }); 38 | }, 1000); 39 | 40 | } else if (recvCount == 2) { 41 | puts('got message 2'); 42 | assert.equal('B', json.name); 43 | assert(timeOutFired); 44 | 45 | puts('closing connection'); 46 | connection.end(); 47 | 48 | } else { 49 | throw new Error('Too many message!'); 50 | } 51 | }) 52 | }) 53 | }); 54 | }); 55 | 56 | 57 | process.addListener('exit', function () { 58 | assert.equal(2, recvCount); 59 | assert.equal(2, pubCount); 60 | }); 61 | -------------------------------------------------------------------------------- /test/test-headers.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var recvCount = 0; 4 | var body = "the devil is in the headers"; 5 | 6 | connection.on('ready', function () { 7 | puts("connected to " + connection.serverProperties.product); 8 | 9 | var exchange = connection.exchange('node-h-fanout', {type: 'fanout'}); 10 | 11 | connection.queue('node-h-queue', function(q) { 12 | q.bind(exchange, "*") 13 | q.on('queueBindOk', function() { 14 | q.on('basicConsumeOk', function () { 15 | puts("publishing message"); 16 | exchange.publish("to.me", body, { headers: { 17 | foo: 'bar', 18 | bar: 'foo', 19 | number: '123', 20 | stuff: [{x:1}, {x:2}] 21 | } }); 22 | 23 | setTimeout(function () { 24 | // wait one second to receive the message, then quit 25 | connection.end(); 26 | }, 1000); 27 | }); 28 | q.subscribeRaw(function (m) { 29 | puts("--- Message (" + m.deliveryTag + ", '" + m.routingKey + "') ---"); 30 | //puts("--- headers: " + JSON.stringify(m.headers)); 31 | 32 | recvCount++; 33 | assert.equal('bar', m.headers['foo']); 34 | assert.equal('foo', m.headers['bar']); 35 | assert.equal('123', m.headers['number'].toString()); 36 | assert.equal(1, m.headers['stuff'][0].x); 37 | assert.equal(2, m.headers['stuff'][1].x); 38 | }) 39 | }) 40 | }); 41 | }); 42 | 43 | process.addListener('exit', function () { 44 | assert.equal(1, recvCount); 45 | }); 46 | -------------------------------------------------------------------------------- /test/test-heartbeat-shutdown.js: -------------------------------------------------------------------------------- 1 | global.options = { heartbeat: 10 }; 2 | 3 | require('./harness').run(); 4 | 5 | connection.addListener('ready', function () { 6 | puts("connected to " + connection.serverProperties.product); 7 | 8 | assert.ok(connection._inboundHeartbeatTimer); 9 | assert.ok(connection._outboundHeartbeatTimer); 10 | 11 | connection.end(); 12 | assert.ok(!connection._inboundHeartbeatTimer); 13 | assert.ok(!connection._outboundHeartbeatTimer); 14 | }); 15 | -------------------------------------------------------------------------------- /test/test-heartbeat.js: -------------------------------------------------------------------------------- 1 | global.options = { heartbeat: 1 }; 2 | 3 | require('./harness').run(); 4 | 5 | var isClosed = false, q; 6 | 7 | setTimeout(function() { 8 | assert.ok(!isClosed); 9 | // Change the local heartbeat interval (without changing the negotiated 10 | // interval). This will cause the server to notice we've dropped off, 11 | // and close the connection. 12 | connection.options['heartbeat'] = 0; 13 | setTimeout(function() { 14 | assert.ok(isClosed); 15 | }, 3500); 16 | }, 1000); 17 | 18 | connection.on('heartbeat', function() { 19 | puts(" <- heartbeat"); 20 | }); 21 | connection.on('close', function() { 22 | puts("closed"); 23 | isClosed = true; 24 | }); 25 | connection.addListener('ready', function () { 26 | puts("connected to " + connection.serverProperties.product); 27 | 28 | q = connection.queue('node-test-heartbeat', {autoDelete: true}); 29 | q.on('queueDeclareOk', function (args) { 30 | puts('queue opened.'); 31 | assert.equal(0, args.messageCount); 32 | assert.equal(0, args.consumerCount); 33 | 34 | q.bind("#"); 35 | q.subscribe(function(json) { 36 | // We should not be subscribed to the queue, the heartbeat will peter out before. 37 | assert.ok(false); 38 | }); 39 | }); 40 | }); -------------------------------------------------------------------------------- /test/test-json.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var recvCount = 0; 4 | var body = "hello world"; 5 | 6 | connection.addListener('ready', function () { 7 | puts("connected to " + connection.serverProperties.product); 8 | 9 | var exchange = connection.exchange('node-json-fanout', {type: 'fanout'}); 10 | 11 | var q = connection.queue('node-json-queue', function() { 12 | var origMessage1 = {two:2, one:1}, 13 | origMessage2 = {foo:'bar', hello: 'world'}, 14 | origMessage3 = {coffee:'caf\u00E9', tea: 'th\u00E9', hearts: (new Array(50000)).join('❤')}; 15 | 16 | q.bind(exchange, "*"); 17 | 18 | q.subscribe(function (json, headers, deliveryInfo) { 19 | recvCount++; 20 | 21 | assert.equal("node-json-fanout", deliveryInfo.exchange); 22 | assert.equal("node-json-queue", deliveryInfo.queue); 23 | assert.equal(false, deliveryInfo.redelivered); 24 | 25 | switch (deliveryInfo.routingKey) { 26 | case 'message.json1': 27 | assert.deepEqual(origMessage1, json); 28 | break; 29 | 30 | case 'message.json2': 31 | assert.deepEqual(origMessage2, json); 32 | break; 33 | 34 | case 'message.json3': 35 | assert.deepEqual(origMessage3, json); 36 | break; 37 | 38 | default: 39 | throw new Error('unexpected routing key: ' + deliveryInfo.routingKey); 40 | } 41 | }) 42 | .addCallback(function () { 43 | puts("publishing 3 json messages"); 44 | exchange.publish('message.json1', origMessage1); 45 | exchange.publish('message.json2', origMessage2, {contentType: 'application/json'}); 46 | exchange.publish('message.json3', origMessage3, {contentType: 'application/json'}); 47 | 48 | setTimeout(function () { 49 | // wait one second to receive the message, then quit 50 | connection.end(); 51 | }, 1000); 52 | }) 53 | }); 54 | }); 55 | 56 | 57 | process.addListener('exit', function () { 58 | assert.equal(3, recvCount); 59 | }); 60 | -------------------------------------------------------------------------------- /test/test-large-body.js: -------------------------------------------------------------------------------- 1 | // This effectively tests that a frame that takes more than one packet 2 | // ('data' event) is parsed correctly. 3 | // https://github.com/postwait/node-amqp/issues/65 4 | 5 | require('./harness').run(); 6 | 7 | var recvCount = 0; 8 | var bodySize = 256000; 9 | var body = new Buffer(bodySize); 10 | 11 | // Fill with random bytes 12 | for (var i = 0; i < bodySize; i++ ){ 13 | body[i] = Math.floor(Math.random()*256); 14 | } 15 | 16 | connection.addListener('ready', function () { 17 | puts("connected to " + connection.serverProperties.product); 18 | 19 | connection.exchange('node-simple-fanout', {type: 'fanout'}, function(exchange) { 20 | connection.queue('node-simple-queue', function(q) { 21 | q.bind(exchange, "*") 22 | q.on('queueBindOk', function() { 23 | q.on('basicConsumeOk', function () { 24 | puts("publishing message"); 25 | exchange.publish("message.text", body, {contentType: 'application/octet-stream'}); 26 | 27 | setTimeout(function () { 28 | // wait one second to receive the message, then quit 29 | connection.end(); 30 | }, 1000); 31 | }); 32 | 33 | q.subscribeRaw(function (m) { 34 | puts("--- Message (" + m.deliveryTag + ", '" + m.routingKey + "') ---"); 35 | puts("--- contentType: " + m.contentType); 36 | 37 | assert.equal('application/octet-stream', m.contentType); 38 | 39 | var chunks = []; 40 | m.addListener('data', function (d) { chunks.push(d); }); 41 | 42 | m.addListener('end', function () { 43 | recvCount++; 44 | assert.equal(body.toString(), Buffer.concat(chunks).toString()); 45 | m.acknowledge(); 46 | }); 47 | }); 48 | }); 49 | }); 50 | }); 51 | }); 52 | 53 | 54 | process.addListener('exit', function () { 55 | assert.equal(1, recvCount); 56 | }); 57 | -------------------------------------------------------------------------------- /test/test-large-multiframe-body.js: -------------------------------------------------------------------------------- 1 | // This effectively tests that a frame that takes more than one packet 2 | // ('data' event) is parsed correctly. 3 | // https://github.com/postwait/node-amqp/issues/65 4 | 5 | require('./harness').run(); 6 | 7 | var recvCount = 0; 8 | var body = new Buffer(2000000); 9 | body = body.toString() 10 | 11 | puts("sending length " + body.length); 12 | 13 | connection.addListener('ready', function () { 14 | puts("connected to " + connection.serverProperties.product); 15 | 16 | connection.exchange('node-simple-fanout', {type: 'fanout'}, function(exchange) { 17 | connection.queue('node-simple-queue', function(q) { 18 | q.bind(exchange, "*") 19 | q.on('queueBindOk', function() { 20 | q.on('basicConsumeOk', function () { 21 | puts("publishing message"); 22 | exchange.publish("message.text", body, {contentType: 'application/octet-stream'}); 23 | 24 | setTimeout(function () { 25 | // wait one second to receive the message, then quit 26 | connection.end(); 27 | }, 1000); 28 | }); 29 | 30 | q.subscribeRaw(function (m) { 31 | puts("--- Message (" + m.deliveryTag + ", '" + m.routingKey + "') ---"); 32 | puts("--- contentType: " + m.contentType); 33 | 34 | 35 | 36 | assert.equal('application/octet-stream', m.contentType); 37 | 38 | var chunks = []; 39 | m.addListener('data', function (d) { chunks.push(d.toString()); }); 40 | 41 | m.addListener('end', function () { 42 | recvCount++; 43 | 44 | puts("--- Message Length (" + chunks.join('').length + ") ---"); 45 | puts("--- Chunks (" + chunks.length + ") ---"); 46 | assert.equal(body, chunks.join('')); 47 | m.acknowledge(); 48 | }); 49 | }); 50 | }); 51 | }); 52 | }); 53 | }); 54 | 55 | 56 | process.addListener('exit', function () { 57 | assert.equal(1, recvCount); 58 | }); 59 | -------------------------------------------------------------------------------- /test/test-parser.js: -------------------------------------------------------------------------------- 1 | // Make sure we get the correct results when splitting frames across 2 | // data packets. See https://github.com/postwait/node-amqp/issues/65 3 | require('./harness'); 4 | var Connection = require('../amqp').Connection; 5 | var _ = require('lodash'); 6 | var EventEmitter = require('events').EventEmitter; 7 | _.assignIn(Connection.prototype, EventEmitter.prototype); 8 | 9 | var errorThrown = false; 10 | 11 | function fresh_connection() { 12 | var c = new Connection(); 13 | EventEmitter.call(c); 14 | c.on('error', function() { 15 | errorThrown = true; 16 | }); 17 | 18 | c.write = function() {}; 19 | c.emit('connect'); 20 | return c; 21 | } 22 | 23 | connection = fresh_connection(); 24 | 25 | function packets() { 26 | for (var i in arguments) { 27 | connection.emit('data', new Buffer(arguments[i])); 28 | } 29 | } 30 | 31 | // First, check that we will wait for a full header if sent only a 32 | // partial one. For this purpose we'll use a heartbeat frame. 33 | var heartbeat = [ 34 | 8, // 'type' 35 | 0, 0, // channel 36 | 0, 0, 0, 0, // frame size, exclusive of the header 37 | 206]; // frame delimiter 38 | 39 | packets(heartbeat.slice(0, 4), heartbeat.slice(4)); 40 | 41 | // Now make sure we can send a full method frame. We'll use 42 | // basic.consume with default arguments. 43 | 44 | connection = fresh_connection(); 45 | 46 | var consume = [ 47 | 1,0,1,0,0,0,13, // frame header 48 | 0,60,0,20, // method header 49 | 0,0,0,0,0,0,0,0,0, // content (no fields) 50 | 206]; // frame delimiter 51 | 52 | for (var i = 0; i < consume.length; i++) { 53 | connection = fresh_connection(); 54 | packets(consume.slice(0, i), consume.slice(i)); 55 | } 56 | 57 | // And test if a packet with more than one frame gets parsed OK 58 | 59 | var consumeX2 = [ 60 | 1,0,1,0,0,0,13, // frame header 61 | 0,60,0,20, // method header 62 | 0,0,0,0,0,0,0,0,0, // content (no fields) 63 | 206, 64 | 1,0,1,0,0,0,13, // frame header 65 | 0,60,0,20, // method header 66 | 0,0,0,0,0,0,0,0,0, // content (no fields) 67 | 206 68 | ] 69 | 70 | for (var j = 0; j < consumeX2.length; j++) { 71 | connection = fresh_connection(); 72 | packets(consumeX2.slice(0, j), consumeX2.slice(j)); 73 | } 74 | 75 | assert(!errorThrown); 76 | -------------------------------------------------------------------------------- /test/test-properties.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var recvCount = 0; 4 | var body = "hello world"; 5 | 6 | var properties = { 7 | "contentType" : "application/test", 8 | "contentEncoding" : "binary", 9 | "priority" : 9, 10 | "correlationId" : "test for correlationId", 11 | "replyTo" : "test for replyTo", 12 | "expiration" : "10000", 13 | "messageId" : "test for messageId", 14 | "timestamp" : parseInt(Date.now() / 1000, 10), 15 | "type" : "test for type", 16 | "userId" : "guest", 17 | "appId" : "test for appId", 18 | "clusterId" : null 19 | }; 20 | 21 | connection.addListener('ready', function () { 22 | puts("connected to " + connection.serverProperties.product); 23 | 24 | var exchange = connection.exchange('node-json-fanout', {type: 'fanout'}); 25 | 26 | var q = connection.queue('node-json-queue', function() { 27 | 28 | q.bind(exchange, "*"); 29 | 30 | q.subscribe(function (json, headers, deliveryInfo) { 31 | var key = deliveryInfo.routingKey; 32 | recvCount++; 33 | assert.equal(properties[key], deliveryInfo[key]); 34 | }) 35 | .addCallback(function () { 36 | puts("publishing " + Object.keys(properties).length + " messages"); 37 | Object.keys(properties).forEach(function(p) { 38 | var props = {}; 39 | props[p] = properties[p]; 40 | exchange.publish(p, body, props); 41 | }); 42 | 43 | setTimeout(function () { 44 | // wait one second to receive the message, then quit 45 | connection.end(); 46 | }, 1000); 47 | }); 48 | }); 49 | }); 50 | 51 | 52 | process.addListener('exit', function () { 53 | assert.equal(Object.keys(properties).length, recvCount); 54 | }); 55 | -------------------------------------------------------------------------------- /test/test-publish-confirms-callback.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var timeout = null; 4 | var confirmed = 0; 5 | 6 | connection.addListener('ready', function () { 7 | connection.exchange('node-publish-confirms', {type: 'fanout', confirm: true}, function(exchange) { 8 | exchange.publish("", "hello", { mandatory: true },function(){ 9 | confirmed++; 10 | clearTimeout(timeout); 11 | connection.end(); 12 | }); 13 | }); 14 | }); 15 | 16 | timeout = setTimeout(function() { 17 | process.exit(); 18 | }, 1000); 19 | 20 | process.on('exit', function(){ 21 | assert.equal(confirmed, 1); 22 | }); 23 | -------------------------------------------------------------------------------- /test/test-publish-confirms-callbacks-not-hanging-after-recovery.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var amqp = require('../amqp'); 3 | var exec = require('child_process').exec; 4 | 5 | var cycleServer = function (stoppedCallback, startedCallback) { 6 | // If you're running a cluster you can do this: 7 | // 'killall -9 beam.smp' 8 | // to test out hard server fails. Note however that you probably do want a 9 | // cluster because killing a single server this way causes even durable 10 | // queues to be deleted, causing the bindings we create to be removed. 11 | exec('rabbitmqctl stop_app', function () { 12 | if (stoppedCallback) { 13 | stoppedCallback(); 14 | } 15 | setTimeout(function () { 16 | // Likewise you can bring up a hard server crash this way: 17 | // 'rabbitmq-server -detached' 18 | exec('rabbitmqctl start_app', function () { 19 | console.log('start triggered'); 20 | if (startedCallback) { 21 | setTimeout(startedCallback, 500); 22 | } 23 | }); 24 | // Leave the server down for 1500ms before restarting. 25 | }, 1500); 26 | }); 27 | }; 28 | 29 | var connection, exchange; 30 | var timeout = null, didntHang = false, confirmed = 0, erroredAfterShutdown = 0; 31 | 32 | 33 | exec('which rabbitmqctl', function(err) { 34 | if (err != null) { 35 | console.log('skipping test, rabbitmqctl not availabe'); 36 | process.exit(0); 37 | } 38 | 39 | connection = amqp.createConnection(global.options || {}, { reconnect: true, reconnectBackoffTime: 500 }, function(connection) { 40 | exchange = connection.exchange('node-publish-confirms', { type: 'fanout', confirm: true }, function(exchange) { 41 | // publishing right before thhe server is stopped; must not "hang". 42 | exchange.publish("", "hello", {}, function() { 43 | console.log('callback fired for a message published right before the shutdown'); 44 | didntHang = true; 45 | }); 46 | cycleServer(function() { 47 | console.log('server stopped'); 48 | var incErrored = function(err) { console.log('received ack error'); if (err) { erroredAfterShutdown++; } }; 49 | exchange.publish("", "hello", {}, incErrored); 50 | exchange.publish("", "hello", {}, incErrored); 51 | exchange.publish("", "hello", {}, incErrored); 52 | }, function() { 53 | console.log('server started'); 54 | exchange.publish("", "hello2", {}, function() { // this one must not "hang" 55 | console.log('acked'); 56 | confirmed++; 57 | clearTimeout(timeout); 58 | connection.end(); 59 | }); 60 | }); 61 | }); 62 | }); 63 | 64 | timeout = setTimeout(function() { 65 | process.exit(); 66 | }, 15000); 67 | 68 | process.on('exit', function(){ 69 | try { 70 | assert(didntHang); 71 | assert.equal(erroredAfterShutdown, 3); 72 | assert.equal(confirmed, 1); 73 | } finally { 74 | if (exchange) { 75 | exchange.destroy(); 76 | } 77 | if (connection) { 78 | connection.setImplOptions({ 'reconnect': false }); 79 | connection.destroy(); 80 | } 81 | } 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/test-publish-confirms-emitter.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var timeout = null; 4 | var confirmed = 0; 5 | 6 | connection.addListener('ready', function () { 7 | connection.exchange('node-publish-confirms', {type: 'fanout', confirm: true}, function(exchange) { 8 | var publish = exchange.publish("", "hello", { mandatory: true }); 9 | 10 | publish.addListener('ack', function(){ 11 | confirmed++; 12 | clearTimeout(timeout); 13 | connection.end(); 14 | }); 15 | 16 | }); 17 | }); 18 | 19 | timeout = setTimeout(function() { 20 | process.exit(); 21 | }, 1000); 22 | 23 | process.on('exit', function(){ 24 | assert.equal(confirmed, 1); 25 | }); 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/test-purge.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var recvCount = 0; 4 | var body = "hello world"; 5 | 6 | connection.addListener('ready', function () { 7 | puts("connected to " + connection.serverProperties.product); 8 | 9 | var e = connection.exchange('node-purge-fanout', {type: 'fanout', confirm: true}); 10 | var q = connection.queue('node-purge-queue', function() { 11 | q.bind(e, "*"); 12 | q.on('queueBindOk', function() { 13 | puts("publishing 1 json message"); 14 | 15 | e.publish('ackmessage.json1', { name: 'A' }, {}, function() { 16 | 17 | puts('Purge queue'); 18 | q.purge().addCallback(function(ok){ 19 | puts('Deleted '+ok.messageCount+' message'); 20 | assert.equal(1,ok.messageCount); 21 | puts("publishing another json message"); 22 | e.publish('ackmessage.json2', { name: 'B' }); 23 | }); 24 | 25 | q.on('basicConsumeOk', function () { 26 | setTimeout(function () { 27 | // wait one second to receive the message, then quit 28 | connection.end(); 29 | }, 1000); 30 | }); 31 | 32 | q.subscribe({ ack: true }, function (json) { 33 | recvCount++; 34 | puts('Got message ' + JSON.stringify(json)); 35 | if (recvCount == 1) { 36 | assert.equal('B', json.name); 37 | q.shift(); 38 | } else { 39 | throw new Error('Too many message!'); 40 | } 41 | }); 42 | }); 43 | }); 44 | }); 45 | }); 46 | 47 | 48 | process.addListener('exit', function () { 49 | assert.equal(1, recvCount); 50 | }); 51 | -------------------------------------------------------------------------------- /test/test-queue-args.js: -------------------------------------------------------------------------------- 1 | var harness = require('./harness'); 2 | harness.run(); 3 | 4 | connection.on('ready', function() { 5 | puts("connected to " + connection.serverProperties.product); 6 | 7 | connection.queue('node-queue-args-queue', { 8 | 'arguments': {'x-expires': 3600000} 9 | }, function(q) { 10 | puts("queue declared"); 11 | var conn = harness.createConnection(); 12 | 13 | conn.on('ready', function() { 14 | var q = conn.queue('node-queue-args-queue', { 15 | 'arguments': {'x-expires': 3600001} 16 | }, function(q2) { 17 | puts("second queue declared"); 18 | }); 19 | q.on('error', function(err) { 20 | assert.equal(err.code, 406); 21 | assert.ok(err.message.indexOf('PRECONDITION_FAILED') === 0); 22 | connection.end(); 23 | conn.end(); 24 | }); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/test-queue-bind-callbacks-cascaded.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | var testName = __filename.replace(__dirname+'/','').replace('.js',''); 3 | 4 | connection.addListener('ready', function () { 5 | puts("connected to " + connection.serverProperties.product); 6 | var callbacksCalled = 0; 7 | 8 | connection.exchange('node.'+testName+'.exchange', {type: 'topic'}, function(exchange) { 9 | connection.queue( 'node.'+testName+'.queue', { durable: false, autoDelete : true }, function (queue) { 10 | puts("Queue ready"); 11 | 12 | // main test for callback 13 | queue.bind(exchange, 'node.'+testName+'.topic.bindCallback.outer', function(q) { 14 | puts("First queue bind callback called"); 15 | callbacksCalled++; 16 | q.bind(exchange, 'node.'+testName+'.topic.bindCallback.inner', function() { 17 | puts("Second queue bind callback called"); 18 | callbacksCalled++; 19 | }); 20 | }); 21 | 22 | setTimeout(function() { 23 | assert.ok(callbacksCalled == 2, "Callback was not called"); 24 | puts("Cascaded queue bind callback succeeded"); 25 | queue.destroy(); 26 | connection.destroy(); 27 | },2000); 28 | }); 29 | }); 30 | }); 31 | 32 | -------------------------------------------------------------------------------- /test/test-queue-bind-callbacks-sequential.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | var testName = __filename.replace(__dirname+'/','').replace('.js',''); 3 | 4 | connection.addListener('ready', function () { 5 | puts("connected to " + connection.serverProperties.product); 6 | 7 | connection.exchange('node.'+testName+'.exchange', {type: 'topic'}, function(exchange) { 8 | connection.queue( 'node.'+testName+'.queue', { durable: false, autoDelete : true }, function (queue) { 9 | puts("Queue ready"); 10 | 11 | // main test for sequential callback issue 12 | queue.bind( exchange, 'node.'+testName+'.topic.bindCallback1', function() { 13 | puts("bind callback called"); 14 | assert.ok(false, "This callback should not be called unless the sequential bind callback issue has been fixed");} 15 | ); 16 | queue.bind( exchange, 'node.'+testName+'.topic.bindCallback2', function() { 17 | puts("bind callback called"); 18 | assert.ok(false, "This callback should not be called unless the sequential bind callback issue has been fixed");} 19 | ); 20 | queue.bind( exchange, 'node.'+testName+'.topic.bindCallback2', function() { 21 | puts("bind callback called"); 22 | assert.ok(true, "This callback should have be called, as the last of the sequential callbacks");} 23 | ); 24 | 25 | setTimeout(function() { 26 | queue.destroy(); 27 | connection.destroy(); 28 | }, 2000); 29 | }); 30 | 31 | 32 | }); 33 | }); 34 | 35 | -------------------------------------------------------------------------------- /test/test-queue-bind-callbacks-single.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | var testName = __filename.replace(__dirname+'/','').replace('.js',''); 3 | connection.addListener('ready', function () { 4 | puts("connected to " + connection.serverProperties.product); 5 | var callbackCalled = false; 6 | 7 | connection.exchange('node.'+testName+'.exchange', {type: 'topic'}, function(exchange) { 8 | connection.queue( 'node.'+testName+'.queue', { durable: false, autoDelete : true }, function (queue) { 9 | puts("Queue ready"); 10 | 11 | // main test for callback 12 | queue.bind(exchange, 'node.'+testName+'.topic.bindCallback', function() { 13 | puts("Single queue bind callback called"); 14 | callbackCalled = true; 15 | }); 16 | 17 | // nothing to be asserted / checked with these, other than they don't blow up. 18 | queue.bind(exchange, 'node.'+testName+'.topic.nullCallback', null); 19 | queue.bind(exchange, 'node.'+testName+'.topic.undefinedCallback', undefined); 20 | queue.bind(exchange, 'node.'+testName+'.topic.nonFunctionCallback', "Not a callback"); 21 | 22 | // Regression test for no callback being supplied not blowing up 23 | queue.bind(exchange, 'node.'+testName+'.topic.noCallback'); 24 | 25 | setTimeout(function() { 26 | assert.ok(callbackCalled, "Callback was not called"); 27 | puts("Single queue bind callback succeeded"); 28 | queue.destroy(); 29 | connection.destroy(); 30 | }, 2000); 31 | }); 32 | }); 33 | }); 34 | 35 | -------------------------------------------------------------------------------- /test/test-queue-bind-headers.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | var testName = __filename.replace(__dirname+'/','').replace('.js',''); 3 | var recvCount = 0; 4 | connection.addListener('ready', function () { 5 | puts("connected to " + connection.serverProperties.product); 6 | 7 | connection.exchange('node.'+testName+'.exchange', {type: 'headers'}, function(exchange) { 8 | connection.queue( 'node.'+testName+'.queue', { durable: false, autoDelete : true }, function (queue) { 9 | puts("Queue ready"); 10 | // main test for callback 11 | queue.bind_headers(exchange, {headerOne: "one", headerTwo: "two"}); 12 | queue.subscribe(function(message){ 13 | puts("Message from queue"); 14 | recvCount+=1; 15 | }); 16 | 17 | exchange.publish('', {body: "body"}, {headers: {headerOne: "one", headerTwo: "two"}}); 18 | exchange.publish('', {body: "body"}, {headers: {headerOne: "two", headerTwo: "one"}}); 19 | setTimeout(function() { 20 | puts("Destroying connection"); 21 | connection.destroy(); 22 | }, 500); 23 | }); 24 | }); 25 | }); 26 | 27 | process.addListener('exit', function () { 28 | puts("Checking assertion of message received"); 29 | assert.equal(1, recvCount); 30 | }); 31 | -------------------------------------------------------------------------------- /test/test-queue-creation.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | // setImmediate should be used in node 0.10 4 | var nextTick = typeof setImmediate !== "undefined" ? setImmediate : process.nextTick; 5 | 6 | function countdownLatch(num, callback) { 7 | var count = num; 8 | 9 | function tick() { 10 | nextTick(function() { 11 | if (0 === count) { 12 | callback(); 13 | } 14 | else { 15 | tick(); 16 | } 17 | }); 18 | } 19 | 20 | tick(); 21 | return { 22 | decr: function() { 23 | count--; 24 | } 25 | }; 26 | } 27 | 28 | var testsLeft = countdownLatch(3, function() { 29 | connection.end(); 30 | }); 31 | 32 | // Test multiple creations of the same queue 33 | 34 | var callbacks = 0; 35 | 36 | connection.on('ready', function() { 37 | 38 | var queueName = 'node-test-queue-creation'; 39 | var queueOpts = {'durable': false}; 40 | 41 | var q = connection.queue(queueName, queueOpts, function() { 42 | callbacks++; 43 | testsLeft.decr(); 44 | connection.queue(queueName, queueOpts, function() { 45 | callbacks++; 46 | testsLeft.decr(); 47 | }); 48 | 49 | }); 50 | 51 | }); 52 | 53 | // Supplying no name makes the server create a name, which we can see 54 | connection.on('ready', function() { 55 | 56 | var q = connection.queue('', function() { 57 | assert.ok(q.name != '' && q.name != undefined); 58 | testsLeft.decr(); 59 | }); 60 | 61 | }); 62 | 63 | process.addListener('exit', function() { 64 | assert.equal(2, callbacks); 65 | }); 66 | -------------------------------------------------------------------------------- /test/test-queue-declare-error.js: -------------------------------------------------------------------------------- 1 | helper = require('./harness').run(); 2 | 3 | connection.removeListener('error', errorCallback); 4 | 5 | assert = require('assert'); 6 | 7 | connection.on('ready', function() { 8 | puts("connected to " + connection.serverProperties.product); 9 | 10 | connection.queue('node-will-not-see-this', { passive: true }, function(q) { 11 | assert.ok(false, 'Not supposed to see this message.'); 12 | process.exit(1); 13 | }); 14 | 15 | connection.on('error', function (exception) { 16 | message = String(exception.message); 17 | assert.equal(true, message.indexOf('NOT_FOUND') === 0, 'This supposed to be a "NOT_FOUND" error.'); 18 | process.exit(0); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/test-queue-subscribe-event.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var basic_qos_emitted = false; 4 | 5 | // make sure the 'basicQosOk' event is emitted properly with prefetchCount: 0 6 | connection.on('ready', function() { 7 | var e = connection.exchange('node-subscribe-event', {type: 'fanout'}); 8 | connection.queue('node-subscribe-event-queue', function(q) { 9 | q.bind(e, ''); 10 | q.subscribe({ ack: true, prefetchCount: 0 }, function() { 11 | connection.end(); 12 | }); 13 | 14 | q.on('basicQosOk', function() { 15 | basic_qos_emitted = true; 16 | }); 17 | 18 | e.publish('node-subscribe-event-queue', { foo: 'bar' }); 19 | }); 20 | }); 21 | 22 | 23 | process.on('exit', function() { 24 | assert( basic_qos_emitted ); 25 | }); 26 | -------------------------------------------------------------------------------- /test/test-queue-unbind-headers.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | var testName = __filename.replace(__dirname+'/','').replace('.js',''); 3 | var recvCount = 0; 4 | connection.addListener('ready', function () { 5 | puts("connected to " + connection.serverProperties.product); 6 | 7 | connection.exchange('node.'+testName+'.exchange', {type: 'headers'}, function(exchange) { 8 | connection.queue( 'node.'+testName+'.queueref', { durable: false, autoDelete : true }, function (queueref) { 9 | queueref.bind_headers(exchange, {headerOne: "one", headerTwo: "two"}); 10 | connection.queue( 'node.'+testName+'.queue', { durable: false, autoDelete : true }, function (queue) { 11 | puts("Queue ready"); 12 | // main test for callback 13 | queue.bind_headers(exchange, {headerOne: "one", headerTwo: "two"}); 14 | queue.subscribe(function(message){ 15 | puts("Message from queue"); 16 | recvCount+=1; 17 | }); 18 | 19 | exchange.publish('', {body: "body"}, {headers: {headerOne: "one", headerTwo: "two"}}); 20 | exchange.publish('', {body: "body"}, {headers: {headerOne: "one", headerTwo: "two"}}); 21 | queue.unbind_headers(exchange, {headerOne: "one", headerTwo: "two"}); 22 | exchange.publish('', {body: "body"}, {headers: {headerOne: "one", headerTwo: "two"}}); 23 | 24 | setTimeout(function() { 25 | puts("Destroying connection"); 26 | connection.destroy(); 27 | }, 1000); 28 | }); 29 | }); 30 | }); 31 | }); 32 | 33 | process.addListener('exit', function () { 34 | puts("Checking assertion of message received"); 35 | assert.equal(2, recvCount); 36 | }); 37 | -------------------------------------------------------------------------------- /test/test-receive-empty-messages.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var timeout = null; 4 | connection.addListener('ready', function () { 5 | var exc = connection.exchange('node-json-exchange'); 6 | 7 | connection.queue('node-json-queue', function(q) { 8 | q.bind('node-json-exchange', '*'); 9 | 10 | q.subscribe(function (json, headers, deliveryInfo) { 11 | clearTimeout(timeout); 12 | connection.end(); 13 | }).addCallback(function () { 14 | puts("Publishing one empty message."); 15 | exc.publish('node-json-queue', ''); 16 | }); 17 | }); 18 | }); 19 | 20 | timeout = setTimeout(function() { 21 | puts("ERROR: Timeout occurred!!!!!!!"); 22 | connection.end(); 23 | assert.ok(1, 2); 24 | }, 5000); 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/test-reconnection-server-named-queue.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var amqp = require('../amqp'); 3 | var exec = require('child_process').exec; 4 | 5 | var options = global.options || {}; 6 | if (process.argv[2]) { 7 | var server = process.argv[2].split(':'); 8 | if (server[0]) options.host = server[0]; 9 | if (server[1]) options.port = parseInt(server[1]); 10 | } 11 | 12 | exec('which rabbitmqctl', function(err,res){ 13 | if(err != null){ 14 | process.exit(0); 15 | }else{ 16 | var implOpts = { 17 | reconnect: true, 18 | reconnectBackoffStrategy: 'exponential', 19 | // A 500ms backoff time with an exponential strategy should cause 20 | // the following to occur: 21 | // t = 0 ms server shutdown 22 | // t = ~0 ms connection severed 23 | // t = ~500 ms reconnection attempt fails 24 | // t = ~1500 ms reconnection attempt fails 25 | // t = ~1500 ms server restarted 26 | // t = ~3500 ms reconnection attempt succeeds 27 | reconnectBackoffTime: 500, 28 | }; 29 | 30 | var cycleServer = function (stoppedCallback, startedCallback) { 31 | // If you're running a cluster you can do this: 32 | // 'killall -9 beam.smp' 33 | // to test out hard server fails. Note however that you probably do want a 34 | // cluster because killing a single server this way causes even durable 35 | // queues to be deleted, causing the bindings we create to be removed. 36 | exec('rabbitmqctl stop_app', function () { 37 | setTimeout(function () { 38 | if (stoppedCallback) { 39 | stoppedCallback(); 40 | } 41 | // Likewise you can bring up a hard server crash this way: 42 | // 'rabbitmq-server -detached' 43 | exec('rabbitmqctl start_app', function () { 44 | if (startedCallback) { 45 | setTimeout(startedCallback, 500); 46 | } 47 | }); 48 | // Leave the server down for 1500ms before restarting. 49 | }, 1500); 50 | }); 51 | } 52 | 53 | var readyCount = 0; 54 | var errorCount = 0; 55 | var closeCount = 0; 56 | var messageCount = 0; 57 | 58 | var runTest = function () { 59 | console.log("Starting..."); 60 | 61 | var connection = amqp.createConnection(options, implOpts); 62 | var exchange = null; 63 | var queue = null; 64 | var exit = false; 65 | var done = function (error) { 66 | if (exchange) { 67 | exchange.destroy(); 68 | } 69 | if (queue) { 70 | queue.destroy(); 71 | } 72 | if (error) { 73 | // Exit loudly. 74 | console.log('Error: "' + error + '", abandoning test cycle'); 75 | throw error; 76 | } else { 77 | console.log('All done!'); 78 | // Exit gracefully. 79 | connection.setImplOptions({'reconnect': false}); 80 | connection.destroy(); 81 | } 82 | exit = true; 83 | } 84 | var connectionDownTimestamp = null; 85 | connection.on('ready', function() { 86 | readyCount += 1; 87 | console.log('Connection ready (' + readyCount + ')'); 88 | 89 | if (readyCount === 1) { 90 | // Create an exchange. Make it durable, because our test case shuts down the exchange 91 | // in lieu of interrupting network communications. 92 | exchange = connection.exchange('node-reconnect-exchange', {type: 'topic', durable: true}); 93 | // Now, create a queue. Bind it to an exchange, and pump a few messages 94 | // in to it. This is just to prove that the queue is working *before* 95 | // we disconnect it. 96 | connection.queue('', {autoDelete: true, durable: false, exclusive: true}, function (q) { 97 | queue = q; 98 | queue.on('queueBindOk', function () { 99 | queue.once('basicConsumeOk', function () { 100 | exchange.publish('node-reconnect', 'one'); 101 | console.log('Message one published'); 102 | exchange.publish('node-reconnect', 'two'); 103 | console.log('Message two published'); 104 | }); 105 | }); 106 | queue.bind(exchange, '#'); 107 | queue.subscribe(function (message) { 108 | messageCount += 1; 109 | console.log('Message received (' + messageCount + '): ' + message.data); 110 | if (messageCount === 2) { 111 | // On the second message, restart the server. 112 | cycleServer(function () { 113 | // Don't wait for it to come back up, publish a message while it is down. 114 | exchange.publish('node-reconnect', 'three'); 115 | console.log('Message three published'); 116 | }); 117 | } else if (messageCount === 6) { 118 | return done(); 119 | } 120 | }); 121 | }); 122 | } else if (readyCount === 2) { 123 | // Ensure that the backoff timeline is approximately correct. We 124 | // expect a 500ms backoff, followed by a 1000ms backoff, followed 125 | // by a 2000ms backoff, resulting in about 3500ms of disconnected 126 | // time. 127 | var disconnectionTime = (Date.now() - connectionDownTimestamp); 128 | console.log('connection down for ' + disconnectionTime + ' ms'); 129 | assert(disconnectionTime >= 3500); 130 | // Allow some grace period for processing and transit, but the tests 131 | // are all done on localhost, so not *too* much. 132 | assert(disconnectionTime <= 3700) 133 | // Ensure we get the rest of the messages from the queue. This means 134 | // that the connection and queue were automatically reconnected with 135 | // no user interaction, and no messages were lost. 136 | // Publish another message after the connection has been restored. 137 | exchange.publish('node-reconnect', 'four'); 138 | console.log('Message four published'); 139 | } 140 | }); 141 | connection.on('error', function (error) { 142 | errorCount += 1; 143 | console.log('Connection error (' + errorCount + '): ' + error); 144 | if (connectionDownTimestamp === null) { 145 | connectionDownTimestamp = Date.now(); 146 | } 147 | }); 148 | connection.on('close', function () { 149 | closeCount += 1; 150 | console.log('Connection close (' + closeCount + ')'); 151 | if (connectionDownTimestamp === null) { 152 | connectionDownTimestamp = Date.now(); 153 | } 154 | }); 155 | var waitForExitConditions = function () { 156 | if (!exit) { 157 | setTimeout(waitForExitConditions, 500); 158 | } 159 | } 160 | waitForExitConditions(); 161 | }; 162 | 163 | runTest(); 164 | 165 | process.addListener('exit', function () { 166 | // 1 ready on initial connection, 1 on reconnection 167 | assert.equal(2, readyCount); 168 | // 6 messages sent and received 169 | assert.equal(6, messageCount); 170 | }); 171 | } 172 | }) 173 | 174 | -------------------------------------------------------------------------------- /test/test-reconnection.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var amqp = require('../amqp'); 3 | var exec = require('child_process').exec; 4 | 5 | var options = global.options || {}; 6 | if (process.argv[2]) { 7 | var server = process.argv[2].split(':'); 8 | if (server[0]) options.host = server[0]; 9 | if (server[1]) options.port = parseInt(server[1]); 10 | } 11 | 12 | exec('which rabbitmqctl', function(err,res){ 13 | if(err != null){ 14 | process.exit(0); 15 | }else{ 16 | var implOpts = { 17 | reconnect: true, 18 | reconnectBackoffStrategy: 'exponential', 19 | // A 500ms backoff time with an exponential strategy should cause 20 | // the following to occur: 21 | // t = 0 ms server shutdown 22 | // t = ~0 ms connection severed 23 | // t = ~500 ms reconnection attempt fails 24 | // t = ~1500 ms reconnection attempt fails 25 | // t = ~1500 ms server restarted 26 | // t = ~3500 ms reconnection attempt succeeds 27 | reconnectBackoffTime: 500, 28 | }; 29 | 30 | var cycleServer = function (stoppedCallback, startedCallback) { 31 | // If you're running a cluster you can do this: 32 | // 'killall -9 beam.smp' 33 | // to test out hard server fails. Note however that you probably do want a 34 | // cluster because killing a single server this way causes even durable 35 | // queues to be deleted, causing the bindings we create to be removed. 36 | exec('rabbitmqctl stop_app', function () { 37 | setTimeout(function () { 38 | if (stoppedCallback) { 39 | stoppedCallback(); 40 | } 41 | // Likewise you can bring up a hard server crash this way: 42 | // 'rabbitmq-server -detached' 43 | exec('rabbitmqctl start_app', function () { 44 | if (startedCallback) { 45 | setTimeout(startedCallback, 500); 46 | } 47 | }); 48 | // Leave the server down for 1500ms before restarting. 49 | }, 1500); 50 | }); 51 | } 52 | 53 | var readyCount = 0; 54 | var errorCount = 0; 55 | var closeCount = 0; 56 | var messageCount = 0; 57 | 58 | var runTest = function () { 59 | console.log("Starting..."); 60 | 61 | var connection = amqp.createConnection(options, implOpts); 62 | var exchange = null; 63 | var queue = null; 64 | var exit = false; 65 | var done = function (error) { 66 | if (exchange) { 67 | exchange.destroy(); 68 | } 69 | if (queue) { 70 | queue.destroy(); 71 | } 72 | if (error) { 73 | // Exit loudly. 74 | console.log('Error: "' + error + '", abandoning test cycle'); 75 | throw error; 76 | } else { 77 | console.log('All done!'); 78 | // Exit gracefully. 79 | connection.setImplOptions({'reconnect': false}); 80 | connection.destroy(); 81 | } 82 | exit = true; 83 | } 84 | var connectionDownTimestamp = null; 85 | connection.on('ready', function() { 86 | readyCount += 1; 87 | console.log('Connection ready (' + readyCount + ')'); 88 | 89 | if (readyCount === 1) { 90 | // Create an exchange. Make it durable, because our test case shuts down the exchange 91 | // in lieu of interrupting network communications. 92 | exchange = connection.exchange('node-reconnect-exchange', {type: 'topic', durable: true}); 93 | // Now, create a queue. Bind it to an exchange, and pump a few messages 94 | // in to it. This is just to prove that the queue is working *before* 95 | // we disconnect it. Remember to make it durable for the same reason 96 | // as the exchange. 97 | connection.queue('node-reconnect-queue', {autoDelete: false, durable: true}, function (q) { 98 | queue = q; 99 | queue.on('queueBindOk', function () { 100 | queue.once('basicConsumeOk', function () { 101 | exchange.publish('node-reconnect', 'one'); 102 | console.log('Message one published'); 103 | exchange.publish('node-reconnect', 'two'); 104 | console.log('Message two published'); 105 | }); 106 | }); 107 | queue.bind(exchange, '#'); 108 | queue.subscribe(function (message) { 109 | messageCount += 1; 110 | console.log('Message received (' + messageCount + '): ' + message.data); 111 | if (messageCount === 2) { 112 | // On the second message, restart the server. 113 | cycleServer(function () { 114 | // Don't wait for it to come back up, publish a message while it is down. 115 | exchange.publish('node-reconnect', 'three'); 116 | console.log('Message three published'); 117 | }); 118 | } else if (messageCount === 4) { 119 | return done(); 120 | } 121 | }); 122 | }); 123 | } else if (readyCount === 2) { 124 | // Ensure that the backoff timeline is approximately correct. We 125 | // expect a 500ms backoff, followed by a 1000ms backoff, followed 126 | // by a 2000ms backoff, resulting in about 3500ms of disconnected 127 | // time. 128 | var disconnectionTime = (Date.now() - connectionDownTimestamp); 129 | console.log('connection down for ' + disconnectionTime + ' ms'); 130 | assert(disconnectionTime >= 3500); 131 | // Allow some grace period for processing and transit, but the tests 132 | // are all done on localhost, so not *too* much. 133 | assert(disconnectionTime <= 3700) 134 | // Ensure we get the rest of the messages from the queue. This means 135 | // that the connection and queue were automatically reconnected with 136 | // no user interaction, and no messages were lost. 137 | // Publish another message after the connection has been restored. 138 | exchange.publish('node-reconnect', 'four'); 139 | console.log('Message four published'); 140 | } 141 | }); 142 | connection.on('error', function (error) { 143 | errorCount += 1; 144 | console.log('Connection error (' + errorCount + '): ' + error); 145 | if (connectionDownTimestamp === null) { 146 | connectionDownTimestamp = Date.now(); 147 | } 148 | }); 149 | connection.on('close', function () { 150 | closeCount += 1; 151 | console.log('Connection close (' + closeCount + ')'); 152 | if (connectionDownTimestamp === null) { 153 | connectionDownTimestamp = Date.now(); 154 | } 155 | }); 156 | var waitForExitConditions = function () { 157 | if (!exit) { 158 | setTimeout(waitForExitConditions, 500); 159 | } 160 | } 161 | waitForExitConditions(); 162 | }; 163 | 164 | runTest(); 165 | 166 | process.addListener('exit', function () { 167 | // 1 ready on initial connection, 1 on reconnection 168 | assert.equal(2, readyCount); 169 | // 4 messages sent and received 170 | assert.equal(4, messageCount); 171 | }); 172 | } 173 | }) 174 | 175 | -------------------------------------------------------------------------------- /test/test-reject.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var received_count = 0; 4 | 5 | connection.addListener('ready', function () { 6 | puts("connected to " + connection.serverProperties.product); 7 | 8 | var exchange = connection.exchange('node-json-fanout', {type: 'fanout'}); 9 | 10 | var q = connection.queue('node-json-queue', {autoDelete: false}, function() { 11 | var origMessage1 = {two:2, one:1}, 12 | origMessage2 = {three:3}; 13 | rejected_count = 0; 14 | 15 | q.bind(exchange, "*"); 16 | 17 | q.subscribe({ack: true}, function (json, headers, deliveryInfo, m) { 18 | received_count++; 19 | if (deliveryInfo.routingKey == 'accept'){ 20 | m.acknowledge(); 21 | } else { 22 | if (++rejected_count < 3){ 23 | m.reject(true); 24 | } else { 25 | m.reject(); 26 | } 27 | } 28 | }) 29 | .addCallback(function () { 30 | exchange.publish('reject', origMessage1); 31 | exchange.publish('accept', origMessage2); 32 | 33 | setTimeout(function () { 34 | // wait one second to receive the message, then quit 35 | // make sure to delete our queue on our way out. 36 | q.destroy(); 37 | connection.end(); 38 | }, 1000); 39 | }) 40 | }); 41 | }); 42 | 43 | 44 | process.addListener('exit', function () { 45 | assert.equal(received_count, 4); 46 | }); 47 | -------------------------------------------------------------------------------- /test/test-shift.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var recvCount = 0; 4 | var body = "hello world"; 5 | 6 | connection.addListener('ready', function () { 7 | puts("connected to " + connection.serverProperties.product); 8 | 9 | var e = connection.exchange('node-ack-fanout', {type: 'fanout'}); 10 | var q = connection.queue('node-123ack-queue', function() { 11 | q.bind(e, 'ackmessage.*'); 12 | q.on('queueBindOk', function() { 13 | q.on('basicConsumeOk', function () { 14 | puts("publishing 2 json messages"); 15 | 16 | e.publish('ackmessage.json1', { name: 'A' }); 17 | e.publish('ackmessage.json2', { name: 'B' }); 18 | e.publish('ackmessage.json3', { name: 'C' }); 19 | }); 20 | 21 | q.subscribe({ ack: true }, function (json) { 22 | recvCount++; 23 | puts('Got message ' + JSON.stringify(json)); 24 | 25 | if (recvCount == 1) { 26 | puts('Got message 1.. waiting'); 27 | assert.equal('A', json.name); 28 | setTimeout(function () { 29 | puts('shift!'); 30 | q.shift(); 31 | }, 1000); 32 | } else if (recvCount == 2) { 33 | puts('got message 2'); 34 | assert.equal('B', json.name); 35 | setTimeout(function () { 36 | puts('reject!'); 37 | q.shift(true); 38 | }, 1000); 39 | } else if (recvCount == 3) { 40 | puts('got message 3'); 41 | assert.equal('C', json.name); 42 | setTimeout(function () { 43 | puts('requeue!'); 44 | q.shift(true, true); 45 | }, 1000); 46 | } else if (recvCount == 4) { 47 | puts('got message 4'); 48 | assert.equal('C', json.name); 49 | 50 | puts('closing connection'); 51 | 52 | connection.end(); 53 | } else { 54 | throw new Error('Too many message!'); 55 | } 56 | }); 57 | }); 58 | }); 59 | }); 60 | 61 | 62 | process.addListener('exit', function () { 63 | assert.equal(4, recvCount); 64 | }); 65 | -------------------------------------------------------------------------------- /test/test-simple.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var recvCount = 0; 4 | var body = "hello world"; 5 | 6 | connection.addListener('ready', function () { 7 | puts("connected to " + connection.serverProperties.product); 8 | 9 | connection.exchange('node-simple-fanout', {type: 'fanout'}, function(exchange) { 10 | connection.queue('node-simple-queue', function(q) { 11 | q.bind(exchange, "*") 12 | q.on('queueBindOk', function() { 13 | q.on('basicConsumeOk', function () { 14 | puts("publishing message"); 15 | exchange.publish("message.text", body, {contentType: 'text/plain'}); 16 | 17 | setTimeout(function () { 18 | // wait one second to receive the message, then quit 19 | connection.end(); 20 | }, 1000); 21 | }); 22 | 23 | q.subscribeRaw(function (m) { 24 | puts("--- Message (" + m.deliveryTag + ", '" + m.routingKey + "') ---"); 25 | puts("--- contentType: " + m.contentType); 26 | 27 | recvCount++; 28 | 29 | assert.equal('text/plain', m.contentType); 30 | 31 | var size = 0; 32 | m.addListener('data', function (d) { size += d.length; }); 33 | 34 | m.addListener('end', function () { 35 | assert.equal(body.length, size); 36 | m.acknowledge(); 37 | }); 38 | }) 39 | }); 40 | }); 41 | }); 42 | }); 43 | 44 | 45 | process.addListener('exit', function () { 46 | assert.equal(1, recvCount); 47 | }); 48 | -------------------------------------------------------------------------------- /test/test-type-and-headers.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | // test-type-and-headers.js 3 | var recvCount = 0; 4 | var body = "the devil is in the type, and also in the headers"; 5 | 6 | connection.addListener('ready', function () { 7 | puts("connected to " + connection.serverProperties.product); 8 | 9 | connection.exchange('node-th-fanout', {type: 'fanout'}, function(exchange) { 10 | connection.queue('node-th-queue', function(q) { 11 | q.bind(exchange, "*"); 12 | q.on('queueBindOk', function() { 13 | q.on('basicConsumeOk', function () { 14 | puts("publishing message"); 15 | exchange.publish("message.text", body, 16 | { 17 | type: 'typeProperty', 18 | headers: 19 | { 20 | stringHeader: "Hello, World", 21 | bigIntP : 0xffffffff + 1, 22 | bigIntN : -0xffffffff - 1, 23 | intP : 1234, 24 | intN : (-1234), 25 | floatP: 1.1234, 26 | floatN: -1.1234, 27 | boolT: true, 28 | boolF: false, 29 | date: new Date(2001, 00, 01), 30 | obj: { hello: "world" }, 31 | buf: new Buffer([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]) 32 | } 33 | }); 34 | }); 35 | q.subscribeRaw(function (m) { 36 | puts("--- Message (" + m.deliveryTag + ", '" + m.routingKey + "') ---"); 37 | puts("--- type: " + m.type); 38 | puts("--- headers: " + JSON.stringify(m.headers)); 39 | puts(""); 40 | recvCount++; 41 | assert.equal('typeProperty', m.type); 42 | assert.equal('Hello, World', m.headers.stringHeader); 43 | assert.equal(0xffffffff + 1, m.headers.bigIntP); 44 | assert.equal(-0xffffffff - 1, m.headers.bigIntN); 45 | assert.equal(1234, m.headers.intP); 46 | assert.equal(-1234, m.headers.intN); 47 | assert.equal(1.1234, m.headers.floatP); 48 | assert.equal(-1.1234, m.headers.floatN); 49 | assert.equal(true, m.headers.boolT); 50 | assert.equal(false, m.headers.boolF); 51 | assert.equal(new Date(2001,00,01).valueOf(), m.headers.date.valueOf()); 52 | assert.equal(JSON.stringify({hello:"world"}), JSON.stringify(m.headers.obj)); 53 | assert.equal(new Buffer([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]).toString(), m.headers.buf.toString()); 54 | 55 | }); 56 | setTimeout(function () { 57 | // wait one second to receive the message, then quit 58 | connection.end(); 59 | }, 1000); 60 | }); 61 | }); 62 | 63 | }); 64 | }); 65 | 66 | 67 | process.addListener('exit', function () { 68 | assert.equal(1, recvCount); 69 | }); 70 | -------------------------------------------------------------------------------- /test/test-unbind-unknown-exchange.js: -------------------------------------------------------------------------------- 1 | var conn = require('./harness').createConnection(); 2 | 3 | var caughtError = false; 4 | 5 | var later = function(fun) { 6 | setTimeout(fun, 500); 7 | }; 8 | conn.once('ready', function () { 9 | puts("connected to " + conn.serverProperties.product); 10 | 11 | var q = conn.queue('node-simple-queue'); 12 | 13 | try { 14 | q.unbind('unknown-exchange', 'routing-key'); 15 | } catch(e) { 16 | caughtError = true; 17 | } 18 | 19 | later(function() { 20 | conn.end(); 21 | process.exit(); 22 | }); 23 | }); 24 | 25 | process.addListener('exit', function () { 26 | assert.equal(caughtError, false); 27 | }); 28 | -------------------------------------------------------------------------------- /test/test-unbind.js: -------------------------------------------------------------------------------- 1 | var conn = require('./harness').createConnection(); 2 | 3 | var recvCount = 0; 4 | var body = "Some say the devil is dead"; 5 | 6 | var later = function(fun) { 7 | setTimeout(fun, 500); 8 | }; 9 | conn.once('ready', function () { 10 | puts("connected to " + conn.serverProperties.product); 11 | 12 | var q = conn.queue('node-simple-queue'); 13 | var exchange = conn.exchange('node-simple-fanout', {type: 'fanout'}); 14 | 15 | exchange.once('open', function(){ 16 | q.bind(exchange, ""); 17 | 18 | q.subscribe(function(message){ 19 | recvCount+=1; 20 | }); 21 | 22 | exchange.publish('', body); 23 | 24 | q.unbind(exchange, ""); 25 | later(function(){ 26 | // This will emit a NOT_FOUND error b/c we're no longer bound to the exchange 27 | var thrown = false; 28 | exchange.addListener('error', function(e){ 29 | thrown = true; 30 | assert.equal(e.code, 404); 31 | }); 32 | exchange.publish('', body); 33 | // Wait a bit before ending so we can receive the message if it does come (it shouldn't). 34 | later(function() { 35 | assert.ok(thrown); 36 | conn.end(); 37 | process.exit(); 38 | }); 39 | }); 40 | }); 41 | }); 42 | 43 | process.addListener('exit', function () { 44 | assert.equal(1, recvCount); 45 | }); 46 | -------------------------------------------------------------------------------- /test/test-unsubscribe.js: -------------------------------------------------------------------------------- 1 | require('./harness').run(); 2 | 3 | var queueName = 'node-unsubscribe-test'; 4 | var counter = 0; 5 | 6 | connection.on('ready', function() { 7 | var ex = connection.exchange(''); 8 | var q = connection.queue(queueName, {autoDelete: false}, function() { 9 | ex.publish(queueName, {"msg": 'Message1'}); 10 | var defr = q.subscribe(function() { counter += 1; }); 11 | defr.addCallback(function(ok) { 12 | // NB there is a race here, since the publish above and this 13 | // unsubscribe are sent over different channels. 14 | var defr2 = q.unsubscribe(ok.consumerTag); 15 | defr2.addCallback(function() { 16 | connection.publish(queueName, {"msg": 'Message2'}); 17 | // alas I cannot think of a way to synchronise on the queue 18 | setTimeout(function() { q.destroy().addCallback(function() { 19 | connection.end(); })}, 500); 20 | }); 21 | }); 22 | }); 23 | }); 24 | 25 | process.addListener('exit', function() { 26 | assert.equal(1, counter); 27 | }); 28 | -------------------------------------------------------------------------------- /test/test-volume.js: -------------------------------------------------------------------------------- 1 | var harness = require('./harness'); 2 | var proxy = require('./proxy'); 3 | 4 | var implOpts = { 5 | reconnect: true, 6 | reconnectBackoffStrategy: 'linear', 7 | reconnectBackoffTime: 1 // set a fast reconnect so we can catch all the messages 8 | }; 9 | 10 | // How many messages to send? 11 | var MESSAGE_COUNT = 1000; 12 | // How many messages can we afford to lose and still pass? 13 | var MESSAGE_GRACE = 10; 14 | // How many milliseconds between proxy kills? 15 | var INTERRUPT_INTERVAL = 300; 16 | // How many milliseconds between message sends? 17 | var MESSAGE_INTERVAL = 1; 18 | 19 | console.log("Starting..."); 20 | 21 | var exit = false; 22 | var done = function (error) { 23 | if (!exit) { 24 | exit = true; 25 | if (exchange) { 26 | exchange.destroy(); 27 | exchange = null; 28 | } 29 | if (queue) { 30 | queue.destroy(); 31 | queue = null; 32 | } 33 | if (error) { 34 | // Exit loudly. 35 | console.error('Error: "' + error + '", abandoning test cycle'); 36 | process.exit(1); 37 | } else { 38 | console.log('All done!'); 39 | // Exit gracefully. 40 | connection.setImplOptions({'reconnect': false}); 41 | connection.destroy(); 42 | process.exit(0); 43 | } 44 | } 45 | }; 46 | 47 | // make sure exchange & queue are destroyed 48 | process.on('exit', done); 49 | 50 | // After 10s, give up. Node < 0.10 is pretty slow, I've seen it go > 6s with console output on 51 | var timeout = setTimeout(function () { 52 | return done(new Error('Time expired without success')); 53 | }, 1000000); 54 | 55 | // Proxy our connection to RabbitMQ through a local port which we can interrupt at will. 56 | var proxyRoute = new proxy.route(9001, options.port, options.host); 57 | var interrupter = setInterval(function () { 58 | proxyRoute.interrupt(); 59 | }, INTERRUPT_INTERVAL); 60 | options.host = '127.0.0.1'; 61 | options.port = 9001; 62 | // Connect to the proxy 63 | var connection = harness.createConnection(options, implOpts); 64 | var exchange; 65 | var queue; 66 | var messageCount = 0; 67 | connection.once('ready', function () { 68 | // Create an exchange. 69 | exchange = connection.exchange('node-volume-exchange', {type: 'topic'}); 70 | // Now, create a queue. Bind it to an exchange, and pump a lot of 71 | // messages in to it. 72 | connection.queue('node-volume-queue', {autoDelete: false, confirm: false}, function (q) { 73 | queue = q; 74 | queue.on('queueBindOk', function () { 75 | queue.once('basicConsumeOk', function () { 76 | var counter = 0; 77 | var interval = setInterval(function () { 78 | counter += 1; 79 | exchange.publish('node-volume', 'this is message ' + counter, {}, function(){ 80 | console.log('confirmed msg', counter); 81 | }.bind(null, counter)); 82 | console.log('Message ' + counter + ' published'); 83 | if (counter === MESSAGE_COUNT) { 84 | clearInterval(interval); 85 | interval = null; 86 | } 87 | }, MESSAGE_INTERVAL); 88 | }); 89 | }); 90 | queue.bind(exchange, '#'); 91 | // We could use acks to lose even fewer messages but since we're not 92 | // using publisher confirms, we'll likely lose a few from the other 93 | // side, so let's not bother. 94 | // queue.subscribe({'ack': true}, function (message) { 95 | // ... 96 | // queue.shift(); 97 | // }); 98 | queue.subscribe(function (message) { 99 | messageCount += 1; 100 | console.log('Message received (' + messageCount + '): ' + message.data); 101 | if (messageCount === MESSAGE_COUNT) { 102 | return done(); 103 | } else if (messageCount + MESSAGE_GRACE >= MESSAGE_COUNT) { 104 | setTimeout(done, 500); 105 | } 106 | }); 107 | }); 108 | }); 109 | 110 | connection.on('ready', function() { 111 | // Just an FYI message 112 | console.log('Connection ready'); 113 | }); 114 | connection.on('error', function (error) { 115 | // Just an FYI message 116 | console.log('Connection error: '); 117 | console.dir(error); 118 | }); 119 | connection.on('close', function (hadError) { 120 | // Just an FYI message 121 | console.log('Connection close' + (hadError ? ' because of error' : '')); 122 | }); 123 | 124 | var waitForExitConditions = function () { 125 | if (!exit) { 126 | setTimeout(waitForExitConditions, 500); 127 | } else { 128 | // Kill everything which would keep this test from exiting. 129 | if (interrupter) { 130 | clearInterval(interrupter); 131 | interrupter = null; 132 | } 133 | if (proxyRoute) { 134 | proxyRoute.close(); 135 | proxyRoute = null; 136 | } 137 | if (timeout) { 138 | clearTimeout(timeout); 139 | timeout = null; 140 | } 141 | } 142 | }; 143 | waitForExitConditions(); 144 | 145 | -------------------------------------------------------------------------------- /test2/federation.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.3.3 2 | (function() { 3 | var amqp, amqp_connection, argv, assert, check_results, count, host, login, message_s, options, password, port, publish_message, test_exchange, test_exchange_name, test_message, test_queue_name, usage_text, vhost; 4 | 5 | usage_text = '\ 6 | Test federated multi-hop message receipt.\ 7 | \n\ 8 | \n* Probably you want to do this on a fresh rabbitmq install...\ 9 | \n\ 10 | \nTo enable this test:\ 11 | \n1. Create a virtual host named "reflector" and provide access\ 12 | \n2. Bring down the rabbit\ 13 | \n3. Enable the federation plugin\ 14 | \n4. Include this federation configuration as or in your rabbitmq.config file and bring up the rabbit:\ 15 | \n[\ 16 | \n {rabbitmq_federation, [ \ 17 | \n {exchanges, [\ 18 | \n [\ 19 | \n {exchange, "test.federation"},\ 20 | \n {virtual_host, "reflector"},\ 21 | \n {type, "topic"},\ 22 | \n {upstream_set, "root.set"}\ 23 | \n ],\ 24 | \n [\ 25 | \n {exchange, "test.federation"},\ 26 | \n {virtual_host, "/"},\ 27 | \n {type, "topic"},\ 28 | \n {upstream_set, "reflector.set"}\ 29 | \n ]\ 30 | \n ]},\ 31 | \n {upstream_sets, [\ 32 | \n {"reflector.set", [\ 33 | \n [\ 34 | \n {connection, "reflector.connection"},\ 35 | \n {max_hops, 2}\ 36 | \n ]\ 37 | \n ]},\ 38 | \n {"root.set", [\ 39 | \n [\ 40 | \n {connection, "root.connection"},\ 41 | \n {max_hops, 2}\ 42 | \n ]\ 43 | \n ]}\ 44 | \n ]},\ 45 | \n {connections, [\ 46 | \n {"reflector.connection", [ \ 47 | \n {host, "localhost"},\ 48 | \n {virtual_host, "reflector"},\ 49 | \n {username, "guest"},\ 50 | \n {password, "guest"}\ 51 | \n ]}, \ 52 | \n {"root.connection", [ \ 53 | \n {host, "localhost"},\ 54 | \n {virtual_host, "/"},\ 55 | \n {username, "guest"},\ 56 | \n {password, "guest"}\ 57 | \n ]}\ 58 | \n ]},\ 59 | \n {local_username, "guest"},\ 60 | \n {local_password, "guest"}\ 61 | \n ]}\ 62 | \n].\ 63 | '; 64 | 65 | argv = require('optimist').usage(usage_text).demand('host').describe('host', 'Host')["default"]('host', 'localhost').demand('port').describe('port', 'Port')["default"]('port', 5672).demand('vhost').describe('vhost', 'Virtual Host')["default"]('vhost', '/').demand('login').describe('login', 'Login')["default"]('login', 'guest').demand('password').describe('password', 'Password').argv; 66 | 67 | amqp = require('../amqp'); 68 | 69 | assert = require('assert'); 70 | 71 | message_s = ""; 72 | 73 | publish_message = function() { 74 | var message; 75 | message = { 76 | hello: 'world' 77 | }; 78 | message_s = JSON.stringify(message); 79 | test_exchange.publish('whatever', message); 80 | return console.log("test message published: " + (JSON.stringify(message)) + " to exchange: " + test_exchange_name); 81 | }; 82 | 83 | count = 0; 84 | 85 | test_message = function(message, headers, properties) { 86 | console.log("" + ("test message received: message: " + (JSON.stringify(message))) + ("\n headers: " + (JSON.stringify(headers, void 0, 4)))); 87 | assert.equal(message_s, JSON.stringify(message, "incorrect message content or content_type")); 88 | if (count === 1) { 89 | assert.equal('reflector', headers["x-received-from"][0]["virtual_host"], "missing or mismatched virtual host name"); 90 | } 91 | return count++; 92 | }; 93 | 94 | check_results = function() { 95 | console.log("" + count + " messages received"); 96 | assert.equal(2, count, "wrong number of messages received"); 97 | return process.exit(0); 98 | }; 99 | 100 | test_exchange = {}; 101 | 102 | test_queue_name = test_exchange_name = 'test.federation'; 103 | 104 | host = argv.host, port = argv.port, vhost = argv.vhost, login = argv.login, password = argv.password; 105 | 106 | options = { 107 | host: host, 108 | port: port, 109 | vhost: vhost, 110 | login: login, 111 | password: password 112 | }; 113 | 114 | amqp_connection = amqp.createConnection(options); 115 | 116 | amqp_connection.on('ready', function() { 117 | return amqp_connection.exchange(test_exchange_name, { 118 | passive: true 119 | }, function(exchange) { 120 | test_exchange = exchange; 121 | return amqp_connection.queue(test_queue_name, function(queue) { 122 | return queue.bind(test_exchange, '#', function() { 123 | queue.subscribe(function(message, headers, properties) { 124 | return test_message(message, headers, properties); 125 | }); 126 | setTimeout(check_results, 1000); 127 | return publish_message(); 128 | }); 129 | }); 130 | }); 131 | }); 132 | 133 | }).call(this); 134 | -------------------------------------------------------------------------------- /test2/firehose.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.3.3 2 | (function() { 3 | var amqp, argv, assert, check_results, count, default_exchange, exec, firehose_message, firehose_queue_name, message_s, publish_message, test_message, test_queue_name; 4 | 5 | argv = require('optimist').usage('' + 'Test tracing/firehose message receipt.' + '\nA firehose message is generated both when a message is published and when it is consumed.' + '\nNOTE: this test will attempt to enable and then disable tracing for the vhost using rabbitmqctl.').demand('host').describe('host', 'Host')["default"]('host', 'localhost').demand('port').describe('port', 'Port')["default"]('port', 5672).demand('vhost').describe('vhost', 'Virtual Host')["default"]('vhost', '/').demand('login').describe('login', 'Login')["default"]('login', 'guest').demand('password').describe('password', 'Password').argv; 6 | 7 | amqp = require('../amqp'); 8 | 9 | exec = require('child_process').exec; 10 | 11 | assert = require('assert'); 12 | 13 | message_s = ""; 14 | 15 | publish_message = function() { 16 | var message; 17 | message = { 18 | hello: 'world' 19 | }; 20 | message_s = JSON.stringify(message); 21 | default_exchange.publish(test_queue_name, message); 22 | return console.log("test message published: " + (JSON.stringify(message)) + " to queue: " + test_queue_name); 23 | }; 24 | 25 | count = 0; 26 | 27 | test_message = function(message, headers, properties) { 28 | console.log("" + ("test message received: routing_key: '" + properties.routingKey + "';") + (" message: " + (JSON.stringify(message)))); 29 | assert.equal(message_s, JSON.stringify(message, "incorrect message content or content_type")); 30 | return count++; 31 | }; 32 | 33 | firehose_message = function(message, headers, properties) { 34 | console.log("" + ("firehose message received: routing_key: '" + properties.routingKey + "';") + (" headers.routing_keys[0]: '" + headers.routing_keys[0] + "';") + (" message: " + (JSON.stringify(message)))); 35 | assert.equal(message_s, JSON.stringify(message, "incorrect message content or content_type")); 36 | assert.equal(test_queue_name, headers.routing_keys[0], "routing key is not queue name in firehose message headers"); 37 | return count++; 38 | }; 39 | 40 | check_results = function() { 41 | console.log("" + count + " messages received"); 42 | assert.equal(3, count, "wrong number of messages received"); 43 | return exec("rabbitmqctl trace_off -p " + argv.vhost, function(error, stdout, stderr) { 44 | if (error != null) { 45 | console.log(error); 46 | process.exit(2); 47 | } 48 | return process.exit(0); 49 | }); 50 | }; 51 | 52 | default_exchange = {}; 53 | 54 | test_queue_name = 'test.firehose.test'; 55 | 56 | firehose_queue_name = 'test.firehose.firehose'; 57 | 58 | exec("rabbitmqctl trace_on -p " + argv.vhost, function(error, stdout, stderr) { 59 | var amqp_connection, host, login, options, password, port, vhost; 60 | if (error != null) { 61 | console.log(error); 62 | process.exit(1); 63 | } 64 | host = argv.host, port = argv.port, vhost = argv.vhost, login = argv.login, password = argv.password; 65 | options = { 66 | host: host, 67 | port: port, 68 | vhost: vhost, 69 | login: login, 70 | password: password 71 | }; 72 | amqp_connection = amqp.createConnection(options); 73 | return amqp_connection.on('ready', function() { 74 | default_exchange = amqp_connection.exchange(); 75 | return amqp_connection.queue(test_queue_name, function(queue) { 76 | queue.subscribe(function(message, headers, properties) { 77 | return test_message(message, headers, properties); 78 | }); 79 | return amqp_connection.queue(firehose_queue_name, function(queue) { 80 | return queue.bind("amq.rabbitmq.trace", "#", function() { 81 | queue.subscribe(function(message, headers, properties) { 82 | return firehose_message(message, headers, properties); 83 | }); 84 | setTimeout(check_results, 1000); 85 | return publish_message(); 86 | }); 87 | }); 88 | }); 89 | }); 90 | }); 91 | 92 | }).call(this); 93 | -------------------------------------------------------------------------------- /test2src/Cakefile: -------------------------------------------------------------------------------- 1 | {exec} = require 'child_process' 2 | task 'build', 'Build project from *.coffee to ../test2/*.js', () -> 3 | exec 'coffee --compile --output ../test2 ./', (err, stdout, stderr) -> 4 | throw err if err 5 | console.log stdout + stderr -------------------------------------------------------------------------------- /test2src/federation.coffee: -------------------------------------------------------------------------------- 1 | usage_text = 2 | ' 3 | Test federated multi-hop message receipt. 4 | \n 5 | \n* Probably you want to do this on a fresh rabbitmq install... 6 | \n 7 | \nTo enable this test: 8 | \n1. Create a virtual host named "reflector" and provide access 9 | \n2. Bring down the rabbit 10 | \n3. Enable the federation plugin 11 | \n4. Include this federation configuration as or in your rabbitmq.config file and bring up the rabbit: 12 | \n[ 13 | \n {rabbitmq_federation, [ 14 | \n {exchanges, [ 15 | \n [ 16 | \n {exchange, "test.federation"}, 17 | \n {virtual_host, "reflector"}, 18 | \n {type, "topic"}, 19 | \n {upstream_set, "root.set"} 20 | \n ], 21 | \n [ 22 | \n {exchange, "test.federation"}, 23 | \n {virtual_host, "/"}, 24 | \n {type, "topic"}, 25 | \n {upstream_set, "reflector.set"} 26 | \n ] 27 | \n ]}, 28 | \n {upstream_sets, [ 29 | \n {"reflector.set", [ 30 | \n [ 31 | \n {connection, "reflector.connection"}, 32 | \n {max_hops, 2} 33 | \n ] 34 | \n ]}, 35 | \n {"root.set", [ 36 | \n [ 37 | \n {connection, "root.connection"}, 38 | \n {max_hops, 2} 39 | \n ] 40 | \n ]} 41 | \n ]}, 42 | \n {connections, [ 43 | \n {"reflector.connection", [ 44 | \n {host, "localhost"}, 45 | \n {virtual_host, "reflector"}, 46 | \n {username, "guest"}, 47 | \n {password, "guest"} 48 | \n ]}, 49 | \n {"root.connection", [ 50 | \n {host, "localhost"}, 51 | \n {virtual_host, "/"}, 52 | \n {username, "guest"}, 53 | \n {password, "guest"} 54 | \n ]} 55 | \n ]}, 56 | \n {local_username, "guest"}, 57 | \n {local_password, "guest"} 58 | \n ]} 59 | \n]. 60 | ' 61 | argv = require('optimist') 62 | .usage(usage_text) 63 | .demand('host').describe('host', 'Host').default('host', 'localhost') 64 | .demand('port').describe('port', 'Port').default('port', 5672) 65 | .demand('vhost').describe('vhost', 'Virtual Host').default('vhost', '/') 66 | .demand('login').describe('login', 'Login').default('login', 'guest') 67 | .demand('password').describe('password', 'Password') 68 | .argv 69 | 70 | amqp = require '../amqp' 71 | assert = require 'assert' 72 | 73 | message_s = "" 74 | publish_message = -> 75 | message = hello:'world' 76 | message_s = JSON.stringify message 77 | test_exchange.publish 'whatever', message 78 | console.log "test message published: #{JSON.stringify message} to exchange: #{test_exchange_name}" 79 | 80 | count = 0 81 | test_message = (message, headers, properties) -> 82 | console.log ""+ 83 | "test message received: message: #{JSON.stringify message}"+ 84 | "\n headers: #{JSON.stringify headers, undefined, 4}" 85 | assert.equal message_s, JSON.stringify message, "incorrect message content or content_type" 86 | assert.equal 'reflector', headers["x-received-from"][0]["virtual_host"], "missing or mismatched virtual host name" if count is 1 87 | count++ 88 | 89 | check_results = -> 90 | console.log "#{count} messages received" 91 | assert.equal 2, count, "wrong number of messages received" 92 | process.exit 0 93 | 94 | # initialize 95 | 96 | test_exchange = {} 97 | test_queue_name = test_exchange_name = 'test.federation' 98 | {host, port, vhost, login, password} = argv 99 | 100 | options = 101 | host:host 102 | port:port 103 | vhost:vhost 104 | login:login 105 | password:password 106 | 107 | amqp_connection = amqp.createConnection options 108 | 109 | amqp_connection.on 'ready', () -> 110 | amqp_connection.exchange test_exchange_name, passive:true, (exchange) -> 111 | test_exchange = exchange 112 | 113 | amqp_connection.queue test_queue_name, (queue) -> 114 | queue.bind test_exchange, '#', -> 115 | queue.subscribe (message, headers, properties) -> 116 | test_message message, headers, properties 117 | 118 | setTimeout check_results, 1000 119 | publish_message() 120 | -------------------------------------------------------------------------------- /test2src/firehose.coffee: -------------------------------------------------------------------------------- 1 | argv = require('optimist') 2 | .usage(''+ 3 | 'Test tracing/firehose message receipt.'+ 4 | '\nA firehose message is generated both when a message is published and when it is consumed.'+ 5 | '\nNOTE: this test will attempt to enable and then disable tracing for the vhost using rabbitmqctl.' 6 | ) 7 | .demand('host').describe('host', 'Host').default('host', 'localhost') 8 | .demand('port').describe('port', 'Port').default('port', 5672) 9 | .demand('vhost').describe('vhost', 'Virtual Host').default('vhost', '/') 10 | .demand('login').describe('login', 'Login').default('login', 'guest') 11 | .demand('password').describe('password', 'Password') 12 | .argv 13 | 14 | amqp = require '../amqp' 15 | {exec} = require 'child_process' 16 | assert = require 'assert' 17 | 18 | message_s = "" 19 | publish_message = -> 20 | message = hello:'world' 21 | message_s = JSON.stringify message 22 | default_exchange.publish test_queue_name, message 23 | console.log "test message published: #{JSON.stringify message} to queue: #{test_queue_name}" 24 | 25 | count = 0 26 | test_message = (message, headers, properties) -> 27 | console.log ""+ 28 | "test message received: routing_key: '#{properties.routingKey}';"+ 29 | " message: #{JSON.stringify message}" 30 | assert.equal message_s, JSON.stringify message, "incorrect message content or content_type" 31 | count++ 32 | 33 | firehose_message = (message, headers, properties) -> 34 | console.log ""+ 35 | "firehose message received: routing_key: '#{properties.routingKey}';"+ 36 | " headers.routing_keys[0]: '#{headers.routing_keys[0]}';"+ 37 | " message: #{JSON.stringify message}" 38 | assert.equal message_s, JSON.stringify message, "incorrect message content or content_type" 39 | assert.equal test_queue_name, headers.routing_keys[0], "routing key is not queue name in firehose message headers" 40 | count++ 41 | 42 | check_results = -> 43 | console.log "#{count} messages received" 44 | assert.equal 3, count, "wrong number of messages received" 45 | 46 | exec "rabbitmqctl trace_off -p #{argv.vhost}", (error, stdout, stderr) -> 47 | if error? 48 | console.log error 49 | process.exit 2 50 | 51 | process.exit 0 52 | 53 | # initialize 54 | 55 | default_exchange = {} 56 | test_queue_name = 'test.firehose.test' 57 | firehose_queue_name = 'test.firehose.firehose' 58 | 59 | exec "rabbitmqctl trace_on -p #{argv.vhost}", (error, stdout, stderr) -> 60 | if error? 61 | console.log error 62 | process.exit 1 63 | 64 | {host, port, vhost, login, password} = argv 65 | 66 | options = 67 | host:host 68 | port:port 69 | vhost:vhost 70 | login:login 71 | password:password 72 | 73 | amqp_connection = amqp.createConnection options 74 | 75 | amqp_connection.on 'ready', () -> 76 | default_exchange = amqp_connection.exchange() 77 | 78 | amqp_connection.queue test_queue_name, (queue) -> 79 | queue.subscribe (message, headers, properties) -> 80 | test_message message, headers, properties 81 | 82 | amqp_connection.queue firehose_queue_name, (queue) -> 83 | queue.bind "amq.rabbitmq.trace", "#", -> 84 | queue.subscribe (message, headers, properties) -> 85 | firehose_message message, headers, properties 86 | 87 | setTimeout check_results, 1000 88 | publish_message() 89 | -------------------------------------------------------------------------------- /util/delete-exchange.js: -------------------------------------------------------------------------------- 1 | amqp = require('../amqp'); 2 | 3 | var name = process.argv[2]; 4 | console.log("exchange: " + name); 5 | 6 | var creds = 7 | { host: process.env['AMQP_HOST'] || 'localhost' 8 | , login: process.env['AMQP_LOGIN'] || 'guest' 9 | , password: process.env['AMQP_PASSWORD'] || 'guest' 10 | , vhost: process.env['AMQP_VHOST'] || '/' 11 | }; 12 | 13 | connection = amqp.createConnection(creds); 14 | 15 | connection.addListener('error', function (e) { 16 | throw e; 17 | }); 18 | 19 | connection.addListener('ready', function () { 20 | console.log("Connected"); 21 | var e = connection.exchange(name); 22 | e.destroy().addCallback(function () { 23 | console.log('exchange destroyed.'); 24 | connection.close(); 25 | }); 26 | }); 27 | 28 | -------------------------------------------------------------------------------- /util/delete-queue.js: -------------------------------------------------------------------------------- 1 | amqp = require('../amqp'); 2 | 3 | var name = process.argv[2]; 4 | console.log("exchange: " + name); 5 | 6 | var creds = 7 | { host: process.env['AMQP_HOST'] || 'localhost' 8 | , login: process.env['AMQP_LOGIN'] || 'guest' 9 | , password: process.env['AMQP_PASSWORD'] || 'guest' 10 | , vhost: process.env['AMQP_VHOST'] || '/' 11 | }; 12 | 13 | connection = amqp.createConnection(creds); 14 | 15 | connection.addListener('error', function (e) { 16 | throw e; 17 | }); 18 | 19 | connection.addListener('ready', function () { 20 | console.log("Connected"); 21 | var q = connection.queue(name); 22 | q.destroy().addCallback(function () { 23 | console.log('queue destroyed.'); 24 | connection.close(); 25 | }); 26 | }); 27 | 28 | --------------------------------------------------------------------------------