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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/EXAMPLES.md +212 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +42 -0
- data/LICENSE +21 -0
- data/README.md +412 -0
- data/Rakefile +5 -0
- data/bin/rack-rabbit +96 -0
- data/bin/rr +99 -0
- data/lib/rack-rabbit.rb +63 -0
- data/lib/rack-rabbit/adapter.rb +85 -0
- data/lib/rack-rabbit/adapter/amqp.rb +114 -0
- data/lib/rack-rabbit/adapter/bunny.rb +87 -0
- data/lib/rack-rabbit/adapter/mock.rb +92 -0
- data/lib/rack-rabbit/client.rb +181 -0
- data/lib/rack-rabbit/config.rb +260 -0
- data/lib/rack-rabbit/handler.rb +44 -0
- data/lib/rack-rabbit/message.rb +95 -0
- data/lib/rack-rabbit/middleware/program_name.rb +34 -0
- data/lib/rack-rabbit/response.rb +43 -0
- data/lib/rack-rabbit/server.rb +263 -0
- data/lib/rack-rabbit/signals.rb +62 -0
- data/lib/rack-rabbit/subscriber.rb +77 -0
- data/lib/rack-rabbit/worker.rb +84 -0
- data/rack-rabbit.gemspec +26 -0
- data/test/apps/config.ru +7 -0
- data/test/apps/custom.conf +27 -0
- data/test/apps/custom.ru +7 -0
- data/test/apps/empty.conf +1 -0
- data/test/apps/error.ru +7 -0
- data/test/apps/mirror.ru +19 -0
- data/test/apps/sinatra.ru +37 -0
- data/test/apps/sleep.ru +21 -0
- data/test/test_case.rb +154 -0
- data/test/unit/middleware/test_program_name.rb +32 -0
- data/test/unit/test_client.rb +275 -0
- data/test/unit/test_config.rb +403 -0
- data/test/unit/test_handler.rb +92 -0
- data/test/unit/test_message.rb +213 -0
- data/test/unit/test_response.rb +59 -0
- data/test/unit/test_signals.rb +45 -0
- data/test/unit/test_subscriber.rb +140 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/EXAMPLES.md
ADDED
@@ -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
data/Gemfile.lock
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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).
|