fluffle 0.6.3 → 0.7.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 +4 -4
- data/examples/client.rb +1 -1
- data/examples/server.rb +1 -1
- data/lib/fluffle.rb +2 -0
- data/lib/fluffle/client.rb +35 -41
- data/lib/fluffle/confirmer.rb +49 -0
- data/lib/fluffle/errors.rb +4 -0
- data/lib/fluffle/middleware_stack.rb +34 -0
- data/lib/fluffle/server.rb +31 -4
- data/lib/fluffle/testing.rb +3 -0
- data/lib/fluffle/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9edeb50743d908defa90bd84bd3eb7fdb4e758d7
|
4
|
+
data.tar.gz: 08e15dce619174c1d68a8cad7ef9f4594db34de7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39a86ec46f7a0de63792e1331980fda6f0938fa14e465e69b08e12f8a50133b69bf072db4c1db73324fc201c8a9ac073cde88e65c0b43e195f03a5e4c5782725
|
7
|
+
data.tar.gz: 16faf0e8c3196225712ada026f3bc6975f3c0fac79d38b9ebc93f509e898f82d020575b69867107ad0240f57b662b003e024919fa104a5d24f058b8a63f4f4e4
|
data/examples/client.rb
CHANGED
@@ -3,7 +3,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
3
|
|
4
4
|
require 'fluffle'
|
5
5
|
|
6
|
-
client = Fluffle::Client.new url: 'amqp://localhost', confirms: true
|
6
|
+
client = Fluffle::Client.new url: 'amqp://localhost', confirms: true, mandatory: true
|
7
7
|
# You can also pass `connection:` to use an existing Bunny connection:
|
8
8
|
# Fluffle::Client.new(connection: Bunny.new('amqp://localhost', heartbeat: 2))
|
9
9
|
|
data/examples/server.rb
CHANGED
@@ -3,7 +3,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
3
|
|
4
4
|
require 'fluffle'
|
5
5
|
|
6
|
-
server = Fluffle::Server.new url: 'amqp://localhost', concurrency: 5
|
6
|
+
server = Fluffle::Server.new url: 'amqp://localhost', concurrency: 5, confirms: true
|
7
7
|
|
8
8
|
server.drain do |dispatcher|
|
9
9
|
dispatcher.handle('foo') { 'bar' }
|
data/lib/fluffle.rb
CHANGED
@@ -3,10 +3,12 @@ require 'logger'
|
|
3
3
|
|
4
4
|
require 'fluffle/version'
|
5
5
|
require 'fluffle/client'
|
6
|
+
require 'fluffle/confirmer'
|
6
7
|
require 'fluffle/errors'
|
7
8
|
require 'fluffle/handlers/base'
|
8
9
|
require 'fluffle/handlers/delegator'
|
9
10
|
require 'fluffle/handlers/dispatcher'
|
11
|
+
require 'fluffle/middleware_stack'
|
10
12
|
require 'fluffle/server'
|
11
13
|
|
12
14
|
module Fluffle
|
data/lib/fluffle/client.rb
CHANGED
@@ -10,13 +10,15 @@ module Fluffle
|
|
10
10
|
include Connectable
|
11
11
|
|
12
12
|
attr_reader :confirms
|
13
|
+
attr_reader :mandatory
|
13
14
|
attr_accessor :default_timeout
|
14
15
|
attr_accessor :logger
|
15
16
|
|
16
|
-
def initialize(url: nil, connection: nil, confirms: false)
|
17
|
+
def initialize(url: nil, connection: nil, confirms: false, mandatory: false)
|
17
18
|
self.connect(url || connection)
|
18
19
|
|
19
20
|
@confirms = confirms
|
21
|
+
@mandatory = mandatory
|
20
22
|
@default_timeout = 5
|
21
23
|
@logger = Fluffle.logger
|
22
24
|
|
@@ -29,8 +31,12 @@ module Fluffle
|
|
29
31
|
@prng = Random.new
|
30
32
|
|
31
33
|
if confirms
|
32
|
-
@
|
33
|
-
confirm_select
|
34
|
+
@confirmer = Fluffle::Confirmer.new channel: @channel
|
35
|
+
@confirmer.confirm_select
|
36
|
+
end
|
37
|
+
|
38
|
+
if mandatory
|
39
|
+
handle_returns
|
34
40
|
end
|
35
41
|
|
36
42
|
@pending_responses = Concurrent::Map.new
|
@@ -51,20 +57,18 @@ module Fluffle
|
|
51
57
|
end
|
52
58
|
end
|
53
59
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
60
|
+
def handle_returns
|
61
|
+
@exchange.on_return do |return_info, properties, payload|
|
62
|
+
id = properties[:correlation_id]
|
63
|
+
ivar = @pending_responses.delete id
|
57
64
|
|
58
65
|
if ivar
|
59
|
-
|
60
|
-
|
61
|
-
|
66
|
+
message = Kernel.sprintf "Received return from exchange for routing key `%s' (%d %s)", return_info.routing_key, return_info.reply_code, return_info.reply_text
|
67
|
+
|
68
|
+
error = Fluffle::Errors::ReturnError.new message
|
69
|
+
ivar.set error
|
62
70
|
end
|
63
71
|
end
|
64
|
-
|
65
|
-
# Set the channel in confirmation mode so that we can receive confirms
|
66
|
-
# of published messages
|
67
|
-
@channel.confirm_select handle_confirm
|
68
72
|
end
|
69
73
|
|
70
74
|
# Fetch and set the `IVar` with a response from the server. This method is
|
@@ -129,14 +133,27 @@ module Fluffle
|
|
129
133
|
response_ivar = Concurrent::IVar.new
|
130
134
|
@pending_responses[id] = response_ivar
|
131
135
|
|
136
|
+
stack = Fluffle::MiddlewareStack.new
|
137
|
+
|
132
138
|
if confirms
|
133
|
-
|
134
|
-
|
139
|
+
stack.push ->(publish) do
|
140
|
+
@confirmer.with_confirmation timeout: timeout, &publish
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
stack.call do
|
135
145
|
publish payload, queue: queue
|
136
146
|
end
|
137
147
|
|
138
148
|
response = response_ivar.value timeout
|
139
|
-
|
149
|
+
|
150
|
+
if response_ivar.incomplete?
|
151
|
+
raise Errors::TimeoutError.new("Timed out waiting for response to `#{describe_payload(payload)}'")
|
152
|
+
elsif response.is_a? StandardError
|
153
|
+
# Exchange returns will preempt the response and set it to an error
|
154
|
+
# that we can raise
|
155
|
+
raise response
|
156
|
+
end
|
140
157
|
|
141
158
|
return response
|
142
159
|
ensure
|
@@ -153,35 +170,12 @@ module Fluffle
|
|
153
170
|
"#{method}/#{arity}"
|
154
171
|
end
|
155
172
|
|
156
|
-
# Wraps a block (which should publish a message) with a blocking check
|
157
|
-
# that the client received a confirmation from the RabbitMQ server
|
158
|
-
# that the message that was received and routed successfully
|
159
|
-
def with_confirmation(timeout:)
|
160
|
-
tag = @channel.next_publish_seq_no
|
161
|
-
confirm_ivar = Concurrent::IVar.new
|
162
|
-
@pending_confirms[tag] = confirm_ivar
|
163
|
-
|
164
|
-
yield
|
165
|
-
|
166
|
-
nack = confirm_ivar.value timeout
|
167
|
-
if confirm_ivar.incomplete?
|
168
|
-
raise_incomplete payload, 'confirm'
|
169
|
-
elsif nack
|
170
|
-
raise Errors::NackError.new('Received nack from confirmation')
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
# event_name - String describing what we timed out waiting for, should
|
175
|
-
# be 'response' or 'confirm'
|
176
|
-
def raise_incomplete(payload, event_name)
|
177
|
-
raise Errors::TimeoutError.new("Timed out waiting for #{event_name} to `#{describe_payload(payload)}'")
|
178
|
-
end
|
179
|
-
|
180
173
|
def publish(payload, queue:)
|
181
174
|
opts = {
|
182
175
|
routing_key: Fluffle.request_queue_name(queue),
|
183
176
|
correlation_id: payload['id'],
|
184
|
-
reply_to: @reply_queue.name
|
177
|
+
reply_to: @reply_queue.name,
|
178
|
+
mandatory: @mandatory,
|
185
179
|
}
|
186
180
|
|
187
181
|
@exchange.publish Oj.dump(payload), opts
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Fluffle
|
2
|
+
class Confirmer
|
3
|
+
attr_reader :channel
|
4
|
+
|
5
|
+
def initialize(channel:)
|
6
|
+
@channel = channel
|
7
|
+
|
8
|
+
@pending_confirms = Concurrent::Map.new
|
9
|
+
end
|
10
|
+
|
11
|
+
# Enables confirms on the channel and sets up callback to receive and
|
12
|
+
# unblock corresponding `with_confirmation` call.
|
13
|
+
def confirm_select
|
14
|
+
handle_confirm = ->(tag, _multiple, nack) do
|
15
|
+
ivar = @pending_confirms.delete tag
|
16
|
+
|
17
|
+
if ivar
|
18
|
+
ivar.set nack
|
19
|
+
else
|
20
|
+
self.logger.error "Missing confirm IVar: tag=#{tag}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Set the channel in confirmation mode so that we can receive confirms
|
25
|
+
# of published messages
|
26
|
+
@channel.confirm_select handle_confirm
|
27
|
+
end
|
28
|
+
|
29
|
+
# Wraps a block (which should publish a message) with a blocking check
|
30
|
+
# that it received a confirmation from the RabbitMQ server that the
|
31
|
+
# message that was received and routed successfully.
|
32
|
+
def with_confirmation(timeout:)
|
33
|
+
tag = @channel.next_publish_seq_no
|
34
|
+
confirm_ivar = Concurrent::IVar.new
|
35
|
+
@pending_confirms[tag] = confirm_ivar
|
36
|
+
|
37
|
+
result = yield
|
38
|
+
|
39
|
+
nack = confirm_ivar.value timeout
|
40
|
+
if confirm_ivar.incomplete?
|
41
|
+
raise Errors::TimeoutError.new('Timed out waiting for confirm')
|
42
|
+
elsif nack
|
43
|
+
raise Errors::NackError.new('Received nack from confirmation')
|
44
|
+
end
|
45
|
+
|
46
|
+
result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/fluffle/errors.rb
CHANGED
@@ -13,6 +13,10 @@ module Fluffle
|
|
13
13
|
class TimeoutError < StandardError
|
14
14
|
end
|
15
15
|
|
16
|
+
# Raised if it received a return from the server
|
17
|
+
class ReturnError < StandardError
|
18
|
+
end
|
19
|
+
|
16
20
|
# Raise this within your own code to get an error that will be faithfully
|
17
21
|
# translated into the code, message, and data member fields of the
|
18
22
|
# spec's `Error` response object
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Fluffle
|
2
|
+
class MiddlewareStack
|
3
|
+
def initialize
|
4
|
+
@stack = []
|
5
|
+
end
|
6
|
+
|
7
|
+
def push(middleware)
|
8
|
+
@stack << middleware
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_method :<<, :push
|
13
|
+
|
14
|
+
# Calls the stack in FIFO order with the callable (passed as an object
|
15
|
+
# receiving `#call` or an `&block`) being called last.
|
16
|
+
#
|
17
|
+
# For example:
|
18
|
+
# stack.push 1
|
19
|
+
# stack.push 2
|
20
|
+
# stack.call 3
|
21
|
+
#
|
22
|
+
# Will be evaluated 1 -> 2 -> 3 -> 2 -> 1.
|
23
|
+
def call(callable = nil, &block)
|
24
|
+
callable ||= block
|
25
|
+
|
26
|
+
@stack
|
27
|
+
.reverse
|
28
|
+
.inject(callable) { |previous, middleware|
|
29
|
+
->{ middleware.call(previous) }
|
30
|
+
}
|
31
|
+
.call
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/fluffle/server.rb
CHANGED
@@ -1,15 +1,23 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
require 'oj'
|
3
|
+
|
1
4
|
module Fluffle
|
2
5
|
class Server
|
3
6
|
include Connectable
|
4
7
|
|
5
|
-
attr_reader :connection, :handlers, :handler_pool
|
8
|
+
attr_reader :confirms, :connection, :handlers, :handler_pool
|
9
|
+
attr_accessor :publish_timeout
|
6
10
|
|
7
11
|
# url: - Optional URL to pass to `Bunny.new` to immediately connect
|
8
12
|
# concurrency: - Number of threads to handle messages on (default: 1)
|
9
|
-
|
13
|
+
# confirms: - Whether or not to use RabbitMQ confirms
|
14
|
+
def initialize(url: nil, connection: nil, concurrency: 1, confirms: false)
|
10
15
|
url_or_connection = url || connection
|
11
16
|
self.connect(url_or_connection) if url_or_connection
|
12
17
|
|
18
|
+
@confirms = confirms
|
19
|
+
@publish_timeout = 5
|
20
|
+
|
13
21
|
@handlers = {}
|
14
22
|
@handler_pool = Concurrent::FixedThreadPool.new concurrency
|
15
23
|
|
@@ -27,13 +35,22 @@ module Fluffle
|
|
27
35
|
|
28
36
|
handler = Fluffle::Handlers::Dispatcher.new(&block) if block
|
29
37
|
|
38
|
+
raise ArgumentError, 'Handler cannot be nil' if handler.nil?
|
39
|
+
|
30
40
|
@handlers[queue.to_s] = handler
|
31
41
|
end
|
32
42
|
|
33
43
|
def start
|
44
|
+
@handlers.freeze
|
45
|
+
|
34
46
|
@channel = @connection.create_channel
|
35
47
|
@exchange = @channel.default_exchange
|
36
48
|
|
49
|
+
if confirms
|
50
|
+
@confirmer = Fluffle::Confirmer.new channel: @channel
|
51
|
+
@confirmer.confirm_select
|
52
|
+
end
|
53
|
+
|
37
54
|
raise 'No handlers defined' if @handlers.empty?
|
38
55
|
|
39
56
|
@handlers.each do |name, handler|
|
@@ -102,8 +119,18 @@ module Fluffle
|
|
102
119
|
}
|
103
120
|
end
|
104
121
|
|
105
|
-
|
106
|
-
|
122
|
+
stack = Fluffle::MiddlewareStack.new
|
123
|
+
|
124
|
+
if confirms
|
125
|
+
stack.push ->(publish) {
|
126
|
+
@confirmer.with_confirmation timeout: publish_timeout, &publish
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
stack.call do
|
131
|
+
@exchange.publish Oj.dump(response), routing_key: reply_to,
|
132
|
+
correlation_id: response['id']
|
133
|
+
end
|
107
134
|
end
|
108
135
|
|
109
136
|
# handler - Instance of a `Handler` that may receive `#call`
|
data/lib/fluffle/testing.rb
CHANGED
data/lib/fluffle/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluffle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dirk Gadsden
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-11-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bunny
|
@@ -129,11 +129,13 @@ files:
|
|
129
129
|
- fluffle.jpg
|
130
130
|
- lib/fluffle.rb
|
131
131
|
- lib/fluffle/client.rb
|
132
|
+
- lib/fluffle/confirmer.rb
|
132
133
|
- lib/fluffle/connectable.rb
|
133
134
|
- lib/fluffle/errors.rb
|
134
135
|
- lib/fluffle/handlers/base.rb
|
135
136
|
- lib/fluffle/handlers/delegator.rb
|
136
137
|
- lib/fluffle/handlers/dispatcher.rb
|
138
|
+
- lib/fluffle/middleware_stack.rb
|
137
139
|
- lib/fluffle/railtie.rb
|
138
140
|
- lib/fluffle/server.rb
|
139
141
|
- lib/fluffle/testing.rb
|