brightbox-warren 0.5 → 0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/{LICENCE → LICENSE} +3 -3
- data/Manifest +12 -8
- data/Rakefile +2 -2
- data/examples/authed/receiver.rb +3 -1
- data/examples/authed/sender.rb +4 -2
- data/examples/simple/{mass_sender.rb → amqp_mass_sender.rb} +0 -0
- data/examples/simple/amqp_receiver.rb +18 -0
- data/examples/simple/{sender.rb → amqp_sender.rb} +7 -4
- data/examples/simple/bunny_receiver.rb +18 -0
- data/examples/simple/bunny_sender.rb +36 -0
- data/lib/warren.rb +8 -9
- data/lib/warren/adapters/amqp_adapter.rb +87 -0
- data/lib/warren/adapters/bunny_adapter.rb +97 -0
- data/lib/warren/connection.rb +56 -34
- data/lib/warren/{message_filters → filters}/shared_secret.rb +12 -12
- data/lib/warren/{message_filters → filters}/yaml.rb +3 -3
- data/lib/warren/message_filter.rb +21 -15
- data/lib/warren/queue.rb +47 -63
- data/readme.rdoc +16 -9
- data/spec/spec_helper.rb +0 -3
- data/spec/warren/connection_spec.rb +65 -46
- data/spec/warren/queue_spec.rb +58 -78
- data/spec/warren/warren_spec.rb +9 -0
- data/warren.gemspec +6 -7
- metadata +20 -14
- data/examples/simple/receiver.rb +0 -17
- data/spec/hash_extend.rb +0 -9
- data/spec/warren/message_filter_spec.rb +0 -84
data/lib/warren/connection.rb
CHANGED
@@ -1,37 +1,59 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
#
|
8
|
-
|
9
|
-
|
1
|
+
require "yaml"
|
2
|
+
module Warren
|
3
|
+
class Connection
|
4
|
+
|
5
|
+
attr_reader :options
|
6
|
+
|
7
|
+
#
|
8
|
+
# Creates a new connection by reading the options from
|
9
|
+
# WARREN_ROOT/config/warren.yml or specify the file to read as an
|
10
|
+
# argument or by passing a hash of connection details in.
|
11
|
+
#
|
12
|
+
# Reads WARREN_ENV out of the yaml'd hash (just like ActiveRecord)
|
13
|
+
# "development" by default (and RAILS_ENV if running under rails)
|
14
|
+
#
|
15
|
+
# Raises InvalidConnectionDetails if no params are found for the current
|
16
|
+
# environment.
|
17
|
+
#
|
18
|
+
def initialize params = nil
|
19
|
+
if params.nil? || !params.is_a?(Hash)
|
20
|
+
file ||= WARREN_ROOT << "/config" << "/warren.yml"
|
21
|
+
raise InvalidConnectionDetails, "Config file not found: #{file}" unless File.exists?(file)
|
22
|
+
opts = YAML.load(file)
|
23
|
+
end
|
24
|
+
opts ||= params
|
25
|
+
|
26
|
+
opts = symbolize_keys(opts[WARREN_ENV])
|
27
|
+
check_connection_details(opts)
|
28
|
+
@options = opts
|
10
29
|
end
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
|
31
|
+
#
|
32
|
+
# Raised if connection details are missing or invalid
|
33
|
+
# Check the error message for more details
|
34
|
+
#
|
35
|
+
InvalidConnectionDetails = Class.new(Exception)
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
#
|
40
|
+
# Changes all keys into symbols
|
41
|
+
#
|
42
|
+
def symbolize_keys(hash)
|
43
|
+
hash.each do |key, value|
|
44
|
+
hash.delete(key)
|
45
|
+
hash[key.to_sym] = value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Calls the adapter to check the connection details
|
51
|
+
# Returns true or raises InvalidConnectionDetails
|
52
|
+
#
|
53
|
+
def check_connection_details params
|
54
|
+
return true unless Warren::Queue.adapter.respond_to?(:check_connection_details)
|
55
|
+
Warren::Queue.adapter.send(:check_connection_details, params)
|
56
|
+
end
|
57
|
+
|
36
58
|
end
|
37
59
|
end
|
@@ -10,41 +10,41 @@ module Warren
|
|
10
10
|
# Hashes the message using a secret salt, stores the hash
|
11
11
|
# in the message and then checks its the same when pulled
|
12
12
|
# off the other end.
|
13
|
-
#
|
13
|
+
#
|
14
14
|
# Basic trust implementation to make sure the message
|
15
15
|
# hasn't been tampered with in transit and came from
|
16
16
|
# an "authorised" app.
|
17
|
-
#
|
17
|
+
#
|
18
18
|
# Make sure both the publisher and subscriber use the same
|
19
19
|
# key else you'll get KeyValidationError error raised.
|
20
|
-
#
|
20
|
+
#
|
21
21
|
class SharedSecret
|
22
22
|
# Raised when no key (salt) is provided
|
23
23
|
class NoKeyError < Exception; end
|
24
24
|
# Raised when there is a key mismatch error
|
25
25
|
class KeyValidationError < Exception; end
|
26
|
-
|
26
|
+
|
27
27
|
# Sets the key to use
|
28
28
|
def self.key= key
|
29
29
|
@@key = key
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
# Returns the current key
|
33
33
|
# Raises NoKeyError if no key has been assigned yet
|
34
34
|
def self.key
|
35
35
|
raise NoKeyError if @@key.nil?
|
36
36
|
@@key
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
# Returns the hashed message
|
40
|
-
#
|
40
|
+
#
|
41
41
|
# Expects that msg#to_s returns a string
|
42
42
|
# to hash against.
|
43
|
-
#
|
43
|
+
#
|
44
44
|
def self.secret msg
|
45
45
|
HMAC::SHA256.hexdigest(self.key, msg.to_s)
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
# Called when the message is being packed for
|
49
49
|
# transit. Returns a hash.
|
50
50
|
def self.pack msg
|
@@ -54,7 +54,7 @@ module Warren
|
|
54
54
|
msg[:secret] = self.secret(msg.to_s)
|
55
55
|
msg
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
# Called when unpacking the message from transit.
|
59
59
|
# Returns the original object.
|
60
60
|
def self.unpack msg
|
@@ -62,9 +62,9 @@ module Warren
|
|
62
62
|
raise KeyValidationError unless msg.delete(:secret) == self.secret(msg)
|
63
63
|
# see if its a hash we created, it'll only contain the key "secret_msg" if it is
|
64
64
|
msg = msg[:secret_msg] if msg.keys == [:secret_msg]
|
65
|
-
msg
|
65
|
+
msg
|
66
66
|
end
|
67
|
-
|
67
|
+
|
68
68
|
end
|
69
69
|
end
|
70
70
|
end
|
@@ -4,13 +4,13 @@ module Warren
|
|
4
4
|
class MessageFilter
|
5
5
|
# Packs the message into a YAML string
|
6
6
|
# for transferring safely across the wire
|
7
|
-
class Yaml
|
8
|
-
|
7
|
+
class Yaml < MessageFilter
|
8
|
+
|
9
9
|
# Returns a YAML string
|
10
10
|
def self.pack msg
|
11
11
|
YAML.dump(msg)
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
# Returns original message
|
15
15
|
def self.unpack msg
|
16
16
|
YAML.load(msg)
|
@@ -1,46 +1,49 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + "/message_filters/yaml")
|
2
|
-
|
3
1
|
module Warren
|
4
2
|
# Handles filtering messages going onto/coming off the queue
|
5
3
|
class MessageFilter
|
6
4
|
# Array of filters to be run on the message before its
|
7
5
|
# pushed to rabbit.
|
8
|
-
#
|
9
|
-
# NB: These get called in reverse order from the array -
|
6
|
+
#
|
7
|
+
# NB: These get called in reverse order from the array -
|
10
8
|
# the last filter to be added gets called first.
|
11
|
-
@@filters = [
|
9
|
+
@@filters = []
|
12
10
|
|
13
11
|
class << self
|
14
12
|
# Adds a filter to the list
|
15
|
-
#
|
13
|
+
#
|
16
14
|
# A valid filter is just a class that defines
|
17
|
-
# <tt>self.pack</tt> and <tt>self.unpack</tt>
|
15
|
+
# <tt>self.pack</tt> and <tt>self.unpack</tt>
|
18
16
|
# methods, which both accept a single argument,
|
19
17
|
# act upon it, and return the output.
|
20
|
-
#
|
18
|
+
#
|
21
19
|
# Example filter class (See also message_filters/*.rb)
|
22
|
-
#
|
20
|
+
#
|
23
21
|
# class Foo
|
24
22
|
# def self.pack msg
|
25
23
|
# msg.reverse # Assumes msg responds to reverse
|
26
24
|
# end
|
27
|
-
#
|
25
|
+
#
|
28
26
|
# def self.unpack msg
|
29
27
|
# msg.reverse # Does the opposite of Foo#pack
|
30
28
|
# end
|
31
29
|
# end
|
32
|
-
#
|
30
|
+
#
|
33
31
|
def << filter
|
34
32
|
@@filters << filter
|
35
33
|
end
|
36
34
|
alias :add_filter :<<
|
37
35
|
end
|
38
|
-
|
36
|
+
|
37
|
+
# Called when a subclass is created, adds the subclass to the queue
|
38
|
+
def self.inherited klass
|
39
|
+
add_filter klass
|
40
|
+
end
|
41
|
+
|
39
42
|
# Returns current array of filters
|
40
43
|
def self.filters
|
41
44
|
@@filters
|
42
45
|
end
|
43
|
-
|
46
|
+
|
44
47
|
# Resets the filters to default
|
45
48
|
def self.reset_filters
|
46
49
|
@@filters = [Warren::MessageFilter::Yaml]
|
@@ -65,6 +68,9 @@ module Warren
|
|
65
68
|
end
|
66
69
|
msg
|
67
70
|
end
|
68
|
-
|
71
|
+
|
69
72
|
end
|
70
|
-
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Make sure the YAML filter is added first
|
76
|
+
require File.expand_path(File.dirname(__FILE__) + "/filters/yaml")
|
data/lib/warren/queue.rb
CHANGED
@@ -1,85 +1,69 @@
|
|
1
1
|
class Warren::Queue
|
2
2
|
@@connection = nil
|
3
|
-
|
3
|
+
@@adapter = nil
|
4
|
+
|
5
|
+
#
|
4
6
|
# Raised if no connection has been defined yet.
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
#
|
8
|
+
NoConnectionDetails = Class.new(Exception)
|
9
|
+
|
10
|
+
#
|
8
11
|
# Raised if a block is expected by the method but none is given.
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
#
|
13
|
-
|
14
|
-
|
12
|
+
#
|
13
|
+
NoBlockGiven = Class.new(Exception)
|
14
|
+
|
15
|
+
#
|
16
|
+
# Raised if an adapter isn't set
|
17
|
+
#
|
18
|
+
NoAdapterSet = Class.new(Exception)
|
19
|
+
|
20
|
+
#
|
21
|
+
# Sets the current connection
|
22
|
+
#
|
23
|
+
def self.connection= conn
|
24
|
+
@@connection = (conn.is_a?(Warren::Connection) ? conn : Warren::Connection.new(conn) )
|
15
25
|
end
|
16
26
|
|
27
|
+
#
|
17
28
|
# Returns the current connection details
|
18
|
-
#
|
19
|
-
# assigned yet.
|
29
|
+
#
|
20
30
|
def self.connection
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
31
|
+
@@connection ||= Warren::Connection.new
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Sets the adapter when this class is subclassed.
|
36
|
+
#
|
37
|
+
def self.inherited klass
|
38
|
+
@@adapter = klass
|
25
39
|
end
|
26
40
|
|
27
|
-
#
|
28
|
-
# Sends a message to a queue. If successfully sent it returns
|
29
|
-
# true, unless callback block is passed (see below)
|
30
41
|
#
|
31
|
-
#
|
42
|
+
# Sets the adapter manually
|
32
43
|
#
|
33
|
-
|
34
|
-
|
35
|
-
|
44
|
+
def self.adapter= klass
|
45
|
+
@@adapter = klass
|
46
|
+
end
|
47
|
+
|
36
48
|
#
|
37
|
-
#
|
49
|
+
# Returns the current adapter or raises NoAdapterSet exception
|
38
50
|
#
|
39
|
-
def self.
|
40
|
-
|
41
|
-
|
42
|
-
msg = Warren::MessageFilter.pack(payload)
|
43
|
-
|
44
|
-
do_connect(true, blk) do
|
45
|
-
queue = MQ::Queue.new(MQ.new, queue_name)
|
46
|
-
queue.publish msg.to_s
|
47
|
-
end
|
51
|
+
def self.adapter
|
52
|
+
@@adapter || raise(NoAdapterSet)
|
53
|
+
end
|
48
54
|
|
55
|
+
#
|
56
|
+
# Publishes the message to the queue
|
57
|
+
#
|
58
|
+
def self.publish *args, &blk
|
59
|
+
self.adapter.publish(*args, &blk)
|
49
60
|
end
|
50
61
|
|
51
|
-
#
|
52
|
-
# Subscribes to a queue and runs the block
|
53
|
-
# for each message received
|
54
|
-
#
|
55
|
-
# Warren::Queue.subscribe("example") {|msg| puts msg }
|
56
62
|
#
|
57
|
-
#
|
63
|
+
# Sends the subscribe message to the adapter class
|
58
64
|
#
|
59
|
-
def self.subscribe
|
60
|
-
|
61
|
-
queue_name = self.connection.queue_name if queue_name == :default
|
62
|
-
# todo: check if its a valid queue?
|
63
|
-
do_connect(false) do
|
64
|
-
queue = MQ::Queue.new(MQ.new, queue_name)
|
65
|
-
queue.subscribe do |msg|
|
66
|
-
msg = Warren::MessageFilter.unpack(msg)
|
67
|
-
block.call(msg)
|
68
|
-
end
|
69
|
-
end
|
65
|
+
def self.subscribe *args, &blk
|
66
|
+
self.adapter.subscribe(*args, &blk)
|
70
67
|
end
|
71
68
|
|
72
|
-
|
73
|
-
private
|
74
|
-
|
75
|
-
# Connects and does the stuff its told to!
|
76
|
-
def self.do_connect should_stop = true, callback = nil, &block
|
77
|
-
AMQP.start(self.connection.options) do
|
78
|
-
block.call
|
79
|
-
AMQP.stop { EM.stop_event_loop } if should_stop
|
80
|
-
end
|
81
|
-
# Returns the block return value or true
|
82
|
-
callback.nil? ? true : callback.call
|
83
|
-
end
|
84
|
-
|
85
69
|
end
|
data/readme.rdoc
CHANGED
@@ -1,19 +1,26 @@
|
|
1
1
|
= Warren
|
2
2
|
|
3
|
-
Library for
|
3
|
+
Library for sending and receiving messages, complete with en/decrypting messages on either side of the transport.
|
4
4
|
|
5
|
-
It
|
5
|
+
It was written to handle sending messages between two nodes using RabbitMQ, which is why the two default adapters are for synchronous and asynchronous rabbitmq client libraries. You can delegate the sending & receiving to any custom class you want, simply by subclassing Warren::Queue. (Isn't ruby magic marvelous!)
|
6
6
|
|
7
|
-
|
7
|
+
The filtering works in much the same way as the adapter class. There is a default YAML filter that is always called last before sending the message and first when receiving the message, simply to make sure the message is a string when sent + received. You can then add custom classes onto the stack in any order you want, simply by subclassing Warren::MessageFilter. Add them in the same order on the receiving side and warren takes care of calling them in reverse order.
|
8
8
|
|
9
|
-
|
9
|
+
Start by looking at examples/ to see how to use it, and then lib/warren/adapters/ to see how to implement your own adapter class and lib/warren/filters to see how to implement your own filters.
|
10
10
|
|
11
|
-
|
11
|
+
== Installation
|
12
12
|
|
13
|
-
|
13
|
+
gem install brightbox-warren
|
14
14
|
|
15
|
-
|
15
|
+
== Usage
|
16
16
|
|
17
|
-
|
17
|
+
require "rubygems"
|
18
|
+
require "warren"
|
19
|
+
# Pull in the bunny adapter
|
20
|
+
require "warren/adapters/bunny_adapter"
|
21
|
+
|
22
|
+
# See examples/ for more
|
18
23
|
|
19
|
-
|
24
|
+
== License
|
25
|
+
|
26
|
+
Licensed under the MIT license. See LICENSE for more details.
|
data/spec/spec_helper.rb
CHANGED
@@ -3,68 +3,87 @@ require File.dirname(__FILE__) + '/../spec_helper'
|
|
3
3
|
describe Warren::Connection do
|
4
4
|
|
5
5
|
before(:each) do
|
6
|
-
@
|
6
|
+
@file = stub 'io', :read => yaml_data
|
7
|
+
setup_adapter
|
7
8
|
end
|
8
|
-
|
9
|
-
it "should
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
it "should require a password" do
|
16
|
-
lambda {
|
17
|
-
Warren::Connection.new(details.except(:pass))
|
18
|
-
}.should raise_error(Warren::Connection::InvalidConnectionDetails)
|
19
|
-
end
|
20
|
-
|
21
|
-
it "should require a queue name if none specified" do
|
22
|
-
lambda {
|
23
|
-
conn = Warren::Connection.new(details)
|
24
|
-
conn.queue_name
|
25
|
-
}.should raise_error(Warren::Connection::InvalidConnectionDetails)
|
9
|
+
|
10
|
+
it "should read from a config file" do
|
11
|
+
YAML.should_receive(:load).with("#{File.dirname($0)}/config/warren.yml").and_return({"development" => {}})
|
12
|
+
|
13
|
+
Warren::Connection.new
|
26
14
|
end
|
27
|
-
|
28
|
-
it "should
|
29
|
-
|
15
|
+
|
16
|
+
it "should parse the right config out" do
|
17
|
+
conn = Warren::Connection.new(@file)
|
18
|
+
conn.instance_variable_get("@opts").should == {
|
19
|
+
:host => "localhost",
|
20
|
+
:user => "rspec",
|
21
|
+
:pass => "password",
|
22
|
+
:logging => false
|
23
|
+
}
|
30
24
|
end
|
31
25
|
|
32
|
-
it "should
|
33
|
-
|
34
|
-
|
26
|
+
it "should symbolize keys in a hash" do
|
27
|
+
conn = Warren::Connection.new(@file)
|
28
|
+
hash = {"one" => "two", "three" => "four", :five => "six"}
|
29
|
+
conn.send(:symbolize_keys, hash).should == {
|
30
|
+
:one => "two",
|
31
|
+
:three => "four",
|
32
|
+
:five => "six"
|
33
|
+
}
|
35
34
|
end
|
36
35
|
|
37
|
-
it "should
|
38
|
-
|
36
|
+
it "should raise if no adapter set to check against" do
|
37
|
+
Warren::Queue.adapter = nil
|
38
|
+
lambda {
|
39
|
+
Warren::Connection.new(@file)
|
40
|
+
}.should raise_error(Warren::Queue::NoAdapterSet)
|
39
41
|
end
|
40
42
|
|
41
|
-
it "should
|
42
|
-
|
43
|
-
|
43
|
+
it "should successfully check against adapter" do
|
44
|
+
_adapter = stub 'queue', :check_connection_details => true
|
45
|
+
|
46
|
+
Warren::Connection.new(@file)
|
44
47
|
end
|
45
48
|
|
46
|
-
it "should
|
47
|
-
|
49
|
+
it "should raise errors for missing connection details" do
|
50
|
+
_adapter = stub 'queue', :check_connection_details => ["one", "two"]
|
51
|
+
|
52
|
+
Warren::Connection.new(@file)
|
48
53
|
end
|
49
54
|
|
50
|
-
it "should
|
51
|
-
|
52
|
-
|
55
|
+
it "should raise errors for other prerequisits in the adapter" do
|
56
|
+
Adapter = Class.new(Warren::Queue) do
|
57
|
+
def self.check_connection_details params
|
58
|
+
raise Warren::Connection::InvalidConnectionDetails, "Missing prerequisites"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
lambda {
|
63
|
+
Warren::Connection.new(@file)
|
64
|
+
}.should raise_error(Warren::Connection::InvalidConnectionDetails, "Missing prerequisites")
|
53
65
|
end
|
54
66
|
|
55
|
-
|
56
|
-
|
57
|
-
|
67
|
+
def setup_adapter
|
68
|
+
_adapter = stub 'queue'
|
69
|
+
Warren::Queue.adapter = _adapter
|
58
70
|
end
|
59
71
|
|
60
|
-
|
72
|
+
def yaml_data
|
73
|
+
a = <<-EOF
|
74
|
+
---
|
75
|
+
development:
|
76
|
+
host: localhost
|
77
|
+
user: rspec
|
78
|
+
pass: password
|
79
|
+
logging: false
|
61
80
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
81
|
+
test:
|
82
|
+
host: localhost
|
83
|
+
user: rspec
|
84
|
+
pass: password
|
85
|
+
logging: true
|
86
|
+
EOF
|
68
87
|
end
|
69
|
-
|
88
|
+
|
70
89
|
end
|