rack-rabbit 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/EXAMPLES.md +212 -0
  4. data/Gemfile +14 -0
  5. data/Gemfile.lock +42 -0
  6. data/LICENSE +21 -0
  7. data/README.md +412 -0
  8. data/Rakefile +5 -0
  9. data/bin/rack-rabbit +96 -0
  10. data/bin/rr +99 -0
  11. data/lib/rack-rabbit.rb +63 -0
  12. data/lib/rack-rabbit/adapter.rb +85 -0
  13. data/lib/rack-rabbit/adapter/amqp.rb +114 -0
  14. data/lib/rack-rabbit/adapter/bunny.rb +87 -0
  15. data/lib/rack-rabbit/adapter/mock.rb +92 -0
  16. data/lib/rack-rabbit/client.rb +181 -0
  17. data/lib/rack-rabbit/config.rb +260 -0
  18. data/lib/rack-rabbit/handler.rb +44 -0
  19. data/lib/rack-rabbit/message.rb +95 -0
  20. data/lib/rack-rabbit/middleware/program_name.rb +34 -0
  21. data/lib/rack-rabbit/response.rb +43 -0
  22. data/lib/rack-rabbit/server.rb +263 -0
  23. data/lib/rack-rabbit/signals.rb +62 -0
  24. data/lib/rack-rabbit/subscriber.rb +77 -0
  25. data/lib/rack-rabbit/worker.rb +84 -0
  26. data/rack-rabbit.gemspec +26 -0
  27. data/test/apps/config.ru +7 -0
  28. data/test/apps/custom.conf +27 -0
  29. data/test/apps/custom.ru +7 -0
  30. data/test/apps/empty.conf +1 -0
  31. data/test/apps/error.ru +7 -0
  32. data/test/apps/mirror.ru +19 -0
  33. data/test/apps/sinatra.ru +37 -0
  34. data/test/apps/sleep.ru +21 -0
  35. data/test/test_case.rb +154 -0
  36. data/test/unit/middleware/test_program_name.rb +32 -0
  37. data/test/unit/test_client.rb +275 -0
  38. data/test/unit/test_config.rb +403 -0
  39. data/test/unit/test_handler.rb +92 -0
  40. data/test/unit/test_message.rb +213 -0
  41. data/test/unit/test_response.rb +59 -0
  42. data/test/unit/test_signals.rb +45 -0
  43. data/test/unit/test_subscriber.rb +140 -0
  44. metadata +91 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 919fa7db9bf34a8358ba5393faac1f4cb777f4e3
4
+ data.tar.gz: 7d102a9f0e29a85ef653d768fc0ef1ccac5d521a
5
+ SHA512:
6
+ metadata.gz: fee6fd72ec96b95ec755f4c2806298ccba06fd84693a5c0b924bf77716f89b3cae2d3b29d1ee98c7f0820682430697507682d16c0a661e7cbe06c09ee331fefe
7
+ data.tar.gz: b7c3e6f3f61b84aeaadd444d20d3f2d0de8814b7ac82cb2465af40ff6062d3f3d56e3e5fc6e8026e34c2c9540a4015724edb09ac990a498b7eeec9ffc952e4ce
@@ -0,0 +1,3 @@
1
+
2
+ *.swp
3
+ *.gem
@@ -0,0 +1,212 @@
1
+ # Examples
2
+
3
+ ## Contents
4
+
5
+ - [Synchronous Request/Response (using Rack)](#synchronous-requestresponse-using-rack)
6
+ - [Synchronous Request/Response (using Sinatra)](#synchronous-requestresponse-using-sinatra)
7
+ - [Asynchronous Worker Queue](#asynchronous-worker-queue)
8
+ - [Asynchronous Publish/Subscribe with a fanout exchange](#asynchronous-publishsubscribe-with-a-fanout-exchange)
9
+ - [Asynchronous Publish/Subscribe with a topic exchange](#asynchronous-publishsubscribe-with-a-topic-exchange)
10
+
11
+ ## Synchronous Request/Response (using Rack)
12
+
13
+ Consider this simple rack application in `config.ru`:
14
+
15
+ class Service
16
+ def self.call(env)
17
+ request = Rack::Request.new(env)
18
+ method = request.request_method
19
+ path = request.path_info
20
+ body = request.body.read
21
+ message = "#{method} #{path} #{body}"
22
+ [ 200, {}, [ message ]]
23
+ end
24
+ end
25
+ run Service
26
+
27
+ Host and load balance this service using `rack-rabbit`:
28
+
29
+ $ rack-rabbit --queue myqueue --workers 4 config.ru
30
+
31
+ Connect to the worker from the command line using the `rr` command:
32
+
33
+ $ rr -q myqueue /hello # synchronous GET request/response
34
+ GET /hello
35
+
36
+ $ rr -q myqueue POST /submit "data" # synchronous POST request/response
37
+ POST /submit data
38
+
39
+ $ rr -q myqueue PUT /update "data" # synchronous PUT request/response
40
+ PUT /update data
41
+
42
+ $ rr -q myqueue DELETE /resource # synchronous DELETE request/response
43
+ DELETE /resource
44
+
45
+
46
+ Connect to the worker from your application using the `RR` class.
47
+
48
+ require 'rack-rabbit/client'
49
+
50
+ RR.get :myqueue, "/hello" # returns "GET /hello"
51
+ RR.post :myqueue, "/submit", "data" # returns "POST /submit data"
52
+ RR.put :myqueue, "/update", "data" # returns "PUT /update data"
53
+ RR.delete :myqueue, "/resource", # returns "DELETE /resource"
54
+
55
+
56
+ ## Synchronous Request/Response (using Sinatra)
57
+
58
+ Consider this simple sinatra application in `config.ru`:
59
+
60
+ require 'sinatra/base'
61
+
62
+ class Service < Sinatra::Base
63
+
64
+ get "/hello" do
65
+ "Hello World"
66
+ end
67
+
68
+ post "/submit" do
69
+ "Submitted #{request.body.read}"
70
+ end
71
+
72
+ put "/update" do
73
+ "Updated #{request.body.read}"
74
+ end
75
+
76
+ delete "/resource" do
77
+ "Deleted resource"
78
+ end
79
+
80
+ end
81
+
82
+ run Service
83
+
84
+ Host and load balance this service using `rack-rabbit`:
85
+
86
+ $ rack-rabbit --queue myqueue --workers 4 config.ru
87
+
88
+ Connect to the worker from the command line using the `rr` command:
89
+
90
+ $ rr request -q myqueue GET /hello
91
+ Hello World
92
+
93
+ $ rr request -q myqueue POST /submit "data"
94
+ Submitted data
95
+
96
+ $ rr request -q myqueue PUT /update "data"
97
+ Updated data
98
+
99
+ $ rr request -q myqueue DELETE /resource
100
+ Deleted resource
101
+
102
+ Connect to the worker from your application using the `RR` class:
103
+
104
+ require 'rack-rabbit/client'
105
+
106
+ RR.get :myqueue, "/hello" # returns "Hello World"
107
+ RR.post :myqueue, "/submit", "data" # returns "Submitted data"
108
+ RR.put :myqueue, "/update", "data" # returns "Updated data"
109
+ RR.delete :myqueue, "/resource" # returns "Deleted resource"
110
+
111
+
112
+ ## Asynchronous Worker Queue
113
+
114
+ Consider this simple sinatra application in `config.ru`:
115
+
116
+ require 'sinatra/base'
117
+
118
+ class Service < Sinatra::Base
119
+
120
+ post "/work do
121
+ logger.info "do some work using #{request.body.read}"
122
+ end
123
+
124
+ post "/more/work" do
125
+ logger.info "do more work using #{request.body.read}"
126
+ end
127
+
128
+ end
129
+
130
+ run Service
131
+
132
+ Host and load balance this service using `rack-rabbit`:
133
+
134
+ $ rack-rabbit --queue myqueue --workers 4 config.ru
135
+
136
+ Enqueue some work from the command line using the `rr` command:
137
+
138
+ $ rr enqueue -q myqueue /work "data" # asynchronous ENQUEUE to a worker
139
+ $ rr enqueue -q myqueue /more/work "more data" # (ditto)
140
+
141
+ Enqueue some work from your application using the `RR` class:
142
+
143
+ require 'rack-rabbit/client'
144
+
145
+ RR.enqueue :myqueue, :path => "/work", :body => "data"
146
+ RR.enqueue :myqueue, :path => "/more/work" :body => "more data"
147
+
148
+ ## Asynchronous Publish/Subscribe with a fanout exchange
149
+
150
+ Consider two potential subscribers:
151
+
152
+ First `foo.ru`
153
+
154
+ require 'sinatra/base'
155
+
156
+ class Foo < Sinatra::Base
157
+ post "/event do
158
+ logger.info "Foo saw the event"
159
+ end
160
+ end
161
+ run Foo
162
+
163
+ Then `bar.ru`
164
+
165
+ require 'sinatra/base'
166
+
167
+ class Bar < Sinatra::Base
168
+ post "/event do
169
+ logger.info "Bar saw the event"
170
+ end
171
+ end
172
+ run Bar
173
+
174
+ Host these subscribers using `rack-rabbit`:
175
+
176
+ $ rack-rabbit --exchange myexchange --type fanout foo.ru &
177
+ $ rack-rabbit --exchange myexchange --type fanout bar.ru &
178
+
179
+ Publish the event from the command line using the `rr` command:
180
+
181
+ $ rr publish -e myexchange -t fanout "/event" "data"
182
+
183
+ Publish the event from your application using the `RR` class:
184
+
185
+ require 'rack-rabbit/client'
186
+
187
+ RR.publish :myexchange, :type => :fanout, :path => "/event", :body => "data"
188
+
189
+ >> **All subscribers should see the event when using a fanout exchange**
190
+
191
+
192
+ ## Asynchronous Publish/Subscribe with a topic exchange
193
+
194
+ Consider the same two subscribers as in the previous example, but host them by binding to a routed topic exchange:
195
+
196
+ $ rack-rabbit --exchange myexchange --type topic --route A foo.ru &
197
+ $ rack-rabbit --exchange myexchange --type topic --route B bar.ru &
198
+
199
+ Publish a routed event from the command line using the `rr` command:
200
+
201
+ $ rr publish -e myexchange -t topic -r A "/event" # only received by foo
202
+ $ rr publish -e myexchange -t topic -r B "/event" # only received by bar
203
+
204
+ Publish a routed event from your application using the `RR` class:
205
+
206
+ require 'rack-rabbit/client'
207
+
208
+ RR.publish :myexchange, :type => :topic, :route => "A", :path => "/event" # only received by foo
209
+ RR.publish :myexchange, :type => :topic, :route => "B", :path => "/event" # only received by bar
210
+
211
+ >> **Subscribers should only see events that match their route when using a topic exchange**
212
+
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :test do
4
+ gem "minitest", "~>4.7.0"
5
+ gem "timecop"
6
+ gem "mocha"
7
+ end
8
+
9
+ group :development do
10
+ gem "pry"
11
+ gem "bunny"
12
+ gem "amqp"
13
+ gem "sinatra"
14
+ end
@@ -0,0 +1,42 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ amq-protocol (1.9.2)
5
+ amqp (1.4.1)
6
+ amq-protocol (>= 1.9.2)
7
+ eventmachine
8
+ bunny (1.4.0)
9
+ amq-protocol (>= 1.9.2)
10
+ coderay (1.1.0)
11
+ eventmachine (1.0.3)
12
+ metaclass (0.0.4)
13
+ method_source (0.8.2)
14
+ minitest (4.7.5)
15
+ mocha (1.1.0)
16
+ metaclass (~> 0.0.1)
17
+ pry (0.10.1)
18
+ coderay (~> 1.1.0)
19
+ method_source (~> 0.8.1)
20
+ slop (~> 3.4)
21
+ rack (1.5.2)
22
+ rack-protection (1.5.3)
23
+ rack
24
+ sinatra (1.4.5)
25
+ rack (~> 1.4)
26
+ rack-protection (~> 1.4)
27
+ tilt (~> 1.3, >= 1.3.4)
28
+ slop (3.6.0)
29
+ tilt (1.4.1)
30
+ timecop (0.7.1)
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ amqp
37
+ bunny
38
+ minitest (~> 4.7.0)
39
+ mocha
40
+ pry
41
+ sinatra
42
+ timecop
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Jake Gordon
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 deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ 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 all
13
+ 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 FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,412 @@
1
+ # Rack Rabbit (v0.5.0)
2
+
3
+ A forking server for hosting rabbitMQ consumer processes as load balanced rack applications.
4
+
5
+ $ rack-rabbit --queue myqueue --workers 4 app/config.ru
6
+
7
+ ## Contents
8
+
9
+ - [Summary](#summary)
10
+ - [Installation](#installation)
11
+ - [Getting started by example](#getting-started-by-example)
12
+ - [More examples](https://github.com/jakesgordon/rack-rabbit/blob/master/EXAMPLES.md)
13
+ - [Server usage](#server-usage)
14
+ - [Server configuration](#server-configuration)
15
+ - [Signals](#signals)
16
+ - [Forking worker processes](#forking-worker-processes)
17
+ - [RabbitMQ acknowledgements](#rabbitmq-acknowledgements)
18
+ - [Client binary](#client-binary)
19
+ - [Client library](#client-library)
20
+ - [Supported platforms](#supported-platforms)
21
+ - [TODO](#todo)
22
+ - [License](#license)
23
+ - [Credits](#credits)
24
+ - [Contact](#contact)
25
+
26
+ ## Summary
27
+
28
+ What Unicorn does for HTTP services, RackRabbit can do for hosting AMQP services, and more:
29
+
30
+ | | HTTP | AMQP |
31
+ |--------------------------------------|-----------------|-----------------------|
32
+ | Make a synchronous request/response | Unicorn | rabbitMQ + RackRabbit |
33
+ | Asynchronous worker queue | Redis + Resque | rabbitMQ + RackRabbit |
34
+ | Asynchronous publish/subscribe | Redis | rabbitMQ + RackRabbit |
35
+
36
+ RackRabbit hosts a cluster of worker processes that:
37
+ * Subscribe to a queue/exchange
38
+ * Convert incoming messages into a suitable Rack environment
39
+ * Call your Rack app to handle the message
40
+ * Publish a reply back to the original caller (if `reply_to` was provided)
41
+
42
+ RackRabbit supports a variety of messaging patterns:
43
+
44
+ * Synchronous Request/Response _(e.g. GET/POST/PUT/DELETE)_
45
+ * Asynchronous Worker queue _(e.g. ENQUEUE)_
46
+ * Asynchronous PubSub _(e.g. PUBLISH)_
47
+
48
+ ## Installation
49
+
50
+ Install a rabbitMQ server if necessary ([docs](https://www.rabbitmq.com/download.html)):
51
+
52
+ $ sudo apt-get install rabbitmq-server
53
+
54
+ Update your Gemfile to include RackRabbit and your preferred rabbitMQ client library
55
+
56
+ gem bunny, "~> 1.4" # or an alternative such as AMQP or march-hare
57
+ gem rack-rabbit, "~> 0.1"
58
+
59
+
60
+ ## Getting started by example
61
+
62
+ You can use RackRabbit to host an AMQP service as a rack application in the same way
63
+ that you might use Unicorn to host an HTTP service.
64
+
65
+ Imagine a simple rack application in `config.ru`:
66
+
67
+ class Service
68
+ def self.call(env)
69
+ request = Rack::Request.new(env)
70
+ method = request.request_method
71
+ path = request.path_info
72
+ body = request.body.read
73
+ message = "#{method} #{path} #{body}"
74
+ [ 200, {}, [ message ]]
75
+ end
76
+ end
77
+ run Service
78
+
79
+ You can host and load balance this service using `rack-rabbit`:
80
+
81
+ $ rack-rabbit --queue myqueue --workers 4 config.ru
82
+
83
+ Ensure the worker processes are running:
84
+
85
+ $ ps xawf | grep rack-rabbit
86
+ 15714 pts/4 Sl+ 0:00 | \_ ruby rack-rabbit --queue myqueue --workers 4 config.ru
87
+ 15716 pts/4 Sl+ 0:00 | \_ rack-rabbit -- waiting for request
88
+ 15718 pts/4 Sl+ 0:00 | \_ rack-rabbit -- waiting for request
89
+ 15721 pts/4 Sl+ 0:00 | \_ rack-rabbit -- waiting for request
90
+ 15723 pts/4 Sl+ 0:00 | \_ rack-rabbit -- waiting for request
91
+
92
+ Connect to the worker from the command line using the `rr` command:
93
+
94
+ $ rr -q myqueue /hello # synchronous GET request/response
95
+ GET /hello
96
+
97
+ $ rr -q myqueue POST /submit "data" # synchronous POST request/response
98
+ POST /submit data
99
+
100
+ $ rr -q myqueue PUT /update "data" # synchronous PUT request/response
101
+ PUT /update data
102
+
103
+ $ rr -q myqueue DELETE /resource # synchronous DELETE request/response
104
+ DELETE /resource
105
+
106
+ Connect to the worker from your applications using the `RR` class.
107
+
108
+ require 'rack-rabbit/client'
109
+
110
+ RR.get :myqueue, "/hello" # returns "GET /hello"
111
+ RR.post :myqueue, "/submit", "data" # returns "POST /submit data"
112
+ RR.put :myqueue, "/update", "data" # returns "PUT /update data"
113
+ RR.delete :myqueue, "/resource", # returns "DELETE /resource"
114
+
115
+
116
+ See [EXAMPLES.md](https://github.com/jakesgordon/rack-rabbit/blob/master/EXAMPLES.md) for
117
+ more detailed examples, including ENQUEUE and PUBLISH communication patterns, and using Sinatra.
118
+
119
+
120
+ ## Server usage
121
+
122
+ Use the `rack-rabbit` executable to host your Rack app in a forking server that
123
+ subscribes to either a named queue or an exchange.
124
+
125
+ $ rack-rabbit --help
126
+
127
+ A load balanced rack server for hosting rabbitMQ consumer processes.
128
+
129
+ Usage: rack-rabbit [options] rack-file
130
+
131
+ Examples:
132
+
133
+ rack-rabbit -h broker -q my.queue # subscribe to a named queue
134
+ rack-rabbit -h broker -e my.exchange -t fanout # subscribe to a fanout exchange
135
+ rack-rabbit -h broker -e my.exchange -t topic -r my.topic # subscribe to a topic exchange with a routing key
136
+ rack-rabbit -c rack-rabbit.conf # subscribe with advanced options provided by a config file
137
+
138
+ RackRabbit options:
139
+ -c, --config CONFIG provide options using a rack-rabbit configuration file
140
+ -q, --queue QUEUE subscribe to a queue for incoming requests
141
+ -e, --exchange EXCHANGE subscribe to an exchange for incoming requests
142
+ -t, --type TYPE subscribe to an exchange for incoming requests - type (e.g. :direct, :fanout, :topic)
143
+ -r, --route ROUTE subscribe to an exchange for incoming requests - routing key
144
+ -a, --app_id ID an app_id for this application server
145
+ --host HOST the rabbitMQ broker IP address (default: 127.0.0.1)
146
+ --port PORT the rabbitMQ broker port (default: 5672)
147
+
148
+ Process options:
149
+ -w, --workers COUNT the number of worker processes (default: 1)
150
+ -d, --daemonize run daemonized in the background (default: false)
151
+ -p, --pid PIDFILE the pid filename (default when daemonized: /var/run/<app_id>.pid)
152
+ -l, --log LOGFILE the log filename (default when daemonized: /var/log/<app_id>.log)
153
+ --log-level LEVEL the log level for rack rabbit output (default: info)
154
+ --preload preload the rack app before forking worker processes (default: false)
155
+
156
+ Ruby options:
157
+ -I, --include PATH an additional $LOAD_PATH (may be used more than once)
158
+ --debug set $DEBUG to true
159
+ --warn enable warnings
160
+
161
+ Common options:
162
+ -h, --help
163
+ -v, --version
164
+
165
+
166
+
167
+ ## Server configuration
168
+
169
+ Detailed configuration can be provided by an external config file using the `--config` option
170
+
171
+ # set the Rack application to be used to handle messages (default 'config.ru'):
172
+ rack_file 'app/config.ru'
173
+
174
+ # set the rabbitMQ connection:
175
+ rabbit :host => '10.0.0.42', # default '127.0.0.1'
176
+ :port => '1234' # default '5672'
177
+ :adapter => :amqp # default :bunny
178
+
179
+ # subscribe to a queue:
180
+ queue 'my.queue'
181
+
182
+ # ... or, subscribe to an exchange:
183
+ exchange 'my.exchange'
184
+ exchange_type :topic
185
+ routing_key 'my.topic'
186
+
187
+ # set the app_id used to identify your application in response messages
188
+ app_id 'my-application'
189
+
190
+ # enable rabbitMQ acknowledgements (default: false):
191
+ ack true
192
+
193
+ # set the initial number of worker processes (default: 1):
194
+ workers 8
195
+
196
+ # set the minimum number of worker processes (default: 1):
197
+ min_workers 1
198
+
199
+ # set the maximum number of worker processes (default: 100):
200
+ max_workers 16
201
+
202
+ # preload the Rack app in the server for faster worker forking (default: false):
203
+ preload_app true
204
+
205
+ # daemonize the process (default: false)
206
+ daemonize true
207
+
208
+ # set the path to the logfile
209
+ logfile "/var/log/my-application.log"
210
+
211
+ # set the path to the pidfile
212
+ pidfile "/var/run/my-application.pid"
213
+
214
+ # set the log level for the Rack Rabbit logger (default: info)
215
+ log_level 'debug'
216
+
217
+ # set the Logger to used by the Rack Rabbit server and the worker Rack applications (default: Logger)
218
+ logger MyLogger.new
219
+
220
+ ## Signals
221
+
222
+ Signals should be sent to the master process
223
+
224
+ * HUP - reload the RackRabbit config file and gracefully restart all workers
225
+ * QUIT - graceful shutdown, waits for workers to complete handling of their current message before finishing
226
+ * TERM - quick shutdown kills all workers immediately
227
+ * INT - quick shutdown kills all workers immediately
228
+ * TTIN - increase the number of worker processes by one
229
+ * TTOU - decrease the number of worker processes by one
230
+
231
+ ## Forking worker processes
232
+
233
+ If you are using the `preload_app` directive, your app will be loaded into the master
234
+ server process before any workers have forked. Therefore, you may need to re-initialize
235
+ resources after each worker process forks, e.g if using ActiveRecord:
236
+
237
+ before_fork do |server|
238
+ # no need for connection in the server process
239
+ defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
240
+ end
241
+
242
+ after_fork do |server, worker|
243
+ # reestablish connection in each worker process
244
+ defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
245
+ end
246
+
247
+ This should NOT be needed when the `preload_app` directive is false.
248
+
249
+ >> _this is an issue with any preforking style server (e.g. Unicorn)_
250
+
251
+ ## RabbitMQ acknowledgements
252
+
253
+ By default, an AMQP broker removes a message from the queue immediately after sending it to
254
+ the consumer. If the consumer dies before processing the message completely then the message
255
+ is lost. Users who need more control can configure the broker to use explicit
256
+ acknowledgements ([learn more](http://rubybunny.info/articles/queues.html#message_acknowledgements))
257
+ by setting the RackRabbit `ack` configuration option to `true`.
258
+
259
+ With explicit acknowledgements enabled...
260
+
261
+ - If your rack handler succeeds (returns a 2xx status code) then RackRabbit will automatically send
262
+ an acknowledgement to rabbitMQ.
263
+
264
+ - If your rack handler fails (throws an exception or returns a non-2xx status code) then RackRabbit
265
+ will automatically send a rejection to rabbitMQ - you might want to setup a dead-letter queue for
266
+ these rejections.
267
+
268
+ - If your rack handler process crashes then rabbitMQ will hand the message off to the next available
269
+ worker process.
270
+
271
+ If your service action is idempotent then nothing more is needed.
272
+
273
+ However, if you need more fine-grained controls, then RackRabbit exposes the underlying message to your
274
+ application in the rack environment as `env['rabbit.message']`. You can use this object to explicitly
275
+ acknowledge or reject the message at any time during your rack handler, e.g:
276
+
277
+ post "/work" do
278
+
279
+ message = request.env['rabbit.message']
280
+
281
+ # ... do some preliminary work (idempotent)
282
+
283
+ if everything_looks_good
284
+
285
+ message.ack # take responsibility
286
+ ... # and do some more work (that might not be idempotent)
287
+
288
+ else
289
+
290
+ message.reject # reject the message
291
+ ... # and (maybe) do some more work
292
+
293
+ end
294
+
295
+ end
296
+
297
+ ## Client binary
298
+
299
+ Communicating with a RackRabbit hosted service from the command line can be done using the `rr` binary:
300
+
301
+ Make a request to a RackRabbit service.
302
+
303
+ Usage: rr <command> [options] [METHOD] [PATH] [BODY]
304
+
305
+ list of commands:
306
+
307
+ request make a synchronous request to a rabbitMQ queue and wait for a reply
308
+ enqueue make an asynchronous request to a rabbitMQ queue and continue
309
+ publish make an asynchronous request to a rabbitMQ exchange with a routing key
310
+ help show help for a given topic or a help overview
311
+ version show version
312
+
313
+ Examples:
314
+
315
+ rr request -q queue GET /hello # submit GET to queue and WAIT for reply
316
+ rr request -q queue POST /submit 'data' # submit POST to queue and WAIT for reply
317
+ rr enqueue -q queue POST /submit 'data' # submit POST to queue and CONTINUE
318
+ rr enqueue -q queue DELETE /resource # submit DELETE to queue and CONTINUE
319
+ rr publish -e ex -t fanout POST /event # submit POST to a fanout exchange and CONTINUE
320
+ rr publish -e ex -t topic -r foo POST /submit 'data' # submit POST to a topic exchange with routing key and CONTINUE
321
+
322
+ RackRabbit options:
323
+ --host HOST the rabbitMQ broker IP address (default: 127.0.0.1)
324
+ --port PORT the rabbitMQ broker port (default: 5672)
325
+ -q, --queue QUEUE a queue for publishing outgoing requests
326
+ -e, --exchange EXCHANGE publish to a non-default exchange - name
327
+ -t, --type TYPE publish to a non-default exchange - type (e.g. :direct, :fanout, :topic)
328
+ -r, --route ROUTE a routing key when publishing to a non-default exchange
329
+
330
+ Ruby options:
331
+ -I, --include PATH specify an additional $LOAD_PATH (may be used more than once)
332
+ --debug set $DEBUG to true
333
+ --warn enable warnings
334
+
335
+ Common options:
336
+ -h, --help
337
+ -v, --version
338
+
339
+ ## Client library
340
+
341
+ Communicating with a RackRabbit hosted service from your application can be done using the `RR` class:
342
+
343
+ RR.get( "myqueue", "/path/to/resource")
344
+ RR.post( "myqueue", "/path/to/resource", "content")
345
+ RR.put( "myqueue", "/path/to/resource", "content")
346
+ RR.delete( "myqueue", "/path/to/resource")
347
+ RR.enqueue("myqueue", "/path/to/resource", "content")
348
+ RR.publish("myexchange", "/path/to/resource", "content")
349
+
350
+ These methods are wrappers around a more detailed `RackRabbit::Client` class:
351
+
352
+ client = RackRabbit::Client.new(:host => "127.0.0.1", :port => 5672, :adapter => :bunny)
353
+
354
+ client.get( "myqueue", "/path/to/resource")
355
+ client.post( "myqueue", "/path/to/resource", "content")
356
+ client.put( "myqueue", "/path/to/resource", "content")
357
+ client.delete("myqueue", "/path/to/resource")
358
+ client.enqueue("myqueue", "/path/to/resource", "content")
359
+ client.publish("myexchange", "/path/to/resource", "content")
360
+
361
+ client.disconnect
362
+
363
+ More advanced options can be passed as an (optional) last parameter, e.g:
364
+
365
+ client.post("myqueue", "/path", content.to_json, {
366
+ :headers => { "additional" => "header" }, # made available in the service's rack env
367
+ :priority => 5, # specify the rabbitMQ message priority
368
+ :content_type => "application/json", # specify the request content_type
369
+ :content_encoding => "utf-8" # specify the request content_enoding
370
+ })
371
+
372
+ client.publish("myexchange", "/path", "content", {
373
+ :exchange_type => :topic, # specify the rabbitMQ exchange type
374
+ :routing_key => "my.custom.topic", # specify a custom rabbitMQ routing key
375
+ })
376
+
377
+
378
+ ## Supported platforms
379
+
380
+ * MRI 2.1.2
381
+ * MRI 1.9.3
382
+
383
+ ## TODO
384
+
385
+ * FEATURE - allow multiple Client#reqeust in parallel (block until all have replied) - a-la-typheous
386
+ * FEATURE - share a single reply queue across all Client#request
387
+ * FEATURE - automatically deserialize body for known content type (e.g. json)
388
+ * FEATURE - have exception stack trace sent back to client in development/test mode
389
+ * FEATURE - support JRuby
390
+ * FEATURE - support Rubinius
391
+ * BUG - avoid infinte worker spawn loop if worker fails during startup (e.g. connection to rabbit fails)
392
+ * TEST - integration tests for worker, server, adapter/bunny, and adapter/amqp
393
+
394
+ ## License
395
+
396
+ See [LICENSE](https://github.com/jakesgordon/rack-rabbit/blob/master/LICENSE) file.
397
+
398
+ ## Credits
399
+
400
+ Thanks to [Jesse Storimer](http://www.jstorimer.com/) for his book
401
+ [Working with Unix Processes](http://www.jstorimer.com/products/working-with-unix-processes)
402
+
403
+ Thanks to the [Unicorn Team](http://unicorn.bogomips.org/) for providing a great
404
+ example of a preforking server.
405
+
406
+ Thanks to the [Bunny Team](http://rubybunny.info/) for providing an easy rabbitMQ Ruby client.
407
+
408
+ ## Contact
409
+
410
+ If you have any ideas, feedback, requests or bug reports, you can reach me at
411
+ [jake@codeincomplete.com](mailto:jake@codeincomplete.com), or via
412
+ my website: [Code inComplete](http://codeincomplete.com).