dripdrop 0.10.0-java
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/.document +5 -0
- data/.gitignore +31 -0
- data/Gemfile +5 -0
- data/LICENSE +20 -0
- data/README.md +164 -0
- data/Rakefile +16 -0
- data/dripdrop.gemspec +37 -0
- data/example/agent_test.rb +14 -0
- data/example/combined.rb +33 -0
- data/example/complex/README +22 -0
- data/example/complex/client.rb +20 -0
- data/example/complex/server.rb +102 -0
- data/example/complex/service.rb +8 -0
- data/example/complex/websocket.rb +442 -0
- data/example/http.rb +23 -0
- data/example/pubsub.rb +29 -0
- data/example/pushpull.rb +21 -0
- data/example/subclass.rb +54 -0
- data/example/xreq_xrep.rb +24 -0
- data/js/dripdrop.html +186 -0
- data/js/dripdrop.js +107 -0
- data/js/qunit.css +155 -0
- data/js/qunit.js +1261 -0
- data/lib/dripdrop.rb +2 -0
- data/lib/dripdrop/agent.rb +40 -0
- data/lib/dripdrop/handlers/base.rb +42 -0
- data/lib/dripdrop/handlers/http_client.rb +38 -0
- data/lib/dripdrop/handlers/http_server.rb +59 -0
- data/lib/dripdrop/handlers/mongrel2.rb +163 -0
- data/lib/dripdrop/handlers/websocket_server.rb +86 -0
- data/lib/dripdrop/handlers/zeromq.rb +300 -0
- data/lib/dripdrop/message.rb +190 -0
- data/lib/dripdrop/node.rb +351 -0
- data/lib/dripdrop/node/nodelet.rb +35 -0
- data/lib/dripdrop/version.rb +3 -0
- data/spec/gimite-websocket.rb +442 -0
- data/spec/message_spec.rb +94 -0
- data/spec/node/http_spec.rb +77 -0
- data/spec/node/nodelet_spec.rb +67 -0
- data/spec/node/routing_spec.rb +67 -0
- data/spec/node/websocket_spec.rb +98 -0
- data/spec/node/zmq_m2_spec.rb +77 -0
- data/spec/node/zmq_pushpull_spec.rb +54 -0
- data/spec/node/zmq_xrepxreq_spec.rb +108 -0
- data/spec/node_spec.rb +85 -0
- data/spec/spec_helper.rb +20 -0
- metadata +167 -0
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
if RUBY_PLATFORM == 'java'
|
4
|
+
require 'json'
|
5
|
+
else
|
6
|
+
require 'yajl'
|
7
|
+
require 'yajl/json_gem'
|
8
|
+
end
|
9
|
+
|
10
|
+
class DripDrop
|
11
|
+
class WrongMessageClassError < StandardError; end
|
12
|
+
|
13
|
+
# DripDrop::Message messages are exchanged between all tiers in the architecture
|
14
|
+
# A Message is composed of a name, head, and body, and should be restricted to types that
|
15
|
+
# can be readily encoded to JSON.
|
16
|
+
# name: Any string
|
17
|
+
# head: A hash containing anything (should be used for metadata)
|
18
|
+
# body: anything you'd like, it can be null even
|
19
|
+
#
|
20
|
+
# Hashes, arrays, strings, integers, symbols, and floats are probably what you should stick to.
|
21
|
+
# Internally, they're just stored using MsgPack, which is pretty much a super-fast, binary JSON
|
22
|
+
#
|
23
|
+
# The basic message format is built to mimic HTTP (s/url_path/name/). Why? Because I'm a dumb web developer :)
|
24
|
+
# The name is kind of like the URL, its what kind of message this is, but it's a loose definition,
|
25
|
+
# use it as you see fit.
|
26
|
+
# head should be used for metadata, body for the actual data.
|
27
|
+
# These definitions are intentionally loose, because protocols tend to be used loosely.
|
28
|
+
class Message
|
29
|
+
|
30
|
+
attr_accessor :name, :head, :body
|
31
|
+
|
32
|
+
# Creates a new message.
|
33
|
+
# example:
|
34
|
+
# DripDrop::Message.new('mymessage', 'head' => {:timestamp => Time.now},
|
35
|
+
# :body => {:mykey => :myval, :other_key => ['complex']})
|
36
|
+
def initialize(name,extra={})
|
37
|
+
raise ArgumentError, "Message names may not be empty or null!" if name.nil? || name.empty?
|
38
|
+
|
39
|
+
@head = extra[:head] || extra['head'] || {}
|
40
|
+
raise ArgumentError, "Invalid head #{@head}. Head must be a hash!" unless @head.is_a?(Hash)
|
41
|
+
@head['message_class'] = self.class.to_s
|
42
|
+
|
43
|
+
@name = name
|
44
|
+
@body = extra[:body] || extra['body']
|
45
|
+
end
|
46
|
+
|
47
|
+
# The encoded message, ready to be sent across the wire via ZMQ
|
48
|
+
def encoded
|
49
|
+
self.to_hash.to_json
|
50
|
+
end
|
51
|
+
|
52
|
+
# (Deprecated) Encodes the hash represntation of the message to JSON
|
53
|
+
def json_encoded
|
54
|
+
encoded
|
55
|
+
end
|
56
|
+
# (Deprecated, use json_encoded)
|
57
|
+
def encode_json; json_encoded; end
|
58
|
+
|
59
|
+
# Convert the Message to a hash like:
|
60
|
+
# {'name' => @name, 'head' => @head, 'body' => @body}
|
61
|
+
def to_hash
|
62
|
+
{'name' => @name, 'head' => @head, 'body' => @body}
|
63
|
+
end
|
64
|
+
|
65
|
+
# Build a new Message from a hash that looks like
|
66
|
+
# {:name => name, :body => body, 'head' => head}
|
67
|
+
def self.from_hash(hash)
|
68
|
+
self.new(hash[:name] || hash['name'],
|
69
|
+
:head => hash[:head] || hash['head'],
|
70
|
+
:body => hash[:body] || hash['body'])
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.create_message(*args)
|
74
|
+
case args[0]
|
75
|
+
when Hash then self.from_hash(args[0])
|
76
|
+
else self.new(args)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.recreate_message(hash)
|
81
|
+
raise ArgumentError, "Message missing head: #{hash.inspect}" unless hash['head']
|
82
|
+
|
83
|
+
klass = (hash['head'] && hash['head']['message_class']) ? constantize(hash['head']['message_class']) : nil
|
84
|
+
if klass && (!(klass == self) && !self.subclasses.include?(klass))
|
85
|
+
raise DripDrop::WrongMessageClassError, "Wrong message class '#{klass}', expected '#{self}' for message #{hash.inspect}"
|
86
|
+
end
|
87
|
+
|
88
|
+
klass ? klass.from_hash(hash) : self.from_hash(hash)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Parses an already encoded string
|
92
|
+
def self.decode(msg)
|
93
|
+
return nil if msg.nil? || msg.empty?
|
94
|
+
|
95
|
+
decoded = JSON.parse(msg)
|
96
|
+
self.recreate_message(decoded)
|
97
|
+
end
|
98
|
+
|
99
|
+
# (Deprecated). Use decode instead
|
100
|
+
def self.parse(msg); self.decode(msg) end
|
101
|
+
|
102
|
+
# (Deprecated) Decodes a string containing a JSON representation of a message
|
103
|
+
def self.decode_json(str)
|
104
|
+
self.decode(str)
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.constantize(str)
|
108
|
+
begin
|
109
|
+
str.split('::').inject(Object) {|memo,name|
|
110
|
+
memo = memo.const_get(name); memo
|
111
|
+
}
|
112
|
+
rescue NameError => e
|
113
|
+
nil
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
#Used for reconstructing messages
|
118
|
+
def self.subclasses(direct = false)
|
119
|
+
classes = []
|
120
|
+
if direct
|
121
|
+
ObjectSpace.each_object(Class) do |c|
|
122
|
+
next unless c.superclass == self
|
123
|
+
classes << c
|
124
|
+
end
|
125
|
+
else
|
126
|
+
ObjectSpace.each_object(Class) do |c|
|
127
|
+
next unless c.ancestors.include?(self) and (c != self)
|
128
|
+
classes << c
|
129
|
+
end
|
130
|
+
end
|
131
|
+
classes
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
#Use of this "metaclass" allows for the automatic recognition of the message's
|
136
|
+
#base class
|
137
|
+
class AutoMessageClass < Message
|
138
|
+
def initialize(*args)
|
139
|
+
raise "Cannot create an instance of this class - please use create_message class method"
|
140
|
+
end
|
141
|
+
|
142
|
+
class << self
|
143
|
+
attr_accessor :message_subclasses
|
144
|
+
|
145
|
+
DripDrop::AutoMessageClass.message_subclasses = {'DripDrop::Message' => DripDrop::Message}
|
146
|
+
|
147
|
+
def verify_args(*args)
|
148
|
+
head =
|
149
|
+
case args[0]
|
150
|
+
when Hash
|
151
|
+
az = args[0]
|
152
|
+
az[:head] || az['head']
|
153
|
+
else
|
154
|
+
args[1]
|
155
|
+
end
|
156
|
+
raise ArgumentError, "Invalid head #{head.inspect}. Head must be a hash! (args: #{args.inspect})" unless head.is_a?(Hash)
|
157
|
+
|
158
|
+
msg_class = head['message_class']
|
159
|
+
unless DripDrop::AutoMessageClass.message_subclasses.has_key?(msg_class)
|
160
|
+
raise ArgumentError, "Unknown AutoMessage message class #{msg_class}"
|
161
|
+
end
|
162
|
+
|
163
|
+
DripDrop::AutoMessageClass.message_subclasses[msg_class]
|
164
|
+
end
|
165
|
+
|
166
|
+
def create_message(*args)
|
167
|
+
klass = verify_args(*args)
|
168
|
+
klass.create_message(*args)
|
169
|
+
end
|
170
|
+
|
171
|
+
def recreate_message(*args)
|
172
|
+
klass = verify_args(*args)
|
173
|
+
klass.recreate_message(*args)
|
174
|
+
end
|
175
|
+
|
176
|
+
def register_subclass(klass)
|
177
|
+
DripDrop::AutoMessageClass.message_subclasses[klass.to_s] = klass
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
#Including this module into your subclass will automatically register the class
|
184
|
+
#with AutoMessageClass
|
185
|
+
module SubclassedMessage
|
186
|
+
def self.included(base)
|
187
|
+
DripDrop::AutoMessageClass.register_subclass base
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,351 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ffi-rzmq'
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'uri'
|
5
|
+
require 'resolv'
|
6
|
+
require 'ipaddr'
|
7
|
+
|
8
|
+
require 'dripdrop/message'
|
9
|
+
require 'dripdrop/node/nodelet'
|
10
|
+
require 'dripdrop/handlers/base'
|
11
|
+
require 'dripdrop/handlers/zeromq'
|
12
|
+
require 'dripdrop/handlers/websocket_server'
|
13
|
+
require 'dripdrop/handlers/mongrel2'
|
14
|
+
require 'dripdrop/handlers/http_client'
|
15
|
+
|
16
|
+
begin
|
17
|
+
require 'dripdrop/handlers/http_server'
|
18
|
+
rescue LoadError => e
|
19
|
+
$stderr.write "Warning, could not load http server, your probably don't have eventmachine_httpserver installed\n"
|
20
|
+
end
|
21
|
+
|
22
|
+
class DripDrop
|
23
|
+
class Node
|
24
|
+
ZCTX = ZMQ::Context.new 1
|
25
|
+
|
26
|
+
attr_reader :zm_reactor, :routing, :nodelets, :run_list
|
27
|
+
attr_accessor :debug
|
28
|
+
|
29
|
+
def initialize(opts={},&block)
|
30
|
+
@block = block
|
31
|
+
@thread = nil # Thread containing the reactors
|
32
|
+
@routing = {} # Routing table
|
33
|
+
@run_list = opts['run_list'] || opts[:run_list] || nil #List of nodelets to run
|
34
|
+
@run_list = @run_list.map(&:to_sym) if @run_list
|
35
|
+
@debug = opts[:debug]
|
36
|
+
@recipients_for = {}
|
37
|
+
@handler_default_opts = {:debug => @debug}
|
38
|
+
@nodelets = {} # Cache of registered nodelets
|
39
|
+
@zctx = ZCTX
|
40
|
+
end
|
41
|
+
|
42
|
+
# Starts the reactors and runs the block passed to initialize.
|
43
|
+
# This is non-blocking.
|
44
|
+
def start
|
45
|
+
@thread = Thread.new do
|
46
|
+
EM.error_handler {|e| self.class.error_handler e}
|
47
|
+
EM.run { action }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Blocking version of start, equivalent to +start+ then +join+
|
52
|
+
def start!
|
53
|
+
self.start
|
54
|
+
self.join
|
55
|
+
end
|
56
|
+
|
57
|
+
# Stops the reactors. If you were blocked on #join, that will unblock.
|
58
|
+
def stop
|
59
|
+
EM.stop
|
60
|
+
end
|
61
|
+
|
62
|
+
# When subclassing +DripDrop::Node+ you probably want to define this method
|
63
|
+
# Otherwise it will attempt to run the @block passed into +DripDrop::Node.new+
|
64
|
+
def action
|
65
|
+
if @block
|
66
|
+
self.instance_eval(&@block)
|
67
|
+
else
|
68
|
+
raise "Could not start, no block or action specified"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# If the reactor has started, this blocks until the thread
|
73
|
+
# running the reactor joins. This should block forever
|
74
|
+
# unless +stop+ is called.
|
75
|
+
def join
|
76
|
+
if @thread
|
77
|
+
@thread.join
|
78
|
+
else
|
79
|
+
raise "Can't join on a node that isn't yet started"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Defines a new route. Routes are the recommended way to instantiate
|
84
|
+
# handlers. For example:
|
85
|
+
#
|
86
|
+
# route :stats_pub, :zmq_publish, 'tcp://127.0.0.1:2200', :bind
|
87
|
+
# route :stats_sub, :zmq_subscribe, stats_pub.address, :connect
|
88
|
+
#
|
89
|
+
# Will make the following methods available within the reactor block:
|
90
|
+
# stats_pub # A regular zmq_publish handler
|
91
|
+
# :stats_sub # A regular zmq_subscribe handler
|
92
|
+
#
|
93
|
+
# See the docs for +routes_for+ for more info in grouping routes for
|
94
|
+
# nodelets and maintaining sanity in larger apps
|
95
|
+
def route(name,handler_type,*handler_args)
|
96
|
+
route_full(nil, name, handler_type, *handler_args)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Probably not useful for most, apps. This is used internally to
|
100
|
+
# create a route for a given nodelet.
|
101
|
+
def route_full(nodelet, name, handler_type, *handler_args)
|
102
|
+
# If we're in a route_for block, prepend appropriately
|
103
|
+
full_name = (nodelet && nodelet.name) ? "#{nodelet.name}_#{name}".to_sym : name
|
104
|
+
|
105
|
+
handler = self.send(handler_type, *handler_args)
|
106
|
+
@routing[full_name] = handler
|
107
|
+
|
108
|
+
# Define the route name as a singleton method
|
109
|
+
(class << self; self; end).class_eval do
|
110
|
+
define_method(full_name) { handler }
|
111
|
+
end
|
112
|
+
|
113
|
+
handler
|
114
|
+
end
|
115
|
+
|
116
|
+
# DEPRECATED, will be deleted in 0.8
|
117
|
+
def routes_for(nodelet_name,&block)
|
118
|
+
$stderr.write "routes_for is now deprecated, use nodelet instead"
|
119
|
+
nlet = nodelet(nodelet_name,&block)
|
120
|
+
block.call(nlet)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Nodelets are a way of segmenting a DripDrop::Node. This can be used
|
124
|
+
# for both organization and deployment. One might want the production
|
125
|
+
# deployment of an app to be broken across multiple servers or processes
|
126
|
+
# for instance:
|
127
|
+
#
|
128
|
+
# nodelet :heartbeat do |nlet|
|
129
|
+
# nlet.route :ticker, :zmq_publish, 'tcp://127.0.0.1', :bind
|
130
|
+
# EM::PeriodicalTimer.new(1) do
|
131
|
+
# nlet.ticker.send_message(:name => 'tick')
|
132
|
+
# end
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
# Nodelets can also be subclassed, for instance:
|
136
|
+
#
|
137
|
+
# class SpecialNodelet < DripDrop::Node::Nodelet
|
138
|
+
# def action
|
139
|
+
# nlet.route :ticker, :zmq_publish, 'tcp://127.0.0.1', :bind
|
140
|
+
# EM::PeriodicalTimer.new(1) do
|
141
|
+
# nlet.ticker.send_message(:name => 'tick')
|
142
|
+
# end
|
143
|
+
# end
|
144
|
+
# end
|
145
|
+
#
|
146
|
+
# nodelet :heartbeat, SpecialNodelet
|
147
|
+
#
|
148
|
+
# If you specify a block, Nodelet#action will be ignored and the block
|
149
|
+
# will be run
|
150
|
+
#
|
151
|
+
# Nodelets are made available as instance methods on the current DripDrop::Nodelet
|
152
|
+
# Object, so the following works as well:
|
153
|
+
#
|
154
|
+
# nodelet :mynodelet
|
155
|
+
#
|
156
|
+
# mynodelet.route :route_name, :zmq_xreq, 'tcp://127.0.0.1:2000', ;bind
|
157
|
+
def nodelet(name,klass=Nodelet,*configure_args,&block)
|
158
|
+
# If there's a run list, only run nodes in that list
|
159
|
+
return nil if @run_list && !@run_list.include?(name.to_sym)
|
160
|
+
|
161
|
+
nlet = @nodelets[name] ||= klass.new(self,name,*configure_args)
|
162
|
+
|
163
|
+
# Define a method returning the nodelet in the current node
|
164
|
+
unless respond_to?(name)
|
165
|
+
(class << self; self; end).class_eval do
|
166
|
+
define_method(name) { nlet }
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
if block
|
171
|
+
block.call(nlet)
|
172
|
+
else
|
173
|
+
nlet.action
|
174
|
+
end
|
175
|
+
nlet
|
176
|
+
end
|
177
|
+
|
178
|
+
def zmq_m2(addresses, opts={}, &block)
|
179
|
+
zmq_handler(DripDrop::Mongrel2Handler, [ZMQ::PULL, ZMQ::PUB], addresses, [:connect, :connect], opts)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Creates a ZMQ::SUB type socket. Can only receive messages via +on_recv+.
|
183
|
+
# zmq_subscribe sockets have a +topic_filter+ option, which restricts which
|
184
|
+
# messages they can receive. It takes a regexp as an option.
|
185
|
+
def zmq_subscribe(address,socket_ctype,opts={},&block)
|
186
|
+
zmq_handler(DripDrop::ZMQSubHandler,ZMQ::SUB,address,socket_ctype,opts)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Creates a ZMQ::PUB type socket, can only send messages via +send_message+
|
190
|
+
def zmq_publish(address,socket_ctype,opts={})
|
191
|
+
zmq_handler(DripDrop::ZMQPubHandler,ZMQ::PUB,address,socket_ctype,opts)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Creates a ZMQ::PULL type socket. Can only receive messages via +on_recv+
|
195
|
+
def zmq_pull(address,socket_ctype,opts={},&block)
|
196
|
+
zmq_handler(DripDrop::ZMQPullHandler,ZMQ::PULL,address,socket_ctype,opts)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Creates a ZMQ::PUSH type socket, can only send messages via +send_message+
|
200
|
+
def zmq_push(address,socket_ctype,opts={})
|
201
|
+
zmq_handler(DripDrop::ZMQPushHandler,ZMQ::PUSH,address,socket_ctype,opts)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Creates a ZMQ::XREP type socket, both sends and receivesc XREP sockets are extremely
|
205
|
+
# powerful, so their functionality is currently limited. XREP sockets in DripDrop can reply
|
206
|
+
# to the original source of the message.
|
207
|
+
#
|
208
|
+
# Receiving with XREP sockets in DripDrop is different than other types of sockets, on_recv
|
209
|
+
# passes 2 arguments to its callback, +message+, and +response+. A minimal example is shown below:
|
210
|
+
#
|
211
|
+
#
|
212
|
+
# zmq_xrep(z_addr, :bind).on_recv do |message,response|
|
213
|
+
# response.send_message(message)
|
214
|
+
# end
|
215
|
+
#
|
216
|
+
def zmq_xrep(address,socket_ctype,opts={})
|
217
|
+
zmq_handler(DripDrop::ZMQXRepHandler,ZMQ::XREP,address,socket_ctype,opts)
|
218
|
+
end
|
219
|
+
|
220
|
+
# See the documentation for +zmq_xrep+ for more info
|
221
|
+
def zmq_xreq(address,socket_ctype,opts={})
|
222
|
+
zmq_handler(DripDrop::ZMQXReqHandler,ZMQ::XREQ,address,socket_ctype,opts)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Binds an EM websocket server connection to +address+. takes blocks for
|
226
|
+
# +on_open+, +on_recv+, +on_close+ and +on_error+.
|
227
|
+
#
|
228
|
+
# For example +on_recv+ could be used to echo incoming messages thusly:
|
229
|
+
# websocket_server(addr).on_open {|conn|
|
230
|
+
# ws.send_message(:name => 'ws_open_ack')
|
231
|
+
# }.on_recv {|msg,conn|
|
232
|
+
# conn.send(msg)
|
233
|
+
# }.on_close {|conn|
|
234
|
+
# }.on_error {|reason,conn|
|
235
|
+
# }
|
236
|
+
#
|
237
|
+
# The +ws+ object that's passed into the handlers is not
|
238
|
+
# the +DripDrop::WebSocketHandler+ object, but an em-websocket object.
|
239
|
+
def websocket_server(address,opts={})
|
240
|
+
uri = URI.parse(address)
|
241
|
+
h_opts = handler_opts_given(opts)
|
242
|
+
DripDrop::WebSocketHandler.new(uri,h_opts)
|
243
|
+
end
|
244
|
+
|
245
|
+
# DEPRECATED: Use websocket_server
|
246
|
+
def websocket(*args)
|
247
|
+
$stderr.write "DripDrop#websocket handler is deprecated, use DripDrop#websocket_server"
|
248
|
+
websocket_server(*args)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Starts a new Thin HTTP server listening on address.
|
252
|
+
# Can have an +on_recv+ handler that gets passed +msg+ and +response+ args.
|
253
|
+
# http_server(addr) {|msg,response| response.send_message(msg)}
|
254
|
+
def http_server(address,opts={},&block)
|
255
|
+
uri = URI.parse(address)
|
256
|
+
h_opts = handler_opts_given(opts)
|
257
|
+
DripDrop::HTTPServerHandler.new(uri, h_opts,&block)
|
258
|
+
end
|
259
|
+
|
260
|
+
# An EM HTTP client.
|
261
|
+
# Example:
|
262
|
+
# client = http_client(addr)
|
263
|
+
# client.send_message(:name => 'name', :body => 'hi') do |resp_msg|
|
264
|
+
# puts resp_msg.inspect
|
265
|
+
# end
|
266
|
+
def http_client(address,opts={})
|
267
|
+
uri = URI.parse(address)
|
268
|
+
h_opts = handler_opts_given(opts)
|
269
|
+
DripDrop::HTTPClientHandler.new(uri, h_opts)
|
270
|
+
end
|
271
|
+
|
272
|
+
# An inprocess pub/sub queue that works similarly to EM::Channel,
|
273
|
+
# but has manually specified identifiers for subscribers letting you
|
274
|
+
# more easily delete subscribers without crazy id tracking.
|
275
|
+
#
|
276
|
+
# This is useful for situations where you want to broadcast messages across your app,
|
277
|
+
# but need a way to properly delete listeners.
|
278
|
+
#
|
279
|
+
# +dest+ is the name of the pub/sub channel.
|
280
|
+
# +data+ is any type of ruby var you'd like to send.
|
281
|
+
def send_internal(dest,data)
|
282
|
+
return false unless @recipients_for[dest]
|
283
|
+
blocks = @recipients_for[dest].values
|
284
|
+
return false unless blocks
|
285
|
+
blocks.each do |block|
|
286
|
+
block.call(data)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# Defines a subscriber to the channel +dest+, to receive messages from +send_internal+.
|
291
|
+
# +identifier+ is a unique identifier for this receiver.
|
292
|
+
# The identifier can be used by +remove_recv_internal+
|
293
|
+
def recv_internal(dest,identifier,&block)
|
294
|
+
if @recipients_for[dest]
|
295
|
+
@recipients_for[dest][identifier] = block
|
296
|
+
else
|
297
|
+
@recipients_for[dest] = {identifier => block}
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
# Deletes a subscriber to the channel +dest+ previously identified by a
|
302
|
+
# reciever created with +recv_internal+
|
303
|
+
def remove_recv_internal(dest,identifier)
|
304
|
+
return false unless @recipients_for[dest]
|
305
|
+
@recipients_for[dest].delete(identifier)
|
306
|
+
end
|
307
|
+
|
308
|
+
# Catch all error handler
|
309
|
+
# Global to all DripDrop Nodes
|
310
|
+
def self.error_handler(e)
|
311
|
+
$stderr.write "#{e.class}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
|
312
|
+
end
|
313
|
+
|
314
|
+
private
|
315
|
+
|
316
|
+
def zmq_handler(klass, sock_type, address, socket_ctype, opts={})
|
317
|
+
h_opts = handler_opts_given(opts)
|
318
|
+
|
319
|
+
sock_type = [sock_type].flatten
|
320
|
+
address = [address].flatten
|
321
|
+
socket_ctype = [socket_ctype].flatten
|
322
|
+
|
323
|
+
handler = klass.new(h_opts)
|
324
|
+
|
325
|
+
sock_type.length.times do |index|
|
326
|
+
addr_uri = URI.parse(address[index])
|
327
|
+
|
328
|
+
host_str = addr_uri.host
|
329
|
+
#if addr_uri.scheme == 'tcp'
|
330
|
+
# host = Resolv.getaddresses(addr_uri.host).first
|
331
|
+
# host_addr = Resolv.getaddresses('localhost').map {|a| IPAddr.new(a)}.find {|a| a.ipv4?}
|
332
|
+
# host_str = host_addr.ipv6? ? "[#{host_addr.to_s}]" : host_addr.to_s
|
333
|
+
#else
|
334
|
+
# host_str = addr_uri.host
|
335
|
+
#end
|
336
|
+
|
337
|
+
z_addr = "#{addr_uri.scheme}://#{host_str}:#{addr_uri.port.to_i}"
|
338
|
+
|
339
|
+
connection = EM::ZeroMQ::Context.new(@zctx).create sock_type[index], socket_ctype[index], z_addr, handler
|
340
|
+
handler.add_connection connection
|
341
|
+
end
|
342
|
+
|
343
|
+
handler.post_setup
|
344
|
+
handler
|
345
|
+
end
|
346
|
+
|
347
|
+
def handler_opts_given(opts)
|
348
|
+
@handler_default_opts.merge(opts)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|