freddy-jruby 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2f29ab8dfbedae9cbd544b20c86c7adf72d4859e
4
+ data.tar.gz: dd7c545605d297356f2fdb27b3fef5fda8735474
5
+ SHA512:
6
+ metadata.gz: 4674e17c54e5dee1b926bf6354987d5a7e2df079a39754d4ed9a332ec705e8046e5fe69112af09d95b0b1be0fa94288c70a12b45eb4f0d5a7c994df9b5024a1b
7
+ data.tar.gz: 20784cb06674e8c0135315dfa9566a0933258e3b8cd7f74a722f68e4d7ff9126cec68d3154211c875af128baa39b8a96a5fd56c95461390ac3a5192ee7f94313
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .bundle/
2
+ Gemfile.lock
3
+ pkg
4
+ .tags
5
+ .tags1
data/.npmignore ADDED
@@ -0,0 +1,8 @@
1
+ .git*
2
+ .rspec
3
+ .ruby*
4
+ Gemfile*
5
+ *.gemspec
6
+ spec/
7
+ *.rb
8
+ lib/messaging
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ sm-messaging
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.0
4
+ - jruby-9.0.0.0
5
+ services:
6
+ - rabbitmq
7
+ before_script:
8
+ - gem install bundler
9
+ - bundle install
10
+ script: bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ group :test, :development do
4
+ gem 'rspec'
5
+ gem 'pry'
6
+ end
7
+
8
+ gemspec
data/LICENCE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013-2015 SaleMove Inc.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,170 @@
1
+ # Messaging API supporting acknowledgements and request-response
2
+
3
+ [![Build Status](https://travis-ci.org/salemove/node-freddy.svg?branch=master)](https://travis-ci.org/salemove/node-freddy)
4
+ [![Code Climate](https://codeclimate.com/github/salemove/freddy/badges/gpa.svg)](https://codeclimate.com/github/salemove/freddy)
5
+
6
+ ## Setup
7
+
8
+ * Inject the appropriate logger and set up connection parameters:
9
+
10
+ ```ruby
11
+ logger = Logger.new(STDOUT)
12
+ freddy = Freddy.build(logger, host: 'localhost', port: 5672, user: 'guest', pass: 'guest')
13
+ ```
14
+
15
+ ## Delivering messages
16
+
17
+ ### Simple delivery
18
+
19
+ #### Send and forget
20
+ Sends a `message` to the given `destination`. If there is no consumer then the
21
+ message stays in the queue until somebody consumes it.
22
+ ```ruby
23
+ freddy.deliver(destination, message)
24
+ ```
25
+
26
+ #### Expiring messages
27
+ Sends a `message` to the given `destination`. If nobody consumes the message in
28
+ `timeout` seconds then the message is discarded. This is useful for showing
29
+ notifications that must happen in a certain timeframe but where we don't really
30
+ care if it reached the destination or not.
31
+ ```ruby
32
+ freddy.deliver(destination, message, timeout: 5)
33
+ ```
34
+
35
+ ### Request delivery
36
+ #### Expiring messages
37
+ Sends a `message` to the given `destination`. Has a default timeout of 3 and
38
+ discards the message from the queue if a response hasn't been returned in that
39
+ time.
40
+ ```ruby
41
+ response = freddy.deliver_with_response(destination, message)
42
+ ```
43
+
44
+ #### Persistant messages
45
+ Sends a `message` to the given `destination`. Keeps the message in the queue if
46
+ a timeout occurs.
47
+ ```ruby
48
+ response = freddy.deliver_with_response(destination, message, timeout: 4, delete_on_timeout: false)
49
+ ```
50
+
51
+ #### Errors
52
+ `deliver_with_response` raises an error if an error is returned. This can be handled by rescuing from `Freddy::InvalidRequestError` and `Freddy::TimeoutError` as:
53
+ ```ruby
54
+ begin
55
+ response = freddy.deliver_with_response 'Q', {}
56
+ # ...
57
+ rescue Freddy::InvalidRequestError => e
58
+ e.response # => { error: 'InvalidRequestError', message: 'Some error message' }
59
+ rescue Freddy::TimeoutError => e
60
+ e.response # => { error: 'RequestTimeout', message: 'Timed out waiting for response' }
61
+ ```
62
+
63
+ ## Responding to messages
64
+ ```ruby
65
+ freddy.respond_to destination do |message, msg_handler|
66
+ # ...
67
+ end
68
+ ```
69
+
70
+ The callback is called with 2 arguments
71
+ * the parsed message (note that in the message all keys are symbolized)
72
+ * the `MessageHandler` (described further down)
73
+
74
+ ## The MessageHandler
75
+
76
+ When responding to messages the MessageHandler is given as the second argument.
77
+
78
+ The following operations are supported:
79
+
80
+ * responding with a successful response
81
+ ```ruby
82
+ msg_handler.success(response = nil)
83
+ ```
84
+
85
+ * responding with an error response
86
+ ```ruby
87
+ msg_handler.error(error: "Couldn't process message")
88
+ ```
89
+
90
+ ## Tapping into messages
91
+ When it's necessary to receive messages but not consume them, consider tapping.
92
+
93
+ ```ruby
94
+ freddy.tap_into pattern do |message, destination|
95
+ ```
96
+
97
+ * `destination` refers to the destination that the message was sent to
98
+ * Note that it is not possible to respond to the message while tapping.
99
+ * When tapping the following wildcards are supported in the `pattern` :
100
+ * `#` matching 0 or more words
101
+ * `*` matching exactly one word
102
+
103
+ Examples:
104
+
105
+ ```ruby
106
+ freddy.tap_into "i.#.free"
107
+ ```
108
+
109
+ receives messages that are delivered to `"i.want.to.break.free"`
110
+
111
+ ```ruby
112
+ freddy.tap_into "somebody.*.love"
113
+ ```
114
+
115
+ receives messages that are delivered to `somebody.to.love` but doesn't receive messages delivered to `someboy.not.to.love`
116
+
117
+ ## The ResponderHandler
118
+
119
+ When responding to a message or tapping the ResponderHandler is returned.
120
+ ```ruby
121
+ responder_handler = freddy.respond_to ....
122
+ ```
123
+
124
+ The following operations are supported:
125
+
126
+ * stop responding
127
+ ```ruby
128
+ responder_handler.cancel
129
+ ```
130
+
131
+ * delete the destination
132
+ ```ruby
133
+ responder_handler.destroy_destination
134
+ ```
135
+
136
+ * Primary use case is in tests to not leave dangling destinations. It deletes the destination even if there are responders for the same destination in other parts of the system. Use with caution in production code.
137
+
138
+
139
+ ## Notes about concurrency
140
+
141
+ The underlying bunny implementation uses 1 responder thread by default. This means that if there is a time-consuming process or a sleep call in a responder then other responders will not receive messages concurrently.
142
+ To resolve this problem *freddy* uses a thread pool for running concurrent responders.
143
+ The thread pool is shared between *tap_into* and *respond_to* callbacks and the default size is 4.
144
+ The thread pool size can be configured by passing the configuration option *max_concurrency*.
145
+
146
+
147
+ Note that while it is possible to use *deliver_with_response* inside a *respond_to* block,
148
+ it is not possible to use another *respond_to* block inside a different *respond_to* block.
149
+
150
+
151
+ Note also that other configuration options for freddy users
152
+ such as pool sizes for DB connections need to match or exceed *max_concurrency*
153
+ to avoid running out of resources.
154
+
155
+ Read more from <http://rubybunny.info/articles/concurrency.html>.
156
+
157
+ ## Credits
158
+
159
+ **freddy** was originally written by [Urmas Talimaa] as part of SaleMove development team.
160
+
161
+ ![SaleMove Inc. 2012][SaleMove Logo]
162
+
163
+ **freddy** is maintained and funded by [SaleMove, Inc].
164
+
165
+ The names and logos for **SaleMove** are trademarks of SaleMove, Inc.
166
+
167
+ [Urmas Talimaa]: https://github.com/urmastalimaa?source=c "Urmas"
168
+ [SaleMove, Inc]: http://salemove.com/ "SaleMove Website"
169
+ [SaleMove Logo]: http://app.salemove.com/assets/logo.png "SaleMove Inc. 2012"
170
+ [Apache License]: http://choosealicense.com/licenses/apache/ "Apache License"
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task ci: :spec
7
+ task default: :spec
data/freddy.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ if RUBY_PLATFORM == 'java'
7
+ spec.name = "freddy-jruby"
8
+ else
9
+ spec.name = "freddy"
10
+ end
11
+ spec.version = '0.4.3'
12
+ spec.authors = ["Urmas Talimaa"]
13
+ spec.email = ["urmas.talimaa@gmail.com"]
14
+ spec.description = %q{Messaging API}
15
+ spec.summary = %q{API for inter-application messaging supporting acknowledgements and request-response}
16
+ spec.license = "Private"
17
+
18
+ spec.files = `git ls-files`.split($/)
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler"
24
+ spec.add_development_dependency "rake"
25
+
26
+ if RUBY_PLATFORM == 'java'
27
+ spec.add_dependency 'march_hare', '~> 2.12.0'
28
+ else
29
+ spec.add_dependency "bunny", "2.2.0"
30
+ end
31
+
32
+ spec.add_dependency "symbolizer"
33
+ spec.add_dependency "hamster", "~> 1.0.1.pre.rc3"
34
+ spec.add_dependency "thread", "~> 0.2"
35
+ end
@@ -0,0 +1,34 @@
1
+ class Freddy
2
+ class AdaptiveQueue
3
+ def initialize(queue)
4
+ @queue = queue
5
+ end
6
+
7
+ def subscribe(&block)
8
+ if hare?
9
+ @queue.subscribe do |meta, payload|
10
+ block.call(payload, Delivery.new(meta, meta.routing_key))
11
+ end
12
+ else
13
+ @queue.subscribe do |info, properties, payload|
14
+ block.call(payload, Delivery.new(properties, info.routing_key))
15
+ end
16
+ end
17
+ end
18
+
19
+ def bind(*args)
20
+ @queue.bind(*args)
21
+ self
22
+ end
23
+
24
+ def name
25
+ @queue.name
26
+ end
27
+
28
+ private
29
+
30
+ def hare?
31
+ RUBY_PLATFORM == 'java'
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,77 @@
1
+ require_relative 'responder_handler'
2
+ require_relative 'message_handler'
3
+ require_relative 'request'
4
+ require_relative 'delivery'
5
+
6
+ class Freddy
7
+ class Consumer
8
+
9
+ class EmptyConsumer < Exception
10
+ end
11
+
12
+ def initialize(channel, logger, consume_thread_pool)
13
+ @channel, @logger = channel, logger
14
+ @topic_exchange = @channel.topic Freddy::FREDDY_TOPIC_EXCHANGE_NAME
15
+ @consume_thread_pool = consume_thread_pool
16
+ @dedicated_thread_pool = Thread.pool(1) # used only internally
17
+ end
18
+
19
+ def consume(destination, options = {}, &block)
20
+ raise EmptyConsumer unless block
21
+ consume_from_queue create_queue(destination), options, &block
22
+ end
23
+
24
+ def consume_from_queue(queue, options = {}, &block)
25
+ consume_using_pool(queue, options, @consume_thread_pool, &block)
26
+ end
27
+
28
+ def dedicated_consume(queue, &block)
29
+ consume_using_pool(queue, {}, @dedicated_thread_pool, &block)
30
+ end
31
+
32
+ def tap_into(pattern, &block)
33
+ queue = create_queue('', exclusive: true).bind(@topic_exchange, routing_key: pattern)
34
+ consumer = queue.subscribe do |payload, delivery|
35
+ @consume_thread_pool.process do
36
+ block.call parse_payload(payload), delivery.routing_key
37
+ end
38
+ end
39
+ @logger.debug "Tapping into messages that match #{pattern}"
40
+ ResponderHandler.new consumer, @channel
41
+ end
42
+
43
+ private
44
+
45
+ def consume_using_pool(queue, options, pool, &block)
46
+ consumer = queue.subscribe do |payload, delivery|
47
+ pool.process do
48
+ parsed_payload = parse_payload(payload)
49
+ log_receive_event(queue.name, parsed_payload, delivery.correlation_id)
50
+ block.call parsed_payload, delivery
51
+ end
52
+ end
53
+ @logger.debug "Consuming messages on #{queue.name}"
54
+ ResponderHandler.new consumer, @channel
55
+ end
56
+
57
+ def parse_payload(payload)
58
+ if payload == 'null'
59
+ {}
60
+ else
61
+ Symbolizer.symbolize(JSON(payload))
62
+ end
63
+ end
64
+
65
+ def create_queue(destination, options={})
66
+ AdaptiveQueue.new(@channel.queue(destination, options))
67
+ end
68
+
69
+ def log_receive_event(queue_name, payload, correlation_id)
70
+ if defined?(Logasm) && @logger.is_a?(Logasm)
71
+ @logger.debug "Received message", queue: queue_name, payload: payload, correlation_id: correlation_id
72
+ else
73
+ @logger.debug "Received message on #{queue_name} with payload #{payload} with correlation_id #{correlation_id}"
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,14 @@
1
+ class Freddy
2
+ class Delivery
3
+ attr_reader :metadata, :routing_key
4
+
5
+ def initialize(metadata, routing_key)
6
+ @metadata = metadata
7
+ @routing_key = routing_key
8
+ end
9
+
10
+ def correlation_id
11
+ @metadata.correlation_id
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ class Freddy
2
+ class MessageHandler
3
+ attr_reader :destination, :correlation_id
4
+
5
+ def initialize(adapter, delivery)
6
+ @adapter = adapter
7
+ @metadata = delivery.metadata
8
+ @correlation_id = @metadata.correlation_id
9
+ end
10
+
11
+ def success(response = nil)
12
+ @adapter.success(@metadata.reply_to, response)
13
+ end
14
+
15
+ def error(error = {error: "Couldn't process message"})
16
+ @adapter.error(@metadata.reply_to, error)
17
+ end
18
+ end
19
+ end