carnivore 0.1.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.
- data/CHANGELOG.md +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +64 -0
- data/README.md +59 -0
- data/carnivore.gemspec +18 -0
- data/examples/test_block.rb +10 -0
- data/examples/test_class.rb +20 -0
- data/examples/test_http.rb +10 -0
- data/lib/carnivore/callback.rb +48 -0
- data/lib/carnivore/config.rb +42 -0
- data/lib/carnivore/container.rb +17 -0
- data/lib/carnivore/runner.rb +22 -0
- data/lib/carnivore/source/http.rb +43 -0
- data/lib/carnivore/source/rabbitmq.rb +37 -0
- data/lib/carnivore/source/sqs.rb +76 -0
- data/lib/carnivore/source/test.rb +31 -0
- data/lib/carnivore/source.rb +151 -0
- data/lib/carnivore/supervisor.rb +9 -0
- data/lib/carnivore/utils.rb +18 -0
- data/lib/carnivore/version.rb +5 -0
- data/lib/carnivore.rb +2 -0
- metadata +146 -0
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
carnivore (0.1.0)
|
5
|
+
bunny
|
6
|
+
celluloid
|
7
|
+
fog
|
8
|
+
mixlib-config
|
9
|
+
reel
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: https://rubygems.org/
|
13
|
+
specs:
|
14
|
+
builder (3.2.2)
|
15
|
+
bunny (0.8.0)
|
16
|
+
celluloid (0.15.1)
|
17
|
+
timers (~> 1.1.0)
|
18
|
+
celluloid-io (0.15.0)
|
19
|
+
celluloid (>= 0.15.0)
|
20
|
+
nio4r (>= 0.5.0)
|
21
|
+
certified (0.1.1)
|
22
|
+
excon (0.25.3)
|
23
|
+
fog (1.15.0)
|
24
|
+
builder
|
25
|
+
excon (~> 0.25.0)
|
26
|
+
formatador (~> 0.2.0)
|
27
|
+
mime-types
|
28
|
+
multi_json (~> 1.0)
|
29
|
+
net-scp (~> 1.1)
|
30
|
+
net-ssh (>= 2.1.3)
|
31
|
+
nokogiri (~> 1.5)
|
32
|
+
ruby-hmac
|
33
|
+
formatador (0.2.4)
|
34
|
+
http (0.4.0)
|
35
|
+
certified
|
36
|
+
http_parser.rb
|
37
|
+
http_parser.rb (0.5.3)
|
38
|
+
mime-types (1.25)
|
39
|
+
mini_portile (0.5.1)
|
40
|
+
mixlib-config (1.1.2)
|
41
|
+
multi_json (1.8.0)
|
42
|
+
net-scp (1.1.2)
|
43
|
+
net-ssh (>= 2.6.5)
|
44
|
+
net-ssh (2.6.8)
|
45
|
+
nio4r (0.5.0)
|
46
|
+
nokogiri (1.6.0)
|
47
|
+
mini_portile (~> 0.5.0)
|
48
|
+
rack (1.5.2)
|
49
|
+
reel (0.3.0)
|
50
|
+
celluloid-io (>= 0.8.0)
|
51
|
+
http (>= 0.2.0)
|
52
|
+
http_parser.rb (>= 0.5.3)
|
53
|
+
rack (>= 1.4.0)
|
54
|
+
websocket_parser (>= 0.1.0)
|
55
|
+
ruby-hmac (0.4.0)
|
56
|
+
timers (1.1.0)
|
57
|
+
websocket_parser (0.1.4)
|
58
|
+
http
|
59
|
+
|
60
|
+
PLATFORMS
|
61
|
+
ruby
|
62
|
+
|
63
|
+
DEPENDENCIES
|
64
|
+
carnivore!
|
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Carnivore
|
2
|
+
|
3
|
+
Eat messages, rule the world.
|
4
|
+
|
5
|
+
## Purpose
|
6
|
+
|
7
|
+
Slim library to consume messages. Sources are defined
|
8
|
+
and callbacks are registered to defined sources. Sources
|
9
|
+
feed messages to callback workers asynchronously and
|
10
|
+
stuff gets done. Super simple!
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
|
14
|
+
1. Build a source
|
15
|
+
2. Add callbacks
|
16
|
+
3. Profit!
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
Carnivore.configure do
|
20
|
+
src = Source.build(:type => :test, :args => {})
|
21
|
+
src.add_callback(:print_message) do |msg|
|
22
|
+
puts "Received message: #{message}"
|
23
|
+
end
|
24
|
+
end.start!
|
25
|
+
```
|
26
|
+
|
27
|
+
### Advanced Usage
|
28
|
+
|
29
|
+
Under the hood, callbacks are built into `Carnivore::Callback`
|
30
|
+
instances. This class can be subclassed and provided directly
|
31
|
+
instead of a simple block. This has the added bonus of being
|
32
|
+
able to define the number of worker instances to be created
|
33
|
+
for the callback (blocks default to 1):
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
require 'carnivore'
|
37
|
+
|
38
|
+
class CustomCallback < Carnivore::Callback
|
39
|
+
|
40
|
+
self.workers = 5
|
41
|
+
|
42
|
+
def setup
|
43
|
+
info "Custom callback setup called!"
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute(message)
|
47
|
+
info "GOT MESSAGE: #{message[:message]} - source: #{message[:source]} - instance: #{self}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Carnivore.configure do
|
52
|
+
s = Carnivore::Source.build(:type => :test, :args => {})
|
53
|
+
s.add_callback(:printer, CustomCallback)
|
54
|
+
end.start!
|
55
|
+
```
|
56
|
+
|
57
|
+
## Info
|
58
|
+
|
59
|
+
* Repository: https://github.com/heavywater/carnivore
|
data/carnivore.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
|
2
|
+
require 'carnivore/version'
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'carnivore'
|
5
|
+
s.version = Carnivore::VERSION.version
|
6
|
+
s.summary = 'Message processing helper'
|
7
|
+
s.author = 'Chris Roberts'
|
8
|
+
s.email = 'chrisroberts.code@gmail.com'
|
9
|
+
s.homepage = 'https://github.com/heavywater/carnivore'
|
10
|
+
s.description = 'Message processing helper'
|
11
|
+
s.require_path = 'lib'
|
12
|
+
s.add_dependency 'fog'
|
13
|
+
s.add_dependency 'celluloid'
|
14
|
+
s.add_dependency 'reel'
|
15
|
+
s.add_dependency 'mixlib-config'
|
16
|
+
s.add_dependency 'bunny'
|
17
|
+
s.files = Dir['**/*']
|
18
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'carnivore'
|
2
|
+
|
3
|
+
Carnivore.configure do
|
4
|
+
s = Carnivore::Source.build(:type => :test, :args => {})
|
5
|
+
|
6
|
+
s.add_callback(:printer) do |message|
|
7
|
+
info "GOT MESSAGE: #{message[:message]} - source: #{message[:source]} - instance: #{self}"
|
8
|
+
end
|
9
|
+
|
10
|
+
end.start!
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'carnivore'
|
2
|
+
|
3
|
+
class CustomCallback < Carnivore::Callback
|
4
|
+
|
5
|
+
self.workers = 5
|
6
|
+
|
7
|
+
def setup
|
8
|
+
info "Custom callback setup called!"
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute(message)
|
12
|
+
info "GOT MESSAGE: #{message[:message]} - source: #{message[:source]} - instance: #{self}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Carnivore.configure do
|
17
|
+
s = Carnivore::Source.build(:type => :test, :args => {})
|
18
|
+
s.add_callback(:printer, CustomCallback)
|
19
|
+
|
20
|
+
end.start!
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'carnivore'
|
2
|
+
|
3
|
+
Carnivore.configure do
|
4
|
+
s = Carnivore::Source.build(:type => :http, :args => {:bind => '0.0.0.0', :port => 3000})
|
5
|
+
|
6
|
+
s.add_callback(:printer) do |message|
|
7
|
+
info "GOT MESSAGE: #{message[:message][:body]} - path: #{message[:message][:request].url} - method: #{message[:message][:request].method} - source: #{message[:source]} - instance: #{self}"
|
8
|
+
end
|
9
|
+
|
10
|
+
end.start!
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Carnivore
|
2
|
+
class Callback
|
3
|
+
|
4
|
+
class << self
|
5
|
+
attr_accessor :workers
|
6
|
+
end
|
7
|
+
|
8
|
+
include Celluloid
|
9
|
+
include Celluloid::Logger
|
10
|
+
|
11
|
+
execute_block_on_receiver :execute
|
12
|
+
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
def initialize(name, block=nil)
|
16
|
+
@name = name
|
17
|
+
@block = block
|
18
|
+
if(@block.nil? && self.class == Callback)
|
19
|
+
raise ArgumentError.new 'Block is required for dynamic callbacks!'
|
20
|
+
end
|
21
|
+
setup
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
"callback<#{self.object_id}>"
|
29
|
+
end
|
30
|
+
|
31
|
+
def valid?(message)
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def call(message)
|
36
|
+
raise TypeError.new('Invalid message for this callback!') unless valid?(message)
|
37
|
+
@block ? execute(message, &@block) : execute(message)
|
38
|
+
rescue => e
|
39
|
+
error "[callback: #{self}, source: #{message[:source]}, message: #{message[:message].object_id}]: #{e.class} - #{e}"
|
40
|
+
debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def execute(message)
|
44
|
+
yield message
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'mixlib/config'
|
3
|
+
|
4
|
+
module Carnivore
|
5
|
+
class Config
|
6
|
+
|
7
|
+
extend Mixlib::Config
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def configure(args)
|
12
|
+
build(args[:config_path]) if args[:config_path]
|
13
|
+
self.merge!(args)
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def build(path_or_hash)
|
18
|
+
if(path_or_hash.is_a?(Hash))
|
19
|
+
conf = path_or_hash
|
20
|
+
else
|
21
|
+
if(File.exists?(path_or_hash.to_s))
|
22
|
+
conf = JSON.load(File.read(path_or_hash))
|
23
|
+
self.config_path = path_or_hash
|
24
|
+
else
|
25
|
+
raise "Failed to load configuration file: #{path_or_hash}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
conf.each do |k,v|
|
29
|
+
self.send(k, v)
|
30
|
+
end
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def get(*ary)
|
35
|
+
ary.flatten.inject(self) do |memo, key|
|
36
|
+
memo[key.to_s] || memo[key.to_sym] || break
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
require 'carnivore/source'
|
3
|
+
require 'carnivore/container'
|
4
|
+
|
5
|
+
module Carnivore
|
6
|
+
class << self
|
7
|
+
def configure(&block)
|
8
|
+
mod = Container.new
|
9
|
+
mod.instance_exec(mod, &block)
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def start!
|
14
|
+
begin
|
15
|
+
require 'carnivore/supervisor'
|
16
|
+
Supervisor.run
|
17
|
+
rescue Exception => e
|
18
|
+
# Gracefully shut down
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'reel'
|
2
|
+
require 'carnivore/source'
|
3
|
+
|
4
|
+
module Carnivore
|
5
|
+
class Source
|
6
|
+
|
7
|
+
class Http < Source
|
8
|
+
|
9
|
+
attr_reader :args
|
10
|
+
|
11
|
+
def setup(args={})
|
12
|
+
@args = default_args(args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_args(args)
|
16
|
+
{
|
17
|
+
:bind => '0.0.0.0',
|
18
|
+
:port => '3000'
|
19
|
+
}.merge(args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def process(*process_args)
|
23
|
+
srv = Reel::Server.supervise(args[:bind], args[:port]) do |con|
|
24
|
+
while(req = con.request)
|
25
|
+
begin
|
26
|
+
msg = format(:request => req, :body => req.body)
|
27
|
+
callbacks.each do |name|
|
28
|
+
c_name = callback_name(name)
|
29
|
+
debug "Dispatching message<#{msg[:message].object_id}> to callback<#{name} (#{c_name})>"
|
30
|
+
Celluloid::Actor[c_name].async.call(msg)
|
31
|
+
end
|
32
|
+
con.respond(:ok, 'So long, and thanks for all the fish!')
|
33
|
+
rescue => e
|
34
|
+
con.respond(:bad_request, 'Failed to process request')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'fog'
|
2
|
+
require 'carnivore/source'
|
3
|
+
|
4
|
+
module Carnivore
|
5
|
+
class Source
|
6
|
+
class RabbitMq < Source
|
7
|
+
|
8
|
+
def setup(args={})
|
9
|
+
@bunny = nil
|
10
|
+
@connection_args = args[:bunny]
|
11
|
+
@queue_name = args[:queue]
|
12
|
+
@exchange_name = args[:exchange]
|
13
|
+
debug "Creating RabbitMq source instance <#{name}>"
|
14
|
+
end
|
15
|
+
|
16
|
+
def connect
|
17
|
+
@bunny = Bunny.new(@connection_args)
|
18
|
+
@bunny.start
|
19
|
+
@channel = @bunny.create_channel
|
20
|
+
@exchange = @channel.topic(@exchange_name)
|
21
|
+
@queue = @channel.queue(@queue_name).bind(@exchange) # TODO: Add topic key
|
22
|
+
end
|
23
|
+
|
24
|
+
def process
|
25
|
+
@queue.subscribe do |info, metadata, payload|
|
26
|
+
msg = format(payload)
|
27
|
+
callbacks.each do |name|
|
28
|
+
c_name = callback_name(name)
|
29
|
+
debug "Dispatching message<#{msg[:message].object_id}> to callback<#{name} (#{c_name})>"
|
30
|
+
Celluloid::Actor[c_name].async.call(msg)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'fog'
|
2
|
+
require 'carnivore/source'
|
3
|
+
|
4
|
+
module Carnivore
|
5
|
+
class Source
|
6
|
+
class Sqs < Source
|
7
|
+
|
8
|
+
OUTPUT_REPEAT_EVERY=5
|
9
|
+
|
10
|
+
attr_reader :pause_time
|
11
|
+
|
12
|
+
def setup(args={})
|
13
|
+
@fog = nil
|
14
|
+
@connection_args = args[:fog]
|
15
|
+
@queue = args[:queue_url]
|
16
|
+
@pause_time = args[:pause] || 5
|
17
|
+
@receive_timeout = after(args[:receive_timeout] || 30){ terminate }
|
18
|
+
debug "Creating SQS source instance <#{name}>"
|
19
|
+
end
|
20
|
+
|
21
|
+
def connect
|
22
|
+
@fog = Fog::AWS::SQS.new(@connection_args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def receive(n=1)
|
26
|
+
count = 0
|
27
|
+
m = nil
|
28
|
+
until(m)
|
29
|
+
m = nil
|
30
|
+
@receive_timeout.reset
|
31
|
+
m = @fog.receive_message(@queue, 'MaxNumberOfMessages' => n).body['Message'].first
|
32
|
+
@receive_timeout.reset
|
33
|
+
unless(m)
|
34
|
+
if(count == 0)
|
35
|
+
debug "Source<#{name}> no message received. Sleeping for #{pause_time} seconds."
|
36
|
+
elsif(count % OUTPUT_REPEAT_EVERY == 0)
|
37
|
+
debug "Source<#{name}> last message repeated #{count} times"
|
38
|
+
end
|
39
|
+
sleep(pause_time)
|
40
|
+
end
|
41
|
+
count += 1
|
42
|
+
end
|
43
|
+
pre_process(m)
|
44
|
+
end
|
45
|
+
|
46
|
+
def send(message)
|
47
|
+
@fog.send_message(@queue, message)
|
48
|
+
end
|
49
|
+
|
50
|
+
def confirm(message)
|
51
|
+
@fog.delete_message(@queue, message['ReceiptHandle'])
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def fog
|
57
|
+
unless(@fog)
|
58
|
+
connect
|
59
|
+
end
|
60
|
+
@fog
|
61
|
+
end
|
62
|
+
|
63
|
+
def pre_process(m)
|
64
|
+
if(m['Body'])
|
65
|
+
begin
|
66
|
+
m['Body'] = JSON.load(m['Body'])
|
67
|
+
rescue JSON::ParserError
|
68
|
+
# well, we did our best
|
69
|
+
end
|
70
|
+
end
|
71
|
+
m
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Carnivore
|
2
|
+
class Source
|
3
|
+
class Test < Source
|
4
|
+
|
5
|
+
RAND_MAX = 99999
|
6
|
+
RAND_DIV = 3
|
7
|
+
RAND_SLEEP = 10
|
8
|
+
|
9
|
+
def setup(args={})
|
10
|
+
end
|
11
|
+
|
12
|
+
def connect(*args)
|
13
|
+
puts 'Test connect called'
|
14
|
+
end
|
15
|
+
|
16
|
+
def receive(*args)
|
17
|
+
if(rand(RAND_MAX) % RAND_DIV == 0)
|
18
|
+
sleep_for = rand(RAND_SLEEP)
|
19
|
+
debug "Test source sleep for: #{sleep_for}"
|
20
|
+
sleep sleep_for
|
21
|
+
end
|
22
|
+
20.times.map{('a'..'z').to_a.shuffle.first}.join
|
23
|
+
end
|
24
|
+
|
25
|
+
def transmit(message)
|
26
|
+
puts "Transmit requested: #{message}"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'carnivore/callback'
|
2
|
+
|
3
|
+
module Carnivore
|
4
|
+
class Source < Celluloid::SupervisionGroup
|
5
|
+
|
6
|
+
class SourceContainer
|
7
|
+
|
8
|
+
attr_reader :klass
|
9
|
+
attr_reader :source_hash
|
10
|
+
|
11
|
+
def initialize(class_name, args={})
|
12
|
+
@klass = class_name
|
13
|
+
@source_hash = args || {}
|
14
|
+
@source_hash[:callbacks] = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_callback(name, klass=nil, &block)
|
18
|
+
@source_hash[:callbacks][name] = klass || block
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
|
24
|
+
def build(args={})
|
25
|
+
[:args, :type].each do |key|
|
26
|
+
unless(args.has_key?(key))
|
27
|
+
raise ArgumentError.new "Missing required parameter `:#{key}`"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
require "carnivore/source/#{args[:type]}"
|
31
|
+
klass = args[:type].to_s.split('_').map(&:capitalize).join
|
32
|
+
klass = Source.const_get(klass)
|
33
|
+
args[:args][:name] ||= Celluloid.uuid
|
34
|
+
inst = SourceContainer.new(klass, args[:args])
|
35
|
+
register(inst)
|
36
|
+
inst
|
37
|
+
end
|
38
|
+
|
39
|
+
def register(inst)
|
40
|
+
@sources ||= []
|
41
|
+
@sources << inst
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def sources
|
46
|
+
@sources || []
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
include Celluloid
|
51
|
+
include Celluloid::Logger
|
52
|
+
|
53
|
+
attr_reader :name
|
54
|
+
attr_reader :callbacks
|
55
|
+
attr_reader :auto_confirm
|
56
|
+
attr_reader :callback_supervisor
|
57
|
+
|
58
|
+
def initialize(args={})
|
59
|
+
@callbacks = []
|
60
|
+
@callback_names = {}
|
61
|
+
@callback_supervisor = Celluloid::SupervisionGroup.run!
|
62
|
+
@name = args[:name] || Celluloid.uuid
|
63
|
+
@auto_confirm = !!args[:auto_confirm]
|
64
|
+
if(args[:callbacks])
|
65
|
+
args[:callbacks].each do |name, block|
|
66
|
+
add_callback(name, block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
setup(args)
|
70
|
+
connect
|
71
|
+
async.process
|
72
|
+
rescue => e
|
73
|
+
debug "Failed to initialize: #{self} - #{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
74
|
+
raise
|
75
|
+
end
|
76
|
+
|
77
|
+
def auto_confirm?
|
78
|
+
@auto_confirm
|
79
|
+
end
|
80
|
+
|
81
|
+
def inspect
|
82
|
+
"<#{self.class.name}:#{object_id} @name=#{name} @callbacks=#{Hash[*callbacks.map{|k,v| [k,v.object_id]}.flatten]}>"
|
83
|
+
end
|
84
|
+
|
85
|
+
def setup(args={})
|
86
|
+
debug "<#{self.class}> No custom setup declared"
|
87
|
+
end
|
88
|
+
|
89
|
+
def connect(args={})
|
90
|
+
debug "<#{self.class}> No custom connect declared"
|
91
|
+
end
|
92
|
+
|
93
|
+
def receive(n=1)
|
94
|
+
raise NoMethodError.new('Abstract method not valid for runtime')
|
95
|
+
end
|
96
|
+
|
97
|
+
def transmit(message)
|
98
|
+
raise NoMethodError.new('Abstract method not valid for runtime')
|
99
|
+
end
|
100
|
+
|
101
|
+
def terminate
|
102
|
+
if(@callback_supervisor)
|
103
|
+
@callback_supervisor.actors.map(&:terminate)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def add_callback(name, block_or_class)
|
108
|
+
if(block_or_class.is_a?(Class))
|
109
|
+
debug "Adding callback class (#{block_or_class}) under supervision. Name: #{callback_name(name)}"
|
110
|
+
size = block_or_class.workers || 1
|
111
|
+
@callback_supervisor.pool block_or_class, as: callback_name(name), size: size, args: [name]
|
112
|
+
else
|
113
|
+
debug "Adding custom callback class from block (#{block_or_class}) under supervision. Name: #{callback_name(name)}"
|
114
|
+
@callback_supervisor.supervise_as callback_name(name), Callback, name, block_or_class
|
115
|
+
end
|
116
|
+
@callbacks.push(name).uniq!
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
def remove_callback(name)
|
121
|
+
unless(@callbacks.include?(callback_name(name)))
|
122
|
+
raise NameError.new("Failed to locate callback named: #{name}")
|
123
|
+
end
|
124
|
+
actors[callback_name(name)].terminate
|
125
|
+
@callbacks.delete(name)
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
def callback_name(name)
|
130
|
+
unless(@callback_names[name])
|
131
|
+
@callback_names[name] = [@name, self.object_id, name].join(':').to_sym
|
132
|
+
end
|
133
|
+
@callback_names[name]
|
134
|
+
end
|
135
|
+
|
136
|
+
def format(msg)
|
137
|
+
{:message => msg, :source => self}
|
138
|
+
end
|
139
|
+
|
140
|
+
def process
|
141
|
+
loop do
|
142
|
+
msg = format(receive)
|
143
|
+
@callbacks.each do |name|
|
144
|
+
debug "Dispatching message<#{msg[:message].object_id}> to callback<#{name} (#{callback_name(name)})>"
|
145
|
+
Celluloid::Actor[callback_name(name)].async.call(msg)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Carnivore
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
module Params
|
5
|
+
def symbolize_hash(hash)
|
6
|
+
Hash[*(
|
7
|
+
hash.map do |k,v|
|
8
|
+
[
|
9
|
+
k.gsub(/(?<![A-Z])([A-Z])/, '_\1').sub(/^_/, '').downcase.to_sym,
|
10
|
+
v.is_a?(Hash) ? symbolize_hash(v) : v
|
11
|
+
]
|
12
|
+
end.flatten
|
13
|
+
)]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/lib/carnivore.rb
ADDED
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: carnivore
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Chris Roberts
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-09-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: fog
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: celluloid
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: reel
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: mixlib-config
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: bunny
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: Message processing helper
|
95
|
+
email: chrisroberts.code@gmail.com
|
96
|
+
executables: []
|
97
|
+
extensions: []
|
98
|
+
extra_rdoc_files: []
|
99
|
+
files:
|
100
|
+
- lib/carnivore/callback.rb
|
101
|
+
- lib/carnivore/supervisor.rb
|
102
|
+
- lib/carnivore/source/http.rb
|
103
|
+
- lib/carnivore/source/sqs.rb
|
104
|
+
- lib/carnivore/source/test.rb
|
105
|
+
- lib/carnivore/source/rabbitmq.rb
|
106
|
+
- lib/carnivore/source.rb
|
107
|
+
- lib/carnivore/version.rb
|
108
|
+
- lib/carnivore/runner.rb
|
109
|
+
- lib/carnivore/config.rb
|
110
|
+
- lib/carnivore/utils.rb
|
111
|
+
- lib/carnivore/container.rb
|
112
|
+
- lib/carnivore.rb
|
113
|
+
- examples/test_http.rb
|
114
|
+
- examples/test_class.rb
|
115
|
+
- examples/test_block.rb
|
116
|
+
- Gemfile
|
117
|
+
- README.md
|
118
|
+
- CHANGELOG.md
|
119
|
+
- Gemfile.lock
|
120
|
+
- carnivore.gemspec
|
121
|
+
homepage: https://github.com/heavywater/carnivore
|
122
|
+
licenses: []
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options: []
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ! '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
none: false
|
135
|
+
requirements:
|
136
|
+
- - ! '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
requirements: []
|
140
|
+
rubyforge_project:
|
141
|
+
rubygems_version: 1.8.24
|
142
|
+
signing_key:
|
143
|
+
specification_version: 3
|
144
|
+
summary: Message processing helper
|
145
|
+
test_files: []
|
146
|
+
has_rdoc:
|