dripdrop 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +31 -11
- data/VERSION +1 -1
- data/dripdrop.gemspec +21 -3
- data/example/http.rb +23 -0
- data/example/pubsub.rb +25 -7
- data/example/pushpull.rb +7 -7
- data/example/xreq_xrep.rb +26 -0
- data/js/dripdrop.html +186 -0
- data/js/dripdrop.js +103 -0
- data/js/jack.js +876 -0
- data/js/qunit.css +155 -0
- data/js/qunit.js +1261 -0
- data/lib/dripdrop/handlers/http.rb +109 -0
- data/lib/dripdrop/handlers/zeromq.rb +153 -66
- data/lib/dripdrop/message.rb +17 -2
- data/lib/dripdrop/node.rb +79 -5
- data/lib/dripdrop.rb +1 -0
- data/spec/node/zmq_pushpull.rb +56 -0
- data/spec/node/zmq_xrepxreq.rb +92 -0
- data/spec/node_spec.rb +41 -0
- data/spec/spec_helper.rb +6 -0
- metadata +20 -12
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'thin'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class DripDrop
|
5
|
+
class HTTPDeferrableBody
|
6
|
+
include EventMachine::Deferrable
|
7
|
+
|
8
|
+
def call(body)
|
9
|
+
body.each do |chunk|
|
10
|
+
@body_callback.call(chunk)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def each(&blk)
|
15
|
+
@body_callback = blk
|
16
|
+
end
|
17
|
+
|
18
|
+
def send_message(msg)
|
19
|
+
if msg.class == DripDrop::Message
|
20
|
+
json = msg.encode_json
|
21
|
+
self.call([json])
|
22
|
+
self.succeed
|
23
|
+
else
|
24
|
+
raise "Message Type not supported"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class HTTPApp
|
30
|
+
|
31
|
+
AsyncResponse = [-1, {}, []].freeze
|
32
|
+
|
33
|
+
def initialize(msg_format,&block)
|
34
|
+
@msg_format = msg_format
|
35
|
+
@recv_cbak = block
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
def call(env)
|
40
|
+
body = HTTPDeferrableBody.new
|
41
|
+
|
42
|
+
EM.next_tick do
|
43
|
+
env['async.callback'].call([200, {'Content-Type' => 'text/plain', 'Access-Control-Allow-Origin' => '*'}, body])
|
44
|
+
EM.next_tick do
|
45
|
+
case @msg_format
|
46
|
+
when :dripdrop_json
|
47
|
+
msg = DripDrop::Message.decode_json(env['rack.input'].read)
|
48
|
+
msg.head[:http_env] = env
|
49
|
+
@recv_cbak.call(body,msg)
|
50
|
+
else
|
51
|
+
raise "Unsupported message type #{@msg_format}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
AsyncResponse
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class HTTPServerHandler
|
61
|
+
attr_reader :address, :opts
|
62
|
+
|
63
|
+
def initialize(address,opts={})
|
64
|
+
@address = address
|
65
|
+
@opts = opts
|
66
|
+
end
|
67
|
+
|
68
|
+
def on_recv(msg_format=:dripdrop_json,&block)
|
69
|
+
#Rack middleware was not meant to be used this way...
|
70
|
+
#Thin's error handling only rescues stuff w/o a backtrace
|
71
|
+
begin
|
72
|
+
Thin::Logging.debug = true
|
73
|
+
Thin::Logging.trace = true
|
74
|
+
Thin::Server.start(@address.host, @address.port) do
|
75
|
+
map '/' do
|
76
|
+
run HTTPApp.new(msg_format,&block)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
rescue Exception => e
|
80
|
+
puts e.message; puts e.backtrace.join("\n");
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class HTTPClientHandler
|
86
|
+
attr_reader :address, :opts
|
87
|
+
|
88
|
+
def initialize(address, opts={})
|
89
|
+
@address = address
|
90
|
+
@opts = opts
|
91
|
+
end
|
92
|
+
|
93
|
+
def send_message(msg,&block)
|
94
|
+
if msg.class == DripDrop::Message
|
95
|
+
req = EM::Protocols::HttpClient.request(
|
96
|
+
:host => address.host, :port => address.port,
|
97
|
+
:request => '/', :verb => 'POST',
|
98
|
+
:contenttype => 'application/json',
|
99
|
+
:content => msg.encode_json
|
100
|
+
)
|
101
|
+
req.callback do |response|
|
102
|
+
block.call(DripDrop::Message.decode_json(response[:content]))
|
103
|
+
end
|
104
|
+
else
|
105
|
+
raise "Unsupported message type '#{msg.class}'"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -3,14 +3,14 @@ require 'ffi-rzmq'
|
|
3
3
|
class DripDrop
|
4
4
|
class ZMQBaseHandler
|
5
5
|
attr_reader :address, :socket_ctype, :socket
|
6
|
-
|
6
|
+
|
7
7
|
def initialize(address,zm_reactor,socket_ctype,opts={})
|
8
8
|
@address = address
|
9
9
|
@zm_reactor = zm_reactor
|
10
10
|
@socket_ctype = socket_ctype # :bind or :connect
|
11
|
-
@debug = opts[:debug]
|
11
|
+
@debug = opts[:debug] # TODO: Start actually using this
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def on_attach(socket)
|
15
15
|
@socket = socket
|
16
16
|
if @socket_ctype == :bind
|
@@ -18,82 +18,70 @@ class DripDrop
|
|
18
18
|
elsif @socket_ctype == :connect
|
19
19
|
socket.connect(@address)
|
20
20
|
else
|
21
|
-
raise "Unsupported socket ctype '#{@socket_ctype}'"
|
21
|
+
raise "Unsupported socket ctype '#{@socket_ctype}'. Expected :bind or :connect"
|
22
22
|
end
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def on_recv(msg_format=:dripdrop,&block)
|
26
|
-
@msg_format = msg_format
|
26
|
+
@msg_format = msg_format
|
27
27
|
@recv_cbak = block
|
28
28
|
self
|
29
29
|
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Normalize Hash objs and DripDrop::Message objs into DripDrop::Message objs
|
34
|
+
def dd_messagify(message)
|
35
|
+
if message.is_a?(Hash)
|
36
|
+
return DripDrop::Message.new(message[:name], :head => message[:head],
|
37
|
+
:body => message[:body])
|
38
|
+
elsif message.is_a?(DripDrop::Message)
|
39
|
+
return message
|
40
|
+
else
|
41
|
+
return message
|
42
|
+
end
|
43
|
+
end
|
30
44
|
end
|
31
45
|
|
32
|
-
|
46
|
+
module ZMQWritableHandler
|
33
47
|
def initialize(*args)
|
34
48
|
super(*args)
|
35
49
|
@send_queue = []
|
36
50
|
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
51
|
|
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
52
|
def on_writable(socket)
|
75
53
|
unless @send_queue.empty?
|
76
54
|
message = @send_queue.shift
|
77
|
-
|
55
|
+
|
78
56
|
num_parts = message.length
|
79
57
|
message.each_with_index do |part,i|
|
80
|
-
|
58
|
+
# Set the multi-part flag unless this is the last message
|
59
|
+
multipart_flag = i + 1 < num_parts ? true : false
|
60
|
+
|
81
61
|
if part.class == ZMQ::Message
|
82
|
-
socket.send_message(part,
|
62
|
+
socket.send_message(part, multipart_flag)
|
83
63
|
else
|
84
|
-
|
64
|
+
if part.class == String
|
65
|
+
socket.send_message_string(part, multipart_flag)
|
66
|
+
else
|
67
|
+
raise "Can only send Strings, not #{part.class}: #{part}"
|
68
|
+
end
|
85
69
|
end
|
86
70
|
end
|
87
71
|
else
|
88
72
|
@zm_reactor.deregister_writable(socket)
|
89
73
|
end
|
90
74
|
end
|
91
|
-
|
92
|
-
#Sends a message
|
75
|
+
|
76
|
+
# Sends a message, accepting either a DripDrop::Message,
|
77
|
+
# a hash that looks like a DripDrop::Message (has keys :name, :head, :body),
|
78
|
+
# or your own custom messages. Custom messages should either be a String, or
|
79
|
+
# for multipart messages, an Array of String objects.
|
93
80
|
def send_message(message)
|
94
|
-
|
95
|
-
|
96
|
-
|
81
|
+
dd_message = dd_messagify(message)
|
82
|
+
if dd_message.is_a?(DripDrop::Message)
|
83
|
+
@send_queue.push([dd_message.encoded])
|
84
|
+
elsif message.class == Array
|
97
85
|
@send_queue.push(message)
|
98
86
|
else
|
99
87
|
@send_queue.push([message])
|
@@ -102,35 +90,134 @@ class DripDrop
|
|
102
90
|
end
|
103
91
|
end
|
104
92
|
|
105
|
-
|
93
|
+
module ZMQReadableHandler
|
94
|
+
def initialize(*args)
|
95
|
+
super(*args)
|
96
|
+
end
|
97
|
+
|
106
98
|
def on_readable(socket, messages)
|
107
|
-
|
99
|
+
case @msg_format
|
100
|
+
when :raw
|
108
101
|
@recv_cbak.call(messages)
|
109
|
-
|
102
|
+
when :dripdrop
|
103
|
+
raise "Expected message in one part" if messages.length > 1
|
110
104
|
body = messages.shift.copy_out_string
|
111
|
-
|
105
|
+
@recv_cbak.call(DripDrop::Message.decode(body))
|
106
|
+
else
|
107
|
+
raise "Unknown message format '#{@msg_format}'"
|
112
108
|
end
|
113
109
|
end
|
114
110
|
end
|
115
111
|
|
116
|
-
class
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
112
|
+
class ZMQSubHandler < ZMQBaseHandler
|
113
|
+
include ZMQReadableHandler
|
114
|
+
|
115
|
+
attr_reader :address, :socket_ctype
|
116
|
+
attr_accessor :topic_filter
|
117
|
+
|
118
|
+
def on_attach(socket)
|
119
|
+
super(socket)
|
120
|
+
socket.subscribe('')
|
121
|
+
end
|
122
|
+
|
123
|
+
def on_readable(socket, messages)
|
124
|
+
if @msg_format == :dripdrop
|
125
|
+
unless messages.length == 2
|
126
|
+
puts "Expected pub/sub message to come in two parts, not #{messages.length}: #{messages.inspect}"
|
127
|
+
return false
|
128
|
+
end
|
129
|
+
topic = messages.shift.copy_out_string
|
130
|
+
if @topic_filter.nil? || topic.match(@topic_filter)
|
131
|
+
body = messages.shift.copy_out_string
|
132
|
+
msg = @recv_cbak.call(DripDrop::Message.decode(body))
|
133
|
+
end
|
121
134
|
else
|
122
|
-
|
135
|
+
super(socket,messages)
|
123
136
|
end
|
124
137
|
end
|
125
|
-
|
138
|
+
end
|
139
|
+
|
140
|
+
class ZMQPubHandler < ZMQBaseHandler
|
141
|
+
include ZMQWritableHandler
|
142
|
+
|
126
143
|
#Sends a message along
|
127
144
|
def send_message(message)
|
145
|
+
dd_message = dd_messagify(message)
|
146
|
+
if dd_message.is_a?(DripDrop::Message)
|
147
|
+
super([dd_message.name, dd_message.encoded])
|
148
|
+
else
|
149
|
+
super(message)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class ZMQPullHandler < ZMQBaseHandler
|
155
|
+
include ZMQReadableHandler
|
156
|
+
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
class ZMQPushHandler < ZMQBaseHandler
|
161
|
+
include ZMQWritableHandler
|
162
|
+
end
|
163
|
+
|
164
|
+
class ZMQXRepHandler < ZMQBaseHandler
|
165
|
+
include ZMQWritableHandler
|
166
|
+
include ZMQReadableHandler
|
167
|
+
|
168
|
+
def initialize(*args)
|
169
|
+
super(*args)
|
170
|
+
end
|
171
|
+
|
172
|
+
def on_readable(socket,messages)
|
173
|
+
if @msg_format == :dripdrop
|
174
|
+
identities = messages[0..-2].map {|m| m.copy_out_string}
|
175
|
+
body = messages.last.copy_out_string
|
176
|
+
message = DripDrop::Message.decode(body)
|
177
|
+
seq = message.head['_dripdrop/x_seq_counter']
|
178
|
+
@recv_cbak.call(identities,seq,message)
|
179
|
+
else
|
180
|
+
super(socket,messages)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def send_message(identities,seq,message)
|
128
185
|
if message.is_a?(DripDrop::Message)
|
129
|
-
|
130
|
-
|
186
|
+
message.head['_dripdrop/x_seq_counter'] = seq
|
187
|
+
super(identities + [message.encoded])
|
131
188
|
else
|
132
|
-
|
189
|
+
super(message)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
class ZMQXReqHandler < ZMQBaseHandler
|
195
|
+
include ZMQWritableHandler
|
196
|
+
include ZMQReadableHandler
|
197
|
+
|
198
|
+
def initialize(*args)
|
199
|
+
super(*args)
|
200
|
+
#Used to keep track of responses
|
201
|
+
@seq_counter = 0
|
202
|
+
@promises = {}
|
203
|
+
|
204
|
+
self.on_recv do |message|
|
205
|
+
seq = message.head['_dripdrop/x_seq_counter']
|
206
|
+
raise "Missing Seq Counter" unless seq
|
207
|
+
promise = @promises.delete(seq)
|
208
|
+
promise.call(message)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def send_message(message,&block)
|
213
|
+
dd_message = dd_messagify(message)
|
214
|
+
if dd_message.is_a?(DripDrop::Message)
|
215
|
+
@seq_counter += 1
|
216
|
+
dd_message.head['_dripdrop/x_seq_counter'] = @seq_counter
|
217
|
+
@promises[@seq_counter] = block if block
|
218
|
+
message = dd_message
|
133
219
|
end
|
220
|
+
super(message)
|
134
221
|
end
|
135
222
|
end
|
136
223
|
end
|
data/lib/dripdrop/message.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bert'
|
3
|
+
require 'json'
|
3
4
|
|
4
5
|
class DripDrop
|
5
6
|
#DripDrop::Message messages are exchanged between all tiers in the architecture
|
@@ -33,6 +34,10 @@ class DripDrop
|
|
33
34
|
BERT.encode(self.to_hash)
|
34
35
|
end
|
35
36
|
|
37
|
+
def encode_json
|
38
|
+
self.to_hash.to_json
|
39
|
+
end
|
40
|
+
|
36
41
|
#Convert the Message to a hash like:
|
37
42
|
#{:name => @name, :head => @head, :body => @body}
|
38
43
|
def to_hash
|
@@ -44,7 +49,7 @@ class DripDrop
|
|
44
49
|
def self.parse(msg)
|
45
50
|
return nil if msg.nil? || msg.empty?
|
46
51
|
#This makes parsing ZMQ messages less painful, even if its ugly here
|
47
|
-
#We check the class name as a string
|
52
|
+
#We check the class name as a string in case we don't have ZMQ loaded
|
48
53
|
if msg.class.to_s == 'ZMQ::Message'
|
49
54
|
msg = msg.copy_out_string
|
50
55
|
return nil if msg.empty?
|
@@ -52,7 +57,17 @@ class DripDrop
|
|
52
57
|
decoded = BERT.decode(msg)
|
53
58
|
self.new(decoded[:name], :head => decoded[:head], :body => decoded[:body])
|
54
59
|
end
|
55
|
-
|
60
|
+
|
61
|
+
def self.decode_json(str)
|
62
|
+
begin
|
63
|
+
json_hash = JSON.parse(str)
|
64
|
+
rescue JSON::ParserError => e
|
65
|
+
puts "Could not parse msg '#{str}': #{e.message}"
|
66
|
+
return nil
|
67
|
+
end
|
68
|
+
self.new(json_hash['name'], :head => json_hash['head'], :body => json_hash['body'])
|
69
|
+
end
|
70
|
+
|
56
71
|
private
|
57
72
|
|
58
73
|
#Sanitize a string so it'll look good for JSON, BERT, and MongoDB
|
data/lib/dripdrop/node.rb
CHANGED
@@ -7,6 +7,7 @@ require 'uri'
|
|
7
7
|
require 'dripdrop/message'
|
8
8
|
require 'dripdrop/handlers/zeromq'
|
9
9
|
require 'dripdrop/handlers/websockets'
|
10
|
+
require 'dripdrop/handlers/http'
|
10
11
|
|
11
12
|
class DripDrop
|
12
13
|
class Node
|
@@ -19,15 +20,43 @@ class DripDrop
|
|
19
20
|
@recipients_for = {}
|
20
21
|
@handler_default_opts = {:debug => @debug}
|
21
22
|
@zm_reactor = nil
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
@block = block
|
24
|
+
@thread = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def start
|
28
|
+
@thread = Thread.new do
|
29
|
+
EM.run do
|
30
|
+
ZM::Reactor.new(:my_reactor).run do |zm_reactor|
|
31
|
+
@zm_reactor = zm_reactor
|
32
|
+
self.instance_eval(&@block)
|
33
|
+
end
|
27
34
|
end
|
28
35
|
end
|
29
36
|
end
|
37
|
+
|
38
|
+
def join
|
39
|
+
if @thread
|
40
|
+
@thread.join
|
41
|
+
else
|
42
|
+
raise "Can't join on a node that isn't yet started"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
#Blocking version of start, equivalent to +start+ then +join+
|
47
|
+
def start!
|
48
|
+
self.start
|
49
|
+
self.join
|
50
|
+
end
|
51
|
+
|
52
|
+
def stop
|
53
|
+
@zm_reactor.stop
|
54
|
+
EM.stop
|
55
|
+
end
|
56
|
+
|
57
|
+
#TODO: All these need to be majorly DRYed up
|
30
58
|
|
59
|
+
# Creates a ZMQ::SUB type socket. Can only receive messages via +on_recv+
|
31
60
|
def zmq_subscribe(address,socket_ctype,opts={},&block)
|
32
61
|
zm_addr = str_to_zm_address(address)
|
33
62
|
h_opts = handler_opts_given(opts)
|
@@ -36,6 +65,7 @@ class DripDrop
|
|
36
65
|
handler
|
37
66
|
end
|
38
67
|
|
68
|
+
# Creates a ZMQ::PUB type socket, can only send messages via +send_message+
|
39
69
|
def zmq_publish(address,socket_ctype,opts={})
|
40
70
|
zm_addr = str_to_zm_address(address)
|
41
71
|
h_opts = handler_opts_given(opts)
|
@@ -44,6 +74,7 @@ class DripDrop
|
|
44
74
|
handler
|
45
75
|
end
|
46
76
|
|
77
|
+
# Creates a ZMQ::PULL type socket. Can only receive messages via +on_recv+
|
47
78
|
def zmq_pull(address,socket_ctype,opts={},&block)
|
48
79
|
zm_addr = str_to_zm_address(address)
|
49
80
|
h_opts = handler_opts_given(opts)
|
@@ -52,6 +83,7 @@ class DripDrop
|
|
52
83
|
handler
|
53
84
|
end
|
54
85
|
|
86
|
+
# Creates a ZMQ::PUSH type socket, can only send messages via +send_message+
|
55
87
|
def zmq_push(address,socket_ctype,opts={})
|
56
88
|
zm_addr = str_to_zm_address(address)
|
57
89
|
h_opts = handler_opts_given(opts)
|
@@ -60,12 +92,54 @@ class DripDrop
|
|
60
92
|
handler
|
61
93
|
end
|
62
94
|
|
95
|
+
# Creates a ZMQ::XREP type socket, both sends and receivesc XREP sockets are extremely
|
96
|
+
# powerful, so their functionality is currently limited. XREP sockets in DripDrop can reply
|
97
|
+
# to the original source of the message.
|
98
|
+
#
|
99
|
+
# Receiving with XREP sockets in DripDrop is different than other types of sockets, on_recv
|
100
|
+
# passes 3 arguments to its callback, +identities+, +seq+, and +message+. Identities is the
|
101
|
+
# socket identity, seq is the sequence number of the message (all messages received at the socket
|
102
|
+
# get a monotonically incrementing +seq+, and +message+ is the message itself.
|
103
|
+
#
|
104
|
+
# To reply from an xrep handler, be sure to call send messages with the same +identities+ and +seq+
|
105
|
+
# arguments that +on_recv+ had. So, send_message takes +identities+, +seq+, and +message+.
|
106
|
+
def zmq_xrep(address,socket_ctype,opts={})
|
107
|
+
zm_addr = str_to_zm_address(address)
|
108
|
+
h_opts = handler_opts_given(opts)
|
109
|
+
handler = DripDrop::ZMQXRepHandler.new(zm_addr,@zm_reactor,socket_ctype,h_opts)
|
110
|
+
@zm_reactor.xrep_socket(handler)
|
111
|
+
handler
|
112
|
+
end
|
113
|
+
|
114
|
+
# See the documentation for +zmq_xrep+ for more info
|
115
|
+
def zmq_xreq(address,socket_ctype,opts={})
|
116
|
+
zm_addr = str_to_zm_address(address)
|
117
|
+
h_opts = handler_opts_given(opts)
|
118
|
+
handler = DripDrop::ZMQXReqHandler.new(zm_addr,@zm_reactor,socket_ctype,h_opts)
|
119
|
+
@zm_reactor.xreq_socket(handler)
|
120
|
+
handler
|
121
|
+
end
|
122
|
+
|
63
123
|
def websocket(address,opts={},&block)
|
64
124
|
uri = URI.parse(address)
|
65
125
|
h_opts = handler_opts_given(opts)
|
66
126
|
handler = DripDrop::WebSocketHandler.new(uri,h_opts)
|
67
127
|
handler
|
68
128
|
end
|
129
|
+
|
130
|
+
def http_server(address,opts={},&block)
|
131
|
+
uri = URI.parse(address)
|
132
|
+
h_opts = handler_opts_given(opts)
|
133
|
+
handler = DripDrop::HTTPServerHandler.new(uri, h_opts,&block)
|
134
|
+
handler
|
135
|
+
end
|
136
|
+
|
137
|
+
def http_client(address,opts={})
|
138
|
+
uri = URI.parse(address)
|
139
|
+
h_opts = handler_opts_given(opts)
|
140
|
+
handler = DripDrop::HTTPClientHandler.new(uri, h_opts)
|
141
|
+
handler
|
142
|
+
end
|
69
143
|
|
70
144
|
def send_internal(dest,data)
|
71
145
|
return false unless @recipients_for[dest]
|
data/lib/dripdrop.rb
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
require 'dripdrop/node'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "zmq push/pull" do
|
4
|
+
def pp_send_messages(to_send)
|
5
|
+
responses = []
|
6
|
+
push = nil
|
7
|
+
pull = nil
|
8
|
+
|
9
|
+
@ddn = DripDrop::Node.new do
|
10
|
+
addr = rand_addr
|
11
|
+
|
12
|
+
push = zmq_push(addr, :bind)
|
13
|
+
|
14
|
+
pull1 = zmq_pull(addr, :connect)
|
15
|
+
pull2 = zmq_pull(addr, :connect)
|
16
|
+
pull = [pull1, pull2]
|
17
|
+
|
18
|
+
pull1.on_recv do |message|
|
19
|
+
message.head['recv_sock'] = 1
|
20
|
+
responses << message
|
21
|
+
end
|
22
|
+
pull2.on_recv do |message|
|
23
|
+
message.head['recv_sock'] = 2
|
24
|
+
responses << message
|
25
|
+
end
|
26
|
+
|
27
|
+
to_send.each {|message| push.send_message(message)}
|
28
|
+
end
|
29
|
+
|
30
|
+
@ddn.start
|
31
|
+
sleep 0.1
|
32
|
+
@ddn.stop
|
33
|
+
|
34
|
+
{:responses => responses, :handlers => { :push => push, :pull => [pull] }}
|
35
|
+
end
|
36
|
+
describe "basic sending and receiving" do
|
37
|
+
before(:all) do
|
38
|
+
@sent = []
|
39
|
+
10.times {|i| @sent << DripDrop::Message.new("test-#{i}")}
|
40
|
+
pp_info = pp_send_messages(@sent)
|
41
|
+
@responses = pp_info[:responses]
|
42
|
+
@push_handler = pp_info[:handlers][:push]
|
43
|
+
@pull_handlers = pp_info[:handlers][:pull]
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should receive all sent messages in order" do
|
47
|
+
@sent.zip(@responses).each do |sent,response|
|
48
|
+
sent.name.should == response.name
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should receive messages on both pull sockets" do
|
53
|
+
@responses.map {|r| r.head['recv_sock']}.uniq.sort.should == [1,2]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|