fleck 0.3.0 → 0.4.0

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