├── .gitignore ├── Makefile ├── README.markdown ├── include └── rabbit_webhooks.hrl ├── priv ├── node_modules │ └── amqp │ │ ├── .gitignore │ │ ├── 0001-Fix-header-parsing.patch │ │ ├── LICENSE-MIT │ │ ├── Makefile │ │ ├── README.md │ │ ├── amqp-0.8.xml │ │ ├── amqp-definitions-0-8.js │ │ ├── amqp.js │ │ ├── index.js │ │ ├── out │ │ ├── package.json │ │ ├── promise.js │ │ ├── qparser.rb │ │ ├── test.js │ │ ├── test │ │ ├── harness.js │ │ ├── test-auto-delete-queue.js │ │ ├── test-default-exchange.js │ │ ├── test-headers.js │ │ ├── test-json.js │ │ ├── test-shift.js │ │ ├── test-simple.js │ │ └── test-type-and-headers.js │ │ ├── util │ │ ├── delete-exchange.js │ │ └── delete-queue.js │ │ └── x.js ├── publish.mq ├── rabbitmq.config ├── send_test.js └── test.yml ├── rebar ├── rebar.config ├── scripts └── gen_config └── src ├── rabbit_webhooks.app.src ├── rabbit_webhooks.erl ├── rabbit_webhooks_app.erl └── rabbit_webhooks_sup.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | dist 4 | ebin 5 | deps 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE=rabbitmq-webhooks 2 | DIST_DIR=dist 3 | EBIN_DIR=ebin 4 | INCLUDE_DIRS=include 5 | DEPS_DIR=deps 6 | DEPS ?= amqp_client rabbit_common lhttpc 7 | DEPS_EZ=$(foreach DEP, $(DEPS), $(DEPS_DIR)/$(DEP).ez) 8 | RABBITMQ_HOME ?= . 9 | 10 | all: compile 11 | 12 | clean: 13 | rm -rf $(DIST_DIR) 14 | rm -rf $(EBIN_DIR) 15 | 16 | distclean: clean 17 | rm -rf $(DEPS_DIR) 18 | 19 | package: compile $(DEPS_EZ) 20 | rm -f $(DIST_DIR)/$(PACKAGE).ez 21 | mkdir -p $(DIST_DIR)/$(PACKAGE) 22 | cp -r $(EBIN_DIR) $(DIST_DIR)/$(PACKAGE) 23 | $(foreach EXTRA_DIR, $(INCLUDE_DIRS), cp -r $(EXTRA_DIR) $(DIST_DIR)/$(PACKAGE);) 24 | (cd $(DIST_DIR); zip -r $(PACKAGE).ez $(PACKAGE)) 25 | 26 | install: package 27 | $(foreach DEP, $(DEPS_EZ), cp $(DEP) $(RABBITMQ_HOME)/plugins;) 28 | cp $(DIST_DIR)/$(PACKAGE).ez $(RABBITMQ_HOME)/plugins 29 | 30 | $(DEPS_DIR): 31 | ./rebar get-deps 32 | 33 | $(DEPS_EZ): 34 | cd $(DEPS_DIR); $(foreach DEP, $(DEPS), zip -r $(DEP).ez $(DEP);) 35 | 36 | compile: $(DEPS_DIR) 37 | ./rebar compile -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ## RabbitMQ Webhooks Plugin 2 | 3 | This plugin provides a "webhook" functionality to a RabbitMQ broker. 4 | Any message processed by this plugin will be forwarded to the URL 5 | you configure, using the method you give it. 6 | 7 | Tested against RabbitMQ versions up to 2.8.1. 8 | 9 | ### Changes 10 | 11 | * 0.15 - Re-built the .tar.gz file to makes sure it included the latest version of plugin code 12 | * 0.14 - Lots better error handling and a Ruby script for generating config files 13 | * 0.13 - Updated for use with the new plugin system in RabbitMQ 2.7 14 | * 0.12 - Updated for use with RabbitMQ 2.3.0, now uses rebar for build 15 | * 0.11 - Updated for use with RabbitMQ 2.2.0 16 | * 0.9 - Incorporated patch from @cameronharris for OTP R13 compatibility, Makefile tweak 17 | * 0.8 - Added error handling for request so bad URLs don't crash broker, fix for no message headers 18 | * 0.7 - Added send window functionality for sending webhook requests only during specified time windows 19 | * 0.6 - Added max_send config param for limiting how many outgoing HTTP requests happen 20 | * 0.5 - Use RabbitMQ's worker_pool for sending requests to handle massive dumps of messages 21 | * 0.4 - Accept more than just 200 status code for CouchDB 22 | * 0.3 - Asynchronous HTTP send, URL and method overrideable per-message. 23 | * 0.2 - URLs can be patterns and headers that start with "X-" get passed to REST URL. 24 | * 0.1 - Synchronous HTTP send, no URL patterns. Rough draft. 25 | 26 | 27 | ### Install from Zip 28 | 29 | Download the .tar.gz file from from the downloads section: 30 | 31 | [http://github.com/jbrisbin/rabbitmq-webhooks/downloads](http://github.com/jbrisbin/rabbitmq-webhooks/downloads) 32 | 33 | cd $RABBITMQ_HOME 34 | mkdir plugins 35 | cd plugins 36 | tar -zxvf ~/rabbit_webhooks-0.x.tar.gz 37 | 38 | You should now have two .ez files in your plugins directory: 39 | 40 | lhttpc.ez 41 | rabbit_webhooks.ez 42 | 43 | In 2.7, you'll have to enable the plugins to get them to work: 44 | 45 | rabbitmq-plugins enable rabbit_webhooks 46 | 47 | To configure your broker, download the `gen_config` script from the source tree and run it, pointing 48 | to a YAML file that contains your configuration (discussed below). 49 | 50 | Copy the output of that generation to your RabbitMQ server config file (should be some place like: 51 | `/etc/rabbitmq/rabbitmq.config`). 52 | 53 | Start your broker and you should see output similar to what's discussed in the "Installing" section. 54 | 55 | ### Install from Source 56 | 57 | The build process for the webhooks plugin has changed. It now uses rebar to build. 58 | 59 | git clone https://github.com/jbrisbin/rabbitmq-webhooks.git 60 | cd rabbitmq-webhooks 61 | make 62 | make package 63 | 64 | You can now install the three .ez files required: 65 | 66 | cp deps/amqp_client.ez $RABBITMQ_HOME/plugins 67 | cp deps/lhttpc.ez $RABBITMQ_HOME/plugins 68 | cp dist/rabbit_webhooks.ez $RABBITMQ_HOME/plugins 69 | 70 | When you start the broker, you should see (at the top): 71 | 72 | ... plugins activated: 73 | * lhttpc-1.2.5 74 | * rabbit_webhooks-0.14 75 | 76 | and when the server is started: 77 | 78 | Configuring Webhooks...done 79 | 80 | Logging is done to the server log file. 81 | 82 | ### What can I use this for? 83 | 84 | If you configure a webhook to bind to exchange "test" with routing key 85 | "#", any messages published with that exchange and routing key will be 86 | automatically sent to an HTTP URL based on pre-configured parameters, or 87 | by specifying overrides in the message properties and headers. 88 | 89 | This would allow you, for example, to drop JSON data into messages in an 90 | AMQP queue which get sent to a REST URL via POST (or PUT or DELETE, etc...). 91 | 92 | Clients with no access to a CouchDB server could send batches of updates 93 | through RabbitMQ. The webhooks plugin then HTTP POSTs those messages to the 94 | CouchDB server. 95 | 96 | If the message is successfully POST/PUT/DELETE'd to the URL, it is ACK'd 97 | from the queue. If there was an error, the message is NOT ACK'd and stays in 98 | the queue for possible later delivery. There's probably a better way to handle 99 | this. I'm open for suggestions! :) 100 | 101 | ### Example Configuration 102 | 103 | As of v0.14, there is a Ruby script (`scripts/gen_config`) you can use to translate 104 | a YAML config file into the more complex and finicky Erlang config file. It will generate 105 | the correct atoms for you to include in your system `rabbitmq.config` file. 106 | 107 | An example YAML file will look like this (with the bare minimum left uncommented, 108 | everything commented out is optional and the values shown are the defaults): 109 | 110 | --- # Test YAML file for driving config file generation. 111 | 112 | # Broker configuration 113 | username : guest 114 | virtual_host : / 115 | 116 | # Use a YAML alias to reference this one exchange for all configs. 117 | exchange: &webhook_exchange 118 | name : webhooks 119 | # type : topic 120 | # auto_delete : true 121 | # durable : false 122 | 123 | # Webhooks configurations 124 | webhooks: 125 | - 126 | name : webhook1 # Name should be unique within the config file 127 | url : http://localhost:8000/rest 128 | # method : post # get | post | put | delete 129 | exchange : *webhook_exchange 130 | queue: 131 | name : webhook1 # Best to have the queue name match the config name 132 | # auto_delete : true 133 | # routing_key : "#" 134 | # max_send: 135 | # frequency : 5 136 | # per : second # second | minute | hour | day | week 137 | # send_if: 138 | # between: 139 | # start_hour : 8 # 24-hour time 140 | # start_min : 0 141 | # end_hour : 17 # 24-hour time 142 | # end_min : 0 143 | 144 | If you want to configure it manually, an example Erlang config file is included in `priv/`: 145 | 146 | [ 147 | {rabbit_webhooks, [ 148 | {username, <<"guest">>}, 149 | {virtual_host, <<"/">>}, 150 | {webhooks, [ 151 | {test_one, [ 152 | {url, "http://localhost:8000/rest"}, 153 | {method, post}, 154 | {exchange, [ 155 | {exchange, <<"webhooks.test">>}, 156 | {type, <<"topic">>}, 157 | {auto_delete, true}, 158 | {durable, false} 159 | ]}, 160 | {queue, [ 161 | {queue, <<"webhooks.test.q">>}, 162 | {auto_delete, true} 163 | ]}, 164 | {routing_key, <<"#">>}, 165 | {max_send, {5, second}}, 166 | {send_if, [{between, {13, 24}, {13, 25}}]} 167 | ]} 168 | ]} 169 | ]} 170 | ]. 171 | 172 | ### TODO 173 | 174 | Lots and lots still to do: 175 | 176 | * Make message sending more robust, including SSL support, authentication, 177 | and several other "would be nice to have"s. 178 | * Expose various statii to the RabbitMQ console. 179 | 180 | ### License 181 | 182 | Licensed under the Mozilla Public License: 183 | 184 | [http://www.rabbitmq.com/mpl.html](http://www.rabbitmq.com/mpl.html) -------------------------------------------------------------------------------- /include/rabbit_webhooks.hrl: -------------------------------------------------------------------------------- 1 | % -*- tab-width: 2 -*- 2 | -include_lib("amqp_client/include/amqp_client.hrl"). 3 | 4 | -record(header, {name, 5 | value}). 6 | -record(webhook, {exchange=#'exchange.declare'{}, 7 | queue=#'queue.declare'{}, 8 | routing_key, 9 | url, 10 | method, 11 | headers=[], 12 | max_send=infinity, 13 | send_if=always}). 14 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/.gitignore: -------------------------------------------------------------------------------- 1 | lib/amqp/constants-generated.js 2 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/0001-Fix-header-parsing.patch: -------------------------------------------------------------------------------- 1 | From cd580c3b5a97cc0948e2716577c336648e961ea8 Mon Sep 17 00:00:00 2001 2 | From: Jaakko Manninen 3 | Date: Thu, 1 Jul 2010 10:47:55 -0700 4 | Subject: [PATCH] Fix header parsing 5 | 6 | --- 7 | amqp.js | 2 +- 8 | test/test-headers.js | 36 +++++++++++++++++++++++++++++++++++ 9 | test/test-type-and-headers.js | 42 +++++++++++++++++++++++++++++++++++++++++ 10 | 3 files changed, 79 insertions(+), 1 deletions(-) 11 | create mode 100644 test/test-headers.js 12 | create mode 100644 test/test-type-and-headers.js 13 | 14 | diff --git a/amqp.js b/amqp.js 15 | index a4f3754..7360d19 100644 16 | --- a/amqp.js 17 | +++ b/amqp.js 18 | @@ -287,7 +287,7 @@ function parseSignedInteger (buffer) { 19 | 20 | 21 | function parseTable (buffer) { 22 | - var length = parseInt(buffer, 4); 23 | + var length = buffer.read + parseInt(buffer, 4); 24 | var table = {}; 25 | while (buffer.read < length) { 26 | var field = parseShortString(buffer); 27 | diff --git a/test/test-headers.js b/test/test-headers.js 28 | new file mode 100644 29 | index 0000000..478432c 30 | --- /dev/null 31 | +++ b/test/test-headers.js 32 | @@ -0,0 +1,36 @@ 33 | +require('./harness'); 34 | + 35 | +var recvCount = 0; 36 | +var body = "the devil is in the headers"; 37 | + 38 | +connection.addListener('ready', function () { 39 | + puts("connected to " + connection.serverProperties.product); 40 | + 41 | + var exchange = connection.exchange('node-simple-fanout', {type: 'fanout'}); 42 | + 43 | + var q = connection.queue('node-simple-queue'); 44 | + 45 | + q.bind(exchange, "*") 46 | + 47 | + q.subscribeRaw(function (m) { 48 | + puts("--- Message (" + m.deliveryTag + ", '" + m.routingKey + "') ---"); 49 | + puts("--- headers: " + JSON.stringify(m.headers)); 50 | + 51 | + recvCount++; 52 | + 53 | + assert.equal('bar', m.headers['foo']); 54 | + }) 55 | + .addCallback(function () { 56 | + puts("publishing message"); 57 | + exchange.publish("message.text", body, { headers: { foo: 'bar' } }); 58 | + 59 | + setTimeout(function () { 60 | + // wait one second to receive the message, then quit 61 | + connection.end(); 62 | + }, 1000); 63 | + }); 64 | +}); 65 | + 66 | +process.addListener('exit', function () { 67 | + assert.equal(1, recvCount); 68 | +}); 69 | diff --git a/test/test-type-and-headers.js b/test/test-type-and-headers.js 70 | new file mode 100644 71 | index 0000000..ea1da03 72 | --- /dev/null 73 | +++ b/test/test-type-and-headers.js 74 | @@ -0,0 +1,42 @@ 75 | +require('./harness'); 76 | +// test-type-and-headers.js 77 | +var recvCount = 0; 78 | +var body = "the devil is in the type, and also in the headers"; 79 | + 80 | +connection.addListener('ready', function () { 81 | + puts("connected to " + connection.serverProperties.product); 82 | + 83 | + var exchange = connection.exchange('node-simple-fanout', {type: 'fanout'}); 84 | + 85 | + var q = connection.queue('node-simple-queue'); 86 | + 87 | + q.bind(exchange, "*") 88 | + 89 | + q.subscribeRaw(function (m) { 90 | + puts("--- Message (" + m.deliveryTag + ", '" + m.routingKey + "') ---"); 91 | + puts("--- type: " + m.type); 92 | + puts("--- headers: " + JSON.stringify(m.headers)); 93 | + 94 | + recvCount++; 95 | + 96 | + assert.equal('typeProperty', m.type); 97 | + assert.equal('fooHeader', m.headers['foo']); 98 | + }) 99 | + .addCallback(function () { 100 | + puts("publishing message"); 101 | + exchange.publish("message.text", body, { 102 | + headers: { foo: 'fooHeader' }, 103 | + type: 'typeProperty', 104 | + }); 105 | + 106 | + setTimeout(function () { 107 | + // wait one second to receive the message, then quit 108 | + connection.end(); 109 | + }, 1000); 110 | + }); 111 | +}); 112 | + 113 | + 114 | +process.addListener('exit', function () { 115 | + assert.equal(1, recvCount); 116 | +}); 117 | -- 118 | 1.7.1 119 | 120 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2010 Xavier Shay and Joyent, Inc. 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 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/Makefile: -------------------------------------------------------------------------------- 1 | NODE=`which node` 2 | SERVER="localhost:5672" 3 | THIS_DIR=$(shell pwd) 4 | 5 | default: test 6 | 7 | FAIL=echo FAIL 8 | PASS=echo PASS 9 | 10 | test: 11 | @for i in test/test-*.js; do \ 12 | echo -n "$$i: "; \ 13 | $(NODE) $$i $(SERVER) > /dev/null && $(PASS) || $(FAIL); \ 14 | done 15 | 16 | .PHONY: test 17 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/README.md: -------------------------------------------------------------------------------- 1 | # node-amqp 2 | 3 | IMPORTANT: This module only works with node v0.1.90 and later. 4 | 5 | This is a client for RabbitMQ (and maybe other servers?). It partially 6 | implements the 0.8 version of the AMQP protocol. 7 | 8 | 9 | ## Synopsis 10 | 11 | An example of connecting to a server and listening on a queue. 12 | 13 | var sys = require('sys'); 14 | var amqp = require('./amqp'); 15 | 16 | var connection = amqp.createConnection({ host: 'dev.rabbitmq.com' }); 17 | 18 | // Wait for connection to become established. 19 | connection.addListener('ready', function () { 20 | // Create a queue and bind to all messages. 21 | // Use the default 'amq.topic' exchange 22 | var q = connection.queue('my-queue'); 23 | // Catch all messages 24 | q.bind('#'); 25 | 26 | // Receive messages 27 | q.subscribe(function (message) { 28 | // Print messages to stdout 29 | sys.p(message); 30 | }); 31 | }); 32 | 33 | 34 | 35 | ## Connection 36 | 37 | `new amqp.Connection()` Instantiates a new connection. Use 38 | `connection.connect()` to connect to a server. 39 | 40 | `amqp.createConnection()` returns an instance of `amqp.Connection`, which is 41 | a subclass of `net.Stream`. All the event and methods which work on 42 | `net.Stream` can also be used on an `amqp.Connection` instace. (E.G. the 43 | events `'connected'` and ``'closed'`.) 44 | 45 | `amqp.createConnection()` takes an options object as its only parameter. 46 | The options object has the these defaults: 47 | 48 | { host: 'localhost' 49 | , port: 5672 50 | , login: 'guest' 51 | , password: 'guest' 52 | , vhost: '/' 53 | } 54 | 55 | After a connection is established the `'connect'` event is fired as it is 56 | with any `net.Connection` instance. AMQP requires a 7-way handshake which 57 | must be completed before any communication can begin. `net.Connection` does 58 | the handshake automatically and emits the `ready` event when the handshaking 59 | is complete. 60 | 61 | 62 | ### connection.publish(routingKey, body) 63 | 64 | Publishes a message to the default 'amq.topic' exchange. 65 | 66 | 67 | ### connection.end() 68 | 69 | `amqp.Connection` is derived from `net.Stream` and has all the same methods. 70 | So use `connection.end()` to terminate a connection gracefully. 71 | 72 | 73 | 74 | 75 | ## Queue 76 | 77 | Events: A queue will call the callback given to the `connection.queue()` 78 | method once it is declared. For example: 79 | 80 | var q = connection.queue('my-queue', function (messageCount, consumerCount) { 81 | puts('There are ' + messageCount + ' messages waiting in the queue.'); 82 | }); 83 | 84 | 85 | 86 | ### connection.queue(name, options, openCallback) 87 | 88 | Returns a reference to a queue. The options are 89 | 90 | - `passive`: boolean, default false. 91 | If set, the server will not create the queue. The client can use 92 | this to check whether a queue exists without modifying the server 93 | state. 94 | - `durable`: boolean, default false. 95 | Durable queues remain active when a server restarts. 96 | Non-durable queues (transient queues) are purged if/when a 97 | server restarts. Note that durable queues do not necessarily 98 | hold persistent messages, although it does not make sense to 99 | send persistent messages to a transient queue. 100 | - `exclusive`: boolean, default false. 101 | Exclusive queues may only be consumed from by the current connection. 102 | Setting the 'exclusive' flag always implies 'auto-delete'. 103 | - `autoDelete`: boolean, default true. 104 | If set, the queue is deleted when all consumers have finished 105 | using it. Last consumer can be cancelled either explicitly or because 106 | its channel is closed. If there was no consumer ever on the queue, it 107 | won't be deleted. 108 | 109 | 110 | 111 | ### queue.subscribe([options,] listener) 112 | 113 | An easy subscription command. It works like this 114 | 115 | q.subscribe(function (message) { 116 | puts('Got a message with routing key ' + message._routingKey); 117 | }); 118 | 119 | It will automatically acknowledge receipt of each message. 120 | 121 | The only option that this method supports right now is the "ack" method, 122 | which defaults to false. Setting the options argument to `{ ack: true }` 123 | will make it so that the AMQP server only delivers a single message at a 124 | time. When you want the next message, call `q.shift()`. When `ack` is false 125 | then you will receive messages as fast as they come in. 126 | 127 | ### queue.subscribeRaw([options,] listener) 128 | 129 | Subscribes to a queue. The `listener` argument should be a function which 130 | receives a message. This is a low-level interface - the message that the 131 | listener receives will be a stream of binary data. You probably want to use 132 | `subscribe` instead. For now this low-level interface is left undocumented. 133 | Look at the source code if you need to this. 134 | 135 | ### queue.shift() 136 | 137 | For use with `subscribe({ack: true}, fn)`. Acknowledges the last 138 | message. 139 | 140 | 141 | ### queue.bind([exchange,] routing) 142 | 143 | This method binds a queue to an exchange. Until a queue is 144 | bound it will not receive any messages. 145 | 146 | If the `exchange` argument is left out `'amq.topic'` will be used. 147 | 148 | 149 | ### queue.delete(options) 150 | 151 | Delete the queue. Without options, the queue will be deleted even if it has 152 | pending messages or attached consumers. If +options.ifUnused+ is true, then 153 | the queue will only be deleted if there are no consumers. If 154 | +options.ifEmpty+ is true, the queue will only be deleted if it has no 155 | messages. 156 | 157 | 158 | 159 | 160 | ## Exchange 161 | 162 | 163 | ### exchange.addListener('open', callback) 164 | 165 | The open event is emitted when the exchange is declared and ready to 166 | be used. 167 | 168 | 169 | ### connection.exchange() 170 | ### connection.exchange(name, options={}) 171 | 172 | An exchange can be created using `connection.exchange()`. The method returns 173 | an `amqp.Exchange` object. 174 | 175 | Without any arguments, this method returns the default exchange `amq.topic`. 176 | Otherwise a string, `name`, is given as the first argument and an `options` 177 | object for the second. The options are 178 | 179 | - `type`: the type of exchange `'direct'`, `'fanout'`, or `'topic'` (default). 180 | - `passive`: boolean, default false. 181 | If set, the server will not create the exchange. The client can use 182 | this to check whether an exchange exists without modifying the server 183 | state. 184 | - `durable`: boolean, default false. 185 | If set when creating a new exchange, the exchange will be marked as 186 | durable. Durable exchanges remain active when a server restarts. 187 | Non-durable exchanges (transient exchanges) are purged if/when a 188 | server restarts. 189 | - `autoDelete`: boolean, default true. 190 | If set, the exchange is deleted when all queues have finished using 191 | it. 192 | 193 | An exchange will emit the `'open'` event when it is finally declared. 194 | 195 | 196 | 197 | ### exchange.publish(routingKey, message, options) 198 | 199 | Publishes a message to the exchange. The `routingKey` argument is a string 200 | which helps routing in `topic` and `direct` exchanges. The `message` can be 201 | either a Buffer or Object. A Buffer is used for sending raw bytes; an Object 202 | is convereted to JSON. 203 | 204 | `options` is an object with any of the following 205 | 206 | - `mandatory`: boolean, default false. 207 | This flag tells the server how to react if the message cannot be 208 | routed to a queue. If this flag is set, the server will return an 209 | unroutable message with a Return method. If this flag is zero, the 210 | server silently drops the message. 211 | - `immediate`: boolean, default false. 212 | This flag tells the server how to react if the message cannot be 213 | routed to a queue consumer immediately. If this flag is set, the 214 | server will return an undeliverable message with a Return method. 215 | If this flag is zero, the server will queue the message, but with 216 | no guarantee that it will ever be consumed. 217 | - `contentType`: default 'application/octet-stream' 218 | - `contentEncoding`: default null. 219 | - `headers`: default `{}`. 220 | - `deliveryMode`: Non-persistent (1) or persistent (2) 221 | - `priority`: The message priority, 0 to 9. 222 | 223 | 224 | ### exchange.destroy(ifUnused = true) 225 | 226 | Deletes an exchange. 227 | If the optional boolean second argument is set, the server will only 228 | delete the exchange if it has no queue bindings. If the exchange has queue 229 | bindings the server does not delete it but raises a channel exception 230 | instead. 231 | 232 | 233 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/amqp-definitions-0-8.js: -------------------------------------------------------------------------------- 1 | exports.constants = [[1,"frameMethod"],[2,"frameHeader"],[3,"frameBody"],[4,"frameOobMethod"],[5,"frameOobHeader"],[6,"frameOobBody"],[7,"frameTrace"],[8,"frameHeartbeat"],[200,"replySuccess"],[206,"frameEnd"],[310,"notDelivered"],[311,"contentTooLarge"],[320,"connectionForced"],[402,"invalidPath"],[403,"accessRefused"],[404,"notFound"],[405,"resourceLocked"],[501,"frameError"],[502,"syntaxError"],[503,"commandInvalid"],[504,"channelError"],[506,"resourceError"],[530,"notAllowed"],[540,"notImplemented"],[541,"internalError"],[4096,"frameMinSize"]]; 2 | exports.classes = [{"name":"connection","index":10,"fields":[],"methods":[{"name":"start","index":10,"fields":[{"name":"versionMajor","domain":"octet"},{"name":"versionMinor","domain":"octet"},{"name":"serverProperties","domain":"table"},{"name":"mechanisms","domain":"longstr"},{"name":"locales","domain":"longstr"}]},{"name":"startOk","index":11,"fields":[{"name":"clientProperties","domain":"table"},{"name":"mechanism","domain":"shortstr"},{"name":"response","domain":"longstr"},{"name":"locale","domain":"shortstr"}]},{"name":"secure","index":20,"fields":[{"name":"challenge","domain":"longstr"}]},{"name":"secureOk","index":21,"fields":[{"name":"response","domain":"longstr"}]},{"name":"tune","index":30,"fields":[{"name":"channelMax","domain":"short"},{"name":"frameMax","domain":"long"},{"name":"heartbeat","domain":"short"}]},{"name":"tuneOk","index":31,"fields":[{"name":"channelMax","domain":"short"},{"name":"frameMax","domain":"long"},{"name":"heartbeat","domain":"short"}]},{"name":"open","index":40,"fields":[{"name":"virtualHost","domain":"shortstr"},{"name":"capabilities","domain":"shortstr"},{"name":"insist","domain":"bit"}]},{"name":"openOk","index":41,"fields":[{"name":"knownHosts","domain":"shortstr"}]},{"name":"redirect","index":50,"fields":[{"name":"host","domain":"shortstr"},{"name":"knownHosts","domain":"shortstr"}]},{"name":"close","index":60,"fields":[{"name":"replyCode","domain":"short"},{"name":"replyText","domain":"shortstr"},{"name":"classId","domain":"short"},{"name":"methodId","domain":"short"}]},{"name":"closeOk","index":61,"fields":[]}]},{"name":"channel","index":20,"fields":[],"methods":[{"name":"open","index":10,"fields":[{"name":"outOfBand","domain":"shortstr"}]},{"name":"openOk","index":11,"fields":[]},{"name":"flow","index":20,"fields":[{"name":"active","domain":"bit"}]},{"name":"flowOk","index":21,"fields":[{"name":"active","domain":"bit"}]},{"name":"alert","index":30,"fields":[{"name":"replyCode","domain":"short"},{"name":"replyText","domain":"shortstr"},{"name":"details","domain":"table"}]},{"name":"close","index":40,"fields":[{"name":"replyCode","domain":"short"},{"name":"replyText","domain":"shortstr"},{"name":"classId","domain":"short"},{"name":"methodId","domain":"short"}]},{"name":"closeOk","index":41,"fields":[]}]},{"name":"access","index":30,"fields":[],"methods":[{"name":"request","index":10,"fields":[{"name":"realm","domain":"shortstr"},{"name":"exclusive","domain":"bit"},{"name":"passive","domain":"bit"},{"name":"active","domain":"bit"},{"name":"write","domain":"bit"},{"name":"read","domain":"bit"}]},{"name":"requestOk","index":11,"fields":[{"name":"ticket","domain":"short"}]}]},{"name":"exchange","index":40,"fields":[],"methods":[{"name":"declare","index":10,"fields":[{"name":"ticket","domain":"short"},{"name":"exchange","domain":"shortstr"},{"name":"type","domain":"shortstr"},{"name":"passive","domain":"bit"},{"name":"durable","domain":"bit"},{"name":"autoDelete","domain":"bit"},{"name":"internal","domain":"bit"},{"name":"nowait","domain":"bit"},{"name":"arguments","domain":"table"}]},{"name":"declareOk","index":11,"fields":[]},{"name":"delete","index":20,"fields":[{"name":"ticket","domain":"short"},{"name":"exchange","domain":"shortstr"},{"name":"ifUnused","domain":"bit"},{"name":"nowait","domain":"bit"}]},{"name":"deleteOk","index":21,"fields":[]}]},{"name":"queue","index":50,"fields":[],"methods":[{"name":"declare","index":10,"fields":[{"name":"ticket","domain":"short"},{"name":"queue","domain":"shortstr"},{"name":"passive","domain":"bit"},{"name":"durable","domain":"bit"},{"name":"exclusive","domain":"bit"},{"name":"autoDelete","domain":"bit"},{"name":"nowait","domain":"bit"},{"name":"arguments","domain":"table"}]},{"name":"declareOk","index":11,"fields":[{"name":"queue","domain":"shortstr"},{"name":"messageCount","domain":"long"},{"name":"consumerCount","domain":"long"}]},{"name":"bind","index":20,"fields":[{"name":"ticket","domain":"short"},{"name":"queue","domain":"shortstr"},{"name":"exchange","domain":"shortstr"},{"name":"routingKey","domain":"shortstr"},{"name":"nowait","domain":"bit"},{"name":"arguments","domain":"table"}]},{"name":"bindOk","index":21,"fields":[]},{"name":"purge","index":30,"fields":[{"name":"ticket","domain":"short"},{"name":"queue","domain":"shortstr"},{"name":"nowait","domain":"bit"}]},{"name":"purgeOk","index":31,"fields":[{"name":"messageCount","domain":"long"}]},{"name":"delete","index":40,"fields":[{"name":"ticket","domain":"short"},{"name":"queue","domain":"shortstr"},{"name":"ifUnused","domain":"bit"},{"name":"ifEmpty","domain":"bit"},{"name":"nowait","domain":"bit"}]},{"name":"deleteOk","index":41,"fields":[{"name":"messageCount","domain":"long"}]},{"name":"unbind","index":"50","fields":[{"name":"ticket","domain":"short"},{"name":"queue","domain":"shortstr"},{"name":"exchange","domain":"shortstr"},{"name":"routing_key","domain":"shortstr"},{"name":"arguments","domain":"table"}]},{"name":"unbind-ok","index":"51","fields":[]}]},{"name":"basic","index":60,"fields":[{"name":"contentType","domain":"shortstr"},{"name":"contentEncoding","domain":"shortstr"},{"name":"headers","domain":"table"},{"name":"deliveryMode","domain":"octet"},{"name":"priority","domain":"octet"},{"name":"correlationId","domain":"shortstr"},{"name":"replyTo","domain":"shortstr"},{"name":"expiration","domain":"shortstr"},{"name":"messageId","domain":"shortstr"},{"name":"timestamp","domain":"timestamp"},{"name":"type","domain":"shortstr"},{"name":"userId","domain":"shortstr"},{"name":"appId","domain":"shortstr"},{"name":"clusterId","domain":"shortstr"}],"methods":[{"name":"qos","index":10,"fields":[{"name":"prefetchSize","domain":"long"},{"name":"prefetchCount","domain":"short"},{"name":"global","domain":"bit"}]},{"name":"qosOk","index":11,"fields":[]},{"name":"consume","index":20,"fields":[{"name":"ticket","domain":"short"},{"name":"queue","domain":"shortstr"},{"name":"consumerTag","domain":"shortstr"},{"name":"noLocal","domain":"bit"},{"name":"noAck","domain":"bit"},{"name":"exclusive","domain":"bit"},{"name":"nowait","domain":"bit"}]},{"name":"consumeOk","index":21,"fields":[{"name":"consumerTag","domain":"shortstr"}]},{"name":"cancel","index":30,"fields":[{"name":"consumerTag","domain":"shortstr"},{"name":"nowait","domain":"bit"}]},{"name":"cancelOk","index":31,"fields":[{"name":"consumerTag","domain":"shortstr"}]},{"name":"publish","index":40,"fields":[{"name":"ticket","domain":"short"},{"name":"exchange","domain":"shortstr"},{"name":"routingKey","domain":"shortstr"},{"name":"mandatory","domain":"bit"},{"name":"immediate","domain":"bit"}]},{"name":"return","index":50,"fields":[{"name":"replyCode","domain":"short"},{"name":"replyText","domain":"shortstr"},{"name":"exchange","domain":"shortstr"},{"name":"routingKey","domain":"shortstr"}]},{"name":"deliver","index":60,"fields":[{"name":"consumerTag","domain":"shortstr"},{"name":"deliveryTag","domain":"longlong"},{"name":"redelivered","domain":"bit"},{"name":"exchange","domain":"shortstr"},{"name":"routingKey","domain":"shortstr"}]},{"name":"get","index":70,"fields":[{"name":"ticket","domain":"short"},{"name":"queue","domain":"shortstr"},{"name":"noAck","domain":"bit"}]},{"name":"getOk","index":71,"fields":[{"name":"deliveryTag","domain":"longlong"},{"name":"redelivered","domain":"bit"},{"name":"exchange","domain":"shortstr"},{"name":"routingKey","domain":"shortstr"},{"name":"messageCount","domain":"long"}]},{"name":"getEmpty","index":72,"fields":[{"name":"clusterId","domain":"shortstr"}]},{"name":"ack","index":80,"fields":[{"name":"deliveryTag","domain":"longlong"},{"name":"multiple","domain":"bit"}]},{"name":"reject","index":90,"fields":[{"name":"deliveryTag","domain":"longlong"},{"name":"requeue","domain":"bit"}]},{"name":"recover","index":100,"fields":[{"name":"requeue","domain":"bit"}]}]},{"name":"file","index":70,"fields":[{"name":"contentType","domain":"shortstr"},{"name":"contentEncoding","domain":"shortstr"},{"name":"headers","domain":"table"},{"name":"priority","domain":"octet"},{"name":"replyTo","domain":"shortstr"},{"name":"messageId","domain":"shortstr"},{"name":"filename","domain":"shortstr"},{"name":"timestamp","domain":"timestamp"},{"name":"clusterId","domain":"shortstr"}],"methods":[{"name":"qos","index":10,"fields":[{"name":"prefetchSize","domain":"long"},{"name":"prefetchCount","domain":"short"},{"name":"global","domain":"bit"}]},{"name":"qosOk","index":11,"fields":[]},{"name":"consume","index":20,"fields":[{"name":"ticket","domain":"short"},{"name":"queue","domain":"shortstr"},{"name":"consumerTag","domain":"shortstr"},{"name":"noLocal","domain":"bit"},{"name":"noAck","domain":"bit"},{"name":"exclusive","domain":"bit"},{"name":"nowait","domain":"bit"}]},{"name":"consumeOk","index":21,"fields":[{"name":"consumerTag","domain":"shortstr"}]},{"name":"cancel","index":30,"fields":[{"name":"consumerTag","domain":"shortstr"},{"name":"nowait","domain":"bit"}]},{"name":"cancelOk","index":31,"fields":[{"name":"consumerTag","domain":"shortstr"}]},{"name":"open","index":40,"fields":[{"name":"identifier","domain":"shortstr"},{"name":"contentSize","domain":"longlong"}]},{"name":"openOk","index":41,"fields":[{"name":"stagedSize","domain":"longlong"}]},{"name":"stage","index":50,"fields":[]},{"name":"publish","index":60,"fields":[{"name":"ticket","domain":"short"},{"name":"exchange","domain":"shortstr"},{"name":"routingKey","domain":"shortstr"},{"name":"mandatory","domain":"bit"},{"name":"immediate","domain":"bit"},{"name":"identifier","domain":"shortstr"}]},{"name":"return","index":70,"fields":[{"name":"replyCode","domain":"short"},{"name":"replyText","domain":"shortstr"},{"name":"exchange","domain":"shortstr"},{"name":"routingKey","domain":"shortstr"}]},{"name":"deliver","index":80,"fields":[{"name":"consumerTag","domain":"shortstr"},{"name":"deliveryTag","domain":"longlong"},{"name":"redelivered","domain":"bit"},{"name":"exchange","domain":"shortstr"},{"name":"routingKey","domain":"shortstr"},{"name":"identifier","domain":"shortstr"}]},{"name":"ack","index":90,"fields":[{"name":"deliveryTag","domain":"longlong"},{"name":"multiple","domain":"bit"}]},{"name":"reject","index":100,"fields":[{"name":"deliveryTag","domain":"longlong"},{"name":"requeue","domain":"bit"}]}]},{"name":"stream","index":80,"fields":[{"name":"contentType","domain":"shortstr"},{"name":"contentEncoding","domain":"shortstr"},{"name":"headers","domain":"table"},{"name":"priority","domain":"octet"},{"name":"timestamp","domain":"timestamp"}],"methods":[{"name":"qos","index":10,"fields":[{"name":"prefetchSize","domain":"long"},{"name":"prefetchCount","domain":"short"},{"name":"consumeRate","domain":"long"},{"name":"global","domain":"bit"}]},{"name":"qosOk","index":11,"fields":[]},{"name":"consume","index":20,"fields":[{"name":"ticket","domain":"short"},{"name":"queue","domain":"shortstr"},{"name":"consumerTag","domain":"shortstr"},{"name":"noLocal","domain":"bit"},{"name":"exclusive","domain":"bit"},{"name":"nowait","domain":"bit"}]},{"name":"consumeOk","index":21,"fields":[{"name":"consumerTag","domain":"shortstr"}]},{"name":"cancel","index":30,"fields":[{"name":"consumerTag","domain":"shortstr"},{"name":"nowait","domain":"bit"}]},{"name":"cancelOk","index":31,"fields":[{"name":"consumerTag","domain":"shortstr"}]},{"name":"publish","index":40,"fields":[{"name":"ticket","domain":"short"},{"name":"exchange","domain":"shortstr"},{"name":"routingKey","domain":"shortstr"},{"name":"mandatory","domain":"bit"},{"name":"immediate","domain":"bit"}]},{"name":"return","index":50,"fields":[{"name":"replyCode","domain":"short"},{"name":"replyText","domain":"shortstr"},{"name":"exchange","domain":"shortstr"},{"name":"routingKey","domain":"shortstr"}]},{"name":"deliver","index":60,"fields":[{"name":"consumerTag","domain":"shortstr"},{"name":"deliveryTag","domain":"longlong"},{"name":"exchange","domain":"shortstr"},{"name":"queue","domain":"shortstr"}]}]},{"name":"tx","index":90,"fields":[],"methods":[{"name":"select","index":10,"fields":[]},{"name":"selectOk","index":11,"fields":[]},{"name":"commit","index":20,"fields":[]},{"name":"commitOk","index":21,"fields":[]},{"name":"rollback","index":30,"fields":[]},{"name":"rollbackOk","index":31,"fields":[]}]},{"name":"dtx","index":100,"fields":[],"methods":[{"name":"select","index":10,"fields":[]},{"name":"selectOk","index":11,"fields":[]},{"name":"start","index":20,"fields":[{"name":"dtxIdentifier","domain":"shortstr"}]},{"name":"startOk","index":21,"fields":[]}]},{"name":"tunnel","index":110,"fields":[{"name":"headers","domain":"table"},{"name":"proxyName","domain":"shortstr"},{"name":"dataName","domain":"shortstr"},{"name":"durable","domain":"octet"},{"name":"broadcast","domain":"octet"}],"methods":[{"name":"request","index":10,"fields":[{"name":"metaData","domain":"table"}]}]},{"name":"test","index":120,"fields":[],"methods":[{"name":"integer","index":10,"fields":[{"name":"integer1","domain":"octet"},{"name":"integer2","domain":"short"},{"name":"integer3","domain":"long"},{"name":"integer4","domain":"longlong"},{"name":"operation","domain":"octet"}]},{"name":"integerOk","index":11,"fields":[{"name":"result","domain":"longlong"}]},{"name":"string","index":20,"fields":[{"name":"string1","domain":"shortstr"},{"name":"string2","domain":"longstr"},{"name":"operation","domain":"octet"}]},{"name":"stringOk","index":21,"fields":[{"name":"result","domain":"longstr"}]},{"name":"table","index":30,"fields":[{"name":"table","domain":"table"},{"name":"integerOp","domain":"octet"},{"name":"stringOp","domain":"octet"}]},{"name":"tableOk","index":31,"fields":[{"name":"integerResult","domain":"longlong"},{"name":"stringResult","domain":"longstr"}]},{"name":"content","index":40,"fields":[]},{"name":"contentOk","index":41,"fields":[{"name":"contentChecksum","domain":"long"}]}]}]; 3 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/amqp.js: -------------------------------------------------------------------------------- 1 | var events = require('events'), 2 | sys = require('sys'), 3 | net = require('net'), 4 | protocol = require('./amqp-definitions-0-8'), 5 | Buffer = require('buffer').Buffer, 6 | Promise = require('./promise').Promise; 7 | 8 | function mixin () { 9 | // copy reference to target object 10 | var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, source; 11 | 12 | // Handle a deep copy situation 13 | if ( typeof target === "boolean" ) { 14 | deep = target; 15 | target = arguments[1] || {}; 16 | // skip the boolean and the target 17 | i = 2; 18 | } 19 | 20 | // Handle case when target is a string or something (possible in deep copy) 21 | if ( typeof target !== "object" && !(typeof target === 'function') ) 22 | target = {}; 23 | 24 | // mixin process itself if only one argument is passed 25 | if ( length == i ) { 26 | target = GLOBAL; 27 | --i; 28 | } 29 | 30 | for ( ; i < length; i++ ) { 31 | // Only deal with non-null/undefined values 32 | if ( (source = arguments[i]) != null ) { 33 | // Extend the base object 34 | Object.getOwnPropertyNames(source).forEach(function(k){ 35 | var d = Object.getOwnPropertyDescriptor(source, k) || {value: source[k]}; 36 | if (d.get) { 37 | target.__defineGetter__(k, d.get); 38 | if (d.set) { 39 | target.__defineSetter__(k, d.set); 40 | } 41 | } 42 | else { 43 | // Prevent never-ending loop 44 | if (target === d.value) { 45 | return; 46 | } 47 | 48 | if (deep && d.value && typeof d.value === "object") { 49 | target[k] = mixin(deep, 50 | // Never move original objects, clone them 51 | source[k] || (d.value.length != null ? [] : {}) 52 | , d.value); 53 | } 54 | else { 55 | target[k] = d.value; 56 | } 57 | } 58 | }); 59 | } 60 | } 61 | // Return the modified object 62 | return target; 63 | } 64 | 65 | 66 | var debugLevel = process.env['NODE_DEBUG_AMQP'] ? 1 : 0; 67 | function debug (x) { 68 | if (debugLevel > 0) sys.error(x + '\n'); 69 | } 70 | 71 | 72 | 73 | // a look up table for methods recieved 74 | // indexed on class id, method id 75 | var methodTable = {}; 76 | 77 | // methods keyed on their name 78 | var methods = {}; 79 | 80 | // classes keyed on their index 81 | var classes = {}; 82 | 83 | (function () { // anon scope for init 84 | //debug("initializing amqp methods..."); 85 | for (var i = 0; i < protocol.classes.length; i++) { 86 | var classInfo = protocol.classes[i]; 87 | classes[classInfo.index] = classInfo; 88 | for (var j = 0; j < classInfo.methods.length; j++) { 89 | var methodInfo = classInfo.methods[j]; 90 | 91 | var name = classInfo.name 92 | + methodInfo.name[0].toUpperCase() 93 | + methodInfo.name.slice(1); 94 | //debug(name); 95 | 96 | var method = { name: name 97 | , fields: methodInfo.fields 98 | , methodIndex: methodInfo.index 99 | , classIndex: classInfo.index 100 | }; 101 | 102 | if (!methodTable[classInfo.index]) methodTable[classInfo.index] = {}; 103 | methodTable[classInfo.index][methodInfo.index] = method; 104 | methods[name] = method; 105 | } 106 | } 107 | })(); // end anon scope 108 | 109 | 110 | 111 | // parser 112 | 113 | 114 | var maxFrameBuffer = 131072; // same as rabbitmq 115 | 116 | 117 | // An interruptible AMQP parser. 118 | // 119 | // type is either 'server' or 'client' 120 | // version is '0-8' or '0-9-1'. Currently only supporting '0-8'. 121 | // 122 | // Instances of this class have several callbacks 123 | // - onMethod(channel, method, args); 124 | // - onHeartBeat() 125 | // - onContent(channel, buffer); 126 | // - onContentHeader(channel, class, weight, properties, size); 127 | // 128 | // This class does not subclass EventEmitter, in order to reduce the speed 129 | // of emitting the callbacks. Since this is an internal class, that should 130 | // be fine. 131 | function AMQPParser (version, type) { 132 | this.isClient = (type == 'client'); 133 | this.state = this.isClient ? 'frameHeader' : 'protocolHeader'; 134 | 135 | if (version != '0-8') throw new Error("Unsupported protocol version"); 136 | 137 | this.frameHeader = new Buffer(7); 138 | this.frameHeader.used = 0; 139 | } 140 | 141 | 142 | // Everytime data is recieved on the socket, pass it to this function for 143 | // parsing. 144 | AMQPParser.prototype.execute = function (data) { 145 | // This function only deals with dismantling and buffering the frames. 146 | // It delegats to other functions for parsing the frame-body. 147 | debug('execute: ' + data.toString()); 148 | for (var i = 0; i < data.length; i++) { 149 | switch (this.state) { 150 | case 'frameHeader': 151 | // Here we buffer the frame header. Remember, this is a fully 152 | // interruptible parser - it could be (although unlikely) 153 | // that we receive only several octets of the frame header 154 | // in one packet. 155 | this.frameHeader[this.frameHeader.used++] = data[i]; 156 | 157 | if (this.frameHeader.used == this.frameHeader.length) { 158 | // Finished buffering the frame header - parse it 159 | //var h = this.frameHeader.unpack("oonN", 0); 160 | 161 | this.frameHeader.read = 0; 162 | this.frameType = this.frameHeader[this.frameHeader.read++]; 163 | this.frameChannel = parseInt(this.frameHeader, 2); 164 | this.frameSize = parseInt(this.frameHeader, 4); 165 | 166 | this.frameHeader.used = 0; // for reuse 167 | 168 | debug("got frame: " + JSON.stringify([ this.frameType 169 | , this.frameChannel 170 | , this.frameSize 171 | ])); 172 | 173 | if (this.frameSize > maxFrameBuffer) { 174 | throw new Error("Oversized frame " + this.frameSize); 175 | } 176 | 177 | // TODO use a free list and keep a bunch of 8k buffers around 178 | this.frameBuffer = new Buffer(this.frameSize); 179 | this.frameBuffer.used = 0; 180 | this.state = 'bufferFrame'; 181 | } 182 | break; 183 | 184 | case 'bufferFrame': 185 | // Buffer the entire frame. I would love to avoid this, but doing 186 | // otherwise seems to be extremely painful. 187 | 188 | // Copy the incoming data byte-by-byte to the buffer. 189 | // FIXME This is slow! Can be improved with a memcpy binding. 190 | this.frameBuffer[this.frameBuffer.used++] = data[i]; 191 | 192 | if (this.frameBuffer.used == this.frameSize) { 193 | // Finished buffering the frame. Parse the frame. 194 | switch (this.frameType) { 195 | case 1: 196 | this._parseMethodFrame(this.frameChannel, this.frameBuffer); 197 | break; 198 | 199 | case 2: 200 | this._parseHeaderFrame(this.frameChannel, this.frameBuffer); 201 | break; 202 | 203 | case 3: 204 | if (this.onContent) { 205 | this.onContent(this.frameChannel, this.frameBuffer); 206 | } 207 | break; 208 | 209 | case 8: 210 | if (this.onHeartBeat) this.onHeartBeat(); 211 | break; 212 | 213 | default: 214 | throw new Error("Unhandled frame type " + this.frameType); 215 | break; 216 | } 217 | this.state = 'frameEnd'; 218 | } 219 | break; 220 | 221 | case 'frameEnd': 222 | // Frames are terminated by a single octet. 223 | if (data[i] != 206 /* constants.frameEnd */) { 224 | debug('data[' + i + '] = ' + data[i].toString(16)); 225 | debug('data = ' + data.toString()); 226 | debug('frameHeader: ' + this.frameHeader.toString()); 227 | debug('frameBuffer: ' + this.frameBuffer.toString()); 228 | throw new Error("Oversized frame"); 229 | } 230 | this.state = 'frameHeader'; 231 | break; 232 | } 233 | } 234 | }; 235 | 236 | 237 | // parse Network Byte Order integers. size can be 1,2,4,8 238 | function parseInt (buffer, size) { 239 | var int = 0; 240 | switch (size) { 241 | case 1: 242 | return buffer[buffer.read++]; 243 | 244 | case 2: 245 | return (buffer[buffer.read++] << 8) + buffer[buffer.read++]; 246 | 247 | case 4: 248 | return (buffer[buffer.read++] << 24) + (buffer[buffer.read++] << 16) + 249 | (buffer[buffer.read++] << 8) + buffer[buffer.read++]; 250 | 251 | case 8: 252 | return (buffer[buffer.read++] << 56) + (buffer[buffer.read++] << 48) + 253 | (buffer[buffer.read++] << 40) + (buffer[buffer.read++] << 32) + 254 | (buffer[buffer.read++] << 24) + (buffer[buffer.read++] << 16) + 255 | (buffer[buffer.read++] << 8) + buffer[buffer.read++]; 256 | 257 | default: 258 | throw new Error("cannot parse ints of that size"); 259 | } 260 | } 261 | 262 | 263 | function parseShortString (buffer) { 264 | var length = buffer[buffer.read++]; 265 | var s = buffer.utf8Slice(buffer.read, buffer.read+length); 266 | buffer.read += length; 267 | return s; 268 | } 269 | 270 | 271 | function parseLongString (buffer) { 272 | var length = parseInt(buffer, 4); 273 | var s = buffer.slice(buffer.read, buffer.read + length); 274 | buffer.read += length; 275 | return s; 276 | } 277 | 278 | 279 | function parseSignedInteger (buffer) { 280 | var int = parseInt(buffer, 4); 281 | if (int & 0x80000000) { 282 | int |= 0xEFFFFFFF; 283 | int = -int; 284 | } 285 | return int; 286 | } 287 | 288 | 289 | function parseTable (buffer) { 290 | var length = buffer.read + parseInt(buffer, 4); 291 | var table = {}; 292 | while (buffer.read < length) { 293 | var field = parseShortString(buffer); 294 | switch (buffer[buffer.read++]) { 295 | case 'S'.charCodeAt(0): 296 | table[field] = parseLongString(buffer); 297 | break; 298 | 299 | case 'I'.charCodeAt(0): 300 | table[field] = parseSignedInteger(buffer); 301 | break; 302 | 303 | case 'D'.charCodeAt(0): 304 | var decimals = buffer[buffer.read++]; 305 | var int = parseInt(buffer, 4); 306 | // TODO make into float...? 307 | // FIXME this isn't correct 308 | table[field] = '?'; 309 | break; 310 | 311 | case 'T'.charCodeAt(0): 312 | // 64bit time stamps. Awesome. 313 | var int = parseInt(buffer, 8); 314 | // TODO FIXME this isn't correct 315 | table[field] = '?'; 316 | break; 317 | 318 | case 'F'.charCodeAt(0): 319 | table[field] = parseTable(buffer); 320 | break; 321 | 322 | default: 323 | throw new Error("Unknown field value type " + buffer[buffer.read-1]); 324 | } 325 | } 326 | return table; 327 | } 328 | 329 | function parseFields (buffer, fields) { 330 | var args = {}; 331 | 332 | var bitIndex = 0; 333 | 334 | var value; 335 | 336 | for (var i = 0; i < fields.length; i++) { 337 | var field = fields[i]; 338 | 339 | //debug("parsing field " + field.name + " of type " + field.domain); 340 | 341 | switch (field.domain) { 342 | case 'bit': 343 | // 8 bits can be packed into one octet. 344 | 345 | // XXX check if bitIndex greater than 7? 346 | 347 | value = (buffer[buffer.read] & (1 << bitIndex)) ? true : false; 348 | 349 | if (fields[i+1] && fields[i+1].domain == 'bit') { 350 | bitIndex++; 351 | } else { 352 | bitIndex = 0; 353 | buffer.read++; 354 | } 355 | break; 356 | 357 | case 'octet': 358 | value = buffer[buffer.read++]; 359 | break; 360 | 361 | case 'short': 362 | value = parseInt(buffer, 2); 363 | break; 364 | 365 | case 'long': 366 | value = parseInt(buffer, 4); 367 | break; 368 | 369 | case 'longlong': 370 | value = parseInt(buffer, 8); 371 | break; 372 | 373 | case 'shortstr': 374 | value = parseShortString(buffer); 375 | break; 376 | 377 | case 'longstr': 378 | value = parseLongString(buffer); 379 | break; 380 | 381 | case 'table': 382 | value = parseTable(buffer); 383 | break; 384 | 385 | default: 386 | throw new Error("Unhandled parameter type " + field.domain); 387 | } 388 | //debug("got " + value); 389 | args[field.name] = value; 390 | } 391 | 392 | return args; 393 | } 394 | 395 | 396 | AMQPParser.prototype._parseMethodFrame = function (channel, buffer) { 397 | buffer.read = 0; 398 | var classId = parseInt(buffer, 2), 399 | methodId = parseInt(buffer, 2); 400 | 401 | 402 | // Make sure that this is a method that we understand. 403 | if (!methodTable[classId] || !methodTable[classId][methodId]) { 404 | throw new Error("Received unknown [classId, methodId] pair [" + 405 | classId + ", " + methodId + "]"); 406 | } 407 | 408 | var method = methodTable[classId][methodId]; 409 | 410 | if (!method) throw new Error("bad method?"); 411 | 412 | var args = parseFields(buffer, method.fields); 413 | 414 | if (this.onMethod) { 415 | this.onMethod(channel, method, args); 416 | } 417 | }; 418 | 419 | 420 | AMQPParser.prototype._parseHeaderFrame = function (channel, buffer) { 421 | buffer.read = 0; 422 | 423 | var classIndex = parseInt(buffer, 2); 424 | var weight = parseInt(buffer, 2); 425 | var size = parseInt(buffer, 8); 426 | 427 | 428 | 429 | var classInfo = classes[classIndex]; 430 | 431 | if (classInfo.fields.length > 15) { 432 | throw new Error("TODO: support more than 15 properties"); 433 | } 434 | 435 | 436 | var propertyFlags = parseInt(buffer, 2); 437 | 438 | var fields = []; 439 | for (var i = 0; i < classInfo.fields.length; i++) { 440 | var field = classInfo.fields[i]; 441 | // groan. 442 | if (propertyFlags & (1 << (15-i))) fields.push(field); 443 | } 444 | 445 | var properties = parseFields(buffer, fields); 446 | 447 | if (this.onContentHeader) { 448 | this.onContentHeader(channel, classInfo, weight, properties, size); 449 | } 450 | }; 451 | 452 | 453 | // Network byte order serialization 454 | // (NOTE: javascript always uses network byte order for its ints.) 455 | function serializeInt (b, size, int) { 456 | if (b.used + size >= b.length) { 457 | throw new Error("write out of bounds"); 458 | } 459 | 460 | // Only 4 cases - just going to be explicit instead of looping. 461 | 462 | switch (size) { 463 | // octet 464 | case 1: 465 | b[b.used++] = int; 466 | break; 467 | 468 | // short 469 | case 2: 470 | b[b.used++] = (int & 0xFF00) >> 8; 471 | b[b.used++] = (int & 0x00FF) >> 0; 472 | break; 473 | 474 | // long 475 | case 4: 476 | b[b.used++] = (int & 0xFF000000) >> 24; 477 | b[b.used++] = (int & 0x00FF0000) >> 16; 478 | b[b.used++] = (int & 0x0000FF00) >> 8; 479 | b[b.used++] = (int & 0x000000FF) >> 0; 480 | break; 481 | 482 | 483 | // long long 484 | case 8: 485 | b[b.used++] = (int & 0xFF00000000000000) >> 56; 486 | b[b.used++] = (int & 0x00FF000000000000) >> 48; 487 | b[b.used++] = (int & 0x0000FF0000000000) >> 40; 488 | b[b.used++] = (int & 0x000000FF00000000) >> 32; 489 | b[b.used++] = (int & 0x00000000FF000000) >> 24; 490 | b[b.used++] = (int & 0x0000000000FF0000) >> 16; 491 | b[b.used++] = (int & 0x000000000000FF00) >> 8; 492 | b[b.used++] = (int & 0x00000000000000FF) >> 0; 493 | break; 494 | 495 | default: 496 | throw new Error("Bad size"); 497 | } 498 | } 499 | 500 | 501 | function serializeShortString (b, string) { 502 | if (typeof(string) != "string") { 503 | throw new Error("param must be a string"); 504 | } 505 | var byteLength = Buffer.byteLength(string, 'utf8'); 506 | if (byteLength > 0xFF) { 507 | throw new Error("String too long for 'shortstr' parameter"); 508 | } 509 | if (1 + byteLength + b.used >= b.length) { 510 | throw new Error("Not enough space in buffer for 'shortstr'"); 511 | } 512 | b[b.used++] = byteLength; 513 | b.utf8Write(string, b.used); // error here 514 | b.used += byteLength; 515 | } 516 | 517 | 518 | function serializeLongString (b, string) { 519 | // we accept string, object, or buffer for this parameter. 520 | // in the case of string we serialize it to utf8. 521 | if (typeof(string) == 'string') { 522 | var byteLength = Buffer.byteLength(string, 'utf8'); 523 | serializeInt(b, 4, byteLength); 524 | b.utf8Write(string, b.used); 525 | b.used += byteLength; 526 | } else if (typeof(string) == 'object') { 527 | serializeTable(b, string); 528 | } else { 529 | // data is Buffer 530 | var byteLength = string.length; 531 | serializeInt(b, 4, byteLength); 532 | b.write(string, b.used); // memcpy 533 | b.used += byteLength; 534 | } 535 | } 536 | 537 | 538 | function serializeTable (b, object) { 539 | if (typeof(object) != "object") { 540 | throw new Error("param must be an object"); 541 | } 542 | 543 | var lengthIndex = b.used; 544 | b.used += 4; // for the long 545 | 546 | var startIndex = b.used; 547 | 548 | for (var key in object) { 549 | if (!object.hasOwnProperty(key)) continue; 550 | 551 | serializeShortString(b, key); 552 | 553 | var value = object[key]; 554 | 555 | switch (typeof(value)) { 556 | case 'string': 557 | b[b.used++] = 'S'.charCodeAt(0); 558 | serializeLongString(b, value); 559 | break; 560 | 561 | case 'number': 562 | if (value < 0) { 563 | b[b.used++] = 'I'.charCodeAt(0); 564 | serializeInt(b, 4, value); 565 | } else if (value > 0xFFFFFFFF) { 566 | b[b.used++] = 'T'.charCodeAt(0); 567 | serializeInt(b, 8, value); 568 | } 569 | // TODO decimal? meh. 570 | break; 571 | 572 | case 'object': 573 | serializeTable(b, value); 574 | break; 575 | 576 | default: 577 | throw new Error("unsupported type in amqp table"); 578 | } 579 | } 580 | 581 | var endIndex = b.used; 582 | 583 | b.used = lengthIndex; 584 | serializeInt(b, 4, endIndex - startIndex); 585 | b.used = endIndex; 586 | } 587 | 588 | 589 | function serializeFields (buffer, fields, args, strict) { 590 | var bitField = 0; 591 | var bitIndex = 0; 592 | 593 | for (var i = 0; i < fields.length; i++) { 594 | var field = fields[i]; 595 | var domain = field.domain; 596 | 597 | if (!(field.name in args)) { 598 | if (strict) { 599 | throw new Error("Missing field '" + field.name + "' of type " + domain); 600 | } 601 | continue; 602 | } 603 | 604 | var param = args[field.name]; 605 | 606 | //debug("domain: " + domain + " param: " + param); 607 | 608 | switch (domain) { 609 | case 'bit': 610 | if (typeof(param) != "boolean") { 611 | throw new Error("Unmatched field " + JSON.stringify(field)); 612 | } 613 | 614 | if (param) bitField |= (1 << bitIndex); 615 | bitIndex++; 616 | 617 | if (!fields[i+1] || fields[i+1].domain != 'bit') { 618 | debug('SET bit field ' + field.name + ' 0x' + bitField.toString(16)); 619 | buffer[buffer.used++] = bitField; 620 | bitField = 0; 621 | bitIndex = 0; 622 | } 623 | break; 624 | 625 | case 'octet': 626 | if (typeof(param) != "number" || param > 0xFF) { 627 | throw new Error("Unmatched field " + JSON.stringify(field)); 628 | } 629 | buffer[buffer.used++] = param; 630 | break; 631 | 632 | case 'short': 633 | if (typeof(param) != "number" || param > 0xFFFF) { 634 | throw new Error("Unmatched field " + JSON.stringify(field)); 635 | } 636 | serializeInt(buffer, 2, param); 637 | break; 638 | 639 | case 'long': 640 | if (typeof(param) != "number" || param > 0xFFFFFFFF) { 641 | throw new Error("Unmatched field " + JSON.stringify(field)); 642 | } 643 | serializeInt(buffer, 4, param); 644 | break; 645 | 646 | case 'longlong': 647 | serializeInt(buffer, 8, param); 648 | break; 649 | 650 | case 'shortstr': 651 | if (typeof(param) != "string" || param.length > 0xFF) { 652 | throw new Error("Unmatched field " + JSON.stringify(field)); 653 | } 654 | serializeShortString(buffer, param); 655 | break; 656 | 657 | case 'longstr': 658 | serializeLongString(buffer, param); 659 | break; 660 | 661 | case 'table': 662 | if (typeof(param) != "object") { 663 | throw new Error("Unmatched field " + JSON.stringify(field)); 664 | } 665 | serializeTable(buffer, param); 666 | break; 667 | 668 | default: 669 | throw new Error("Unknown domain value type " + domain); 670 | } 671 | } 672 | } 673 | 674 | 675 | 676 | 677 | function Connection (options) { 678 | net.Stream.call(this); 679 | 680 | var self = this; 681 | 682 | this.setOptions(options); 683 | 684 | var state = 'handshake'; 685 | var parser; 686 | 687 | this._defaultExchange = null; 688 | 689 | self.addListener('connect', function () { 690 | // channel 0 is the control channel. 691 | self.channels = [self]; 692 | self.queues = {}; 693 | self.exchanges = {}; 694 | 695 | parser = new AMQPParser('0-8', 'client'); 696 | 697 | parser.onMethod = function (channel, method, args) { 698 | self._onMethod(channel, method, args); 699 | }; 700 | 701 | parser.onContent = function (channel, data) { 702 | debug(channel + " > content " + data.length); 703 | if (self.channels[channel] && self.channels[channel]._onContent) { 704 | self.channels[channel]._onContent(channel, data); 705 | } else { 706 | debug("unhandled content: " + data); 707 | } 708 | }; 709 | 710 | parser.onContentHeader = function (channel, classInfo, weight, properties, size) { 711 | debug(channel + " > content header " + JSON.stringify([classInfo.name, weight, properties, size])); 712 | if (self.channels[channel] && self.channels[channel]._onContentHeader) { 713 | self.channels[channel]._onContentHeader(channel, classInfo, weight, properties, size); 714 | } else { 715 | debug("unhandled content header"); 716 | } 717 | }; 718 | 719 | parser.onHeartBeat = function () { 720 | debug("heartbeat"); 721 | }; 722 | 723 | //debug("connected..."); 724 | // Time to start the AMQP 7-way connection initialization handshake! 725 | // 1. The client sends the server a version string 726 | self.write("AMQP" + String.fromCharCode(1,1,8,0)); 727 | state = 'handshake'; 728 | }); 729 | 730 | self.addListener('data', function (data) { 731 | parser.execute(data); 732 | }); 733 | 734 | self.addListener('end', function () { 735 | self.end(); 736 | // in order to allow reconnects, have to clear the 737 | // state. 738 | parser = null; 739 | }); 740 | } 741 | sys.inherits(Connection, net.Stream); 742 | exports.Connection = Connection; 743 | 744 | 745 | var defaultOptions = { host: 'localhost' 746 | , port: 5672 747 | , login: 'guest' 748 | , password: 'guest' 749 | , vhost: '/' 750 | }; 751 | 752 | 753 | exports.createConnection = function (options) { 754 | var c = new Connection(); 755 | c.setOptions(options); 756 | c.reconnect(); 757 | return c; 758 | }; 759 | 760 | Connection.prototype.setOptions = function (options) { 761 | var o = {}; 762 | mixin(o, defaultOptions, options || {}); 763 | this.options = o; 764 | }; 765 | 766 | Connection.prototype.reconnect = function () { 767 | this.connect(this.options.port, this.options.host); 768 | }; 769 | 770 | Connection.prototype._onMethod = function (channel, method, args) { 771 | debug(channel + " > " + method.name + " " + JSON.stringify(args)); 772 | 773 | // Channel 0 is the control channel. If not zero then deligate to 774 | // one of the channel objects. 775 | 776 | if (channel > 0) { 777 | if (!this.channels[channel]) { 778 | debug("Received message on untracked channel."); 779 | return; 780 | } 781 | if (!this.channels[channel]._onMethod) { 782 | throw new Error('Channel ' + channel + ' has no _onMethod method.'); 783 | } 784 | this.channels[channel]._onMethod(channel, method, args); 785 | return; 786 | } 787 | 788 | // channel 0 789 | 790 | switch (method) { 791 | // 2. The server responds, after the version string, with the 792 | // 'connectionStart' method (contains various useless information) 793 | case methods.connectionStart: 794 | // We check that they're serving us AMQP 0-8 795 | if (args.versionMajor != 8 && args.versionMinor != 0) { 796 | this.end(); 797 | this.emit('error', new Error("Bad server version")); 798 | return; 799 | } 800 | this.serverProperties = args.serverProperties; 801 | // 3. Then we reply with StartOk, containing our useless information. 802 | this._sendMethod(0, methods.connectionStartOk, 803 | { clientProperties: 804 | { version: '0.0.1' 805 | , platform: 'node-' + process.version 806 | , product: 'node-amqp' 807 | } 808 | , mechanism: 'AMQPLAIN' 809 | , response: 810 | { LOGIN: this.options.login 811 | , PASSWORD: this.options.password 812 | } 813 | , locale: 'en_US' 814 | }); 815 | break; 816 | 817 | // 4. The server responds with a connectionTune request 818 | case methods.connectionTune: 819 | // 5. We respond with connectionTuneOk 820 | this._sendMethod(0, methods.connectionTuneOk, 821 | { channelMax: 0 822 | , frameMax: maxFrameBuffer 823 | , heartbeat: 0 824 | }); 825 | // 6. Then we have to send a connectionOpen request 826 | this._sendMethod(0, methods.connectionOpen, 827 | { virtualHost: this.options.vhost 828 | , capabilities: '' 829 | , insist: true 830 | }); 831 | break; 832 | 833 | 834 | case methods.connectionOpenOk: 835 | // 7. Finally they respond with connectionOpenOk 836 | // Whew! That's why they call it the Advanced MQP. 837 | this.emit('ready'); 838 | break; 839 | 840 | case methods.connectionClose: 841 | var e = new Error(args.replyText); 842 | e.code = args.replyCode; 843 | if (!this.listeners('close').length) { 844 | sys.puts('Unhandled connection error: ' + args.replyText); 845 | } 846 | this.destroy(e); 847 | break; 848 | 849 | default: 850 | throw new Error("Uncaught method '" + method.name + "' with args " + 851 | JSON.stringify(args)); 852 | } 853 | }; 854 | 855 | 856 | Connection.prototype._sendMethod = function (channel, method, args) { 857 | debug(channel + " < " + method.name + " " + JSON.stringify(args)); 858 | var b = new Buffer(maxFrameBuffer); 859 | b.used = 0; 860 | 861 | b[b.used++] = 1; // constants.frameMethod 862 | 863 | serializeInt(b, 2, channel); 864 | 865 | var lengthIndex = b.used; 866 | 867 | serializeInt(b, 4, 42); // replace with actual length. 868 | 869 | var startIndex = b.used; 870 | 871 | 872 | serializeInt(b, 2, method.classIndex); // short, classId 873 | serializeInt(b, 2, method.methodIndex); // short, methodId 874 | 875 | serializeFields(b, method.fields, args, true); 876 | 877 | var endIndex = b.used; 878 | 879 | // write in the frame length now that we know it. 880 | b.used = lengthIndex; 881 | serializeInt(b, 4, endIndex - startIndex); 882 | b.used = endIndex; 883 | 884 | b[b.used++] = 206; // constants.frameEnd; 885 | 886 | var c = b.slice(0, b.used); 887 | 888 | //debug("sending frame: " + c); 889 | 890 | this.write(c); 891 | }; 892 | 893 | 894 | // connection: the connection 895 | // channel: the channel to send this on 896 | // size: size in bytes of the following message 897 | // properties: an object containing any of the following: 898 | // - contentType (default 'application/octet-stream') 899 | // - contentEncoding 900 | // - headers 901 | // - deliveryMode 902 | // - priority (0-9) 903 | // - correlationId 904 | // - replyTo 905 | // - experation 906 | // - messageId 907 | // - timestamp 908 | // - userId 909 | // - appId 910 | // - clusterId 911 | function sendHeader (connection, channel, size, properties) { 912 | var b = new Buffer(maxFrameBuffer); // FIXME allocating too much. 913 | // use freelist? 914 | b.used = 0; 915 | 916 | var classInfo = classes[60]; // always basic class. 917 | 918 | // 7 OCTET FRAME HEADER 919 | 920 | b[b.used++] = 2; // constants.frameHeader 921 | 922 | serializeInt(b, 2, channel); 923 | 924 | var lengthStart = b.used; 925 | 926 | serializeInt(b, 4, 0 /*dummy*/); // length 927 | 928 | var bodyStart = b.used; 929 | 930 | // HEADER'S BODY 931 | 932 | serializeInt(b, 2, classInfo.index); // class 60 for Basic 933 | serializeInt(b, 2, 0); // weight, always 0 for rabbitmq 934 | serializeInt(b, 8, size); // byte size of body 935 | 936 | // properties - first propertyFlags 937 | var props = {'contentType': 'application/octet-stream'}; 938 | mixin(props, properties); 939 | var propertyFlags = 0; 940 | for (var i = 0; i < classInfo.fields.length; i++) { 941 | if (props[classInfo.fields[i].name]) propertyFlags |= 1 << (15-i); 942 | } 943 | serializeInt(b, 2, propertyFlags); 944 | // now the actual properties. 945 | serializeFields(b, classInfo.fields, props, false); 946 | 947 | //serializeTable(b, props); 948 | 949 | var bodyEnd = b.used; 950 | 951 | // Go back to the header and write in the length now that we know it. 952 | b.used = lengthStart; 953 | serializeInt(b, 4, bodyEnd - bodyStart); 954 | b.used = bodyEnd; 955 | 956 | // 1 OCTET END 957 | 958 | b[b.used++] = 206; // constants.frameEnd; 959 | 960 | var s = b.slice(0, b.used); 961 | 962 | //debug('header sent: ' + JSON.stringify(s)); 963 | 964 | connection.write(s); 965 | } 966 | 967 | 968 | Connection.prototype._sendBody = function (channel, body, properties) { 969 | // Handles 3 cases 970 | // - body is utf8 string 971 | // - body is instance of Buffer 972 | // - body is an object and its JSON representation is sent 973 | // Does not handle the case for streaming bodies. 974 | if (typeof(body) == 'string') { 975 | var length = Buffer.byteLength(body); 976 | //debug('send message length ' + length); 977 | 978 | sendHeader(this, channel, length, properties); 979 | 980 | //debug('header sent'); 981 | 982 | var b = new Buffer(7+length+1); 983 | b.used = 0; 984 | b[b.used++] = 3; // constants.frameBody 985 | serializeInt(b, 2, channel); 986 | serializeInt(b, 4, length); 987 | 988 | b.utf8Write(body, b.used); 989 | b.used += length; 990 | 991 | b[b.used++] = 206; // constants.frameEnd; 992 | return this.write(b); 993 | 994 | //debug('body sent: ' + JSON.stringify(b)); 995 | 996 | } else if (body instanceof Buffer) { 997 | sendHeader(this, channel, body.length, properties); 998 | 999 | var b = new Buffer(7); 1000 | b.used = 0; 1001 | b[b.used++] = 3; // constants.frameBody 1002 | serializeInt(b, 2, channel); 1003 | serializeInt(b, 4, body.length); 1004 | this.write(b); 1005 | 1006 | this.write(body); 1007 | 1008 | return this.write(String.fromCharCode(206)); // frameEnd 1009 | 1010 | } else { 1011 | // Optimize for JSON. 1012 | // Use asciiWrite() which is much faster than utf8Write(). 1013 | var jsonBody = JSON.stringify(body); 1014 | var length = jsonBody.length; 1015 | 1016 | debug('sending json: ' + jsonBody); 1017 | 1018 | properties = mixin({contentType: 'text/json' }, properties); 1019 | 1020 | sendHeader(this, channel, length, properties); 1021 | 1022 | var b = new Buffer(7+length+1); 1023 | b.used = 0; 1024 | 1025 | b[b.used++] = 3; // constants.frameBody 1026 | serializeInt(b, 2, channel); 1027 | serializeInt(b, 4, length); 1028 | 1029 | b.asciiWrite(jsonBody, b.used); 1030 | b.used += length; 1031 | 1032 | b[b.used++] = 206; // constants.frameEnd; 1033 | return this.write(b); 1034 | } 1035 | }; 1036 | 1037 | 1038 | // Options 1039 | // - passive (boolean) 1040 | // - durable (boolean) 1041 | // - exclusive (boolean) 1042 | // - autoDelete (boolean, default true) 1043 | Connection.prototype.queue = function (name /* , options, openCallback */) { 1044 | if (this.queues[name]) return this.queues[name]; 1045 | var channel = this.channels.length; 1046 | 1047 | var options, callback; 1048 | if (typeof arguments[1] == 'object') { 1049 | options = arguments[1]; 1050 | callback = arguments[2]; 1051 | } else { 1052 | callback = arguments[1]; 1053 | } 1054 | 1055 | 1056 | var q = new Queue(this, channel, name, options, callback); 1057 | this.channels.push(q); 1058 | this.queues[name] = q; 1059 | return q; 1060 | }; 1061 | 1062 | 1063 | // connection.exchange('my-exchange', { type: 'topic' }); 1064 | // Options 1065 | // - type 'fanout', 'direct', or 'topic' (default) 1066 | // - passive (boolean) 1067 | // - durable (boolean) 1068 | // - autoDelete (boolean, default true) 1069 | Connection.prototype.exchange = function (name, options) { 1070 | if (!name) name = 'amq.topic'; 1071 | 1072 | if (!options) options = {}; 1073 | if (options.type === undefined) options.type = 'topic'; 1074 | 1075 | if (this.exchanges[name]) return this.exchanges[name]; 1076 | var channel = this.channels.length; 1077 | var exchange = new Exchange(this, channel, name, options); 1078 | this.channels.push(exchange); 1079 | this.exchanges[name] = exchange; 1080 | return exchange; 1081 | }; 1082 | 1083 | // Publishes a message to the amq.topic exchange. 1084 | Connection.prototype.publish = function (routingKey, body) { 1085 | if (!this._defaultExchange) this._defaultExchange = this.exchange(); 1086 | return this._defaultExchange.publish(routingKey, body); 1087 | }; 1088 | 1089 | 1090 | 1091 | // Properties: 1092 | // - routingKey 1093 | // - size 1094 | // - deliveryTag 1095 | // 1096 | // - contentType (default 'application/octet-stream') 1097 | // - contentEncoding 1098 | // - headers 1099 | // - deliveryMode 1100 | // - priority (0-9) 1101 | // - correlationId 1102 | // - replyTo 1103 | // - experation 1104 | // - messageId 1105 | // - timestamp 1106 | // - userId 1107 | // - appId 1108 | // - clusterId 1109 | function Message (queue, args) { 1110 | events.EventEmitter.call(this); 1111 | 1112 | this.queue = queue; 1113 | 1114 | this.deliveryTag = args.deliveryTag; 1115 | this.redelivered = args.redelivered; 1116 | this.exchange = args.exchange; 1117 | this.routingKey = args.routingKey; 1118 | } 1119 | sys.inherits(Message, events.EventEmitter); 1120 | 1121 | 1122 | // Acknowledge recept of message. 1123 | // Set first arg to 'true' to acknowledge this and all previous messages 1124 | // received on this queue. 1125 | Message.prototype.acknowledge = function (all) { 1126 | this.queue.connection._sendMethod(this.queue.channel, methods.basicAck, 1127 | { ticket: 0 1128 | , deliveryTag: this.deliveryTag 1129 | , multiple: all ? true : false 1130 | }); 1131 | }; 1132 | 1133 | 1134 | // This class is not exposed to the user. Queue and Exchange are subclasses 1135 | // of Channel. This just provides a task queue. 1136 | function Channel (connection, channel) { 1137 | events.EventEmitter.call(this); 1138 | 1139 | this.channel = channel; 1140 | this.connection = connection; 1141 | this._tasks = []; 1142 | 1143 | this.connection._sendMethod(channel, methods.channelOpen, {outOfBand: ""}); 1144 | } 1145 | sys.inherits(Channel, events.EventEmitter); 1146 | 1147 | 1148 | Channel.prototype._taskPush = function (reply, cb) { 1149 | var promise = new Promise(); 1150 | this._tasks.push({ promise: promise 1151 | , reply: reply 1152 | , sent: false 1153 | , cb: cb 1154 | }); 1155 | this._tasksFlush(); 1156 | return promise; 1157 | }; 1158 | 1159 | 1160 | Channel.prototype._tasksFlush = function () { 1161 | if (this.state != 'open') return; 1162 | 1163 | for (var i = 0; i < this._tasks.length; i++) { 1164 | var task = this._tasks[i]; 1165 | if (task.sent) continue; 1166 | task.cb(); 1167 | task.sent = true; 1168 | if (!task.reply) { 1169 | // if we don't expect a reply, just delete it now 1170 | this._tasks.splice(i, 1); 1171 | i = i-1; 1172 | } 1173 | } 1174 | }; 1175 | 1176 | Channel.prototype._handleTaskReply = function (channel, method, args) { 1177 | var task = this._tasks[0]; 1178 | if (task && task.reply == method) { 1179 | this._tasks.shift(); 1180 | task.promise.emitSuccess(); 1181 | this._tasksFlush(); 1182 | return true; 1183 | } 1184 | return false; 1185 | }; 1186 | 1187 | 1188 | 1189 | function Queue (connection, channel, name, options, callback) { 1190 | Channel.call(this, connection, channel); 1191 | 1192 | this.name = name; 1193 | 1194 | this.options = { autoDelete: true }; 1195 | if (options) mixin(this.options, options); 1196 | 1197 | this._openCallback = callback; 1198 | } 1199 | sys.inherits(Queue, Channel); 1200 | 1201 | 1202 | Queue.prototype.subscribeRaw = function (/* options, messageListener */) { 1203 | var self = this; 1204 | 1205 | var messageListener = arguments[arguments.length-1]; 1206 | this.addListener('rawMessage', messageListener); 1207 | 1208 | var options = { }; 1209 | if (typeof arguments[0] == 'object') { 1210 | mixin(options, arguments[0]); 1211 | } 1212 | 1213 | return this._taskPush(methods.basicConsumeOk, function () { 1214 | self.connection._sendMethod(self.channel, methods.basicConsume, 1215 | { ticket: 0 1216 | , queue: self.name 1217 | , consumerTag: "." 1218 | , noLocal: options.noLocal ? true : false 1219 | , noAck: options.noAck ? true : false 1220 | , exclusive: options.exclusive ? true : false 1221 | , nowait: false 1222 | , "arguments": {} 1223 | }); 1224 | }); 1225 | }; 1226 | 1227 | 1228 | Queue.prototype.subscribe = function (/* options, messageListener */) { 1229 | var self = this; 1230 | 1231 | var messageListener = arguments[arguments.length-1]; 1232 | 1233 | var options = { ack: false }; 1234 | if (typeof arguments[0] == 'object') { 1235 | if (arguments[0].ack) options.ack = true; 1236 | } 1237 | 1238 | this.addListener('message', messageListener); 1239 | 1240 | if (options.ack) { 1241 | this._taskPush(methods.basicQosOk, function () { 1242 | self.connection._sendMethod(self.channel, methods.basicQos, 1243 | { ticket: 0 1244 | , prefetchSize: 0 1245 | , prefetchCount: 1 1246 | , global: false 1247 | }); 1248 | }); 1249 | } 1250 | 1251 | // basic consume 1252 | var rawOptions = { noAck: !options.ack }; 1253 | return this.subscribeRaw(rawOptions, function (m) { 1254 | var isJSON = (m.contentType == 'text/json') || (m.contentType == 'application/json'); 1255 | 1256 | var b; 1257 | 1258 | if (isJSON) { 1259 | b = "" 1260 | } else { 1261 | b = new Buffer(m.size); 1262 | b.used = 0; 1263 | } 1264 | 1265 | self._lastMessage = m; 1266 | 1267 | m.addListener('data', function (d) { 1268 | if (isJSON) { 1269 | b += d.toString(); 1270 | } else { 1271 | d.copy(b, b.used); 1272 | b.used += d.length; 1273 | } 1274 | }); 1275 | 1276 | m.addListener('end', function () { 1277 | var json; 1278 | if (isJSON) { 1279 | json = JSON.parse(b); 1280 | } else { 1281 | json = { data: b, contentType: m.contentType }; 1282 | } 1283 | 1284 | json._routingKey = m.routingKey; 1285 | json._deliveryTag = m.deliveryTag; 1286 | 1287 | 1288 | self.emit('message', json); 1289 | }); 1290 | }); 1291 | }; 1292 | Queue.prototype.subscribeJSON = Queue.prototype.subscribe; 1293 | 1294 | 1295 | /* Acknowledges the last message */ 1296 | Queue.prototype.shift = function () { 1297 | if (this._lastMessage) { 1298 | this._lastMessage.acknowledge(); 1299 | } 1300 | }; 1301 | 1302 | 1303 | Queue.prototype.bind = function (/* [exchange,] routingKey */) { 1304 | var self = this; 1305 | 1306 | // The first argument, exchange is optional. 1307 | // If not supplied the connection will use the default 'amq.topic' 1308 | // exchange. 1309 | 1310 | var exchange, routingKey; 1311 | 1312 | if (arguments.length == 2) { 1313 | exchange = arguments[0]; 1314 | routingKey = arguments[1]; 1315 | } else { 1316 | exchange = 'amq.topic'; 1317 | routingKey = arguments[0]; 1318 | } 1319 | 1320 | 1321 | return this._taskPush(methods.queueBindOk, function () { 1322 | var exchangeName = exchange instanceof Exchange ? exchange.name : exchange; 1323 | self.connection._sendMethod(self.channel, methods.queueBind, 1324 | { ticket: 0 1325 | , queue: self.name 1326 | , exchange: exchangeName 1327 | , routingKey: routingKey 1328 | , nowait: false 1329 | , "arguments": {} 1330 | }); 1331 | }); 1332 | }; 1333 | 1334 | 1335 | Queue.prototype.destroy = function (options) { 1336 | var self = this; 1337 | options = options || {}; 1338 | return this._taskPush(methods.queueDeleteOk, function () { 1339 | self.connection._sendMethod(self.channel, methods.queueDelete, 1340 | { ticket: 0 1341 | , queue: self.name 1342 | , ifUnused: options.ifUnused ? true : false 1343 | , ifEmpty: options.ifEmpty ? true : false 1344 | , nowait: false 1345 | , "arguments": {} 1346 | }); 1347 | }); 1348 | }; 1349 | 1350 | 1351 | Queue.prototype._onMethod = function (channel, method, args) { 1352 | if (this._handleTaskReply.apply(this, arguments)) return; 1353 | 1354 | switch (method) { 1355 | case methods.channelOpenOk: 1356 | this.connection._sendMethod(channel, methods.queueDeclare, 1357 | { ticket: 0 1358 | , queue: this.name 1359 | , passive: this.options.passive ? true : false 1360 | , durable: this.options.durable ? true : false 1361 | , exclusive: this.options.exclusive ? true : false 1362 | , autoDelete: this.options.autoDelete ? true : false 1363 | , nowait: false 1364 | , "arguments": {} 1365 | }); 1366 | this.state = "declare queue"; 1367 | break; 1368 | 1369 | case methods.queueDeclareOk: 1370 | this.state = 'open'; 1371 | if (this._openCallback) { 1372 | this._openCallback(args.messageCount, args.consumerCount); 1373 | this._openCallback = null; 1374 | } 1375 | // TODO this is legacy interface, remove me 1376 | this.emit('open', args.messageCount, args.consumerCount); 1377 | break; 1378 | 1379 | case methods.basicConsumeOk: 1380 | break; 1381 | 1382 | case methods.queueBindOk: 1383 | break; 1384 | 1385 | case methods.basicQosOk: 1386 | break; 1387 | 1388 | case methods.channelClose: 1389 | this.state = "closed"; 1390 | var e = new Error(args.replyText); 1391 | e.code = args.replyCode; 1392 | if (!this.listeners('close').length) { 1393 | sys.puts('Unhandled channel error: ' + args.replyText); 1394 | } 1395 | this.emit('error', e); 1396 | this.emit('close', e); 1397 | break; 1398 | 1399 | case methods.basicDeliver: 1400 | this.currentMessage = new Message(this, args); 1401 | break; 1402 | 1403 | default: 1404 | throw new Error("Uncaught method '" + method.name + "' with args " + 1405 | JSON.stringify(args)); 1406 | } 1407 | 1408 | this._tasksFlush(); 1409 | }; 1410 | 1411 | 1412 | Queue.prototype._onContentHeader = function (channel, classInfo, weight, properties, size) { 1413 | mixin(this.currentMessage, properties); 1414 | this.currentMessage.read = 0; 1415 | this.currentMessage.size = size; 1416 | 1417 | this.emit('rawMessage', this.currentMessage); 1418 | }; 1419 | 1420 | 1421 | Queue.prototype._onContent = function (channel, data) { 1422 | this.currentMessage.read += data.length 1423 | this.currentMessage.emit('data', data); 1424 | if (this.currentMessage.read == this.currentMessage.size) { 1425 | this.currentMessage.emit('end'); 1426 | } 1427 | }; 1428 | 1429 | 1430 | 1431 | 1432 | function Exchange (connection, channel, name, options) { 1433 | Channel.call(this, connection, channel); 1434 | this.name = name; 1435 | this.options = options || { autoDelete: true}; 1436 | } 1437 | sys.inherits(Exchange, Channel); 1438 | 1439 | 1440 | 1441 | Exchange.prototype._onMethod = function (channel, method, args) { 1442 | if (this._handleTaskReply.apply(this, arguments)) return true; 1443 | 1444 | switch (method) { 1445 | case methods.channelOpenOk: 1446 | // Default exchanges don't need to be declared 1447 | if (/^amq\./.test(this.name)) { 1448 | this.state = 'open'; 1449 | this.emit('open'); 1450 | } else { 1451 | this.connection._sendMethod(channel, methods.exchangeDeclare, 1452 | { ticket: 0 1453 | , exchange: this.name 1454 | , type: this.options.type || 'topic' 1455 | , passive: this.options.passive ? true : false 1456 | , durable: this.options.durable ? true : false 1457 | , autoDelete: this.options.autoDelete ? true : false 1458 | , internal: this.options.internal ? true : false 1459 | , nowait: false 1460 | , "arguments": {} 1461 | }); 1462 | this.state = 'declaring'; 1463 | } 1464 | break; 1465 | 1466 | case methods.exchangeDeclareOk: 1467 | this.state = 'open'; 1468 | this.emit('open'); 1469 | break; 1470 | 1471 | case methods.channelClose: 1472 | this.state = "closed"; 1473 | var e = new Error(args.replyText); 1474 | e.code = args.replyCode; 1475 | if (!this.listeners('close').length) { 1476 | sys.puts('Unhandled channel error: ' + args.replyText); 1477 | } 1478 | this.emit('close', e); 1479 | break; 1480 | 1481 | case methods.basicReturn: 1482 | sys.puts("Warning: Uncaught basicReturn: "+JSON.stringify(args)); 1483 | this.emit('basicReturn', args); 1484 | break; 1485 | 1486 | default: 1487 | throw new Error("Uncaught method '" + method.name + "' with args " + 1488 | JSON.stringify(args)); 1489 | } 1490 | 1491 | this._tasksFlush(); 1492 | }; 1493 | 1494 | 1495 | // exchange.publish('routing.key', 'body'); 1496 | // 1497 | // the thrid argument can specify additional options 1498 | // - mandatory (boolean, default false) 1499 | // - immediate (boolean, default false) 1500 | // - contentType (default 'application/octet-stream') 1501 | // - contentEncoding 1502 | // - headers 1503 | // - deliveryMode 1504 | // - priority (0-9) 1505 | // - correlationId 1506 | // - replyTo 1507 | // - experation 1508 | // - messageId 1509 | // - timestamp 1510 | // - userId 1511 | // - appId 1512 | // - clusterId 1513 | Exchange.prototype.publish = function (routingKey, data, options) { 1514 | options = options || {}; 1515 | 1516 | var self = this; 1517 | return this._taskPush(null, function () { 1518 | self.connection._sendMethod(self.channel, methods.basicPublish, 1519 | { ticket: 0 1520 | , exchange: self.name 1521 | , routingKey: routingKey 1522 | , mandatory: options.mandatory ? true : false 1523 | , immediate: options.immediate ? true : false 1524 | }); 1525 | // This interface is probably not appropriate for streaming large files. 1526 | // (Of course it's arguable about whether AMQP is the appropriate 1527 | // transport for large files.) The content header wants to know the size 1528 | // of the data before sending it - so there's no point in trying to have a 1529 | // general streaming interface - streaming messages of unknown size simply 1530 | // isn't possible with AMQP. This is all to say, don't send big messages. 1531 | // If you need to stream something large, chunk it yourself. 1532 | self.connection._sendBody(self.channel, data, options); 1533 | }); 1534 | }; 1535 | 1536 | 1537 | Exchange.prototype.destroy = function (ifUnused) { 1538 | var self = this; 1539 | return this._taskPush(methods.exchangeDeleteOk, function () { 1540 | self.connection._sendMethod(self.channel, methods.exchangeDelete, 1541 | { ticket: 0 1542 | , exchange: self.name 1543 | , ifUnused: ifUnused ? true : false 1544 | , nowait: false 1545 | }); 1546 | }); 1547 | }; 1548 | 1549 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/index.js: -------------------------------------------------------------------------------- 1 | amqp.js -------------------------------------------------------------------------------- /priv/node_modules/amqp/out: -------------------------------------------------------------------------------- 1 | got ready event 2 | SYSCALL(args) = return 3 | getpid(0x7FFF5FBFEE40, 0x7FFFFFE00050, 0x0) = 16929 0 4 | open_nocancel("/dev/urandom\0", 0x0, 0x0) = 4 0 5 | read_nocancel(0x4, "\303\020\204\316tc9\255\255P\376\261\020\375\035\263a\206U7\231\025\344\232Oi\310%K\025\370\022i\230\214v\335\021\330\214\327Kn\333\331\n\373\035\330\035/\201\023\314\316\273X\325\301\017\315\342\376%\320\220@ec\b_\313\231+\373\036\311\025~,M\324O\337k\305\373\t%\2111&\332\n\300\243\311\270\360g\275\262\222\304\022\"\260t\0", 0x6C) = 108 0 6 | close_nocancel(0x4) = 0 0 7 | issetugid(0x100000000, 0x7FFF5FBFF188, 0x7FFF5FC40530) = 0 0 8 | geteuid(0x100000000, 0x7FFF5FBFF188, 0x0) = 0 0 9 | __sysctl(0x7FFF5FBFCD30, 0x2, 0x7FFF5FBFCCF0) = 0 0 10 | __sysctl(0x7FFF5FBFCCF0, 0x2, 0x7FFF5FBFCD8C) = 0 0 11 | shared_region_check_np(0x7FFF5FBFCEF8, 0x0, 0x7FFF5FC1DC86) = 0 0 12 | stat64("/usr/lib/dtrace/libdtrace_dyld.dylib\0", 0x7FFF5FBFC300, 0x7FFF5FBFC940) = 0 0 13 | open("/usr/lib/dtrace/libdtrace_dyld.dylib\0", 0x0, 0x0) = 4 0 14 | pread(0x4, "\312\376\272\276\0", 0x1000, 0x0) = 4096 0 15 | pread(0x4, "\317\372\355\376\a\0", 0x1000, 0x1000) = 4096 0 16 | mmap(0x10047F000, 0x2000, 0x5, 0x12, 0x4, 0x7FFF00000001) = 0x47F000 0 17 | mmap(0x100481000, 0x1000, 0x3, 0x12, 0x4, 0x7FFF00000001) = 0x481000 0 18 | mmap(0x100482000, 0x1F10, 0x1, 0x12, 0x4, 0x7FFF00000001) = 0x482000 0 19 | close(0x4) = 0 0 20 | stat64("/opt/local/lib/libssl.0.9.8.dylib\0", 0x7FFF5FBFC050, 0x7FFF5FBFC690) = 0 0 21 | open("/opt/local/lib/libssl.0.9.8.dylib\0", 0x0, 0x0) = 4 0 22 | pread(0x4, "\317\372\355\376\a\0", 0x1000, 0x0) = 4096 0 23 | mmap(0x100484000, 0x42000, 0x5, 0x12, 0x4, 0x7FFF00000001) = 0x484000 0 24 | mmap(0x1004C6000, 0x6000, 0x3, 0x12, 0x4, 0x7FFF00000001) = 0x4C6000 0 25 | mmap(0x1004CC000, 0xCBB8, 0x1, 0x12, 0x4, 0x7FFF00000001) = 0x4CC000 0 26 | fcntl(0x4, 0x2C, 0x7FFF5FBFA350) = 0 0 27 | close(0x4) = 0 0 28 | stat64("/opt/local/lib/libcrypto.0.9.8.dylib\0", 0x7FFF5FBFC050, 0x7FFF5FBFC690) = 0 0 29 | open("/opt/local/lib/libcrypto.0.9.8.dylib\0", 0x0, 0x0) = 4 0 30 | pread(0x4, "\317\372\355\376\a\0", 0x1000, 0x0) = 4096 0 31 | mmap(0x1004D9000, 0x114000, 0x5, 0x12, 0x4, 0x7FFF00000001) = 0x4D9000 0 32 | mmap(0x1005ED000, 0x25000, 0x3, 0x12, 0x4, 0x7FFF00000001) = 0x5ED000 0 33 | mmap(0x100614000, 0x3B008, 0x1, 0x12, 0x4, 0x7FFF00000001) = 0x614000 0 34 | fcntl(0x4, 0x2C, 0x7FFF5FBFA340) = 0 0 35 | madvise(0x100614000, 0x2000, 0x2) = 0 0 36 | close(0x4) = 0 0 37 | stat64("/opt/local/lib/libz.1.dylib\0", 0x7FFF5FBFC050, 0x7FFF5FBFC690) = 0 0 38 | open("/opt/local/lib/libz.1.dylib\0", 0x0, 0x0) = 4 0 39 | pread(0x4, "\317\372\355\376\a\0", 0x1000, 0x0) = 4096 0 40 | mmap(0x100650000, 0x15000, 0x5, 0x12, 0x4, 0x7FFF00000001) = 0x650000 0 41 | mmap(0x100665000, 0x1000, 0x3, 0x12, 0x4, 0x7FFF00000001) = 0x665000 0 42 | mmap(0x100666000, 0x1818, 0x1, 0x12, 0x4, 0x7FFF00000001) = 0x666000 0 43 | fcntl(0x4, 0x2C, 0x7FFF5FBFA420) = 0 0 44 | close(0x4) = 0 0 45 | stat64("/usr/lib/libSystem.B.dylib\0", 0x7FFF5FBFC050, 0x7FFF5FBFC690) = 0 0 46 | stat64("/usr/lib/libstdc++.6.dylib\0", 0x7FFF5FBFC050, 0x7FFF5FBFC690) = 0 0 47 | stat64("/usr/lib/system/libmathCommon.A.dylib\0", 0x7FFF5FBFBAD0, 0x7FFF5FBFC110) = 0 0 48 | ioctl(0x4, 0x80086804, 0x7FFF5FBFCC90) = 0 0 49 | close(0x4) = 0 0 50 | __sysctl(0x7FFF5FBFCB80, 0x2, 0x7FFF5FBFCB70) = 0 0 51 | bsdthread_register(0x7FFF83AA875C, 0x7FFF83A89378, 0x2000) = 0 0 52 | thread_selfid(0x7FFF83AA875C, 0x7FFF83A89378, 0x0) = 1024888 0 53 | open_nocancel("/dev/urandom\0", 0x0, 0x7FFF707CCB20) = 4 0 54 | read_nocancel(0x4, "\275\371\032*-\372\201\v5\2779g\351\204\236\177\257\374\034\363\2726\365+\235\277]DR\361\216\001\320;w\022\016\226\377\266)\236X\212^\236\200\206\347v\036\310\223?mOL\325#k\354\207v\376\0", 0x40) = 64 0 55 | close_nocancel(0x4) = 0 0 56 | mmap(0x0, 0x3000, 0x3, 0x1002, 0x1000000, 0x7FFF00000001) = 0x668000 0 57 | __sysctl(0x7FFF5FBFCA00, 0x2, 0x7FFF5FBFC9C0) = 0 0 58 | __sysctl(0x7FFF5FBFC9C0, 0x2, 0x7FFF5FBFCA58) = 0 0 59 | getpid(0x7FFF5FBFC950, 0x7FFFFFE00050, 0x0) = 16929 0 60 | open_nocancel("/dev/urandom\0", 0x0, 0x0) = 4 0 61 | read_nocancel(0x4, "\373!k\256Z\020\357g\225\264\005\362-nA\231\376@\222\252\216\0", 0x6C) = 108 0 62 | close_nocancel(0x4) = 0 0 63 | __sysctl(0x7FFF5FBFCA00, 0x2, 0x7FFF5FBFCA2C) = 0 0 64 | mmap(0x0, 0xD000, 0x3, 0x1002, 0x1000000, 0x7FFF00000001) = 0x66B000 0 65 | mmap(0x0, 0xD000, 0x3, 0x1002, 0x1000000, 0x7FFF00000001) = 0x678000 0 66 | mmap(0x0, 0x1000, 0x3, 0x1002, 0x1000000, 0x7FFF00000001) = 0x685000 0 67 | mmap(0x0, 0x200000, 0x3, 0x1002, 0x7000000, 0x7FFF00000001) = 0x686000 0 68 | munmap(0x100686000, 0x7A000) = 0 0 69 | munmap(0x100800000, 0x86000) = 0 0 70 | __sysctl(0x7FFF5FBFCB60, 0x2, 0x7FFF5FBFCB20) = 0 0 71 | __sysctl(0x7FFF5FBFCB20, 0x2, 0x7FFF707D9680) = 0 0 72 | __sysctl(0x7FFF5FBFCB60, 0x2, 0x7FFF5FBFCB20) = 0 0 73 | __sysctl(0x7FFF5FBFCB20, 0x2, 0x7FFF707D9684) = 0 0 74 | __sysctl(0x7FFF5FBFCB60, 0x2, 0x7FFF5FBFCB20) = 0 0 75 | __sysctl(0x7FFF5FBFCB20, 0x2, 0x7FFF707D9688) = 0 0 76 | mmap(0x0, 0x3000, 0x3, 0x1002, 0x1000000, 0x7FFF00000001) = 0x686000 0 77 | __sysctl(0x7FFF5FBFCB30, 0x2, 0x7FFF5FBFCAF0) = 0 0 78 | __sysctl(0x7FFF5FBFCAF0, 0x2, 0x7FFF5FBFCB88) = 0 0 79 | __sysctl(0x7FFF5FBFCB30, 0x2, 0x7FFF5FBFCB5C) = 0 0 80 | mmap(0x0, 0xD000, 0x3, 0x1002, 0x1000000, 0x7FFF00000001) = 0x689000 0 81 | mmap(0x0, 0xD000, 0x3, 0x1002, 0x1000000, 0x7FFF00000001) = 0x696000 0 82 | mmap(0x0, 0x200000, 0x3, 0x1002, 0x7000000, 0x7FFF00000001) = 0x800000 0 83 | munmap(0x100900000, 0x100000) = 0 0 84 | mmap(0x0, 0x21000, 0x3, 0x1002, 0x3000000, 0x100000001) = 0x6CC000 0 85 | madvise(0x1006CC000, 0x21000, 0x9) = 0 0 86 | mmap(0x0, 0x41000, 0x3, 0x1002, 0x3000000, 0x100000001) = 0xAC4000 0 87 | madvise(0x100AC4000, 0x41000, 0x9) = 0 0 88 | madvise(0x1006CC000, 0x21000, 0x9) = 0 0 89 | open_nocancel(".\0", 0x0, 0x0) = 9 0 90 | fstat64(0x9, 0x7FFF5FBFE2A0, 0x0) = 0 0 91 | fcntl_nocancel(0x9, 0x32, 0x7FFF5FBFE4E0) = 0 0 92 | close_nocancel(0x9) = 0 0 93 | stat64("/Users/ryan/projects/node-amqp\0", 0x7FFF5FBFE210, 0x0) = 0 0 94 | mmap(0x105840000, 0x40000, 0x7, 0x1012, 0xFF000000, 0x100000001) = 0x5840000 0 95 | mmap(0x0, 0x82000, 0x3, 0x1002, 0x3000000, 0x100000001) = 0xB05000 0 96 | mmap(0x105880000, 0x4000, 0x7, 0x1012, 0xFF000000, 0x100000001) = 0x5880000 0 97 | madvise(0x100B05000, 0x82000, 0x9) = 0 0 98 | madvise(0x100AC4000, 0x41000, 0x9) = 0 0 99 | madvise(0x1006CC000, 0x21000, 0x9) = 0 0 100 | madvise(0x1006CC000, 0x21000, 0x9) = 0 0 101 | open("/Users/ryan/projects/node-amqp/x.js\0", 0x0, 0x1B6) = 9 0 102 | read(0x9, "amqp = require('./amqp');\nsys = require('sys');\n\ninspect = sys.inspect;\nputs = sys.puts;\nvar creds = { host: process.env['AMQP_HOST'] || 'localhost'\n , login: process.env['AMQP_LOGIN'] || 'guest'\n , password: process.e", 0x1000) = 638 0 103 | pread(0x9, "amqp = require('./amqp');\nsys = require('sys');\n\ninspect = sys.inspect;\nputs = sys.puts;\nvar creds = { host: process.env['AMQP_HOST'] || 'localhost'\n , login: process.env['AMQP_LOGIN'] || 'guest'\n , password: process.e", 0x1000, 0x27E) = 0 0 104 | close(0x9) = 0 0 105 | stat64("/Users/ryan/projects/node-amqp/amqp.js\0", 0x7FFF5FBFE470, 0x100940151) = 0 0 106 | open("/Users/ryan/projects/node-amqp/amqp.js\0", 0x0, 0x1B6) = 9 0 107 | read(0x9, "var events = require('events'),\n sys = require('sys'),\n net = require('net'),\n protocol = require('./amqp-definitions-0-8'),\n Buffer = require('buffer').Buffer,\n Promise = require('./promise').Promise;\n\nfunction mixin () {\n // copy referenc", 0x1000) = 4096 0 108 | pread(0x9, " other functions for parsing the frame-body.\n debug('execute: ' + data.toString());\n for (var i = 0; i < data.length; i++) {\n switch (this.state) {\n case 'frameHeader':\n // Here we buffer the frame header. Remember, this is a fully\n ", 0x1000, 0x1000) = 4096 0 109 | pread(0x9, "er.utf8Slice(buffer.read, buffer.read+length);\n buffer.read += length;\n return s;\n}\n\n\nfunction parseLongString (buffer) {\n var length = parseInt(buffer, 4);\n var s = buffer.slice(buffer.read, buffer.read + length);\n buffer.read += length;\n return s;\n", 0x1000, 0x2000) = 4096 0 110 | pread(0x9, "nel, classInfo, weight, properties, size);\n }\n};\n\n\n// Network byte order serialization\n// (NOTE: javascript always uses network byte order for its ints.)\nfunction serializeInt (b, size, int) {\n if (b.used + size >= b.length) {\n throw new Error(\"write ", 0x1000, 0x3000) = 4096 0 111 | pread(0x9, " if (param) bitField |= (1 << bitIndex);\n bitIndex++;\n\n if (!fields[i+1] || fields[i+1].domain != 'bit') {\n debug('SET bit field ' + field.name + ' 0x' + bitField.toString(16));\n buffer[buffer.used++] = bitField;\n ", 0x1000, 0x4000) = 4096 0 112 | pread(0x9, "ngify(args));\n\n // Channel 0 is the control channel. If not zero then deligate to\n // one of the channel objects.\n\n if (channel > 0) {\n if (!this.channels[channel]) {\n debug(\"Received message on untracked channel.\");\n return;\n }\n if (", 0x1000, 0x5000) = 4096 0 113 | pread(0x9, " b[b.used++] = 2; // constants.frameHeader\n\n serializeInt(b, 2, channel);\n\n var lengthStart = b.used;\n\n serializeInt(b, 4, 0 /*dummy*/); // length\n\n var bodyStart = b.used;\n\n // HEADER'S BODY\n\n serializeInt(b, 2, classInfo.index); // class 60 for ", 0x1000, 0x6000) = 4096 0 114 | pread(0x9, "me, options);\n this.channels.push(exchange);\n this.exchanges[name] = exchange;\n return exchange;\n};\n\n// Publishes a message to the amq.topic exchange.\nConnection.prototype.publish = function (routingKey, body) {\n if (!this._defaultExchange) this._defau", 0x1000, 0x7000) = 4096 0 115 | pread(0x9, "icQos,\n { ticket: 0\n , prefetchSize: 0\n , prefetchCount: 1\n , global: false\n });\n });\n }\n\n // basic consume\n var rawOptions = { noAck: !options.ack };\n return this.subscribeRaw(rawOptions, function (m) {\n", 0x1000, 0x8000) = 4096 0 116 | pread(0x9, "annel, classInfo, weight, properties, size) {\n mixin(this.currentMessage, properties);\n this.currentMessage.read = 0;\n this.currentMessage.size = size;\n\n this.emit('rawMessage', this.currentMessage);\n};\n\n\nQueue.prototype._onContent = function (channel,", 0x1000, 0x9000) = 3740 0 117 | pread(0x9, "annel, classInfo, weight, properties, size) {\n mixin(this.currentMessage, properties);\n this.currentMessage.read = 0;\n this.currentMessage.size = size;\n\n this.emit('rawMessage', this.currentMessage);\n};\n\n\nQueue.prototype._onContent = function (channel,", 0x1000, 0x9E9C) = 0 0 118 | close(0x9) = 0 0 119 | mmap(0x105884000, 0x4000, 0x7, 0x1012, 0xFF000000, 0x100000001) = 0x5884000 0 120 | mmap(0x105888000, 0x40000, 0x7, 0x1012, 0xFF000000, 0x100000001) = 0x5888000 0 121 | mmap(0x1058C8000, 0x4000, 0x7, 0x1012, 0xFF000000, 0x100000001) = 0x58C8000 0 122 | madvise(0x100B05000, 0x82000, 0x9) = 0 0 123 | madvise(0x100AC4000, 0x41000, 0x9) = 0 0 124 | madvise(0x1006CC000, 0x21000, 0x9) = 0 0 125 | mmap(0x1058CC000, 0x4000, 0x7, 0x1012, 0xFF000000, 0x100000001) = 0x58CC000 0 126 | mmap(0x1058D0000, 0x4000, 0x7, 0x1012, 0xFF000000, 0x100000001) = 0x58D0000 0 127 | mmap(0x1058D4000, 0x6000, 0x7, 0x1012, 0xFF000000, 0x100000001) = 0x58D4000 0 128 | madvise(0x100B05000, 0x82000, 0x9) = 0 0 129 | madvise(0x100AC4000, 0x41000, 0x9) = 0 0 130 | madvise(0x1006CC000, 0x21000, 0x9) = 0 0 131 | madvise(0x1006CC000, 0x21000, 0x9) = 0 0 132 | getrlimit(0x1008, 0x7FFF5FBFDCE0, 0x7FFF83A72CCC) = 0 0 133 | open_nocancel("/etc/resolv.conf\0", 0x0, 0x1B6) = 9 0 134 | fstat64(0x9, 0x7FFF5FBFDC70, 0x7FFF5FBFDD3C) = 0 0 135 | read_nocancel(0x9, "#\n# Mac OS X Notice\n#\n# This file is not used by the host name and address resolution\n# or the DNS query routing mechanisms used by most processes on\n# this Mac OS X system.\n#\n# This file is automatically generated.\n#\ndomain joyent.local\nnameserver 8.8.8.8", 0x1000) = 276 0 136 | read_nocancel(0x9, "\0", 0x1000) = 0 0 137 | close_nocancel(0x9) = 0 0 138 | open_nocancel("/etc/nsswitch.conf\0", 0x0, 0x1B6) = -1 Err#2 139 | open_nocancel("/etc/host.conf\0", 0x0, 0x1B6) = -1 Err#2 140 | open_nocancel("/etc/svc.conf\0", 0x0, 0x1B6) = -1 Err#2 141 | open_nocancel("/dev/urandom\0", 0x0, 0x1B6) = 9 0 142 | fstat64(0x9, 0x7FFF5FBFDCB0, 0x7FFF5FBFDD7C) = 0 0 143 | ioctl(0x9, 0x4004667A, 0x7FFF5FBFDCFC) = 0 0 144 | read_nocancel(0x9, "\327\376\214\307\235\361\336\357\006\234\315\331\204v\323\016'\326\303\266',\177\036\226\356\343\316\3169\210\274DD\333\322\271+1ix\254\337\312~\231\207B\214\327>\305C8Vr^\362\352QD\035\247\027\346?\205:\032\200\263\326\222D\324\217\352\202\033\343\213.\240\316\005\223d\216\365Q\t\037HQ\323\354\240u\027\345\262L\227\177\221&S\"\302\3506?\023*W\206*\355\3530\240\213\226\032\2404\026Q\020\374\177\376\260@\241\347\032V\304h\0", 0x10000) = 65536 0 145 | close_nocancel(0x9) = 0 0 146 | mmap(0x0, 0x40000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0xB87000 0 147 | mmap(0x0, 0x4000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0x6ED000 0 148 | mmap(0x0, 0x7000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0x6F1000 0 149 | mmap(0x0, 0x8000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0x6F8000 0 150 | mmap(0x0, 0x4000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0xBC7000 0 151 | mmap(0x0, 0x4000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0xBCB000 0 152 | mmap(0x0, 0x4000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0xBCF000 0 153 | mmap(0x0, 0x7000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0xBD3000 0 154 | mmap(0x0, 0x7000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0xBDA000 0 155 | mmap(0x0, 0x41000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0xBE1000 0 156 | stat64("/Users/ryan/projects/node-amqp/amqp-definitions-0-8.js\0", 0x7FFF5FBFE210, 0x100940151) = 0 0 157 | open("/Users/ryan/projects/node-amqp/amqp-definitions-0-8.js\0", 0x0, 0x1B6) = 9 0 158 | read(0x9, "exports.constants = [[1,\"frameMethod\"],[2,\"frameHeader\"],[3,\"frameBody\"],[4,\"frameOobMethod\"],[5,\"frameOobHeader\"],[6,\"frameOobBody\"],[7,\"frameTrace\"],[8,\"frameHeartbeat\"],[200,\"replySuccess\"],[206,\"frameEnd\"],[310,\"notDelivered\"],[311,\"contentTooLarge\"],[", 0x1000) = 4096 0 159 | pread(0x9, "it\"},{\"name\":\"durable\",\"domain\":\"bit\"},{\"name\":\"exclusive\",\"domain\":\"bit\"},{\"name\":\"autoDelete\",\"domain\":\"bit\"},{\"name\":\"nowait\",\"domain\":\"bit\"},{\"name\":\"arguments\",\"domain\":\"table\"}]},{\"name\":\"declareOk\",\"index\":11,\"fields\":[{\"name\":\"queue\",\"domain\":\"shor", 0x1000, 0x1000) = 4096 0 160 | pread(0x9, ",{\"name\":\"reject\",\"index\":90,\"fields\":[{\"name\":\"deliveryTag\",\"domain\":\"longlong\"},{\"name\":\"requeue\",\"domain\":\"bit\"}]},{\"name\":\"recover\",\"index\":100,\"fields\":[{\"name\":\"requeue\",\"domain\":\"bit\"}]}]},{\"name\":\"file\",\"index\":70,\"fields\":[{\"name\":\"contentType\",\"d", 0x1000, 0x2000) = 4096 0 161 | pread(0x9, "liver\",\"index\":60,\"fields\":[{\"name\":\"consumerTag\",\"domain\":\"shortstr\"},{\"name\":\"deliveryTag\",\"domain\":\"longlong\"},{\"name\":\"exchange\",\"domain\":\"shortstr\"},{\"name\":\"queue\",\"domain\":\"shortstr\"}]}]},{\"name\":\"tx\",\"index\":90,\"fields\":[],\"methods\":[{\"name\":\"selec", 0x1000, 0x3000) = 2086 0 162 | pread(0x9, "liver\",\"index\":60,\"fields\":[{\"name\":\"consumerTag\",\"domain\":\"shortstr\"},{\"name\":\"deliveryTag\",\"domain\":\"longlong\"},{\"name\":\"exchange\",\"domain\":\"shortstr\"},{\"name\":\"queue\",\"domain\":\"shortstr\"}]}]},{\"name\":\"tx\",\"index\":90,\"fields\":[],\"methods\":[{\"name\":\"selec", 0x1000, 0x3826) = 0 0 163 | close(0x9) = 0 0 164 | madvise(0x1006CC000, 0x21000, 0x9) = 0 0 165 | stat64("/Users/ryan/projects/node-amqp/promise.js\0", 0x7FFF5FBFE260, 0x100940151) = 0 0 166 | open("/Users/ryan/projects/node-amqp/promise.js\0", 0x0, 0x1B6) = 9 0 167 | read(0x9, "var events = require('events');\nvar inherits = require('sys').inherits;\n\nexports.Promise = function () {\n events.EventEmitter.call(this);\n this._blocking = false;\n this.hasFired = false;\n this._values = undefined;\n};\ninherits(exports.Promise, events.Ev", 0x1000) = 2038 0 168 | pread(0x9, "var events = require('events');\nvar inherits = require('sys').inherits;\n\nexports.Promise = function () {\n events.EventEmitter.call(this);\n this._blocking = false;\n this.hasFired = false;\n this._values = undefined;\n};\ninherits(exports.Promise, events.Ev", 0x1000, 0x7F6) = 0 0 169 | close(0x9) = 0 0 170 | open_nocancel("/etc/hosts\0", 0x0, 0x1B6) = 9 0 171 | fstat64(0x9, 0x7FFF5FBFE230, 0x7FFF5FBFE2FC) = 0 0 172 | read_nocancel(0x9, "##\n# Host Database\n#\n# localhost is used to configure the loopback interface\n# when the system is booting. Do not change this entry.\n##\n127.0.0.1\tlocalhost\n255.255.255.255\tbroadcasthost\n::1 localhost \nfe80::1%lo0\tlocalhost\nme\":\"prefetchCount\",", 0x1000) = 236 0 173 | close_nocancel(0x9) = 0 0 174 | socket(0x2, 0x1, 0x0) = 9 0 175 | fcntl(0x9, 0x4, 0x4) = 0 0 176 | fcntl(0x9, 0x2, 0x1) = 0 0 177 | connect(0x9, 0x100381F00, 0x10) = -1 Err#36 178 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 1 0 179 | getsockopt(0x9, 0xFFFF, 0x1007) = 0 0 180 | write(0x9, "AMQP\001\001\b\0", 0x8) = 8 0 181 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 1 0 182 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 0 0 183 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 1 0 184 | read(0x9, "\001\0", 0xA000) = 298 0 185 | write(0x9, "\001\0", 0x94) = 148 0 186 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 1 0 187 | read(0x9, "\001\0", 0x9ED6) = 20 0 188 | mmap(0x0, 0x20000, 0x3, 0x1002, 0x3000000, 0x7FFF00000000) = 0xC22000 0 189 | write(0x9, "\001\0", 0x14) = 20 0 190 | mmap(0x0, 0x20000, 0x3, 0x1002, 0x3000000, 0x7FFF00000000) = 0xC42000 0 191 | write(0x9, "\001\0", 0x10) = 16 0 192 | thread_selfid(0x7FFF707C3600, 0x0, 0xFFFFFFFF) = 1024895 0 193 | write(0x1, "got ready event\n@\246\241\001\027\021\211\aC\"\211\027\337\016e\r1\037l\344\244\321\305a\200)(7\310\322%\313\270\373\225]\267\b\031ot\320\257;TMz$Aaz\223\374\023\333\220z|\246\204^\242\364Q\"\266\356\301l\234\365\311R\025\231X\3320l8\374\037FT\276\241\203d\366A\247\246\376\250\030vd\215\257:\313\300\f\262\2736\250\336\227oIQ\300\b\361\3654\372\351[[j+\321D\210X`\260\006 \361\266E\035\307\360\356\003\306\341u\204\235\336\331\256\373\273?i\321&\366\001\310\252\315.p%\353\027\206\360%\217\202\263|\301&CrU\212\210\313\252\2213\020E#H\351v\b\203\203\353\016\301x]k\"\n\f\312\342Q\253\032o\201\273\nW\271\037\\\330\322\032\312@\375L\357L\n\241Z\310\231\245\250\250\314\0", 0x10) = 16 0 194 | write(0x6, "\0", 0x1) = 1 0 195 | madvise(0x7FFF8925F000, 0x2000, 0x5) = 0 0 196 | madvise(0x100614000, 0x2000, 0x5) = 0 0 197 | open("/dev/dtracehelper\0", 0x2, 0x7FFF5FC454A8) = 4 0 198 | mmap(0x0, 0x28000, 0x3, 0x1002, 0x3000000, 0x22800900000000) = 0x6A3000 0 199 | mmap(0x0, 0x1000, 0x3, 0x1002, 0x3000000, 0x22800900000000) = 0x6CB000 0 200 | mmap(0x0, 0x1000000, 0x3, 0x1002, 0x2000000, 0x22800900000000) = 0x900000 0 201 | munmap(0x100900000, 0x700000) = 0 0 202 | munmap(0x101800000, 0x100000) = 0 0 203 | sigaction(0xD, 0x7FFF5FBFEED0, 0x0) = 0 0 204 | getuid(0x0, 0x7FFF5FBFEED0, 0x0) = 502 0 205 | geteuid(0x0, 0x7FFF5FBFEED0, 0x0) = 0 0 206 | pipe(0x100382190, 0x7FFFFFE00050, 0x0) = 4 0 207 | fcntl(0x4, 0x2, 0x1) = 0 0 208 | fcntl(0x4, 0x4, 0x4) = 0 0 209 | fcntl(0x6, 0x2, 0x1) = 0 0 210 | fcntl(0x6, 0x4, 0x4) = 0 0 211 | sigaction(0x14, 0x7FFF5FBFEE90, 0x0) = 0 0 212 | sigprocmask(0x2, 0x7FFF5FBFEEC8, 0x0) = 0x0 0 213 | mmap(0x0, 0x4000000, 0x0, 0x1042, 0xFF000000, 0x100000001) = 0x1800000 0 214 | mmap(0x102000000, 0x100000, 0x3, 0x1012, 0xFF000000, 0x100000001) = 0x2000000 0 215 | mmap(0x103000000, 0x100000, 0x3, 0x1012, 0xFF000000, 0x100000001) = 0x3000000 0 216 | __sysctl(0x7FFF5FBFEC70, 0x2, 0x7FFF707D1658) = 0 0 217 | mmap(0x0, 0x40000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0x900000 0 218 | mmap(0x0, 0x40000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0x940000 0 219 | mmap(0x0, 0x20000000, 0x0, 0x1042, 0xFF000000, 0x100000001) = 0x5800000 0 220 | mmap(0x105800000, 0x40000, 0x7, 0x1012, 0xFF000000, 0x100000001) = 0x5800000 0 221 | mmap(0x0, 0x81000, 0x3, 0x1002, 0x3000000, 0x100000001) = 0x980000 0 222 | mmap(0x0, 0x40000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0xA01000 0 223 | mmap(0x0, 0x40000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0xA41000 0 224 | mmap(0x0, 0x43000, 0x3, 0x1002, 0xFF000000, 0x100000001) = 0xA81000 0 225 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 1 0 226 | read(0x9, "\001\0", 0x9EC2) = 21 0 227 | ioctl(0x1, 0x4004667A, 0x7FFF5FBFD6BC) = -1 Err#25 228 | ioctl(0x1, 0x40487413, 0x7FFF5FBFD6C0) = -1 Err#25 229 | ioctl(0x1, 0x4004667A, 0x7FFF5FBFD74C) = -1 Err#25 230 | ioctl(0x1, 0x40487413, 0x7FFF5FBFD750) = -1 Err#25 231 | __pthread_sigmask(0x3, 0x7FFF5FBFDABC, 0x7FFF5FBFDAB8) = 0 0 232 | bsdthread_create(0x1000353C0, 0x100714730, 0x10000) = 13053952 0 233 | __pthread_sigmask(0x3, 0x7FFF5FBFDAB8, 0x0) = 0 0 234 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 1 0 235 | mmap(0x0, 0x200000, 0x3, 0x1002, 0x7000000, 0x1FFFFFFFF) = 0xC75000 0 236 | munmap(0x100C75000, 0x8B000) = 0 0 237 | munmap(0x100E00000, 0x75000) = 0 0 238 | read(0x4, "\0", 0x1) = 1 0 239 | write(0x6, "\031\0", 0x1) = 1 0 240 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 1 0 241 | read(0x4, "\031\0", 0x1) = 1 0 242 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 0 0 243 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 0 0 244 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 0 0 245 | mmap(0x102000000, 0x100000, 0x0, 0x1052, 0xFF000000, 0x22800907E94B7C) = 0x2000000 0 246 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 0 0 247 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 0 0 248 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 0 0 249 | mmap(0x102000000, 0x100000, 0x3, 0x1012, 0xFF000000, 0x22800907E94B7C) = 0x2000000 0 250 | mmap(0x0, 0x9000, 0x3, 0x1002, 0xFF000000, 0x22800907E94B7C) = 0xC75000 0 251 | mmap(0x0, 0x9000, 0x3, 0x1002, 0xFF000000, 0x22800907E94B7C) = 0xC7E000 0 252 | mmap(0x0, 0xC000, 0x3, 0x1002, 0xFF000000, 0x22800907E94B7C) = 0xC87000 0 253 | mmap(0x0, 0xA000, 0x3, 0x1002, 0xFF000000, 0x22800907E94B7C) = 0xC93000 0 254 | mmap(0x0, 0x4000, 0x3, 0x1002, 0xFF000000, 0x22800907E94B7C) = 0xC9D000 0 255 | mmap(0x0, 0x4000, 0x3, 0x1002, 0xFF000000, 0x22800907E94B7C) = 0xCA1000 0 256 | mmap(0x0, 0x4000, 0x3, 0x1002, 0xFF000000, 0x22800907E94B7C) = 0xCA5000 0 257 | mmap(0x0, 0x4000, 0x3, 0x1002, 0xFF000000, 0x22800907E94B7C) = 0xCA9000 0 258 | mmap(0x0, 0x8000, 0x3, 0x1002, 0xFF000000, 0x22800907E94B7C) = 0xCAD000 0 259 | mmap(0x0, 0x5000, 0x3, 0x1002, 0xFF000000, 0x22800907E94B7C) = 0xCB5000 0 260 | mmap(0x0, 0x5000, 0x3, 0x1002, 0xFF000000, 0x22800907E94B7C) = 0xCBA000 0 261 | madvise(0x100C42000, 0x20000, 0x9) = 0 0 262 | madvise(0x100C22000, 0x20000, 0x9) = 0 0 263 | madvise(0x1006CC000, 0x21000, 0x9) = 0 0 264 | mmap(0x103000000, 0x100000, 0x0, 0x1052, 0xFF000000, 0x100000001) = 0x3000000 0 265 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 0 0 266 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 0 0 267 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 0 0 268 | mmap(0x103000000, 0x100000, 0x3, 0x1012, 0xFF000000, 0x100000001) = 0x3000000 0 269 | munmap(0x100CB5000, 0x5000) = 0 0 270 | mmap(0x102100000, 0x100000, 0x3, 0x1012, 0xFF000000, 0x100000001) = 0x2100000 0 271 | mmap(0x103100000, 0x100000, 0x3, 0x1012, 0xFF000000, 0x100000001) = 0x3100000 0 272 | mmap(0x103100000, 0x100000, 0x0, 0x1052, 0xFF000000, 0x100000001) = 0x3100000 0 273 | mmap(0x102100000, 0x100000, 0x0, 0x1052, 0xFF000000, 0x100000001) = 0x2100000 0 274 | mmap(0x102000000, 0x100000, 0x0, 0x1052, 0xFF000000, 0x100000001) = 0x2000000 0 275 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 0 0 276 | mmap(0x102000000, 0x100000, 0x3, 0x1012, 0xFF000000, 0x100000001) = 0x2000000 0 277 | mmap(0x103000000, 0x100000, 0x0, 0x1052, 0xFF000000, 0x100000001) = 0x3000000 0 278 | select(0x20, 0x100700A70, 0x100700380, 0x0, 0x7FFF5FBFE800) = 1 0 279 | read(0x9, "/dev/dtracehelper\0", 0x9EAD) = 0 0 280 | close(0x1) = 0 0 281 | __semwait_signal(0x1503, 0x1603, 0x1) = -1 Err#4 282 | 283 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/package.json: -------------------------------------------------------------------------------- 1 | { "name" : "amqp" 2 | , "version" : "0.0.2" 3 | , "description" : "amqp bindings for RabbitMQ" 4 | , "author": "Ryan Dahl" 5 | , "main": "./amqp" 6 | } 7 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/promise.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var inherits = require('sys').inherits; 3 | 4 | exports.Promise = function () { 5 | events.EventEmitter.call(this); 6 | this._blocking = false; 7 | this.hasFired = false; 8 | this._values = undefined; 9 | }; 10 | inherits(exports.Promise, events.EventEmitter); 11 | 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 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/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 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/test.js: -------------------------------------------------------------------------------- 1 | var sys = require('sys'); 2 | var amqp = require('./amqp'); 3 | 4 | 5 | var connection = amqp.createConnection({host: 'localhost'}); 6 | 7 | 8 | connection.addListener('close', function (e) { 9 | if (e) { 10 | throw e; 11 | } else { 12 | sys.puts('connection closed.'); 13 | } 14 | }); 15 | 16 | 17 | connection.addListener('ready', function () { 18 | sys.puts("connected to " + connection.serverProperties.product); 19 | 20 | var exchange = connection.exchange('clock', {type: 'fanout'}); 21 | 22 | var q = connection.queue('my-events-receiver'); 23 | 24 | q.bind(exchange, "*").addCallback(function () { 25 | sys.puts("publishing message"); 26 | exchange.publish("message.json", {hello: 'world', foo: 'bar'}); 27 | exchange.publish("message.text", 'hello world', {contentType: 'text/plain'}); 28 | }); 29 | 30 | q.subscribe(function (m) { 31 | sys.puts("--- Message (" + m.deliveryTag + ", '" + m.routingKey + "') ---"); 32 | sys.puts("--- contentType: " + m.contentType); 33 | 34 | m.addListener('data', function (d) { 35 | sys.puts(d); 36 | }); 37 | 38 | m.addListener('end', function () { 39 | m.acknowledge(); 40 | sys.puts("--- END (" + m.deliveryTag + ", '" + m.routingKey + "') ---"); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/test/harness.js: -------------------------------------------------------------------------------- 1 | global.sys = require('sys'); 2 | puts = sys.puts; 3 | global.assert = require('assert'); 4 | global.amqp = require('../amqp'); 5 | 6 | var options = {}; 7 | if (process.argv[2]) { 8 | var server = process.argv[2].split(':'); 9 | if (server[0]) options.host = server[0]; 10 | if (server[1]) options.port = parseInt(server[1]); 11 | } 12 | 13 | global.connection = amqp.createConnection(); 14 | 15 | global.connection.addListener('error', function (e) { 16 | throw e; 17 | }) 18 | 19 | global.connection.addListener('close', function (e) { 20 | sys.puts('connection closed.'); 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/test/test-auto-delete-queue.js: -------------------------------------------------------------------------------- 1 | require('./harness'); 2 | 3 | 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 | function (messageCount, consumerCount) { 13 | puts('queue opened.'); 14 | assert.equal(0, messageCount); 15 | assert.equal(0, consumerCount); 16 | }); 17 | 18 | q.bind(e, "#").addCallback(function () { 19 | puts('bound'); 20 | // publish message, but don't consume it. 21 | e.publish('routingKey', {hello: 'world'}); 22 | puts('message published'); 23 | puts('closing connection...'); 24 | connection.end(); 25 | }); 26 | }); 27 | 28 | connection.addListener('close', function () { 29 | if (connects < 3) connection.reconnect(); 30 | }); 31 | 32 | 33 | process.addListener('exit', function () { 34 | assert.equal(3, connects); 35 | }); 36 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/test/test-default-exchange.js: -------------------------------------------------------------------------------- 1 | require('./harness'); 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'); 10 | 11 | q.bind("#"); 12 | 13 | q.subscribe(function (msg) { 14 | recvCount++; 15 | 16 | switch (msg._routingKey) { 17 | case 'message.msg1': 18 | assert.equal(1, msg.one); 19 | assert.equal(2, msg.two); 20 | break; 21 | 22 | case 'message.msg2': 23 | assert.equal('world', msg.hello); 24 | assert.equal('bar', msg.foo); 25 | break; 26 | 27 | default: 28 | throw new Error('unexpected routing key: ' + msg._routingKey); 29 | } 30 | }) 31 | .addCallback(function () { 32 | puts("publishing 2 msg messages"); 33 | connection.publish('message.msg1', {two:2, one:1}); 34 | connection.publish('message.msg2', {foo:'bar', hello: 'world'}); 35 | 36 | setTimeout(function () { 37 | // wait one second to receive the message, then quit 38 | connection.end(); 39 | }, 1000); 40 | }); 41 | }); 42 | 43 | 44 | process.addListener('exit', function () { 45 | assert.equal(2, recvCount); 46 | }); 47 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/test/test-headers.js: -------------------------------------------------------------------------------- 1 | require('./harness'); 2 | 3 | var recvCount = 0; 4 | var body = "the devil is in the headers"; 5 | 6 | connection.addListener('ready', function () { 7 | puts("connected to " + connection.serverProperties.product); 8 | 9 | var exchange = connection.exchange('node-simple-fanout', {type: 'fanout'}); 10 | 11 | var q = connection.queue('node-simple-queue'); 12 | 13 | q.bind(exchange, "*") 14 | 15 | q.subscribeRaw(function (m) { 16 | puts("--- Message (" + m.deliveryTag + ", '" + m.routingKey + "') ---"); 17 | puts("--- headers: " + JSON.stringify(m.headers)); 18 | 19 | recvCount++; 20 | 21 | assert.equal('bar', m.headers['foo']); 22 | }) 23 | .addCallback(function () { 24 | puts("publishing message"); 25 | exchange.publish("message.text", body, { headers: { foo: 'bar' } }); 26 | 27 | setTimeout(function () { 28 | // wait one second to receive the message, then quit 29 | connection.end(); 30 | }, 1000); 31 | }); 32 | }); 33 | 34 | process.addListener('exit', function () { 35 | assert.equal(1, recvCount); 36 | }); 37 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/test/test-json.js: -------------------------------------------------------------------------------- 1 | require('./harness'); 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'); 12 | 13 | q.bind(exchange, "*"); 14 | 15 | q.subscribe(function (json) { 16 | recvCount++; 17 | 18 | switch (json._routingKey) { 19 | case 'message.json1': 20 | assert.equal(1, json.one); 21 | assert.equal(2, json.two); 22 | break; 23 | 24 | case 'message.json2': 25 | assert.equal('world', json.hello); 26 | assert.equal('bar', json.foo); 27 | break; 28 | 29 | default: 30 | throw new Error('unexpected routing key: ' + json._routingKey); 31 | } 32 | }) 33 | .addCallback(function () { 34 | puts("publishing 2 json messages"); 35 | exchange.publish('message.json1', {two:2, one:1}); 36 | exchange.publish('message.json2', {foo:'bar', hello: 'world'}, {contentType: 'application/json'}); 37 | 38 | setTimeout(function () { 39 | // wait one second to receive the message, then quit 40 | connection.end(); 41 | }, 1000); 42 | }); 43 | }); 44 | 45 | 46 | process.addListener('exit', function () { 47 | assert.equal(2, recvCount); 48 | }); 49 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/test/test-shift.js: -------------------------------------------------------------------------------- 1 | require('./harness'); 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 e = connection.exchange(); 11 | var q = connection.queue('node-123ack-queue'); 12 | 13 | q.bind(e, 'ackmessage.*'); 14 | 15 | q.subscribe({ ack: true }, function (json) { 16 | recvCount++; 17 | puts('Got message ' + JSON.stringify(json)); 18 | 19 | if (recvCount == 1) { 20 | puts('Got message 1.. waiting'); 21 | assert.equal('A', json.name); 22 | setTimeout(function () { 23 | puts('shift!'); 24 | q.shift(); 25 | }, 1000); 26 | } else if (recvCount == 2) { 27 | puts('got message 2'); 28 | assert.equal('B', json.name); 29 | 30 | puts('closing connection'); 31 | 32 | connection.end(); 33 | } else { 34 | throw new Error('Too many message!'); 35 | } 36 | }) 37 | .addCallback(function () { 38 | puts("publishing 2 json messages"); 39 | 40 | e.publish('ackmessage.json1', { name: 'A' }); 41 | e.publish('ackmessage.json2', { name: 'B' }); 42 | }); 43 | }); 44 | 45 | 46 | process.addListener('exit', function () { 47 | assert.equal(2, recvCount); 48 | }); 49 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/test/test-simple.js: -------------------------------------------------------------------------------- 1 | require('./harness'); 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-simple-fanout', {type: 'fanout'}); 10 | 11 | var q = connection.queue('node-simple-queue'); 12 | 13 | q.bind(exchange, "*") 14 | 15 | q.subscribeRaw(function (m) { 16 | puts("--- Message (" + m.deliveryTag + ", '" + m.routingKey + "') ---"); 17 | puts("--- contentType: " + m.contentType); 18 | 19 | recvCount++; 20 | 21 | assert.equal('text/plain', m.contentType); 22 | 23 | var size = 0; 24 | m.addListener('data', function (d) { size += d.length; }); 25 | 26 | m.addListener('end', function () { 27 | assert.equal(body.length, size); 28 | m.acknowledge(); 29 | }); 30 | }) 31 | .addCallback(function () { 32 | puts("publishing message"); 33 | exchange.publish("message.text", body, {contentType: 'text/plain'}); 34 | 35 | setTimeout(function () { 36 | // wait one second to receive the message, then quit 37 | connection.end(); 38 | }, 1000); 39 | }); 40 | }); 41 | 42 | 43 | process.addListener('exit', function () { 44 | assert.equal(1, recvCount); 45 | }); 46 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/test/test-type-and-headers.js: -------------------------------------------------------------------------------- 1 | require('./harness'); 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 | var exchange = connection.exchange('node-simple-fanout', {type: 'fanout'}); 10 | 11 | var q = connection.queue('node-simple-queue'); 12 | 13 | q.bind(exchange, "*") 14 | 15 | q.subscribeRaw(function (m) { 16 | puts("--- Message (" + m.deliveryTag + ", '" + m.routingKey + "') ---"); 17 | puts("--- type: " + m.type); 18 | puts("--- headers: " + JSON.stringify(m.headers)); 19 | 20 | recvCount++; 21 | 22 | assert.equal('typeProperty', m.type); 23 | assert.equal('fooHeader', m.headers['foo']); 24 | }) 25 | .addCallback(function () { 26 | puts("publishing message"); 27 | exchange.publish("message.text", body, { 28 | headers: { foo: 'fooHeader' }, 29 | type: 'typeProperty', 30 | }); 31 | 32 | setTimeout(function () { 33 | // wait one second to receive the message, then quit 34 | connection.end(); 35 | }, 1000); 36 | }); 37 | }); 38 | 39 | 40 | process.addListener('exit', function () { 41 | assert.equal(1, recvCount); 42 | }); 43 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/util/delete-exchange.js: -------------------------------------------------------------------------------- 1 | sys = require('sys'); 2 | amqp = require('../amqp'); 3 | 4 | var name = process.argv[2]; 5 | sys.puts("exchange: " + name); 6 | 7 | var creds = 8 | { host: process.env['AMQP_HOST'] || 'localhost' 9 | , login: process.env['AMQP_LOGIN'] || 'guest' 10 | , password: process.env['AMQP_PASSWORD'] || 'guest' 11 | , vhost: process.env['AMQP_VHOST'] || '/' 12 | }; 13 | 14 | connection = amqp.createConnection(creds); 15 | 16 | connection.addListener('error', function (e) { 17 | throw e; 18 | }); 19 | 20 | connection.addListener('ready', function () { 21 | sys.puts("Connected"); 22 | var e = connection.exchange(name); 23 | e.destroy().addCallback(function () { 24 | sys.puts('exchange destroyed.'); 25 | connection.close(); 26 | }); 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/util/delete-queue.js: -------------------------------------------------------------------------------- 1 | sys = require('sys'); 2 | amqp = require('../amqp'); 3 | 4 | var name = process.argv[2]; 5 | sys.puts("exchange: " + name); 6 | 7 | var creds = 8 | { host: process.env['AMQP_HOST'] || 'localhost' 9 | , login: process.env['AMQP_LOGIN'] || 'guest' 10 | , password: process.env['AMQP_PASSWORD'] || 'guest' 11 | , vhost: process.env['AMQP_VHOST'] || '/' 12 | }; 13 | 14 | connection = amqp.createConnection(creds); 15 | 16 | connection.addListener('error', function (e) { 17 | throw e; 18 | }); 19 | 20 | connection.addListener('ready', function () { 21 | sys.puts("Connected"); 22 | var q = connection.queue(name); 23 | q.destroy().addCallback(function () { 24 | sys.puts('queue destroyed.'); 25 | connection.close(); 26 | }); 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /priv/node_modules/amqp/x.js: -------------------------------------------------------------------------------- 1 | amqp = require('./amqp'); 2 | sys = require('sys'); 3 | 4 | inspect = sys.inspect; 5 | puts = sys.puts; 6 | var creds = { host: process.env['AMQP_HOST'] || 'localhost' 7 | , login: process.env['AMQP_LOGIN'] || 'guest' 8 | , password: process.env['AMQP_PASSWORD'] || 'guest' 9 | , vhost: process.env['AMQP_VHOST'] || '/' 10 | }; 11 | 12 | connection = amqp.createConnection(creds); 13 | connection.addListener('ready', function () { 14 | sys.debug("got ready event"); 15 | }); 16 | connection.addListener('error', function () { 17 | sys.debug("got error event"); 18 | }); 19 | connection.addListener('close', function () { 20 | sys.debug("got close event"); 21 | }); 22 | -------------------------------------------------------------------------------- /priv/publish.mq: -------------------------------------------------------------------------------- 1 | mq.on error: {err -> 2 | err.printStackTrace() 3 | } 4 | 5 | mq.exchange(name: "webhooks.test", type: "topic", durable: false, autoDelete: true) { 6 | queue(name: "webhooks.test.q", durable: false, autoDelete: true, routingKey: "test.key") { 7 | publish id: "", method: "post", body: { msg, out -> 8 | msg.properties.contentType = "application/json" 9 | out.write("""1""".bytes) 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /priv/rabbitmq.config: -------------------------------------------------------------------------------- 1 | [ 2 | {rabbit_webhooks, [ 3 | {username, <<"guest">>}, 4 | {virtual_host, <<"/">>}, 5 | {webhooks, [ 6 | {test_one, [ 7 | {url, "http://localhost:8098/riak/status"}, 8 | {method, post}, 9 | {exchange, [ 10 | {exchange, <<"webhooks.test">>}, 11 | {type, <<"topic">>}, 12 | {auto_delete, true}, 13 | {durable, false} 14 | ]}, 15 | {queue, [ 16 | {queue, <<"webhooks.test.q">>}, 17 | {auto_delete, true} 18 | ]}, 19 | {routing_key, <<"#">>}, 20 | {max_send, {5, second}}, 21 | {send_if, [{between, {08, 00}, {17, 00}}]} 22 | ]} 23 | ]} 24 | ]} 25 | ]. -------------------------------------------------------------------------------- /priv/send_test.js: -------------------------------------------------------------------------------- 1 | var amqp = require("amqp"); 2 | 3 | var conn = amqp.createConnection(); 4 | conn.addListener("ready", function() { 5 | var x = conn.exchange("webhooks.test", {type: "topic", durable: false, autoDelete: true}); 6 | x.publish("test", {test: "value"}); 7 | 8 | setTimeout(function() { conn.end(); }, 500); 9 | }); -------------------------------------------------------------------------------- /priv/test.yml: -------------------------------------------------------------------------------- 1 | --- # Test YAML file for driving config file generation. 2 | 3 | # Broker configuration 4 | username : guest 5 | virtual_host : / 6 | 7 | # Use a YAML alias to reference this one exchange for all configs. 8 | exchange: &webhook_exchange 9 | name : webhooks 10 | # type : topic 11 | # auto_delete : true 12 | # durable : false 13 | 14 | # Webhooks configurations 15 | webhooks: 16 | - 17 | name : webhook1 # Name should be unique within the config file 18 | url : http://localhost:8098/riak/status 19 | method : get # get | post | put | delete 20 | exchange : *webhook_exchange 21 | queue: 22 | name : webhook1 # Best to have the queue name match the config name 23 | # auto_delete : true 24 | # routing_key : "#" 25 | # max_send: 26 | # frequency : 5 27 | # per : second # second | minute | hour | day | week 28 | # send_if: 29 | # between: 30 | # start_hour : 8 # 24-hour time 31 | # start_min : 0 32 | # end_hour : 17 # 24-hour time 33 | # end_min : 0 34 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbrisbin/rabbitmq-webhooks/c631358e957b9c07ea5910f8fa41ac6d1f60c2e5/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {lib_dirs, ["deps"]}. 2 | 3 | {deps, [ 4 | {amqp_client, ".*", {git, "git://github.com/jbrisbin/amqp_client.git", {tag, "rabbitmq-2.8.1"}}}, 5 | {lhttpc, ".*", {git, "git://github.com/benoitc/lhttpc.git", "HEAD"}} 6 | ]}. -------------------------------------------------------------------------------- /scripts/gen_config: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'rubygems' 3 | require 'liquid' 4 | require 'optparse' 5 | require 'yaml' 6 | 7 | TEMPLATE = " 8 | [ 9 | {rabbit_webhooks, [ 10 | {username, <<\"{{ username }}\">>}, 11 | {virtual_host, <<\"{{ virtual_host }}\">>}, 12 | {webhooks, [ 13 | {% for webhook in webhooks %} 14 | {'{{ webhook.name }}', [ 15 | {url, \"{{ webhook.url }}\"}, 16 | {method, {% if webhook.method %}{{ webhook.method | downcase }}{% else %}post{% endif %}}, 17 | {exchange, [ 18 | {exchange, <<\"{{ webhook.exchange.name }}\">>}, 19 | {type, <<\"{% if webhook.exchange.type %}{{ webhook.exchange.type }}{% else %}topic{% endif %}\">>}, 20 | {auto_delete, {% if webhook.exchange.auto_delete %}{{ webhook.exchange.auto_delete }}{% else %}true{% endif %}}, 21 | {durable, {% if webhook.exchange.durable %}{{ webhook.exchange.durable }}{% else %}false{% endif %}} 22 | ]}, 23 | {queue, [ 24 | {queue, <<\"{{ webhook.queue.name }}\">>}, 25 | {auto_delete, {% if webhook.queue.auto_delete %}{{ webhook.queue.auto_delete }}{% else %}true{% endif %}} 26 | ]}, 27 | {routing_key, <<\"{% if webhook.routing_key %}{{ webhook.routing_key }}{% else %}\#{% endif %}\">>} 28 | {% if webhook.max_send %} 29 | ,{max_send, { {{ webhook.max_send.frequency }}, {{ webhook.max_send.per }} } } 30 | {% endif %} 31 | {% if webhook.send_if %} 32 | ,{send_if, [{between, { {{ webhook.send_if.between.start_hour }}, {{ webhook.send_if.between.start_min }} }, { {{ webhook.send_if.between.end_hour }}, {{ webhook.send_if.between.end_min }} } }]} 33 | {% endif %} 34 | ]}{% unless forloop.last %},{% endunless %} 35 | {% endfor %} 36 | ]} 37 | ]} 38 | ]. 39 | " 40 | 41 | class OptsParser 42 | 43 | def self.parse(args) 44 | options 45 | end 46 | 47 | end 48 | 49 | options = {} 50 | OptionParser.new do |opts| 51 | opts.banner = "Usage: gen_config -c my_config.yml" 52 | 53 | opts.on("-c", "--config CONFIG", "Config file to drive generation") do |c| 54 | options[:config] = c 55 | end 56 | 57 | opts.on_tail("-h", "--help", "Show this message") do 58 | puts opts 59 | exit 60 | end 61 | end.parse!(ARGV) 62 | 63 | if options[:config] 64 | File.open(options[:config]) do |config| 65 | c = YAML::load(config) 66 | puts c 67 | 68 | t = Liquid::Template.parse(TEMPLATE) 69 | result = t.render(c) 70 | puts result 71 | end 72 | end -------------------------------------------------------------------------------- /src/rabbit_webhooks.app.src: -------------------------------------------------------------------------------- 1 | {application, rabbit_webhooks, [ 2 | {description, "Rabbit Webhooks Plugin"}, 3 | {vsn, "0.14"}, 4 | {modules, []}, 5 | {registered, []}, 6 | {mod, {rabbit_webhooks_app, []}}, 7 | {env, []}, 8 | {applications, [kernel, stdlib, rabbit, amqp_client, lhttpc]} 9 | ]}. 10 | -------------------------------------------------------------------------------- /src/rabbit_webhooks.erl: -------------------------------------------------------------------------------- 1 | % -*- tab-width: 2 -*- 2 | -module(rabbit_webhooks). 3 | -behaviour(gen_server). 4 | 5 | -include("rabbit_webhooks.hrl"). 6 | 7 | -export([start_link/2]). 8 | % gen_server callbacks 9 | -export([ 10 | init/1, 11 | handle_call/3, 12 | handle_cast/2, 13 | handle_info/2, 14 | terminate/2, 15 | code_change/3 16 | ]). 17 | % Helper methods 18 | -export([ 19 | send_request/6 20 | ]). 21 | 22 | -define(REQUESTED_WITH, "RabbitMQ-Webhooks"). 23 | -define(VERSION, "0.14"). 24 | -define(ACCEPT, "application/json;q=.9,text/plain;q=.8,text/xml;q=.6,application/xml;q=.7,text/html;q=.5,*/*;q=.4"). 25 | -define(ACCEPT_ENCODING, "gzip"). 26 | -define(SEC_MSEC, 1000). 27 | -define(MIN_MSEC, 60000). 28 | -define(HOUR_MSEC, 3600000). 29 | -define(DAY_MSEC, 86400000). 30 | -define(WEEK_MSEC, 604800000). 31 | 32 | -record(state, { channel, config=#webhook{}, queue, consumer, sent=0, mark, next_window }). 33 | 34 | start_link(_Name, Config) -> 35 | gen_server:start_link(?MODULE, [Config], []). 36 | 37 | init([Config]) -> 38 | Username = case application:get_env(username) of 39 | {ok, U} -> U; 40 | _ -> <<"guest">> 41 | end, 42 | %io:format("username: ~p~n", [Username]), 43 | VHost = case application:get_env(virtual_host) of 44 | {ok, V} -> V; 45 | _ -> <<"/">> 46 | end, 47 | %io:format("vhost: ~p~n", [VHost]), 48 | AmqpParams = #amqp_params_direct{ username = Username, virtual_host = VHost }, 49 | %io:format("params: ~p~n", [AmqpParams]), 50 | {ok, Connection} = amqp_connection:start(AmqpParams), 51 | {ok, Channel} = amqp_connection:open_channel(Connection), 52 | 53 | Webhook = #webhook{ 54 | url = proplists:get_value(url, Config), 55 | method = proplists:get_value(method, Config), 56 | exchange = case proplists:get_value(exchange, Config) of 57 | [{exchange, Xname} | Xconfig] -> #'exchange.declare'{ 58 | exchange = Xname, 59 | type = proplists:get_value(type, Xconfig, <<"topic">>), 60 | auto_delete = proplists:get_value(auto_delete, Xconfig, true), 61 | durable = proplists:get_value(durable, Xconfig, false) 62 | } 63 | end, 64 | queue = case proplists:get_value(queue, Config) of 65 | % Allow for load-balancing by using named queues (optional) 66 | [{queue, Qname} | Qconfig] -> #'queue.declare'{ 67 | queue=Qname, 68 | auto_delete=proplists:get_value(auto_delete, Qconfig, true), 69 | durable=proplists:get_value(durable, Qconfig, false) 70 | }; 71 | % Default to an anonymous queue 72 | _ -> #'queue.declare'{ auto_delete=true } 73 | end, 74 | routing_key = proplists:get_value(routing_key, Config), 75 | max_send = case proplists:get_value(max_send, Config) of 76 | {Max, second} -> {second, Max, ?SEC_MSEC}; 77 | {Max, minute} -> {minute, Max, ?MIN_MSEC}; 78 | {Max, hour} -> {hour, Max, ?HOUR_MSEC}; 79 | {Max, day} -> {day, Max, ?DAY_MSEC}; 80 | {Max, week} -> {week, Max, ?WEEK_MSEC}; 81 | {_, Freq} -> 82 | io:format("Invalid frequency: ~p~n", [Freq]), 83 | invalid; 84 | _ -> infinity 85 | end, 86 | send_if = proplists:get_value(send_if, Config, always) 87 | }, 88 | 89 | % Declare exchange 90 | case amqp_channel:call(Channel, Webhook#webhook.exchange) of 91 | #'exchange.declare_ok'{} -> error_logger:info_msg("Declared webhooks exchange ~p~n", [Webhook#webhook.exchange]); 92 | XError -> error_logger:error_msg("ERROR creating exchange ~p~n", [XError]) 93 | end, 94 | 95 | % Declare queue 96 | QName = case amqp_channel:call(Channel, Webhook#webhook.queue) of 97 | #'queue.declare_ok'{ queue=Q } -> 98 | error_logger:info_msg("Declared webhooks queue ~p~n", [Q]), 99 | Q; 100 | QError -> error_logger:error_msg("ERROR creating queue ~p~n", [QError]) 101 | end, 102 | 103 | % Bind queue 104 | QueueBind = #'queue.bind'{ 105 | queue=QName, 106 | exchange=(Webhook#webhook.exchange)#'exchange.declare'.exchange, 107 | routing_key=Webhook#webhook.routing_key 108 | }, 109 | case amqp_channel:call(Channel, QueueBind) of 110 | #'queue.bind_ok'{} -> error_logger:info_msg("Bound webhooks queue ~p -> ~p (~p)~n", [ 111 | Webhook#webhook.queue, 112 | Webhook#webhook.exchange, 113 | Webhook#webhook.routing_key 114 | ]); 115 | BError -> error_logger:error_msg("ERROR binding webhooks queue ~p~n", [BError]) 116 | end, 117 | 118 | amqp_selective_consumer:register_default_consumer(Channel, self()), 119 | 120 | erlang:send_after(100, self(), check_window), 121 | {ok, #state{ 122 | channel = Channel, 123 | config = Webhook, 124 | queue = QName, 125 | mark = get_time() } 126 | }. 127 | 128 | handle_call(Msg, _From, State=#state{ channel=_Channel, config=_Config }) -> 129 | rabbit_log:warning(" Unkown call: ~p~n State: ~p~n", [Msg, State]), 130 | {noreply, State}. 131 | 132 | handle_cast(Msg, State=#state{ channel=_Channel, config=_Config }) -> 133 | rabbit_log:warning(" Unkown cast: ~p~n State: ~p~n", [Msg, State]), 134 | {noreply, State}. 135 | 136 | handle_info(subscribe, State=#state{ channel=Channel, queue=Q }) -> 137 | % Subscribe to these events 138 | #'basic.consume_ok'{ consumer_tag=Tag } = amqp_channel:subscribe(Channel, #'basic.consume'{ queue=Q, no_ack=false}, self()), 139 | {noreply, State#state{ consumer=Tag }}; 140 | 141 | handle_info(cancel, State=#state{ channel=Channel, consumer=Tag }) -> 142 | amqp_channel:call(Channel, #'basic.cancel'{ consumer_tag=Tag }), 143 | {noreply, State#state{ consumer=undefined }}; 144 | 145 | handle_info(check_window, State=#state{ config=Config }) -> 146 | [Vote, NextWindow] = case Config#webhook.send_if of 147 | always -> [-1, always]; 148 | SendIf -> 149 | {_, {Hr, Min, _}} = erlang:localtime(), 150 | Time = hour_to_msec(Hr) + min_to_msec(Min), 151 | lists:foldl(fun (C, Acc) -> 152 | [Vote, Delay] = Acc, 153 | case C of 154 | {between, {StartHr, StartMin}, {EndHr, EndMin}} -> 155 | Start = hour_to_msec(StartHr) + min_to_msec(StartMin), 156 | End = hour_to_msec(EndHr) + min_to_msec(EndMin), 157 | %io:format("~p ~p ~p ~p ~p ~p~n", [Hr, Min, StartHr, StartMin, EndHr, EndMin]), 158 | case Time >= Start andalso Time < End of 159 | false -> 160 | NewDelay = case Start > Time of 161 | true -> (Start - Time); 162 | false -> (Start + (hour_to_msec(24) - Time)) 163 | end, 164 | %io:format("time: ~p~n", [[Vote - 1, NewDelay]]), 165 | [Vote - 1, case NewDelay > Delay of 166 | true -> 167 | case Delay of 168 | 0 -> NewDelay; 169 | _ -> Delay 170 | end; 171 | false -> NewDelay 172 | end]; 173 | true -> [Vote, 0] 174 | end; 175 | _ -> Acc 176 | end 177 | end, [length(SendIf), 0], SendIf) 178 | end, 179 | NewState = case Vote of 180 | 0 -> 181 | % Outside any submission window, update state 182 | rabbit_log:info("Outside submission window, checking again in: ~p minutes~n", [erlang:round(NextWindow/1000/60)]), 183 | self() ! cancel, 184 | erlang:send_after(NextWindow, self(), check_window), 185 | State#state{ next_window=NextWindow, mark=get_time() }; 186 | _ -> 187 | self() ! subscribe, 188 | case NextWindow of 189 | always -> 190 | State#state{ next_window=always }; 191 | NextWindow -> 192 | % Within submission window, check again in 15 secs 193 | erlang:send_after(15000, self(), check_window), 194 | State#state{ next_window=0 } 195 | end 196 | end, 197 | {noreply, NewState}; 198 | 199 | handle_info(#'basic.cancel_ok'{ consumer_tag=_Tag }, State) -> 200 | {noreply, State}; 201 | 202 | handle_info(#'basic.consume_ok'{ consumer_tag=_Tag }, State) -> 203 | {noreply, State}; 204 | 205 | handle_info({#'basic.deliver'{ delivery_tag=DeliveryTag }, 206 | #amqp_msg{ props=#'P_basic'{ 207 | content_type=ContentType, 208 | headers=Headers, 209 | reply_to=ReplyTo}, 210 | payload=Payload }=_Msg }, 211 | State=#state{ channel=Channel, config=Config }) -> 212 | 213 | % rabbit_log:debug("msg: ~p~n", [Msg]), 214 | % Transform message headers to HTTP headers 215 | [Xhdrs, Params] = process_headers(Headers), 216 | CT = case ContentType of 217 | undefined -> "application/octet-stream"; 218 | C -> C 219 | end, 220 | HttpHdrs = try Xhdrs ++ [{"Content-Type", binary_to_list(CT)}, 221 | {"Accept", ?ACCEPT}, 222 | {"Accept-Encoding", ?ACCEPT_ENCODING}, 223 | {"X-Requested-With", ?REQUESTED_WITH}, 224 | {"X-Webhooks-Version", ?VERSION} 225 | ] ++ case ReplyTo of 226 | undefined -> []; 227 | _ -> [{"X-ReplyTo", binary_to_list(ReplyTo)}] 228 | end 229 | catch 230 | Ex -> error_logger:error_msg("Error creating headaers: ~p~n", [Ex]) 231 | end, 232 | 233 | % Parameter-replace the URL 234 | Url = case proplists:get_value("url", Params) of 235 | undefined -> parse_url(Config#webhook.url, Params); 236 | NewUrl -> parse_url(NewUrl, Params) 237 | end, 238 | Method = case proplists:get_value("method", Params) of 239 | undefined -> Config#webhook.method; 240 | NewMethod -> list_to_atom(string:to_lower(NewMethod)) 241 | end, 242 | 243 | % Issue the actual request. 244 | worker_pool:submit_async( 245 | fun () -> 246 | send_request(Channel, DeliveryTag, Url, Method, HttpHdrs, Payload) 247 | end), 248 | 249 | Sent = State#state.sent + 1, 250 | NewState = case Config#webhook.max_send of 251 | infinity -> 252 | State#state{ sent=Sent }; 253 | {_, Max, _} when Sent < Max -> 254 | State#state{ sent=Sent }; 255 | {_, _, Delay} -> 256 | timer:sleep(Delay), 257 | State#state{ sent=0 } 258 | end, 259 | 260 | {noreply, NewState}; 261 | 262 | handle_info(Msg, State) -> 263 | rabbit_log:warning(" Unkown message: ~p~n State: ~p~n", [Msg, State]), 264 | {noreply, State}. 265 | 266 | terminate(_, #state{ channel=Channel, config=_Webhook, queue=_Q, consumer=Tag }) -> 267 | error_logger:info_msg("Terminating ~p ~p~n", [self(), Tag]), 268 | if 269 | Tag /= undefined -> amqp_channel:call(Channel, #'basic.cancel'{ consumer_tag = Tag }) 270 | end, 271 | amqp_channel:call(Channel, #'channel.close'{}), 272 | ok. 273 | 274 | code_change(_OldVsn, State, _Extra) -> 275 | {ok, State}. 276 | 277 | get_time() -> 278 | {Msec, Sec, Misecs} = now(), 279 | Misecs + (1000 * Sec) + (Msec * 1000000). 280 | 281 | hour_to_msec(Hr) -> 282 | (Hr * 60 * 60 * 1000). 283 | 284 | min_to_msec(Min) -> 285 | (Min * 60 * 1000). 286 | 287 | process_headers(Headers) -> 288 | lists:foldl(fun (Hdr, AllHdrs) -> 289 | case Hdr of 290 | {Key, _, Value} -> 291 | [HttpHdrs, Params] = AllHdrs, 292 | case <> of 293 | <<"X-">> -> 294 | [[{binary_to_list(Key), binary_to_list(Value)} | HttpHdrs], Params]; 295 | _ -> 296 | [HttpHdrs, [{binary_to_list(Key), binary_to_list(Value)} | Params]] 297 | end 298 | end 299 | end, [[], []], case Headers of 300 | undefined -> []; 301 | Else -> Else 302 | end). 303 | 304 | parse_url(From, Params) -> 305 | lists:foldl(fun (P, NewUrl) -> 306 | case P of 307 | {Param, Value} -> 308 | re:replace(NewUrl, io_lib:format("{~s}", [Param]), Value, [{return, list}]) 309 | end 310 | end, From, Params). 311 | 312 | send_request(Channel, DeliveryTag, Url, Method, HttpHdrs, Payload) -> 313 | try 314 | % Issue the actual request. 315 | case lhttpc:request(Url, Method, HttpHdrs, Payload, infinity) of 316 | % Only process if the server returns 20x. 317 | {ok, {{Status, _}, Hdrs, _Response}} when Status >= 200 andalso Status < 300 -> 318 | % TODO: Place result back on a queue? 319 | % rabbit_log:debug(" hdrs: ~p~n response: ~p~n", [Hdrs, Response]), 320 | % Check to see if we need to unzip this response 321 | case re:run(proplists:get_value("Content-Encoding", Hdrs, ""), "(gzip)", [{capture, [1], list}]) of 322 | nomatch -> 323 | % rabbit_log:debug("plain response: ~p~n", [Response]), 324 | ok; 325 | {match, ["gzip"]} -> 326 | % _Content = case Response of 327 | % <<>> -> <<>>; % Skip unzipping response 328 | % _ -> zlib:gunzip(Response) 329 | % end, 330 | % rabbit_log:debug("gzipped response: ~p~n", [Content]), 331 | ok 332 | end, 333 | amqp_channel:call(Channel, #'basic.ack'{ delivery_tag=DeliveryTag }); 334 | Else -> 335 | error_logger:error_msg("~p", [Else]) 336 | end 337 | catch Ex -> error_logger:error_msg("Error requesting ~p: ~p~n", [Url, Ex]) end. 338 | 339 | -------------------------------------------------------------------------------- /src/rabbit_webhooks_app.erl: -------------------------------------------------------------------------------- 1 | -module(rabbit_webhooks_app). 2 | -export([start/0, stop/0, start/2, stop/1]). 3 | 4 | start() -> 5 | rabbit_webhooks_sup:start_link(), ok. 6 | 7 | stop() -> 8 | ok. 9 | 10 | start(normal, []) -> 11 | rabbit_webhooks_sup:start_link(). 12 | 13 | stop(_State) -> 14 | ok. -------------------------------------------------------------------------------- /src/rabbit_webhooks_sup.erl: -------------------------------------------------------------------------------- 1 | % -*- tab-width: 2 -*- 2 | %%% ------------------------------------------------------------------- 3 | %%% Author : J. Brisbin 4 | %%% Description : RabbitMQ plugin providing webhooks functionality. 5 | %%% 6 | %%% Created : Aug 24, 2010 7 | %%% ------------------------------------------------------------------- 8 | -module(rabbit_webhooks_sup). 9 | -author("J. Brisbin "). 10 | 11 | -behaviour(supervisor). 12 | 13 | -include("rabbit_webhooks.hrl"). 14 | 15 | -export([start_link/0, init/1]). 16 | 17 | %% -------------------------------------------------------------------- 18 | %% Macros 19 | %% -------------------------------------------------------------------- 20 | -define(SERVER, ?MODULE). 21 | 22 | %% -------------------------------------------------------------------- 23 | %% Records 24 | %% -------------------------------------------------------------------- 25 | 26 | %% ==================================================================== 27 | %% External functions 28 | %% ==================================================================== 29 | start_link() -> 30 | io:format("Configuring Webhooks...", []), 31 | Pid = supervisor:start_link({local, ?MODULE}, ?MODULE, []), 32 | io:format("done~n", []), 33 | Pid. 34 | 35 | %% ==================================================================== 36 | %% Server functions 37 | %% ==================================================================== 38 | %% -------------------------------------------------------------------- 39 | %% Func: init/1 40 | %% Returns: {ok, {SupFlags, [ChildSpec]}} | 41 | %% ignore | 42 | %% {error, Reason} 43 | %% -------------------------------------------------------------------- 44 | init([]) -> 45 | Workers = case application:get_env(webhooks) of 46 | {ok, W} -> make_worker_configs(W); 47 | _ -> 48 | io:format("no configs found...", []), 49 | [] 50 | end, 51 | % One worker per config element 52 | {ok, {{one_for_one, 3, 10}, Workers}}. 53 | 54 | make_worker_configs(Configs) -> 55 | lists:foldl(fun (Config, Acc) -> 56 | case Config of 57 | {Name, C} -> 58 | [{Name, 59 | {rabbit_webhooks, start_link, [Name, C] }, 60 | permanent, 61 | 10000, 62 | worker, 63 | [ rabbit_webhooks ] 64 | } | Acc] 65 | end 66 | end, [], Configs). 67 | --------------------------------------------------------------------------------