fleck 0.4.1 → 0.5.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: e5454dfcbd47d6772c526a087805a390bfdbd3d4
4
- data.tar.gz: 69e60a050d19f5885dcddf3f675daf371589a713
3
+ metadata.gz: da4bc26b24264aa2f53ee7c9a7c5635e33b82dea
4
+ data.tar.gz: 5d0cef36b2f1ccdf1a28165656137d5500289e6a
5
5
  SHA512:
6
- metadata.gz: 0227d8e60ffb297daa8d7b614ea592017fa02b6ffc5b8dfa41e95f5453abbf97d3208144e11db1500f5d356ccaa7ca6bfdc9405fb2880014b922f8527e998d34
7
- data.tar.gz: f65dcf475be639faa1cf1df2a51d3dc22ef5d738c69e1189cf1efd10b3f122450bc212eb4a35ce8658279769d1ce94f7e4e904a7bb303ceea8867d6c8e11f3ce
6
+ metadata.gz: ce5817faf826334632d2d49ff11feb49692efc7917a22ff01e914f8bc7f26fa0b0c78d39b8ab927945888b1a5980cfa50350414b397c14f4f875d85a35e6eda9
7
+ data.tar.gz: 67f734fcc912163bc900b52808fe0e995647e43dddc29441f8ddf4859800c13ec13ba1414f26ef40e1d3e2ff20c326c702598275717eeb898a0c6d6e54bc14fb
data/CHANGELOG.md CHANGED
@@ -1,6 +1,25 @@
1
1
  # CHANGELOG #
2
2
 
3
3
  ## develop ##
4
+ - **NEW** Added `:autostart` option to `Fleck::Consumer` configuration, so that the developer could decide to start the consumer manually or automatically. By default
5
+ the consumer will start automatically.
6
+ - **NEW** Implemented the feature that allows to define an initialization block for `Fleck::Consumer`. This feature should be used to initialize consumer instance
7
+ variables so that it is not necessary to overwrite `Fleck::Consumer#initialize` method.
8
+ - **NEW** Implemented the feature that allows to define a map of actions to consumer methods, so that requests actions are automatically mapped to
9
+ consumer methods.
10
+ - **NEW** Implemented `#expired?` method for `Fleck::Client::Request`, that tells if the request is expired or not. It makes possible to
11
+ distinguish service unavailable responses from expired requests.
12
+ - **NEW** Added `:concurrency` option to `Fleck::Client`, that allows to specify the concurrency level for responses parsing.
13
+ - **NEW** Add `:version` option to `Fleck::Client#request` and implement `#version` method for `Fleck::Consumer::Request`.
14
+ - **NEW** Implemented `#request` and `#response` methods for `Fleck::Consumer`, so that you don't have to pass them as argument every time you
15
+ delegate the logic to a different method.
16
+ - **NEW** Implemented the feature that allows to deprecate actions within a consumer. Now you can call `deprecated!` inside a consumer to
17
+ reply with a response that is marked as **deprecated**.
18
+ - **NEW** Add `app_name` configuration, that allows to configure the default `app_id` to set for RabbitMQ messages.
19
+ - **NEW** Add process ID to logs, so that if you have multiple instances of the same application writting to the same log file, you'll be able to filter logs by process ID. Also changed logs format.
20
+
21
+ ## v0.4.1 (18 April 2016) ##
22
+ - **FIX** Fixed a bug of `Fleck::Consumer::Request` class, that was causing errors when RabbitMQ message header wasn't set.
4
23
 
5
24
  ## v0.4.0 (15 April 2016) ##
6
25
  - **NEW** Support different types of exchanges in both `Fleck::Client` and `Fleck::Consumer`.
data/README.md CHANGED
@@ -148,20 +148,19 @@ To use `Fleck::Consumer` all you need is to inherit it by an another class:
148
148
  class MyConsumer < Fleck::Consumer
149
149
  configure queue: 'my.queue', concurrency: 2
150
150
 
151
- def on_message(request, response)
152
- logger.debug "HEADERS: #{request.headers}"
153
- logger.debug "PARAMS: #{request.params}"
154
-
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
151
+ actions :random
152
+
153
+ initialize do
154
+ # initialization stuff
155
+ @my_message = "Hi! :)"
156
+ end
157
+
158
+ def random
159
+ if rand > 0.1
160
+ response.status = 200 # this is not strictly necessary (200 is the default status)
161
+ response.body = {x: rand, y: rand, message: @my_message}
163
162
  else
164
- response.not_found
163
+ response.render_error(500, 'Internal Server Error (just a joke)')
165
164
  end
166
165
  end
167
166
  end
@@ -183,16 +182,10 @@ can specify it in consumer configuration.
183
182
  class MyConsumer < Fleck::Consumer
184
183
  configure queue: '', concurrency: 1, exchange_type: :fanout, exchange_name: 'my.fanout.exchange'
185
184
 
186
- def on_message(request, response)
187
- logger.debug "HEADERS: #{request.headers}"
188
- logger.debug "PARAMS: #{request.params}"
185
+ actions :status
189
186
 
190
- case request.action
191
- when 'status'
192
- response.body = {status: 'up & running'}
193
- else
194
- response.not_found
195
- end
187
+ def status
188
+ response.body = {status: 'up & running'}
196
189
  end
197
190
  end
198
191
  ```
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require 'fleck'
5
+
6
+ user = ENV['USER'] || 'guest'
7
+ pass = ENV['PASS'] || 'guest'
8
+
9
+ CONCURRENCY = (ENV['CONCURRENCY'] || 2).to_i
10
+ SAMPLES = (ENV['SAMPLES'] || 10).to_i
11
+
12
+ Fleck.configure do |config|
13
+ config.default_user = user
14
+ config.default_pass = pass
15
+ config.loglevel = Logger::DEBUG
16
+ end
17
+
18
+ connection = Fleck.connection(host: "127.0.0.1", port: 5672, user: user, pass: pass, vhost: "/")
19
+ client = Fleck::Client.new(connection, "actions.example.queue", concurrency: CONCURRENCY.to_i)
20
+
21
+ class MyConsumer < Fleck::Consumer
22
+ configure queue: 'actions.example.queue', concurrency: CONCURRENCY.to_i
23
+ actions :hello, ciao: 'my_custom_method', aloha: 'my_aloha'
24
+
25
+ def hello
26
+ response.body = "Hello!"
27
+ end
28
+
29
+ def my_custom_method
30
+ response.body = "Ciao!"
31
+ end
32
+
33
+ def my_aloha
34
+ response.body = "Aloha!"
35
+ end
36
+
37
+ def not_an_action
38
+ logger.warn("I'm not an action, so you should not be able to call me!")
39
+ end
40
+ end
41
+
42
+ actions = [:hello, :ciao, :aloha, :not_an_action]
43
+
44
+ SAMPLES.to_i.times do |num|
45
+ action = actions[(rand * actions.size).to_i]
46
+ client.request(action: action, params: {num: num}, timeout: 5) do |request, response|
47
+ if response.status == 200
48
+ Fleck.logger.info "ACTION: (#{action.inspect}) #{response.body}"
49
+ else
50
+ Fleck.logger.error "ACTION: (#{action.inspect}) #{response.errors.join(", ")}"
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require 'fleck'
5
+
6
+ user = ENV['USER'] || 'guest'
7
+ pass = ENV['PASS'] || 'guest'
8
+
9
+ CONCURRENCY = (ENV['CONCURRENCY'] || 2).to_i
10
+ SAMPLES = (ENV['SAMPLES'] || 10).to_i
11
+
12
+ Fleck.configure do |config|
13
+ config.default_user = user
14
+ config.default_pass = pass
15
+ config.loglevel = Logger::DEBUG
16
+ end
17
+
18
+ connection = Fleck.connection(host: "127.0.0.1", port: 5672, user: user, pass: pass, vhost: "/")
19
+ client = Fleck::Client.new(connection, "consumer.initialization.example.queue", concurrency: CONCURRENCY.to_i)
20
+
21
+ class MyConsumer < Fleck::Consumer
22
+ configure queue: 'consumer.initialization.example.queue', concurrency: CONCURRENCY.to_i
23
+ actions :hello
24
+
25
+ initialize do
26
+ @value = "MY CONSUMER :) #{self.object_id}"
27
+ end
28
+
29
+ def hello
30
+ response.body = "#{@value} Hello!"
31
+ end
32
+ end
33
+
34
+ SAMPLES.to_i.times do |num|
35
+ client.request(action: 'hello', params: {num: num}, timeout: 5) do |request, response|
36
+ if response.status == 200
37
+ Fleck.logger.info response.body
38
+ else
39
+ Fleck.logger.error response.errors.join(", ")
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require 'fleck'
5
+
6
+ user = ENV['USER'] || 'guest'
7
+ pass = ENV['PASS'] || 'guest'
8
+
9
+ CONCURRENCY = (ENV['CONCURRENCY'] || 2).to_i
10
+ SAMPLES = (ENV['SAMPLES'] || 10).to_i
11
+
12
+ Fleck.configure do |config|
13
+ config.default_user = user
14
+ config.default_pass = pass
15
+ config.loglevel = Logger::DEBUG
16
+ end
17
+
18
+ connection = Fleck.connection(host: "127.0.0.1", port: 5672, user: user, pass: pass, vhost: "/")
19
+ client = Fleck::Client.new(connection, "deprecation.example.queue", concurrency: CONCURRENCY.to_i)
20
+
21
+ class MyConsumer < Fleck::Consumer
22
+ configure queue: 'deprecation.example.queue', concurrency: CONCURRENCY.to_i
23
+
24
+ def on_message(request, response)
25
+ case request.action
26
+ when 'hello' then hello
27
+ else
28
+ response.not_found!
29
+ end
30
+ end
31
+
32
+ def hello
33
+ version = request.version || 'v1'
34
+ case version.to_s
35
+ when 'v1' then hello_v1
36
+ when 'v2' then hello_v2
37
+ else
38
+ response.not_found!
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def hello_v1
45
+ deprecated!
46
+ response.body = "#{request.params[:num]}. Hello V1!"
47
+ end
48
+
49
+ def hello_v2
50
+ response.body = "#{request.params[:num] + 1}. Hello V2!"
51
+ end
52
+ end
53
+
54
+ SAMPLES.to_i.times do |num|
55
+ response = client.request(action: 'hello', version: (rand >= 0.5 ? 'v2' : 'v1'), params: {num: num}, timeout: 5)
56
+ puts (response.deprecated? ? "DEPRECATED: #{response.body}" : response.body)
57
+ end
@@ -1,3 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
1
4
  require 'fleck'
2
5
 
3
6
  user = ENV['USER'] || 'guest'
@@ -13,7 +16,7 @@ Fleck.configure do |config|
13
16
  end
14
17
 
15
18
  connection = Fleck.connection(host: "127.0.0.1", port: 5672, user: user, pass: pass, vhost: "/")
16
- client = Fleck::Client.new(connection, "example.queue")
19
+ client = Fleck::Client.new(connection, "example.queue", concurrency: CONCURRENCY.to_i)
17
20
 
18
21
  count = 0
19
22
  success = 0
@@ -42,7 +45,7 @@ end
42
45
 
43
46
  Thread.new do
44
47
  SAMPLES.times do |i|
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|
48
+ client.request(action: 'incr', params: {num: i}, async: true, timeout: 1, rmq_options: { priority: (rand * 9).round(0), mandatory: false}) do |request, response|
46
49
  if response.status == 200
47
50
  request.logger.debug response.body
48
51
  else
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require 'fleck'
5
+
6
+ user = ENV['USER'] || 'guest'
7
+ pass = ENV['PASS'] || 'guest'
8
+
9
+ CONCURRENCY = (ENV['CONCURRENCY'] || 2).to_i
10
+ SAMPLES = (ENV['SAMPLES'] || 10).to_i
11
+
12
+ Fleck.configure do |config|
13
+ config.default_user = user
14
+ config.default_pass = pass
15
+ config.loglevel = Logger::DEBUG
16
+ end
17
+
18
+ connection = Fleck.connection(host: "127.0.0.1", port: 5672, user: user, pass: pass, vhost: "/")
19
+ client = Fleck::Client.new(connection, "expiration.example.queue", concurrency: CONCURRENCY.to_i)
20
+ success_probability = 0.8
21
+
22
+ count = 0
23
+ success = 0
24
+ failure = 0
25
+
26
+ mutex = Mutex.new
27
+ lock = Mutex.new
28
+ condition = ConditionVariable.new
29
+
30
+ class MyConsumer < Fleck::Consumer
31
+ configure queue: 'expiration.example.queue', concurrency: CONCURRENCY.to_i
32
+
33
+ def on_message(request, response)
34
+ case request.action
35
+ when 'hello' then hello
36
+ else
37
+ response.not_found!
38
+ end
39
+ end
40
+
41
+ def hello
42
+ sleep rand
43
+ response.body = "#{request.params[:num] + 1}. Hello!"
44
+ end
45
+ end
46
+
47
+ SAMPLES.to_i.times do |num|
48
+ client.request(action: 'hello', params: {num: num}, async: true, timeout: (success_probability * SAMPLES.to_f)/CONCURRENCY.to_f) do |request, response|
49
+ if request.expired?
50
+ puts "EXPIRED: #{response.inspect}"
51
+ elsif response.status == 200
52
+ puts "SUCCESS: #{response.body}"
53
+ else
54
+ puts "ERROR: #{response.inspect}"
55
+ end
56
+
57
+ mutex.synchronize do
58
+ count += 1
59
+ if response.status == 200
60
+ success += 1
61
+ else
62
+ failure += 1
63
+ end
64
+
65
+ lock.synchronize { condition.signal } if count >= SAMPLES
66
+ end
67
+ end
68
+ end
69
+
70
+ at_exit do
71
+ puts "Ztimer: (count: #{Ztimer.count}, jobs: #{Ztimer.jobs_count})"
72
+ puts "Total: #{count}, Success: #{success}, Failure: #{failure}"
73
+ end
74
+
75
+ lock.synchronize { condition.wait(lock) }
76
+ exit
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require 'fleck'
5
+
6
+ user = ENV['USER'] || 'guest'
7
+ pass = ENV['PASS'] || 'guest'
8
+
9
+ CONCURRENCY = (ENV['CONCURRENCY'] || 10).to_i
10
+ SAMPLES = (ENV['SAMPLES'] || 1_000).to_i
11
+
12
+ Fleck.configure do |config|
13
+ config.default_user = user
14
+ config.default_pass = pass
15
+ config.loglevel = Logger::DEBUG
16
+ end
17
+
18
+ connection = Fleck.connection(host: "127.0.0.1", port: 5672, user: user, pass: pass, vhost: "/")
19
+ client = Fleck::Client.new(connection, "example.queue", concurrency: CONCURRENCY.to_i, exchange_type: :fanout, exchange_name: 'fanout.example.queue', multiple_responses: true)
20
+
21
+ count = 0
22
+ success = 0
23
+ failure = 0
24
+
25
+ mutex = Mutex.new
26
+ lock = Mutex.new
27
+ condition = ConditionVariable.new
28
+
29
+ class First < Fleck::Consumer
30
+ configure queue: "example.queue", concurrency: CONCURRENCY.to_i, exchange_type: :fanout, exchange_name: 'fanout.example.queue'
31
+
32
+ def on_message(request, response)
33
+ if request.action == "incr"
34
+ response.body = "#{request.params[:num].to_i + 1}. Hello, World!"
35
+ else
36
+ response.not_found
37
+ end
38
+ end
39
+ end
40
+
41
+ Thread.new do
42
+ SAMPLES.times do |i|
43
+ client.request(action: 'incr', params: {num: i}, timeout: 60) do |request, response|
44
+ request.logger.debug response.body
45
+ mutex.synchronize do
46
+ count += 1
47
+ if response.status == 200
48
+ success += 1
49
+ else
50
+ failure += 1
51
+ end
52
+
53
+ lock.synchronize { condition.signal } if count >= SAMPLES
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ at_exit do
60
+ puts "Total: #{count}, Success: #{success}, Failure: #{failure}"
61
+ end
62
+
63
+ lock.synchronize { condition.wait(lock) }
64
+ exit
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require 'fleck'
5
+
6
+ user = ENV['USER'] || 'guest'
7
+ pass = ENV['PASS'] || 'guest'
8
+
9
+ CONCURRENCY = (ENV['CONCURRENCY'] || 2).to_i
10
+ SAMPLES = (ENV['SAMPLES'] || 10).to_i
11
+
12
+ Fleck.configure do |config|
13
+ config.default_user = user
14
+ config.default_pass = pass
15
+ config.loglevel = Logger::DEBUG
16
+ end
17
+
18
+ connection = Fleck.connection(host: "127.0.0.1", port: 5672, user: user, pass: pass, vhost: "/")
19
+ client = Fleck::Client.new(connection, "no.autostart.example.queue", concurrency: CONCURRENCY.to_i)
20
+
21
+ class MyConsumer < Fleck::Consumer
22
+ configure queue: 'no.autostart.example.queue', concurrency: CONCURRENCY.to_i, autostart: false
23
+ actions :hello
24
+
25
+ initialize do
26
+ @value = "MY CONSUMER :) #{self.object_id}"
27
+ end
28
+
29
+ def hello
30
+ response.body = "#{@value} Hello!"
31
+ end
32
+ end
33
+
34
+ MyConsumer.start # IMPORTANT: you have to explicitly start the consumer
35
+
36
+ SAMPLES.to_i.times do |num|
37
+ client.request(action: 'hello', params: {num: num}, timeout: 5) do |request, response|
38
+ if response.status == 200
39
+ Fleck.logger.info response.body
40
+ else
41
+ Fleck.logger.error response.errors.join(", ")
42
+ end
43
+ end
44
+ end
data/fleck.gemspec CHANGED
@@ -32,5 +32,5 @@ Gem::Specification.new do |spec|
32
32
  spec.add_dependency "bunny", "~> 2.2"
33
33
  spec.add_dependency "thread_safe", "~> 0.3"
34
34
  spec.add_dependency "oj", "~> 2.14"
35
- spec.add_dependency "ztimer", "~> 0.3"
35
+ spec.add_dependency "ztimer", "~> 0.4"
36
36
  end
@@ -3,29 +3,38 @@ module Fleck
3
3
  class Client::Request
4
4
  include Fleck::Loggable
5
5
 
6
- attr_reader :id, :response, :completed
6
+ attr_reader :id, :response, :completed, :expired
7
7
 
8
- def initialize(client, routing_key, reply_to, action: nil, headers: {}, params: {}, timeout: nil, rmq_options: {}, &callback)
8
+ def initialize(client, routing_key, reply_to, action: nil, version: nil, headers: {}, params: {}, timeout: nil, multiple_responses: false, 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
- @client = client
15
- @response = nil
16
- @lock = Mutex.new
17
- @condition = ConditionVariable.new
18
- @callback = callback
19
- @started_at = nil
20
- @ended_at = nil
21
- @completed = false
22
- @async = false
14
+ @client = client
15
+ @response = nil
16
+ @lock = Mutex.new
17
+ @condition = ConditionVariable.new
18
+ @callback = callback
19
+ @started_at = nil
20
+ @ended_at = nil
21
+ @completed = false
22
+ @async = false
23
+ @action = action || headers[:action] || headers['action']
24
+ @version = version || headers[:version] || headers['version']
25
+ @routing_key = routing_key
26
+ @timeout = (timeout * 1000).to_i unless timeout.nil?
27
+ @multiple_responses = multiple_responses
28
+ @ztimer_slot = nil
29
+ @expired = false
30
+
31
+ headers[:version] = @version
23
32
 
24
33
  @options = {
25
- routing_key: routing_key,
34
+ routing_key: @routing_key,
26
35
  reply_to: reply_to,
27
36
  correlation_id: @id,
28
- type: action || headers[:action] || headers['action'],
37
+ type: action,
29
38
  headers: headers,
30
39
  mandatory: rmq_options[:mandatory] || true,
31
40
  persistent: rmq_options[:persistent] || false,
@@ -33,8 +42,8 @@ module Fleck
33
42
  content_encoding: 'UTF-8'
34
43
  }
35
44
  @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?
45
+ @options[:app_id] = rmq_options[:app_id] || Fleck.config.app_name
46
+ @options[:expiration] = @timeout
38
47
 
39
48
  @message = Oj.dump({headers: headers, params: params}, mode: :compat)
40
49
 
@@ -45,7 +54,9 @@ module Fleck
45
54
  logger.debug "Response: #{value.inspect}"
46
55
  raise ArgumentError.new("Invalid response type: #{value.class}") unless value.is_a?(Fleck::Client::Response)
47
56
  @response = value
57
+ deprecated! if @response.deprecated?
48
58
  @callback.call(self, value) if @callback
59
+ complete! unless @multiple_responses
49
60
  return value
50
61
  end
51
62
 
@@ -54,6 +65,10 @@ module Fleck
54
65
  @async = async
55
66
  logger.debug("Sending request with (options: #{@options}, message: #{@message})")
56
67
 
68
+ if @timeout
69
+ @ztimer_slot = Ztimer.after(@timeout){ expire! }
70
+ end
71
+
57
72
  @client.publish(@message, @options)
58
73
 
59
74
  @lock.synchronize do
@@ -66,18 +81,43 @@ module Fleck
66
81
  end
67
82
 
68
83
  def complete!
84
+ @ztimer_slot.cancel! if @ztimer_slot
69
85
  @lock.synchronize do
70
86
  @completed = true
71
87
  @ended_at = Time.now.to_f
72
88
  logger.debug "Done #{@async ? 'async' : 'synchronized'} in #{((@ended_at - @started_at).round(5) * 1000).round(2)} ms"
73
89
  @condition.signal unless @async
90
+ @client.remove_request(@id)
74
91
  end
75
92
  end
76
93
 
77
94
  def cancel!
78
95
  logger.warn "Request canceled!"
79
96
  self.response = Fleck::Client::Response.new(Oj.dump({status: 503, errors: ['Service Unavailable'], body: nil} , mode: :compat))
80
- complete!
97
+ end
98
+
99
+ def expire!
100
+ if @multiple_responses
101
+ if @completed
102
+ complete!
103
+ else
104
+ @expired = true
105
+ cancel!
106
+ end
107
+ elsif !@completed
108
+ @expired = true
109
+ cancel!
110
+ end
111
+ end
112
+
113
+ def expired?
114
+ return @expired
115
+ end
116
+
117
+ protected
118
+
119
+ def deprecated!
120
+ logger.warn("DEPRECATION: the method `#{@action}` of version '#{@version.inspect}' on queue '#{@routing_key}' is going to be deprecated. Please, consider using a newer version of this method.")
81
121
  end
82
122
  end
83
123
  end
@@ -3,13 +3,18 @@ module Fleck
3
3
  class Client::Response
4
4
  include Fleck::Loggable
5
5
 
6
- attr_accessor :status, :headers, :body, :errors
6
+ attr_accessor :status, :headers, :body, :errors, :deprecated
7
7
  def initialize(payload)
8
- @data = Oj.load(payload, mode: :compat).to_hash_with_indifferent_access
9
- @status = @data["status"]
10
- @headers = @data["headers"] || {}
11
- @body = @data["body"]
12
- @errors = @data["errors"] || []
8
+ @data = Oj.load(payload, mode: :compat).to_hash_with_indifferent_access
9
+ @status = @data["status"]
10
+ @headers = @data["headers"] || {}
11
+ @body = @data["body"]
12
+ @errors = @data["errors"] || []
13
+ @deprecated = !!@data["deprecated"]
14
+ end
15
+
16
+ def deprecated?
17
+ @deprecated
13
18
  end
14
19
  end
15
20
  end
data/lib/fleck/client.rb CHANGED
@@ -3,12 +3,14 @@ module Fleck
3
3
  class Client
4
4
  include Fleck::Loggable
5
5
 
6
- def initialize(connection, queue_name = "", exchange_type: :direct, exchange_name: "", multiple_responses: false)
6
+ def initialize(connection, queue_name = "", exchange_type: :direct, exchange_name: "", multiple_responses: false, concurrency: 1)
7
7
  @connection = connection
8
8
  @queue_name = queue_name
9
9
  @multiple_responses = multiple_responses
10
10
  @default_timeout = multiple_responses ? 60 : nil
11
+ @concurrency = [concurrency.to_i, 1].max
11
12
  @requests = ThreadSafe::Hash.new
13
+ @subscriptions = ThreadSafe::Array.new
12
14
  @terminated = false
13
15
  @mutex = Mutex.new
14
16
 
@@ -18,7 +20,7 @@ module Fleck
18
20
  @reply_queue = @channel.queue("", exclusive: true, auto_delete: true)
19
21
 
20
22
  handle_returned_messages!
21
- handle_responses!
23
+ @concurrency.times { handle_responses! }
22
24
 
23
25
  logger.debug("Client initialized!")
24
26
 
@@ -27,53 +29,46 @@ module Fleck
27
29
  end
28
30
  end
29
31
 
30
- def request(action: nil, headers: {}, params: {}, async: @multiple_responses || false, timeout: @default_timeout, queue: @queue_name, rmq_options: {}, &block)
32
+ def request(action: nil, version: nil, headers: {}, params: {}, async: @multiple_responses || false, timeout: @default_timeout, queue: @queue_name, rmq_options: {}, &block)
33
+
31
34
  if @terminated
32
35
  return Fleck::Client::Response.new(Oj.dump({status: 503, errors: ['Service Unavailable'], body: nil} , mode: :compat))
33
36
  end
34
37
 
35
- request = Fleck::Client::Request.new(self, queue, @reply_queue.name, action: action, headers: headers, params: params, timeout: timeout, rmq_options: rmq_options, &block)
36
- @requests[request.id] = request
37
- if timeout && !async
38
- begin
39
- Timeout.timeout(timeout.to_f) do
40
- request.send!(false)
41
- end
42
- rescue Timeout::Error
43
- logger.warn "Failed to get any response in #{timeout} seconds for request #{request.id.to_s.color(:red)}! The request will be canceled."
44
- request.cancel!
45
- @requests.delete request.id
46
- end
47
- elsif timeout && async
48
- request.send!(async)
49
- Ztimer.after(timeout * 1000) do |slot|
50
- if @multiple_responses && !request.response.nil?
51
- request.complete!
52
- @requests.delete request.id
53
- end
38
+ request = Fleck::Client::Request.new(
39
+ self, queue, @reply_queue.name,
40
+ action: action,
41
+ version: version,
42
+ headers: headers,
43
+ params: params,
44
+ timeout: timeout,
45
+ multiple_responses: @multiple_responses,
46
+ rmq_options: rmq_options,
47
+ &block
48
+ )
54
49
 
55
- unless request.completed
56
- logger.warn "TIMEOUT #{request.id} (#{((slot.executed_at - slot.enqueued_at) / 1000.to_f).round(2)} ms)"
57
- request.cancel!
58
- @requests.delete request.id
59
- end
60
- end
61
- else
62
- request.send!(async)
63
- end
50
+ @requests[request.id] = request
51
+ request.send!(async)
64
52
 
65
53
  return request.response
66
54
  end
67
55
 
56
+
68
57
  def publish(data, options)
69
58
  return if @terminated
70
59
  @mutex.synchronize { @publisher.publish(data, options) }
71
60
  end
72
61
 
62
+
63
+ def remove_request(request_id)
64
+ @requests.delete request_id
65
+ end
66
+
67
+
73
68
  def terminate
74
69
  @terminated = true
75
70
  logger.info "Unsubscribing from #{@reply_queue.name}"
76
- @subscription.cancel # stop receiving new messages
71
+ @subscriptions.map(&:cancel) # stop receiving new messages
77
72
  logger.info "Canceling pending requests"
78
73
  # cancel pending requests
79
74
  while item = @requests.shift do
@@ -95,7 +90,6 @@ module Fleck
95
90
  request = @requests[metadata[:correlation_id]]
96
91
  if request
97
92
  request.cancel!
98
- @requests.delete metadata[:correlation_id]
99
93
  end
100
94
  rescue => e
101
95
  logger.error e.inspect + "\n" + e.backtrace.join("\n")
@@ -104,13 +98,12 @@ module Fleck
104
98
  end
105
99
 
106
100
  def handle_responses!
107
- @subscription = @reply_queue.subscribe do |delivery_info, metadata, payload|
101
+ @subscriptions << @reply_queue.subscribe do |delivery_info, metadata, payload|
108
102
  begin
109
103
  logger.debug "Response received: #{payload}"
110
- request = @multiple_responses ? @requests[metadata[:correlation_id]] : @requests.delete(metadata[:correlation_id])
104
+ request = @requests[metadata[:correlation_id]]
111
105
  if request
112
106
  request.response = Fleck::Client::Response.new(payload)
113
- request.complete! unless @multiple_responses
114
107
  else
115
108
  logger.warn "Request #{metadata[:correlation_id]} not found!"
116
109
  end
@@ -3,12 +3,13 @@ module Fleck
3
3
  class Configuration
4
4
 
5
5
  attr_reader :logfile, :loglevel, :progname
6
- attr_accessor :default_user, :default_pass, :default_host, :default_port, :default_vhost, :default_queue
6
+ attr_accessor :default_user, :default_pass, :default_host, :default_port, :default_vhost, :default_queue, :app_name
7
7
 
8
8
  def initialize
9
9
  @logfile = STDOUT
10
10
  @loglevel = ::Logger::INFO
11
11
  @progname = "Fleck"
12
+ @app_name = $0
12
13
  @default_host = "127.0.0.1"
13
14
  @default_port = 5672
14
15
  @default_user = nil
@@ -99,7 +100,8 @@ module Fleck
99
100
  else
100
101
  color = "#00BCD4"
101
102
  end
102
- "[#{datetime.strftime('%F %T.%L')}]".color(:cyan) + (progname ? " #{progname}".color(:yellow) : "") + " #{severity} ".color(color) + "#{msg}\n"
103
+ "[#{datetime.strftime('%F %T.%L')}]".color(:cyan) + "(#{$$})".color(:blue) + "|#{severity}|".color(color) +
104
+ (progname ? "<#{progname}>".color(:yellow) : "") + " #{msg}\n"
103
105
  end
104
106
 
105
107
  return @formatter
@@ -3,20 +3,23 @@ module Fleck
3
3
  class Consumer::Request
4
4
  include Fleck::Loggable
5
5
 
6
- attr_reader :id, :metadata, :payload, :action, :data, :headers, :action, :params, :status, :errors
6
+ attr_reader :id, :metadata, :payload, :action, :data, :headers, :action, :version, :params, :status, :errors
7
7
 
8
- def initialize(metadata, payload)
8
+ def initialize(metadata, payload, delivery_info)
9
9
  @id = metadata.correlation_id
10
10
  logger.progname += " #{@id}"
11
11
 
12
- @metadata = metadata
13
- @payload = payload
14
- @data = {}
15
- @headers = (@metadata.headers || {}).to_hash_with_indifferent_access
16
- @action = @metadata.type
17
- @params = {}
18
- @status = 200
19
- @errors = []
12
+ @metadata = metadata
13
+ @payload = payload
14
+ @exchange = delivery_info.exchange.inspect
15
+ @queue = delivery_info.routing_key.inspect
16
+ @data = {}
17
+ @headers = (@metadata.headers || {}).to_hash_with_indifferent_access
18
+ @action = @metadata.type
19
+ @version = nil
20
+ @params = {}
21
+ @status = 200
22
+ @errors = []
20
23
 
21
24
  parse_request!
22
25
  end
@@ -24,13 +27,14 @@ module Fleck
24
27
  protected
25
28
 
26
29
  def parse_request!
27
- logger.debug "Parsing request (options: #{@metadata}, message: #{@payload})"
30
+ logger.debug "Parsing request (exchange: #{@exchange}, queue: #{@queue}, options: #{@metadata}, message: #{@payload})"
28
31
 
29
32
  @data = Oj.load(@payload, mode: :compat).to_hash_with_indifferent_access
30
33
  @headers.merge!(@data["headers"] || {})
31
34
 
32
35
  @action ||= @headers["action"]
33
36
  @headers["action"] ||= @action
37
+ @version = @headers["version"]
34
38
  @params = @data["params"] || {}
35
39
  rescue Oj::ParseError => e
36
40
  logger.error(e.inspect + "\n" + e.backtrace.join("\n"))
@@ -9,12 +9,13 @@ module Fleck
9
9
  @id = request_id
10
10
  logger.progname += " #{@id}"
11
11
 
12
- @status = 200
13
- @errors = []
14
- @headers = {}
15
- @body = nil
16
- @rejected = false
17
- @requeue = false
12
+ @status = 200
13
+ @errors = []
14
+ @headers = {}
15
+ @body = nil
16
+ @rejected = false
17
+ @requeue = false
18
+ @deprecated = false
18
19
  end
19
20
 
20
21
  def reject!(requeue: false)
@@ -30,6 +31,10 @@ module Fleck
30
31
  return @requeue
31
32
  end
32
33
 
34
+ def deprecated!
35
+ @deprecated = true
36
+ end
37
+
33
38
  def not_found(msg = nil)
34
39
  @status = 404
35
40
  @errors << 'Resource Not Found'
@@ -47,10 +52,11 @@ module Fleck
47
52
 
48
53
  def to_json
49
54
  return Oj.dump({
50
- "status" => @status,
51
- "errors" => @errors,
52
- "headers" => @headers,
53
- "body" => @body
55
+ "status" => @status,
56
+ "errors" => @errors,
57
+ "headers" => @headers,
58
+ "body" => @body,
59
+ "deprecated" => @deprecated
54
60
  }, mode: :compat)
55
61
  rescue => e
56
62
  logger.error e.inspect + "\n" + e.backtrace.join("\n")
@@ -2,7 +2,7 @@
2
2
  module Fleck
3
3
  class Consumer
4
4
  class << self
5
- attr_accessor :logger, :configs, :consumers
5
+ attr_accessor :logger, :configs, :actions_map, :consumers, :initialize_block
6
6
  end
7
7
 
8
8
  def self.inherited(subclass)
@@ -17,14 +17,43 @@ module Fleck
17
17
  logger.debug "Consumer configurations updated."
18
18
  end
19
19
 
20
+ def self.actions(*args)
21
+ args.each do |item|
22
+ case item
23
+ when Hash
24
+ item.each do |k,v|
25
+ self.register_action(k.to_s, v.to_s)
26
+ end
27
+ else
28
+ self.register_action(item.to_s, item.to_s)
29
+ end
30
+ end
31
+ end
32
+
33
+ def self.register_action(action, method_name)
34
+ self.actions_map[action.to_s] = method_name.to_s
35
+ end
36
+
37
+ def self.initialize(&block)
38
+ self.initialize_block = block
39
+ end
40
+
41
+ def self.start
42
+ self.consumers.each do |consumer|
43
+ consumer.start
44
+ end
45
+ end
46
+
20
47
  def self.init_consumer(subclass)
21
48
  subclass.logger = Fleck.logger.clone
22
49
  subclass.logger.progname = subclass.to_s
23
50
 
24
51
  subclass.logger.debug "Setting defaults for #{subclass.to_s.color(:yellow)} consumer"
25
52
 
26
- subclass.configs = Fleck.config.default_options
27
- subclass.consumers = []
53
+ subclass.configs = Fleck.config.default_options
54
+ subclass.configs[:autostart] = true if subclass.configs[:autostart].nil?
55
+ subclass.actions_map = {}
56
+ subclass.consumers = []
28
57
  end
29
58
 
30
59
  def self.autostart(subclass)
@@ -46,6 +75,8 @@ module Fleck
46
75
  @__thread_id = thread_id
47
76
  @__connection = nil
48
77
  @__consumer_tag = nil
78
+ @__request = nil
79
+ @__response = nil
49
80
 
50
81
  @__host = configs[:host]
51
82
  @__port = configs[:port]
@@ -55,26 +86,40 @@ module Fleck
55
86
  @__exchange_type = configs[:exchange_type] || :direct
56
87
  @__exchange_name = configs[:exchange_name] || ""
57
88
  @__queue_name = configs[:queue]
89
+ @__autostart = configs[:autostart]
90
+
91
+ if self.class.initialize_block
92
+ self.instance_eval(&self.class.initialize_block)
93
+ end
58
94
 
59
95
  logger.info "Launching #{self.class.to_s.color(:yellow)} consumer ..."
60
96
 
61
- connect!
62
- create_channel!
63
- subscribe!
97
+ start if @__autostart
64
98
 
65
99
  at_exit do
66
100
  terminate
67
101
  end
68
102
  end
69
103
 
104
+ def start
105
+ connect!
106
+ create_channel!
107
+ subscribe!
108
+ end
109
+
70
110
  def on_message(request, response)
71
- raise NotImplementedError.new("You must implement #on_message(delivery_info, metadata, payload) method")
111
+ method_name = actions[request.action.to_s]
112
+ if method_name
113
+ self.send(method_name)
114
+ else
115
+ response.not_found
116
+ end
72
117
  end
73
118
 
74
119
  def terminate
75
120
  pause
76
- unless @__channel.closed?
77
- @__channel.close
121
+ unless channel.nil? || channel.closed?
122
+ channel.close
78
123
  logger.info "Consumer successfully terminated."
79
124
  end
80
125
  end
@@ -91,6 +136,10 @@ module Fleck
91
136
  @configs ||= self.class.configs
92
137
  end
93
138
 
139
+ def actions
140
+ @actions ||= self.class.actions_map
141
+ end
142
+
94
143
  def connection
95
144
  return @__connection
96
145
  end
@@ -116,14 +165,29 @@ module Fleck
116
165
  end
117
166
 
118
167
  def pause
119
- cancel_ok = @__subscription.cancel
120
- @__consumer_tag = cancel_ok.consumer_tag
168
+ if subscription
169
+ cancel_ok = subscription.cancel
170
+ @__consumer_tag = cancel_ok.consumer_tag
171
+ end
121
172
  end
122
173
 
123
174
  def resume
124
175
  subscribe!
125
176
  end
126
177
 
178
+ def request
179
+ @__request
180
+ end
181
+
182
+ def response
183
+ @__response
184
+ end
185
+
186
+ def deprecated!
187
+ logger.warn("DEPRECATION: the method `#{caller_locations(1,1)[0].label}` is going to be deprecated. Please, consider using a newer version of this method.")
188
+ @__response.deprecated! if @__response
189
+ end
190
+
127
191
  protected
128
192
 
129
193
  def connect!
@@ -155,31 +219,31 @@ module Fleck
155
219
  options[:consumer_tag] = @__consumer_tag if @__consumer_tag
156
220
 
157
221
  @__subscription = @__queue.subscribe(options) do |delivery_info, metadata, payload|
158
- response = Fleck::Consumer::Response.new(metadata.correlation_id)
222
+ @__response = Fleck::Consumer::Response.new(metadata.correlation_id)
159
223
  begin
160
- request = Fleck::Consumer::Request.new(metadata, payload)
161
- if request.errors.empty?
162
- on_message(request, response)
224
+ @__request = Fleck::Consumer::Request.new(metadata, payload, delivery_info)
225
+ if @__request.errors.empty?
226
+ on_message(@__request, @__response)
163
227
  else
164
- response.status = request.status
165
- response.errors += request.errors
228
+ @__response.status = @__request.status
229
+ @__response.errors += @__request.errors
166
230
  end
167
231
  rescue => e
168
232
  logger.error e.inspect + "\n" + e.backtrace.join("\n")
169
- response.status = 500
170
- response.errors << 'Internal Server Error'
233
+ @__response.status = 500
234
+ @__response.errors << 'Internal Server Error'
171
235
  end
172
236
 
173
- if response.rejected?
237
+ if @__response.rejected?
174
238
  # the request was rejected, so we have to notify the reject
175
- logger.warn "Request #{response.id} was rejected!"
176
- @__channel.reject(delivery_info.delivery_tag, response.requeue?)
239
+ logger.warn "Request #{@__response.id} was rejected!"
240
+ @__channel.reject(delivery_info.delivery_tag, @__response.requeue?)
177
241
  else
178
- logger.debug "Sending response: #{response}"
242
+ logger.debug "Sending response: #{@__response}"
179
243
  if @__channel.closed?
180
244
  logger.warn "Channel already closed! The response #{metadata.correlation_id} is going to be dropped."
181
245
  else
182
- @__publisher.publish(response.to_json, routing_key: metadata.reply_to, correlation_id: metadata.correlation_id, mandatory: true)
246
+ @__publisher.publish(@__response.to_json, routing_key: metadata.reply_to, correlation_id: metadata.correlation_id, mandatory: true)
183
247
  @__channel.ack(delivery_info.delivery_tag)
184
248
  end
185
249
  end
data/lib/fleck/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Fleck
2
- VERSION = "0.4.1"
2
+ VERSION = "0.5.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.4.1
4
+ version: 0.5.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-18 00:00:00.000000000 Z
11
+ date: 2016-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '0.3'
117
+ version: '0.4'
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '0.3'
124
+ version: '0.4'
125
125
  description: "\n Fleck is a library for syncronous and asyncronous communication
126
126
  over Message Queues services. Unlike a common\n HTTP communication, Fleck requests
127
127
  and responses are pure JSON messages.\n "
@@ -142,7 +142,13 @@ files:
142
142
  - Rakefile
143
143
  - bin/console
144
144
  - bin/setup
145
- - example.rb
145
+ - examples/actions.rb
146
+ - examples/consumer_initialization.rb
147
+ - examples/deprecation.rb
148
+ - examples/example.rb
149
+ - examples/expired.rb
150
+ - examples/fanout.rb
151
+ - examples/no_autostart.rb
146
152
  - fleck.gemspec
147
153
  - lib/fleck.rb
148
154
  - lib/fleck/client.rb