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 CHANGED
@@ -12,6 +12,7 @@ tmtags
12
12
 
13
13
  ## VIM
14
14
  *.swp
15
+ *.swo
15
16
 
16
17
  ## PROJECT::GENERAL
17
18
  coverage
@@ -19,4 +20,7 @@ rdoc
19
20
  doc
20
21
  pkg
21
22
 
23
+ #RBX
24
+ *.rbc
25
+
22
26
  ## PROJECT::SPECIFIC
data/README.md CHANGED
@@ -1,115 +1,34 @@
1
- # dripdrop
1
+ # DripDrop
2
2
 
3
- 0MQ Based App Event Monitoring / processing.
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
- # Why use dripdrop?
5
+ DripDrop is ZeroMQ(using zmqmachine) + Event Machine simplified for the general use case + serialization helpers.
7
6
 
8
- You want to asynchronously process / monitor arbitrary messages from your app.
9
- dripdrop does this well for a few reasons.
10
-
11
- * It's fast. dripdrop doesn't slow down your app. 0MQ + Bert are fast. Sending a message never blocks.
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
- class DripDrop
73
- class MLoggerCollector < Collector
74
- attr_accessor :mongo_collection
75
-
76
- #Messages are a DripDrop::Message
77
- def on_recv(message)
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
- class MLogger
85
- attr_reader :sub_address, :sub_reactor, :mongo_host, :mongo_port, :mongo_db,
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
- ## Note on Patches/Pull Requests
106
-
107
- * Fork the project.
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
- ## Copyright
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('zmq')
14
- gem.add_dependency('ffi')
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.3
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.3"
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-08-14}
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/forwarder.cfg",
33
- "example/web/public/js/jquery.gracefulWebSocket.js",
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/collector.rb",
33
+ "lib/dripdrop/handlers/websockets.rb",
34
+ "lib/dripdrop/handlers/zeromq.rb",
41
35
  "lib/dripdrop/message.rb",
42
- "lib/dripdrop/mlogger.rb",
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<zmq>, [">= 0"])
57
- s.add_runtime_dependency(%q<ffi>, [">= 0"])
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<zmq>, [">= 0"])
60
- s.add_dependency(%q<ffi>, [">= 0"])
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<zmq>, [">= 0"])
64
- s.add_dependency(%q<ffi>, [">= 0"])
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
 
@@ -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
@@ -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
@@ -1,5 +1,13 @@
1
1
  require 'dripdrop/message'
2
- require 'zmq'
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(ZMQ::PUB)
15
- @socket.connect(@address)
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
- puts @socket.send(DripDrop::Message.new(name,:body => body, :head => head).encoded, 0)
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
@@ -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. The name exists primarily for the
7
- #purpose of native ZMQ filtering, since ZMQ can filter based on a message prefix.
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 name is any string consisting of non-null chars.
10
- #The rest of the payload is a BERT encoded head and body, both of which are hashes.
11
- #The head and body don't have rigid definitions yet, use as you please.
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
- "#{@name}\0#{BERT.encode({:head => @head, :body => @body})}"
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 message
42
+ #Parses an already encoded string
43
+ def self.decode(*args); self.parse(*args) end
41
44
  def self.parse(msg)
42
- name, encoded_body = msg.split("\0",2)
43
- decoded = BERT.decode(encoded_body)
44
- self.new(name, :head => decoded[:head], :body => decoded[:body])
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