rack-rabbit 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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).