fleck 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0a342aa3de500e0ece6c60e76e57122a869c5e74
4
- data.tar.gz: 5256c6fcebf654fc57e86189b6c1023b54d6e742
3
+ metadata.gz: a11a477a98c5158a25c1c7063ff34394815c3a1e
4
+ data.tar.gz: e16d6089f7856a9ec393e427fd892bb399b820a6
5
5
  SHA512:
6
- metadata.gz: ec260bbe626542c479ab2dc5e3fd78baa44b9cda4b521ae5cfe8f8a07e4c8090c1cae76081cf07e21af13249595e9ebd48dd39ad254a8f94db3ab700e13fb56c
7
- data.tar.gz: 23b1e4c40ad25edaa644e920f288a0fb38784a07cf6d327dde7727394f10e6c5fe1eff773c8a89f52d80dfbf47d69cc337f4a9f5bca91dbe20c72463a5bf2139
6
+ metadata.gz: c1fd9222f3ab6a8dd0f256a1ef37eae64e77105e5ba39cf61c9f6adc19455e8b2c0c77a0676bb69a54e421ff3fcb1ea8e2c3dc7f1c25b94e47cc7354d7a80c75
7
+ data.tar.gz: f4fb7a2f47d32f8496ebea65ac2ec9da2c4be939a32de3da2d1eed9c3e90c618d4dbfb252eac9a3d59f3966bddbfcbce266e5e45dbf747ab9de87687a971ce3f
data/CHANGELOG.md CHANGED
@@ -1,6 +1,17 @@
1
- # CHANGELOG
1
+ # CHANGELOG #
2
2
 
3
- ## (develop)
3
+ ## develop ##
4
+
5
+ ## v0.4.0 (15 April 2016) ##
6
+ - **NEW** Support different types of exchanges in both `Fleck::Client` and `Fleck::Consumer`.
7
+ - **FIX** Use `auto_delete` queue for `Fleck::Client`, so that it is deleted when the client is terminated.
8
+ - **NEW** Add `:rmq_options` option to `Fleck::Client::Request`, which can be used to pass options like `:persistent`, `mandatory`, etc.
9
+ to RabbitMQ message on publish.
10
+ - **NEW** Store `:headers` attribute of `Fleck::Client::Request` into RabbitMQ message `:headers`, so that in the future only
11
+ `:params` option will be converted to JSON.
12
+ - **NEW** Add `:action` option to `Fleck::Client::Request`, which will replace the action passed within `:headers` hash.
13
+
14
+ ## v0.3.0 (1 April 2016)
4
15
  - **FIX** Use `:compat` mode when using `Oj` gem to dump/load JSON content.
5
16
  - **FIX** Prevent unnecessary `Fleck::Request` lock for response reception if the response already received.
6
17
  - **NEW** Implemented a timeout functionality for asynchronous request, so that if the request isn't completed within that timeout, it will be canceled and removed from
data/README.md CHANGED
@@ -47,6 +47,7 @@ end
47
47
  You could use **Fleck** for both making requests and consuming requests from the queues. Now we are going to see how to enqueue a request to a specific queue:
48
48
 
49
49
  ```ruby
50
+ ACTION = 'do_something' # the action to be executed by the consumer
50
51
  QUEUE = 'my.queue' # the name of the queue where to enqueue the request
51
52
  HEADERS = {my_header: 'a header'} # the headers of the request
52
53
  PARAMS = {parameter: 'a parameter'} # the parameters of the request
@@ -55,7 +56,7 @@ ASYNC = false # a flag to indicate if the request is asy
55
56
 
56
57
  connection = Fleck.connection(host: '127.0.0.1', port: 5672, user: 'guest', pass: 'guest', vhost: '/')
57
58
  client = Fleck::Client.new(connection, QUEUE)
58
- response = client.request(headers: HEADERS, params: PARAMS, async: ASYNC)
59
+ response = client.request(action: ACTION, headers: HEADERS, params: PARAMS, async: ASYNC)
59
60
 
60
61
  response.status # => returns the status code of the response
61
62
  response.headers # => returns the headers Hash of the response
@@ -64,11 +65,12 @@ response.errors # => returns the Array of errors
64
65
  ```
65
66
 
66
67
  All the options of the requests are optional. The available options for request are:
67
- - `headers:` - (default: `{}`) - allows to set headers for the request
68
- - `params` - (default: `{}`) - allows to set the parameters of the request
69
- - `async` - (default: `false`) - indicates if the request should be executed asynchronously
70
- - `timeout` - (default: `nil`) - when set, indicates the request timeout in seconds after which the request will be canceled
71
- - `queue` - (default: `<client queue>`) - allows to specify a different queue where to enqueue the request
68
+ - `action` - (default: nil) - used to indicate the action to be executed by the consumer
69
+ - `headers` - (default: `{}`) - allows to set headers for the request
70
+ - `params` - (default: `{}`) - allows to set the parameters of the request
71
+ - `async` - (default: `false`) - indicates if the request should be executed asynchronously
72
+ - `timeout` - (default: `nil`) - when set, indicates the request timeout in seconds after which the request will be canceled
73
+ - `queue` - (default: `<client queue>`) - allows to specify a different queue where to enqueue the request
72
74
 
73
75
  #### Request with block
74
76
 
@@ -76,7 +78,7 @@ You might want to process the response of asynchronous requests when the respons
76
78
  so that the block is called when the response is completed:
77
79
 
78
80
  ```ruby
79
- client.request(headers: {}, params: {param1: 'myparam'}, async: true) do |request, response|
81
+ client.request(action: 'do_something', headers: {}, params: {param1: 'myparam'}, async: true) do |request, response|
80
82
  if response.status == 200
81
83
  puts "#{response.status} #{response.body}"
82
84
  else
@@ -86,6 +88,58 @@ end
86
88
  ```
87
89
 
88
90
 
91
+ #### Exchage type ####
92
+
93
+ By default `Fleck::Client` will use the default exchage, which is a `:direct` exchange named `""`. But, if you need a different type of exchage,
94
+ you could specify it by setting `:exchange_type` amd `:exchange_name` options when creating the client.
95
+
96
+ ```ruby
97
+ connection = Fleck.connection(host: '127.0.0.1', port: 5672, user: 'guest', pass: 'guest', vhost: '/') # get a connection
98
+ client = Fleck::Client.new(connection, 'my.queue', exchange_type: :fanout, exchange_name: 'my.fanout.exchange') # create a new client
99
+
100
+ # make a request
101
+ client.request(action: 'task', params: {x: 1, y: 2}, async: true, timeout: 5) do |request, response|
102
+ if response.status == 200
103
+ # we did it!
104
+ puts response.body
105
+ else
106
+ # something went wrong
107
+ puts "Something went wrong!"
108
+ end
109
+ end
110
+ ```
111
+
112
+
113
+ #### Multiple responses ####
114
+
115
+ Sometimes you might need to receive multiple responses to a single request, for example if you're using a `:fanout` exchange, and
116
+ there're multiple consumer that will respond to your request. The common `request <--> response` model won't match this situation,
117
+ because after the first response the request will be terminated, that will cause a warning message for each response received after
118
+ the first response. To solve this problem you could use the `:multiple_responses` option on client creation (by default is set to `false`),
119
+ so that the client will be able to manage multiple responses.
120
+
121
+ ```ruby
122
+ connection = Fleck.connection(host: '127.0.0.1', port: 5672, user: 'guest', pass: 'guest', vhost: '/') # get a connection
123
+ client = Fleck::Client.new(connection, 'my.queue', exchange_type: :fanout, exchange_name: 'my.fanout.exchange', multiple_responses: true) # create a new client
124
+
125
+ # make a request
126
+ client.request(action: 'status', timeout: 5) do |request, response|
127
+ # this block will be executed for each received response
128
+ if response.status == 200
129
+ # we did it!
130
+ puts response.body
131
+ else
132
+ # something went wrong
133
+ puts "Something went wrong!"
134
+ end
135
+ end
136
+ ```
137
+
138
+ **NOTE**: when you enable the `:multiple_responses` option, this will forse `async: true` for each request. Furthermore, this will set a default
139
+ timeout to `60` seconds, in order to prevent requests that are never completed, which may result in a memory leak. But if you need a request that
140
+ is never completed, you could set `timeout: nil` when making the request.
141
+
142
+
89
143
  ### Fleck::Consumer
90
144
 
91
145
  To use `Fleck::Consumer` all you need is to inherit it by an another class:
@@ -98,11 +152,16 @@ class MyConsumer < Fleck::Consumer
98
152
  logger.debug "HEADERS: #{request.headers}"
99
153
  logger.debug "PARAMS: #{request.params}"
100
154
 
101
- if rand > 0.1
102
- response.status = 200
103
- response.body = {x: rand, y: rand}
155
+ case request.action
156
+ when 'random'
157
+ if rand > 0.1
158
+ response.status = 200
159
+ response.body = {x: rand, y: rand}
160
+ else
161
+ response.render_error(500, 'Internal Server Error (just a joke)')
162
+ end
104
163
  else
105
- response.render_error(500, 'Internal Server Error (just a joke)')
164
+ response.not_found
106
165
  end
107
166
  end
108
167
  end
@@ -112,7 +171,31 @@ This code will automatically automatically start `N` instances of MyConsumer in
112
171
  messages from `my.queue` and will respond with a 200 status when the randomly generated number is greater than `0.1` and with a 500 otherwise.
113
172
 
114
173
  **NOTE**: the default status code of the response is 200, but if any uncaught exception is raised from within `#on_message` method, the status
115
- will automatically change to 500 and will add `"Internal Server Error"` message to response errors array.
174
+ will automatically change to 500 and will add `"Internal Server Error"` message to response errors array.
175
+
176
+
177
+ #### Exchange type for consumers ####
178
+
179
+ By default `Fleck::Consumer` will use the default exchange to consume messages from a queue. But if you need a different type of exchange, you
180
+ can specify it in consumer configuration.
181
+
182
+ ```ruby
183
+ class MyConsumer < Fleck::Consumer
184
+ configure queue: '', concurrency: 1, exchange_type: :fanout, exchange_name: 'my.fanout.exchange'
185
+
186
+ def on_message(request, response)
187
+ logger.debug "HEADERS: #{request.headers}"
188
+ logger.debug "PARAMS: #{request.params}"
189
+
190
+ case request.action
191
+ when 'status'
192
+ response.body = {status: 'up & running'}
193
+ else
194
+ response.not_found
195
+ end
196
+ end
197
+ end
198
+ ```
116
199
 
117
200
 
118
201
  ## Contributing
data/example.rb CHANGED
@@ -42,7 +42,7 @@ end
42
42
 
43
43
  Thread.new do
44
44
  SAMPLES.times do |i|
45
- client.request(headers: {action: :incr}, params: {num: i}, async: true, timeout: 1) do |request, response|
45
+ client.request(action: 'incr', params: {num: i}, async: true, timeout: 1, rmq_options: {app_id: 'My App', priority: (rand * 9).round(0)}) do |request, response|
46
46
  if response.status == 200
47
47
  request.logger.debug response.body
48
48
  else
@@ -5,18 +5,13 @@ module Fleck
5
5
 
6
6
  attr_reader :id, :response, :completed
7
7
 
8
- def initialize(client, routing_key, reply_to, headers: {}, params: {}, timeout: nil, &callback)
8
+ def initialize(client, routing_key, reply_to, action: nil, headers: {}, params: {}, timeout: nil, rmq_options: {}, &callback)
9
9
  @id = SecureRandom.uuid
10
10
  logger.progname += " #{@id}"
11
11
 
12
12
  logger.debug "Preparing new request"
13
13
 
14
14
  @client = client
15
- @routing_key = routing_key
16
- @reply_to = reply_to
17
- @params = params
18
- @headers = headers
19
- @timeout = timeout
20
15
  @response = nil
21
16
  @lock = Mutex.new
22
17
  @condition = ConditionVariable.new
@@ -26,6 +21,23 @@ module Fleck
26
21
  @completed = false
27
22
  @async = false
28
23
 
24
+ @options = {
25
+ routing_key: routing_key,
26
+ reply_to: reply_to,
27
+ correlation_id: @id,
28
+ type: action || headers[:action] || headers['action'],
29
+ headers: headers,
30
+ mandatory: rmq_options[:mandatory] || true,
31
+ persistent: rmq_options[:persistent] || false,
32
+ content_type: 'application/json',
33
+ content_encoding: 'UTF-8'
34
+ }
35
+ @options[:priority] = rmq_options[:priority] unless rmq_options[:priority].nil?
36
+ @options[:app_id] = rmq_options[:app_id] unless rmq_options[:app_id].nil?
37
+ @options[:expiration] = (timeout * 1000).to_i unless timeout.nil?
38
+
39
+ @message = Oj.dump({headers: headers, params: params}, mode: :compat)
40
+
29
41
  logger.debug "Request prepared"
30
42
  end
31
43
 
@@ -40,16 +52,9 @@ module Fleck
40
52
  def send!(async = false)
41
53
  @started_at = Time.now.to_f
42
54
  @async = async
43
- data = Oj.dump({
44
- headers: @headers,
45
- params: @params
46
- }, mode: :compat)
47
- logger.debug("Sending request with data: #{data}")
48
-
49
- options = { routing_key: @routing_key, reply_to: @reply_to, correlation_id: @id, mandatory: true }
50
- options[:expiration] = (@timeout * 1000).to_i unless @timeout.nil?
55
+ logger.debug("Sending request with (options: #{@options}, message: #{@message})")
51
56
 
52
- @client.publish(data, options)
57
+ @client.publish(@message, @options)
53
58
 
54
59
  @lock.synchronize do
55
60
  unless @async || @completed
data/lib/fleck/client.rb CHANGED
@@ -3,43 +3,22 @@ module Fleck
3
3
  class Client
4
4
  include Fleck::Loggable
5
5
 
6
- def initialize(connection, queue_name)
7
- @connection = connection
8
- @queue_name = queue_name
6
+ def initialize(connection, queue_name = "", exchange_type: :direct, exchange_name: "", multiple_responses: false)
7
+ @connection = connection
8
+ @queue_name = queue_name
9
+ @multiple_responses = multiple_responses
10
+ @default_timeout = multiple_responses ? 60 : nil
11
+ @requests = ThreadSafe::Hash.new
12
+ @terminated = false
13
+ @mutex = Mutex.new
14
+
9
15
  @channel = @connection.create_channel
10
16
  @exchange = @channel.default_exchange
11
- @reply_queue = @channel.queue("", exclusive: true)
12
- @requests = ThreadSafe::Hash.new
13
- @terminated = false
14
- @mutex = Mutex.new
17
+ @publisher = Bunny::Exchange.new(@channel, exchange_type, exchange_name)
18
+ @reply_queue = @channel.queue("", exclusive: true, auto_delete: true)
15
19
 
16
- @exchange.on_return do |return_info, metadata, content|
17
- begin
18
- logger.warn "Request #{metadata[:correlation_id]} returned"
19
- request = @requests[metadata[:correlation_id]]
20
- if request
21
- request.cancel!
22
- @requests.delete metadata[:correlation_id]
23
- end
24
- rescue => e
25
- logger.error e.inspect + "\n" + e.backtrace.join("\n")
26
- end
27
- end
28
-
29
- @subscription = @reply_queue.subscribe do |delivery_info, metadata, payload|
30
- begin
31
- logger.debug "Response received: #{payload}"
32
- request = @requests.delete metadata[:correlation_id]
33
- if request
34
- request.response = Fleck::Client::Response.new(payload)
35
- request.complete!
36
- else
37
- logger.warn "Request #{metadata[:correlation_id]} not found!"
38
- end
39
- rescue => e
40
- logger.error e.inspect + "\n" + e.backtrace.join("\n")
41
- end
42
- end
20
+ handle_returned_messages!
21
+ handle_responses!
43
22
 
44
23
  logger.debug("Client initialized!")
45
24
 
@@ -48,12 +27,12 @@ module Fleck
48
27
  end
49
28
  end
50
29
 
51
- def request(headers: {}, params: {}, async: false, timeout: nil, queue: @queue_name, &block)
30
+ def request(action: nil, headers: {}, params: {}, async: @multiple_responses || false, timeout: @default_timeout, queue: @queue_name, rmq_options: {}, &block)
52
31
  if @terminated
53
32
  return Fleck::Client::Response.new(Oj.dump({status: 503, errors: ['Service Unavailable'], body: nil} , mode: :compat))
54
33
  end
55
34
 
56
- request = Fleck::Client::Request.new(self, queue, @reply_queue.name, headers: headers, params: params, timeout: timeout, &block)
35
+ request = Fleck::Client::Request.new(self, queue, @reply_queue.name, action: action, headers: headers, params: params, timeout: timeout, rmq_options: rmq_options, &block)
57
36
  @requests[request.id] = request
58
37
  if timeout && !async
59
38
  begin
@@ -68,6 +47,11 @@ module Fleck
68
47
  elsif timeout && async
69
48
  request.send!(async)
70
49
  Ztimer.after(timeout * 1000) do |slot|
50
+ if @multiple_responses && !request.response.nil?
51
+ request.complete!
52
+ @requests.delete request.id
53
+ end
54
+
71
55
  unless request.completed
72
56
  logger.warn "TIMEOUT #{request.id} (#{((slot.executed_at - slot.enqueued_at) / 1000.to_f).round(2)} ms)"
73
57
  request.cancel!
@@ -83,7 +67,7 @@ module Fleck
83
67
 
84
68
  def publish(data, options)
85
69
  return if @terminated
86
- @mutex.synchronize { @exchange.publish(data, options) }
70
+ @mutex.synchronize { @publisher.publish(data, options) }
87
71
  end
88
72
 
89
73
  def terminate
@@ -100,6 +84,41 @@ module Fleck
100
84
  end
101
85
  end
102
86
  end
87
+
88
+
89
+ protected
90
+
91
+ def handle_returned_messages!
92
+ @exchange.on_return do |return_info, metadata, content|
93
+ begin
94
+ logger.warn "Request #{metadata[:correlation_id]} returned"
95
+ request = @requests[metadata[:correlation_id]]
96
+ if request
97
+ request.cancel!
98
+ @requests.delete metadata[:correlation_id]
99
+ end
100
+ rescue => e
101
+ logger.error e.inspect + "\n" + e.backtrace.join("\n")
102
+ end
103
+ end
104
+ end
105
+
106
+ def handle_responses!
107
+ @subscription = @reply_queue.subscribe do |delivery_info, metadata, payload|
108
+ begin
109
+ logger.debug "Response received: #{payload}"
110
+ request = @multiple_responses ? @requests[metadata[:correlation_id]] : @requests.delete(metadata[:correlation_id])
111
+ if request
112
+ request.response = Fleck::Client::Response.new(payload)
113
+ request.complete! unless @multiple_responses
114
+ else
115
+ logger.warn "Request #{metadata[:correlation_id]} not found!"
116
+ end
117
+ rescue => e
118
+ logger.error e.inspect + "\n" + e.backtrace.join("\n")
119
+ end
120
+ end
121
+ end
103
122
  end
104
123
  end
105
124
 
@@ -3,7 +3,7 @@ module Fleck
3
3
  class Consumer::Request
4
4
  include Fleck::Loggable
5
5
 
6
- attr_reader :id, :metadata, :payload, :data, :headers, :action, :params, :status, :errors
6
+ attr_reader :id, :metadata, :payload, :action, :data, :headers, :action, :params, :status, :errors
7
7
 
8
8
  def initialize(metadata, payload)
9
9
  @id = metadata.correlation_id
@@ -12,8 +12,8 @@ module Fleck
12
12
  @metadata = metadata
13
13
  @payload = payload
14
14
  @data = {}
15
- @headers = {}
16
- @action = nil
15
+ @headers = @metadata.headers.to_hash_with_indifferent_access
16
+ @action = @metadata.type
17
17
  @params = {}
18
18
  @status = 200
19
19
  @errors = []
@@ -24,11 +24,14 @@ module Fleck
24
24
  protected
25
25
 
26
26
  def parse_request!
27
- logger.debug "Parsing request: #{@payload}"
28
- @data = Oj.load(@payload, mode: :compat).to_hash_with_indifferent_access
29
- @headers = @data["headers"] || {}
30
- @action = @headers["action"]
31
- @params = @data["params"] || {}
27
+ logger.debug "Parsing request (options: #{@metadata}, message: #{@payload})"
28
+
29
+ @data = Oj.load(@payload, mode: :compat).to_hash_with_indifferent_access
30
+ @headers.merge!(@data["headers"] || {})
31
+
32
+ @action ||= @headers["action"]
33
+ @headers["action"] ||= @action
34
+ @params = @data["params"] || {}
32
35
  rescue Oj::ParseError => e
33
36
  logger.error(e.inspect + "\n" + e.backtrace.join("\n"))
34
37
  @status = 400
@@ -47,12 +47,14 @@ module Fleck
47
47
  @__connection = nil
48
48
  @__consumer_tag = nil
49
49
 
50
- @__host = configs[:host]
51
- @__port = configs[:port]
52
- @__user = configs[:user] || 'guest'
53
- @__pass = configs[:password] || configs[:pass]
54
- @__vhost = configs[:vhost] || "/"
55
- @__queue_name = configs[:queue]
50
+ @__host = configs[:host]
51
+ @__port = configs[:port]
52
+ @__user = configs[:user] || 'guest'
53
+ @__pass = configs[:password] || configs[:pass]
54
+ @__vhost = configs[:vhost] || "/"
55
+ @__exchange_type = configs[:exchange_type] || :direct
56
+ @__exchange_name = configs[:exchange_name] || ""
57
+ @__queue_name = configs[:queue]
56
58
 
57
59
  logger.info "Launching #{self.class.to_s.color(:yellow)} consumer ..."
58
60
 
@@ -105,6 +107,10 @@ module Fleck
105
107
  return @__exchange
106
108
  end
107
109
 
110
+ def publisher
111
+ return @__publisher
112
+ end
113
+
108
114
  def subscription
109
115
  return @__subscription
110
116
  end
@@ -133,8 +139,13 @@ module Fleck
133
139
  logger.debug "Creating a new channel for #{self.class.to_s.color(:yellow)} consumer"
134
140
  @__channel = @__connection.create_channel
135
141
  @__channel.prefetch(1) # prevent from dispatching a new RabbitMQ message, until the previous message is not processed
136
- @__queue = @__channel.queue(@__queue_name, auto_delete: false)
137
- @__exchange = @__channel.default_exchange
142
+ @__publisher = @__channel.default_exchange
143
+ if @__exchange_type == :direct && @__exchange_name == ""
144
+ @__queue = @__channel.queue(@__queue_name, auto_delete: false)
145
+ else
146
+ @__exchange = Bunny::Exchange.new(@__channel, @__exchange_type, @__exchange_name)
147
+ @__queue = @__channel.queue("", exclusive: true, auto_delete: true).bind(@__exchange, routing_key: @__queue_name)
148
+ end
138
149
  end
139
150
 
140
151
  def subscribe!
@@ -168,7 +179,7 @@ module Fleck
168
179
  if @__channel.closed?
169
180
  logger.warn "Channel already closed! The response #{metadata.correlation_id} is going to be dropped."
170
181
  else
171
- @__exchange.publish(response.to_json, routing_key: metadata.reply_to, correlation_id: metadata.correlation_id, mandatory: true)
182
+ @__publisher.publish(response.to_json, routing_key: metadata.reply_to, correlation_id: metadata.correlation_id, mandatory: true)
172
183
  @__channel.ack(delivery_info.delivery_tag)
173
184
  end
174
185
  end
data/lib/fleck/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Fleck
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fleck
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Groza Sergiu
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-04-01 00:00:00.000000000 Z
11
+ date: 2016-04-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler