dripdrop 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/README.md +21 -102
- data/Rakefile +4 -2
- data/VERSION +1 -1
- data/doc_img/topology.png +0 -0
- data/dripdrop.gemspec +19 -20
- data/example/agent_test.rb +1 -1
- data/example/pubsub.rb +18 -0
- data/example/pushpull.rb +21 -0
- data/lib/dripdrop/agent.rb +24 -6
- data/lib/dripdrop/handlers/websockets.rb +68 -0
- data/lib/dripdrop/handlers/zeromq.rb +136 -0
- data/lib/dripdrop/message.rb +26 -10
- data/lib/dripdrop/node.rb +103 -0
- metadata +45 -19
- data/bin/drip-mlogger +0 -8
- data/bin/drip-publisher +0 -8
- data/example/forwarder.cfg +0 -8
- data/example/web/public/js/jquery.gracefulWebSocket.js +0 -165
- data/example/web/public/js/jquery.js +0 -154
- data/example/web/public/js/jquery.websocket.js +0 -45
- data/example/web/public/view.html +0 -30
- data/example/web/server.rb +0 -11
- data/lib/dripdrop/collector.rb +0 -87
- data/lib/dripdrop/mlogger.rb +0 -35
- data/lib/dripdrop/publisher.rb +0 -53
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,115 +1,34 @@
|
|
1
|
-
#
|
1
|
+
# DripDrop
|
2
2
|
|
3
|
-
|
4
|
-
A work in progress.
|
3
|
+
**Note: For now, for PUSH/PULL support you'll need to build zmqmachine from my fork of zmqmachine, the official gem does not yet have this**
|
5
4
|
|
6
|
-
|
5
|
+
DripDrop is ZeroMQ(using zmqmachine) + Event Machine simplified for the general use case + serialization helpers.
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
* It's flexible. By leveraging 0MQ pub/sub sockets you can have many different processors (collectors in dripdrop) that don't impact or even care about each other
|
13
|
-
* It's easy. Check out the agent and collector examples below. You can be processing stuff in no time.
|
14
|
-
|
15
|
-
## An example with a WebSocket UI:
|
16
|
-
|
17
|
-
### You'll need to have the zmq dev libs on your machine. On OSX this means
|
18
|
-
|
19
|
-
1. Download and build zeromq from [zeromq.org](http://www.zeromq.org/area:download)
|
20
|
-
1. The agent just uses the plain zmq gem, which runs fine on ruby 1.8.7+, *EDIT!* If you use my fork of ffi-rzmq everything should work with 1.8.7! This isn't fully tested yet though *END EDIT* this is so you can use it in say your rails app. Everything else needs ruby 1.9.2 or jruby and uses Chuck Remes [ffi-rzmq](http://github.com/chuckremes/ffi-rzmq), and [zmqmachine](http://github.com/chuckremes/zmqmachine) gems which you must build yourself. I recommend using rvm to enable the use of multiple rubies on one machine.
|
21
|
-
1. zmq_forwarder comes with zmq, use this to aggregate agent messages using the example config shown below
|
22
|
-
|
23
|
-
### To run a simple example, feeding data to a websockets UI
|
24
|
-
|
25
|
-
#### Aggregate agents with zmq_forwarder (comes with zmq)
|
26
|
-
$ zmq_forwarder examples/forwarder.cfg
|
27
|
-
|
28
|
-
#### Start up the drip drop publisher example
|
29
|
-
$ drip-publisher
|
30
|
-
|
31
|
-
#### Assuming you have mongodb running
|
32
|
-
$ drip-mlogger
|
33
|
-
|
34
|
-
#### Start up a webserver to host the HTML/JS for a sample websocket client
|
35
|
-
$ cd DRIPDROPFOLDER/example/web/
|
36
|
-
$ ruby server
|
37
|
-
|
38
|
-
## Example Topology
|
39
|
-
|
40
|
-
You can add as many listeners as you want, or reconfigure things any way you want. Heres how I plan on using it.
|
41
|
-
|
42
|
-
![topology](http://github.com/andrewvc/dripdrop/raw/master/doc_img/topology.png "Topology")
|
43
|
-
|
44
|
-
## Sending Messages
|
45
|
-
|
46
|
-
Sending messages is easy with the agent, an example:
|
47
|
-
|
48
|
-
require 'rubygems'
|
49
|
-
require 'dripdrop/agent'
|
50
|
-
|
51
|
-
agent = DripDrop::Agent.new('tcp://127.0.0.1:2900')
|
52
|
-
|
53
|
-
loop do
|
54
|
-
#Test is the message name, this is the first part of the 0MQ message, used for filtering
|
55
|
-
#at the 0MQ sub socket level, :head is always a hash, :body is freeform
|
56
|
-
#EVERYTHING must be serializable to BERT
|
57
|
-
agent.send_message('test', :body => 'hello', :head => {:key => 'value'})
|
58
|
-
puts "SEND"
|
59
|
-
sleep 1
|
60
|
-
end
|
61
|
-
|
62
|
-
## Writing a custom message processor
|
63
|
-
|
64
|
-
Writing custom message processors is super easy, just create a new DripDrop::Collector
|
65
|
-
and run it. DripDrop::Collector is based on Chuck Remes' awesome zmqmachine, an evented
|
66
|
-
0MQ processor. Heres' the MongoDB logger as an example:
|
67
|
-
|
68
|
-
require 'rubygems'
|
69
|
-
require 'mongo'
|
70
|
-
require 'dripdrop/collector'
|
7
|
+
Here's an example of the kind of thing DripDrop makes easy, from [examples/pubsub.rb](http://github.com/andrewvc/dripdrop/blob/master/example/pubsub.rb)
|
8
|
+
|
9
|
+
DripDrop::Node.new do |node|
|
10
|
+
z_addr = 'tcp://127.0.0.1:2200'
|
71
11
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
if @mongo_collection
|
79
|
-
@mongo_collection.insert(message.to_hash)
|
80
|
-
end
|
81
|
-
end
|
12
|
+
pub = node.zmq_publish(z_addr,:bind)
|
13
|
+
sub = node.zmq_subscribe(z_addr,:connect).on_recv do |message|
|
14
|
+
puts "Receiver 1 #{message.inspect}"
|
15
|
+
end
|
16
|
+
sub = node.zmq_subscribe(z_addr, :connect).on_recv do |message|
|
17
|
+
puts "Receiver 2 #{message.inspect}"
|
82
18
|
end
|
83
19
|
|
84
|
-
|
85
|
-
|
86
|
-
:mongo_connection, :mongo_collection
|
87
|
-
|
88
|
-
def initialize(sub_address='tcp://127.0.0.1:2901',mhost='127.0.0.1',mport=27017,mdb='dripdrop')
|
89
|
-
@sub_address = URI.parse(sub_address)
|
90
|
-
@sub_collector = MLoggerCollector.new('tcp://127.0.0.1:2901')
|
91
|
-
|
92
|
-
@mongo_host, @mongo_port, @mongo_db = mhost, mport, mdb
|
93
|
-
@mongo_connection = Mongo::Connection.new(@mongo_host,@mongo_port).db(@mongo_db)
|
94
|
-
@mongo_collection = @mongo_connection.collection('raw')
|
95
|
-
end
|
96
|
-
|
97
|
-
def run
|
98
|
-
@sub_collector.mongo_collection = @mongo_collection
|
99
|
-
@sub_collector.run.join
|
100
|
-
end
|
20
|
+
node.zm_reactor.periodical_timer(5) do
|
21
|
+
pub.send_message(DripDrop::Message.new('test', :body => 'Test Payload'))
|
101
22
|
end
|
102
23
|
end
|
24
|
+
|
103
25
|
|
26
|
+
Want to see a longer example encapsulating both zmqmachine and eventmachine functionality? Check out [this file](http://github.com/andrewvc/dripdrop-webstats/blob/master/lib/dripdrop-webstats.rb), which encapsulates all the functionality of the diagram below:
|
104
27
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
* Make your feature addition or bug fix.
|
109
|
-
* Commit, do not mess with rakefile, version, or history.
|
110
|
-
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
111
|
-
* Send me a pull request. Bonus points for topic branches.
|
28
|
+
![topology](http://github.com/andrewvc/dripdrop/raw/master/doc_img/topology.png "Topology")
|
29
|
+
|
30
|
+
#How It Works
|
112
31
|
|
113
|
-
|
32
|
+
DripDrop encapsulates both zmqmachine, and eventmachine. It provides some sane default messaging choices, using [BERT](http://github.com/blog/531-introducing-bert-and-bert-rpc) (A binary, JSON, like serialization format) and JSON to automatically. zmqmachine and eventmachine have some good APIs, some convoluted ones, the goal here is to smooth over the bumps, and make writing highly concurrent programs both as terse and beautiful as possible.
|
114
33
|
|
115
34
|
Copyright (c) 2010 Andrew Cholakian. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -10,8 +10,10 @@ begin
|
|
10
10
|
gem.email = "andrew@andrewvc.com"
|
11
11
|
gem.homepage = "http://github.com/andrewvc/dripdrop"
|
12
12
|
gem.authors = ["Andrew Cholakian"]
|
13
|
-
gem.add_dependency('
|
14
|
-
gem.add_dependency('
|
13
|
+
gem.add_dependency('ffi-rzmq')
|
14
|
+
gem.add_dependency('eventmachine')
|
15
|
+
gem.add_dependency('bert')
|
16
|
+
gem.add_dependency('json')
|
15
17
|
end
|
16
18
|
Jeweler::GemcutterTasks.new
|
17
19
|
rescue LoadError
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
data/doc_img/topology.png
CHANGED
Binary file
|
data/dripdrop.gemspec
CHANGED
@@ -5,14 +5,13 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{dripdrop}
|
8
|
-
s.version = "0.0
|
8
|
+
s.version = "0.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Andrew Cholakian"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-09-05}
|
13
13
|
s.description = %q{0MQ App stats}
|
14
14
|
s.email = %q{andrew@andrewvc.com}
|
15
|
-
s.executables = ["drip-mlogger", "drip-publisher"]
|
16
15
|
s.extra_rdoc_files = [
|
17
16
|
"LICENSE",
|
18
17
|
"README.md"
|
@@ -24,23 +23,17 @@ Gem::Specification.new do |s|
|
|
24
23
|
"README.md",
|
25
24
|
"Rakefile",
|
26
25
|
"VERSION",
|
27
|
-
"bin/drip-mlogger",
|
28
|
-
"bin/drip-publisher",
|
29
26
|
"doc_img/topology.png",
|
30
27
|
"dripdrop.gemspec",
|
31
28
|
"example/agent_test.rb",
|
32
|
-
"example/
|
33
|
-
"example/
|
34
|
-
"example/web/public/js/jquery.js",
|
35
|
-
"example/web/public/js/jquery.websocket.js",
|
36
|
-
"example/web/public/view.html",
|
37
|
-
"example/web/server.rb",
|
29
|
+
"example/pubsub.rb",
|
30
|
+
"example/pushpull.rb",
|
38
31
|
"lib/dripdrop.rb",
|
39
32
|
"lib/dripdrop/agent.rb",
|
40
|
-
"lib/dripdrop/
|
33
|
+
"lib/dripdrop/handlers/websockets.rb",
|
34
|
+
"lib/dripdrop/handlers/zeromq.rb",
|
41
35
|
"lib/dripdrop/message.rb",
|
42
|
-
"lib/dripdrop/
|
43
|
-
"lib/dripdrop/publisher.rb"
|
36
|
+
"lib/dripdrop/node.rb"
|
44
37
|
]
|
45
38
|
s.homepage = %q{http://github.com/andrewvc/dripdrop}
|
46
39
|
s.rdoc_options = ["--charset=UTF-8"]
|
@@ -53,15 +46,21 @@ Gem::Specification.new do |s|
|
|
53
46
|
s.specification_version = 3
|
54
47
|
|
55
48
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
56
|
-
s.add_runtime_dependency(%q<
|
57
|
-
s.add_runtime_dependency(%q<
|
49
|
+
s.add_runtime_dependency(%q<ffi-rzmq>, [">= 0"])
|
50
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
|
51
|
+
s.add_runtime_dependency(%q<bert>, [">= 0"])
|
52
|
+
s.add_runtime_dependency(%q<json>, [">= 0"])
|
58
53
|
else
|
59
|
-
s.add_dependency(%q<
|
60
|
-
s.add_dependency(%q<
|
54
|
+
s.add_dependency(%q<ffi-rzmq>, [">= 0"])
|
55
|
+
s.add_dependency(%q<eventmachine>, [">= 0"])
|
56
|
+
s.add_dependency(%q<bert>, [">= 0"])
|
57
|
+
s.add_dependency(%q<json>, [">= 0"])
|
61
58
|
end
|
62
59
|
else
|
63
|
-
s.add_dependency(%q<
|
64
|
-
s.add_dependency(%q<
|
60
|
+
s.add_dependency(%q<ffi-rzmq>, [">= 0"])
|
61
|
+
s.add_dependency(%q<eventmachine>, [">= 0"])
|
62
|
+
s.add_dependency(%q<bert>, [">= 0"])
|
63
|
+
s.add_dependency(%q<json>, [">= 0"])
|
65
64
|
end
|
66
65
|
end
|
67
66
|
|
data/example/agent_test.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'dripdrop/agent'
|
3
3
|
|
4
|
-
agent = DripDrop::Agent.new('tcp://127.0.0.1:2900')
|
4
|
+
agent = DripDrop::Agent.new(ZMQ::PUB,'tcp://127.0.0.1:2900',:connect)
|
5
5
|
|
6
6
|
loop do
|
7
7
|
#Test is the message name, this is the first part of the 0MQ message, used for filtering
|
data/example/pubsub.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'dripdrop/node'
|
2
|
+
Thread.abort_on_exception = true
|
3
|
+
|
4
|
+
DripDrop::Node.new do |node|
|
5
|
+
z_addr = 'tcp://127.0.0.1:2200'
|
6
|
+
|
7
|
+
pub = node.zmq_publish(z_addr,:bind)
|
8
|
+
sub = node.zmq_subscribe(z_addr,:connect).on_recv do |message|
|
9
|
+
puts "Receiver 1 #{message.inspect}"
|
10
|
+
end
|
11
|
+
sub = node.zmq_subscribe(z_addr, :connect).on_recv do |message|
|
12
|
+
puts "Receiver 2 #{message.inspect}"
|
13
|
+
end
|
14
|
+
|
15
|
+
node.zm_reactor.periodical_timer(5) do
|
16
|
+
pub.send_message(DripDrop::Message.new('test', :body => 'Test Payload'))
|
17
|
+
end
|
18
|
+
end
|
data/example/pushpull.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'dripdrop/node'
|
2
|
+
Thread.abort_on_exception = true
|
3
|
+
|
4
|
+
DripDrop::Node.new do |node|
|
5
|
+
z_addr = 'tcp://127.0.0.1:2200'
|
6
|
+
|
7
|
+
node.zmq_pull(z_addr, :connect).on_recv do |message|
|
8
|
+
puts "Receiver 2 #{message.body}"
|
9
|
+
end
|
10
|
+
node.zmq_pull(z_addr, :connect).on_recv do |message|
|
11
|
+
puts "Receiver 1 #{message.body}"
|
12
|
+
end
|
13
|
+
push = node.zmq_push(z_addr, :bind)
|
14
|
+
|
15
|
+
i = 0
|
16
|
+
node.zm_reactor.periodical_timer(800) do
|
17
|
+
i += 1
|
18
|
+
puts i
|
19
|
+
push.send_message(DripDrop::Message.new('test', :body => "Test Payload #{i}"))
|
20
|
+
end
|
21
|
+
end
|
data/lib/dripdrop/agent.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
require 'dripdrop/message'
|
2
|
-
|
2
|
+
#Check if we're in 1.8.7
|
3
|
+
unless defined?(RUBY_ENGINE)
|
4
|
+
require 'zmq'
|
5
|
+
ZMQGEM = :rbzmq
|
6
|
+
else
|
7
|
+
require 'ffi-rzmq'
|
8
|
+
ZMQGEM = :ffirzmq
|
9
|
+
end
|
10
|
+
require 'uri'
|
3
11
|
require 'bert'
|
4
12
|
|
5
13
|
class DripDrop
|
@@ -8,16 +16,26 @@ class DripDrop
|
|
8
16
|
attr_reader :address, :context, :socket
|
9
17
|
|
10
18
|
#address should be a string like tcp://127.0.0.1
|
11
|
-
def initialize(address)
|
12
|
-
@address = address
|
19
|
+
def initialize(sock_type,address,sock_ctype)
|
13
20
|
@context = ZMQ::Context.new(1)
|
14
|
-
@socket = @context.socket(
|
15
|
-
|
21
|
+
@socket = @context.socket(sock_type)
|
22
|
+
if sock_ctype == :bind
|
23
|
+
@socket.bind(address)
|
24
|
+
else
|
25
|
+
@socket.connect(address)
|
26
|
+
end
|
16
27
|
end
|
17
28
|
|
18
29
|
#Sends a DripDrop::Message to the socket
|
19
30
|
def send_message(name,body,head={})
|
20
|
-
|
31
|
+
message = DripDrop::Message.new(name,:body => body, :head => head).encoded
|
32
|
+
if ZMQGEM == :rbzmq
|
33
|
+
@socket.send name, ZMQ::SNDMORE
|
34
|
+
@socket.send message
|
35
|
+
else
|
36
|
+
@socket.send_string name, ZMQ::SNDMORE
|
37
|
+
@socket.send_string message
|
38
|
+
end
|
21
39
|
end
|
22
40
|
end
|
23
41
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'em-websocket'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class DripDrop
|
5
|
+
class WebSocketHandler
|
6
|
+
attr_reader :ws, :address, :thread
|
7
|
+
|
8
|
+
def initialize(address,opts={})
|
9
|
+
@raw = false #Deal in strings or ZMQ::Message objects
|
10
|
+
@thread = Thread.new do
|
11
|
+
host, port = address.host, address.port.to_i
|
12
|
+
@debug = opts[:debug] || false
|
13
|
+
|
14
|
+
ws_conn = EventMachine::WebSocket::Connection
|
15
|
+
EventMachine::start_server(host,port,ws_conn,:debug => @debug) do |ws|
|
16
|
+
@ws = ws
|
17
|
+
@ws.onopen do
|
18
|
+
@onopen_handler.call(ws) if @onopen_handler
|
19
|
+
end
|
20
|
+
@ws.onmessage do |message|
|
21
|
+
unless @raw
|
22
|
+
begin
|
23
|
+
parsed = JSON.parse(message)
|
24
|
+
message = DripDrop::Message.new(parsed['name'], :body => parsed['body'], :head => parsed['head'] || {})
|
25
|
+
rescue StandardError => e
|
26
|
+
puts "Could not parse message: #{e.message}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
@onmessage_handler.call(message,ws) if @onmessage_handler
|
30
|
+
end
|
31
|
+
@ws.onclose do
|
32
|
+
@onclose_handler.call(@ws) if @onclose_handler
|
33
|
+
end
|
34
|
+
@ws.onerror do
|
35
|
+
@onerror_handler.call(@ws) if @onerror_handler
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def on_recv(&block)
|
42
|
+
@raw = false
|
43
|
+
@onmessage_handler = block
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_recv_raw(&block)
|
48
|
+
@raw = true
|
49
|
+
@onmessage_handler = block
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def on_open(&block)
|
54
|
+
@onopen_handler = block
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def on_close(&block)
|
59
|
+
@onclose_handler = block
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def on_error(&block)
|
64
|
+
@onerror_handler = block
|
65
|
+
self
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'ffi-rzmq'
|
2
|
+
|
3
|
+
class DripDrop
|
4
|
+
class ZMQBaseHandler
|
5
|
+
attr_reader :address, :socket_ctype, :socket
|
6
|
+
|
7
|
+
def initialize(address,zm_reactor,socket_ctype,opts={})
|
8
|
+
@address = address
|
9
|
+
@zm_reactor = zm_reactor
|
10
|
+
@socket_ctype = socket_ctype # :bind or :connect
|
11
|
+
@debug = opts[:debug]
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_attach(socket)
|
15
|
+
@socket = socket
|
16
|
+
if @socket_ctype == :bind
|
17
|
+
socket.bind(@address)
|
18
|
+
elsif @socket_ctype == :connect
|
19
|
+
socket.connect(@address)
|
20
|
+
else
|
21
|
+
raise "Unsupported socket ctype '#{@socket_ctype}'"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_recv(msg_format=:dripdrop,&block)
|
26
|
+
@msg_format = msg_format
|
27
|
+
@recv_cbak = block
|
28
|
+
self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class ZMQWritableHandler < ZMQBaseHandler
|
33
|
+
def initialize(*args)
|
34
|
+
super(*args)
|
35
|
+
@send_queue = []
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class ZMQReadableHandler < ZMQBaseHandler
|
40
|
+
def initialize(*args,&block)
|
41
|
+
super(*args)
|
42
|
+
@recv_cbak = block
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class ZMQSubHandler < ZMQReadableHandler
|
47
|
+
attr_reader :address, :socket_ctype
|
48
|
+
|
49
|
+
def on_attach(socket)
|
50
|
+
super(socket)
|
51
|
+
socket.subscribe('')
|
52
|
+
end
|
53
|
+
|
54
|
+
def on_readable(socket, messages)
|
55
|
+
if @msg_format == :raw
|
56
|
+
@recv_cbak.call(messages)
|
57
|
+
elsif @msg_format == :dripdrop
|
58
|
+
unless messages.length == 2
|
59
|
+
puts "Expected pub/sub message to come in two parts"
|
60
|
+
return false
|
61
|
+
end
|
62
|
+
topic = messages.shift.copy_out_string
|
63
|
+
body = messages.shift.copy_out_string
|
64
|
+
msg = @recv_cbak.call(DripDrop::Message.decode(body))
|
65
|
+
else
|
66
|
+
raise "Unsupported message format '#{@msg_format}'"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
class ZMQPubHandler < ZMQWritableHandler
|
73
|
+
#Send any messages buffered in @send_queue
|
74
|
+
def on_writable(socket)
|
75
|
+
unless @send_queue.empty?
|
76
|
+
message = @send_queue.shift
|
77
|
+
|
78
|
+
num_parts = message.length
|
79
|
+
message.each_with_index do |part,i|
|
80
|
+
multipart = i + 1 < num_parts ? true : false
|
81
|
+
if part.class == ZMQ::Message
|
82
|
+
socket.send_message(part, multipart)
|
83
|
+
else
|
84
|
+
socket.send_message_string(part, multipart)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
else
|
88
|
+
@zm_reactor.deregister_writable(socket)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
#Sends a message along
|
93
|
+
def send_message(message)
|
94
|
+
if message.is_a?(DripDrop::Message)
|
95
|
+
@send_queue.push([message.name, message.encoded])
|
96
|
+
elsif message.is_a?(Array)
|
97
|
+
@send_queue.push(message)
|
98
|
+
else
|
99
|
+
@send_queue.push([message])
|
100
|
+
end
|
101
|
+
@zm_reactor.register_writable(@socket)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class ZMQPullHandler < ZMQReadableHandler
|
106
|
+
def on_readable(socket, messages)
|
107
|
+
if @msg_format == :raw
|
108
|
+
@recv_cbak.call(messages)
|
109
|
+
else
|
110
|
+
body = messages.shift.copy_out_string
|
111
|
+
msg = @recv_cbak.call(DripDrop::Message.decode(body))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class ZMQPushHandler < ZMQWritableHandler
|
117
|
+
def on_writable(socket)
|
118
|
+
unless @send_queue.empty?
|
119
|
+
message = @send_queue.shift
|
120
|
+
socket.send_message_string(message)
|
121
|
+
else
|
122
|
+
@zm_reactor.deregister_writable(socket)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
#Sends a message along
|
127
|
+
def send_message(message)
|
128
|
+
if message.is_a?(DripDrop::Message)
|
129
|
+
@send_queue.push(message.encoded)
|
130
|
+
@zm_reactor.register_writable(@socket)
|
131
|
+
else
|
132
|
+
@send_queue.push(message)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/lib/dripdrop/message.rb
CHANGED
@@ -3,12 +3,14 @@ require 'bert'
|
|
3
3
|
|
4
4
|
class DripDrop
|
5
5
|
#DripDrop::Message messages are exchanged between all tiers in the architecture
|
6
|
-
#A Message is composed of a name, head, and body
|
7
|
-
#
|
6
|
+
#A Message is composed of a name, head, and body, and should be restricted to types that
|
7
|
+
#can be readily encoded to JSON, that means hashes, arrays, strings, integers, and floats
|
8
|
+
#internally, they're just stored as BERT
|
8
9
|
#
|
9
|
-
#The
|
10
|
-
#The
|
11
|
-
#
|
10
|
+
#The basic message format is built to mimic HTTP. Why? Because I'm a dumb web developer :)
|
11
|
+
#The name is kind of like the URL, its what kind of message this is.
|
12
|
+
#head should be used for metadata, body for the actual data.
|
13
|
+
#These definitions are intentionally loose, because protocols tend to be used loosely.
|
12
14
|
class Message
|
13
15
|
attr_accessor :name, :head, :body
|
14
16
|
|
@@ -28,7 +30,7 @@ class DripDrop
|
|
28
30
|
|
29
31
|
#The encoded message, ready to be sent across the wire via ZMQ
|
30
32
|
def encoded
|
31
|
-
|
33
|
+
BERT.encode(self.to_hash)
|
32
34
|
end
|
33
35
|
|
34
36
|
#Convert the Message to a hash like:
|
@@ -37,11 +39,25 @@ class DripDrop
|
|
37
39
|
{:name => @name, :head => @head, :body => @body}
|
38
40
|
end
|
39
41
|
|
40
|
-
#Parses an encoded
|
42
|
+
#Parses an already encoded string
|
43
|
+
def self.decode(*args); self.parse(*args) end
|
41
44
|
def self.parse(msg)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
+
return nil if msg.nil? || msg.empty?
|
46
|
+
#This makes parsing ZMQ messages less painful, even if its ugly here
|
47
|
+
#We check the class name as a string if case we don't have ZMQ loaded
|
48
|
+
if msg.class.to_s == 'ZMQ::Message'
|
49
|
+
msg = msg.copy_out_string
|
50
|
+
return nil if msg.empty?
|
51
|
+
end
|
52
|
+
decoded = BERT.decode(msg)
|
53
|
+
self.new(decoded[:name], :head => decoded[:head], :body => decoded[:body])
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
#Sanitize a string so it'll look good for JSON, BERT, and MongoDB
|
59
|
+
def sanitize_structure(structure)
|
60
|
+
#TODO: Make this work, and called for head, and body
|
45
61
|
end
|
46
62
|
end
|
47
63
|
end
|