dripdrop 0.9.10 → 0.10.0.beta1

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.
@@ -12,16 +12,28 @@ class DripDrop
12
12
  end
13
13
 
14
14
  class ZMQBaseHandler < BaseHandler
15
- attr_accessor :connection
15
+ attr_reader :connection
16
16
 
17
17
  def initialize(opts={})
18
18
  @opts = opts
19
19
  @connection = nil
20
20
  @msg_format = opts[:msg_format] || :dripdrop
21
+ @message_class = @opts[:message_class] || DripDrop.default_message_class
22
+ end
23
+
24
+ def add_connection(connection)
25
+ @connection = connection
26
+ end
27
+
28
+ def read_connection
29
+ @connection
30
+ end
31
+
32
+ def write_connection
33
+ @connection
21
34
  end
22
35
 
23
36
  def on_recv(msg_format=:dripdrop,&block)
24
- @msg_format = msg_format
25
37
  @recv_cbak = block
26
38
  self
27
39
  end
@@ -41,27 +53,13 @@ class DripDrop
41
53
  @send_queue_enabled = false
42
54
  end
43
55
 
44
- def on_writable(socket)
56
+ def on_writable(conn)
45
57
  unless @send_queue.empty?
46
58
  message = @send_queue.shift
47
59
 
48
- num_parts = message.length
49
- message.each_with_index do |part,i|
50
- # Set the multi-part flag unless this is the last message
51
- flags = (i + 1 < num_parts ? ZMQ::SNDMORE : 0) | ZMQ::NOBLOCK
52
-
53
- if part.class == ZMQ::Message
54
- socket.send(part, flags)
55
- else
56
- if part.class == String
57
- socket.send_string(part, flags)
58
- else
59
- $stderr.write "Can only send Strings, not #{part.class}: #{part}" if @debug
60
- end
61
- end
62
- end
60
+ conn.send_msg(*message)
63
61
  else
64
- @connection.deregister_writable if @send_queue_enabled
62
+ conn.deregister_writable if @send_queue_enabled
65
63
  end
66
64
  end
67
65
 
@@ -78,27 +76,18 @@ class DripDrop
78
76
  else
79
77
  @send_queue.push([message])
80
78
  end
81
-
82
79
 
83
- if @send_queue_enabled
84
- @connection.register_writable
80
+ self.write_connection.register_writable if @send_queue_enabled
85
81
 
86
- # Not sure why this is necessary, this is likely a bug in em-zeromq
87
- on_writable(@connection.socket)
88
- else
89
- on_writable(@connection.socket)
90
- end
82
+ EM::next_tick {
83
+ on_writable(self.write_connection)
84
+ }
91
85
  end
92
86
  end
93
87
 
94
88
  module ZMQReadableHandler
95
89
  attr_accessor :message_class
96
-
97
- def initialize(*args)
98
- super(*args)
99
- @message_class = @opts[:message_class] || DripDrop.default_message_class
100
- end
101
-
90
+
102
91
  def decode_message(msg)
103
92
  @message_class.decode(msg)
104
93
  end
@@ -109,7 +98,10 @@ class DripDrop
109
98
  when :raw
110
99
  @recv_cbak.call(messages)
111
100
  when :dripdrop
112
- raise "Expected message in one part" if messages.length > 1
101
+ if messages.length > 1
102
+ raise "Expected message in one part for #{self.inspect}, got #{messages.map(&:copy_out_string)}"
103
+ end
104
+
113
105
  body = messages.shift.copy_out_string
114
106
  @recv_cbak.call(decode_message(body))
115
107
  else
@@ -119,10 +111,6 @@ class DripDrop
119
111
  handle_error(e)
120
112
  end
121
113
  end
122
-
123
- def post_setup
124
- @connection.register_readable
125
- end
126
114
  end
127
115
 
128
116
  class ZMQSubHandler < ZMQBaseHandler
@@ -136,17 +124,21 @@ class DripDrop
136
124
  end
137
125
 
138
126
  def on_readable(socket, messages)
139
- if @msg_format == :dripdrop
140
- unless messages.length == 2
141
- return false
142
- end
143
- topic = messages.shift.copy_out_string
144
- if @topic_filter.nil? || topic.match(@topic_filter)
145
- body = messages.shift.copy_out_string
146
- @recv_cbak.call(decode_message(body))
127
+ begin
128
+ if @msg_format == :dripdrop
129
+ unless messages.length == 2
130
+ return false
131
+ end
132
+ topic = messages.shift.copy_out_string
133
+ if @topic_filter.nil? || topic.match(@topic_filter)
134
+ body = messages.shift.copy_out_string
135
+ @recv_cbak.call(decode_message(body))
136
+ end
137
+ else
138
+ super(socket,messages)
147
139
  end
148
- else
149
- super(socket,messages)
140
+ rescue StandardError => e
141
+ handle_error(e)
150
142
  end
151
143
  end
152
144
 
@@ -161,7 +153,7 @@ class DripDrop
161
153
 
162
154
  #Sends a message along
163
155
  def send_message(message)
164
- dd_message = dd_messagify(message)
156
+ dd_message = dd_messagify(message,@message_class)
165
157
  if dd_message.is_a?(DripDrop::Message)
166
158
  super([dd_message.name, dd_message.encoded])
167
159
  else
@@ -172,8 +164,6 @@ class DripDrop
172
164
 
173
165
  class ZMQPullHandler < ZMQBaseHandler
174
166
  include ZMQReadableHandler
175
-
176
-
177
167
  end
178
168
 
179
169
  class ZMQPushHandler < ZMQBaseHandler
@@ -190,12 +180,43 @@ class DripDrop
190
180
 
191
181
  def on_readable(socket,messages)
192
182
  if @msg_format == :dripdrop
193
- identities = messages[0..-2].map {|m| m.copy_out_string}
194
- body = messages.last.copy_out_string
195
- message = decode_message(body)
196
- seq = message.head[SEQ_CTR_KEY]
197
- response = ZMQXRepHandler::Response.new(self, identities,seq)
198
- @recv_cbak.call(message,response)
183
+ begin
184
+ if messages.length < 3
185
+ raise "Expected message in at least 3 parts, got #{messages.map(&:copy_out_string).inspect}"
186
+ end
187
+
188
+ message_strings = messages.map(&:copy_out_string)
189
+
190
+ # parse the message into identities, delimiter and body
191
+ identities = []
192
+ delimiter = nil
193
+ body = nil
194
+ # It's an identitiy if it isn't an empty string
195
+ # Once we hit the delimiter, we know the rest after is the body
196
+ message_strings.each_with_index do |ms,i|
197
+ unless ms.empty?
198
+ identities << ms
199
+ else
200
+ delimiter = ms
201
+
202
+ unless message_strings.length == i+2
203
+ raise "Expected body in 1 part got '#{message_strings.inspect}'"
204
+ end
205
+
206
+ body = message_strings[i+1]
207
+ break
208
+ end
209
+ end
210
+
211
+ raise "Received xreq message with no body!" unless body
212
+ message = decode_message(body)
213
+ raise "Received nil message! #{body}" unless message
214
+ seq = message.head[SEQ_CTR_KEY]
215
+ response = ZMQXRepHandler::Response.new(self,identities,seq,@message_class)
216
+ @recv_cbak.call(message,response) if @recv_cbak
217
+ rescue StandardError => e
218
+ handle_error(e)
219
+ end
199
220
  else
200
221
  super(socket,messages)
201
222
  end
@@ -217,14 +238,15 @@ class DripDrop
217
238
  class ZMQXRepHandler::Response < ZMQBaseHandler
218
239
  attr_accessor :xrep, :seq, :identities
219
240
 
220
- def initialize(xrep,identities,seq)
241
+ def initialize(xrep,identities,seq,message_class)
221
242
  @xrep = xrep
222
243
  @seq = seq
223
- @identities = identities
244
+ @identities = identities
245
+ @message_class = message_class
224
246
  end
225
247
 
226
248
  def send_message(message)
227
- dd_message = dd_messagify(message)
249
+ dd_message = dd_messagify(message,@message_class)
228
250
  @xrep.send_message(dd_message,identities,seq)
229
251
  end
230
252
  end
@@ -240,22 +262,30 @@ class DripDrop
240
262
  @promises = {}
241
263
 
242
264
  self.on_recv do |message|
243
- seq = message.head[SEQ_CTR_KEY]
244
- raise "Missing Seq Counter" unless seq
245
- promise = @promises.delete(seq)
246
- promise.call(message) if promise
265
+ begin
266
+ seq = message.head[SEQ_CTR_KEY]
267
+ raise "Missing Seq Counter" unless seq
268
+ promise = @promises.delete(seq)
269
+ promise.call(message) if promise
270
+ rescue StandardError => e
271
+ handle_error(e)
272
+ end
247
273
  end
248
274
  end
249
275
 
250
276
  def send_message(message,&block)
251
- dd_message = dd_messagify(message)
252
- if dd_message.is_a?(DripDrop::Message)
253
- @seq_counter += 1
254
- dd_message.head[SEQ_CTR_KEY] = @seq_counter
255
- @promises[@seq_counter] = block if block
256
- message = dd_message
277
+ begin
278
+ dd_message = dd_messagify(message,@message_class)
279
+ if dd_message.is_a?(DripDrop::Message)
280
+ @seq_counter += 1
281
+ dd_message.head[SEQ_CTR_KEY] = @seq_counter
282
+ @promises[@seq_counter] = block if block
283
+ message = dd_message
284
+ end
285
+ rescue StandardError => e
286
+ handle_error(e)
257
287
  end
258
- super(message)
288
+ super(['', message.encoded])
259
289
  end
260
290
 
261
291
  def on_readable(socket, messages)
@@ -1,6 +1,6 @@
1
1
  require 'rubygems'
2
2
 
3
- if PLATFORM == 'java'
3
+ if RUBY_PLATFORM == 'java'
4
4
  require 'json'
5
5
  else
6
6
  require 'yajl'
@@ -82,7 +82,7 @@ class DripDrop
82
82
 
83
83
  klass = (hash['head'] && hash['head']['message_class']) ? constantize(hash['head']['message_class']) : nil
84
84
  if klass && (!(klass == self) && !self.subclasses.include?(klass))
85
- raise DripDrop::WrongMessageClassError, "Wrong message class '#{klass}', expected '#{self}'"
85
+ raise DripDrop::WrongMessageClassError, "Wrong message class '#{klass}', expected '#{self}' for message #{hash.inspect}"
86
86
  end
87
87
 
88
88
  klass ? klass.from_hash(hash) : self.from_hash(hash)
data/lib/dripdrop/node.rb CHANGED
@@ -9,28 +9,29 @@ require 'dripdrop/message'
9
9
  require 'dripdrop/node/nodelet'
10
10
  require 'dripdrop/handlers/base'
11
11
  require 'dripdrop/handlers/zeromq'
12
- require 'dripdrop/handlers/websockets'
12
+ require 'dripdrop/handlers/websocket_server'
13
+ require 'dripdrop/handlers/mongrel2'
13
14
  require 'dripdrop/handlers/http_client'
14
15
 
15
16
  begin
16
17
  require 'dripdrop/handlers/http_server'
17
18
  rescue LoadError => e
18
- $stderr.write "Could not load http server, your probably don't have eventmachine_httpserver installed\n"
19
- $stderr.write e.message + "\n"
20
- $stderr.write e.backtrace.join("\t\n")
19
+ $stderr.write "Warning, could not load http server, your probably don't have eventmachine_httpserver installed\n"
21
20
  end
22
21
 
23
22
  class DripDrop
24
23
  class Node
25
24
  ZCTX = ZMQ::Context.new 1
26
25
 
27
- attr_reader :zm_reactor, :routing, :nodelets
26
+ attr_reader :zm_reactor, :routing, :nodelets, :run_list
28
27
  attr_accessor :debug
29
28
 
30
29
  def initialize(opts={},&block)
31
30
  @block = block
32
31
  @thread = nil # Thread containing the reactors
33
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
34
35
  @debug = opts[:debug]
35
36
  @recipients_for = {}
36
37
  @handler_default_opts = {:debug => @debug}
@@ -42,7 +43,7 @@ class DripDrop
42
43
  # This is non-blocking.
43
44
  def start
44
45
  @thread = Thread.new do
45
- EM.error_handler {|e| self.error_handler e}
46
+ EM.error_handler {|e| self.class.error_handler e}
46
47
  EM.run { action }
47
48
  end
48
49
  end
@@ -146,8 +147,26 @@ class DripDrop
146
147
  #
147
148
  # If you specify a block, Nodelet#action will be ignored and the block
148
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
149
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
+
150
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
+
151
170
  if block
152
171
  block.call(nlet)
153
172
  else
@@ -156,6 +175,10 @@ class DripDrop
156
175
  nlet
157
176
  end
158
177
 
178
+ def zmq_m2(addresses, opts={}, &block)
179
+ zmq_handler(DripDrop::Mongrel2Handler, [ZMQ::PULL, ZMQ::PUB], addresses, [:connect, :connect], opts)
180
+ end
181
+
159
182
  # Creates a ZMQ::SUB type socket. Can only receive messages via +on_recv+.
160
183
  # zmq_subscribe sockets have a +topic_filter+ option, which restricts which
161
184
  # messages they can receive. It takes a regexp as an option.
@@ -199,11 +222,11 @@ class DripDrop
199
222
  zmq_handler(DripDrop::ZMQXReqHandler,ZMQ::XREQ,address,socket_ctype,opts)
200
223
  end
201
224
 
202
- # Binds an EM websocket connection to +address+. takes blocks for
225
+ # Binds an EM websocket server connection to +address+. takes blocks for
203
226
  # +on_open+, +on_recv+, +on_close+ and +on_error+.
204
227
  #
205
228
  # For example +on_recv+ could be used to echo incoming messages thusly:
206
- # websocket(addr).on_open {|conn|
229
+ # websocket_server(addr).on_open {|conn|
207
230
  # ws.send_message(:name => 'ws_open_ack')
208
231
  # }.on_recv {|msg,conn|
209
232
  # conn.send(msg)
@@ -213,12 +236,18 @@ class DripDrop
213
236
  #
214
237
  # The +ws+ object that's passed into the handlers is not
215
238
  # the +DripDrop::WebSocketHandler+ object, but an em-websocket object.
216
- def websocket(address,opts={})
239
+ def websocket_server(address,opts={})
217
240
  uri = URI.parse(address)
218
241
  h_opts = handler_opts_given(opts)
219
242
  DripDrop::WebSocketHandler.new(uri,h_opts)
220
243
  end
221
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
+
222
251
  # Starts a new Thin HTTP server listening on address.
223
252
  # Can have an +on_recv+ handler that gets passed +msg+ and +response+ args.
224
253
  # http_server(addr) {|msg,response| response.send_message(msg)}
@@ -285,22 +314,32 @@ class DripDrop
285
314
  private
286
315
 
287
316
  def zmq_handler(klass, sock_type, address, socket_ctype, opts={})
288
- addr_uri = URI.parse(address)
289
-
290
- host_str = addr_uri.host
291
- #if addr_uri.scheme == 'tcp'
292
- # host = Resolv.getaddresses(addr_uri.host).first
293
- # host_addr = Resolv.getaddresses('localhost').map {|a| IPAddr.new(a)}.find {|a| a.ipv4?}
294
- # host_str = host_addr.ipv6? ? "[#{host_addr.to_s}]" : host_addr.to_s
295
- #else
296
- # host_str = addr_uri.host
297
- #end
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
298
342
 
299
- z_addr = "#{addr_uri.scheme}://#{host_str}:#{addr_uri.port.to_i}"
300
- h_opts = handler_opts_given(opts)
301
- connection = EM::ZeroMQ.create @zctx, sock_type, socket_ctype, address, klass.new(h_opts)
302
- handler = connection.handler
303
- handler.connection = connection
304
343
  handler.post_setup
305
344
  handler
306
345
  end
@@ -17,7 +17,7 @@ describe "websockets" do
17
17
  @node = run_reactor(2) do
18
18
  addr = rand_addr('ws')
19
19
 
20
- server = websocket(addr)
20
+ server = websocket_server(addr)
21
21
  server.on_open do |conn|
22
22
  conn.send_message(open_message)
23
23
  seen_signatures << conn.signature
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ m2_req = '34f9cfef-dc52-4b7f-b197-098765432112 16 /handlertest 537:{"PATH":"/handlertest","accept-language":"en-us,en;q=0.5","user-agent":"Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20110207 Gentoo Firefox/3.6.13","host":"it.wishdev.org:6767","accept-charset":"ISO-8859-1,utf-8;q=0.7,*;q=0.7","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","keep-alive":"115","x-forwarded-for":"127.0.0.1","cache-control":"max-age=0","connection":"keep-alive","accept-encoding":"gzip,deflate","METHOD":"GET","VERSION":"HTTP/1.1","URI":"/handlertest","PATTERN":"/handlertest"},0:,'
4
+ dd_resp = "34f9cfef-dc52-4b7f-b197-098765432112 2:16, HTTP/1.1 200 OK\r\nContent-Length: 19\r\n\r\nHello from DripDrop"
5
+
6
+ body = ""
7
+ conn_id = "16"
8
+ headers = {"PATH"=>"/handlertest",
9
+ "accept-language"=>"en-us,en;q=0.5",
10
+ "user-agent"=>
11
+ "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20110207 Gentoo Firefox/3.6.13",
12
+ "host"=>"it.wishdev.org:6767",
13
+ "accept-charset"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.7",
14
+ "accept"=>"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
15
+ "keep-alive"=>"115",
16
+ "x-forwarded-for"=>"127.0.0.1",
17
+ "cache-control"=>"max-age=0",
18
+ "connection"=>"keep-alive",
19
+ "accept-encoding"=>"gzip,deflate",
20
+ "METHOD"=>"GET",
21
+ "VERSION"=>"HTTP/1.1",
22
+ "URI"=>"/handlertest",
23
+ "PATTERN"=>"/handlertest"}
24
+ path = "/handlertest"
25
+ sender = "34f9cfef-dc52-4b7f-b197-098765432112"
26
+
27
+ describe "zmq m2" do
28
+ def pp_send_messages(to_send)
29
+ responses = []
30
+ requests = []
31
+
32
+ @node = run_reactor(2) do
33
+ addr = rand_addr
34
+ addr2 = rand_addr
35
+
36
+ m2_send = zmq_push(addr, :bind, {:msg_format => :raw})
37
+ m2_recv = zmq_subscribe(addr2, :bind, {:msg_format => :raw})
38
+
39
+ dd = zmq_m2([addr, addr2])
40
+
41
+ dd.on_recv do |req|
42
+ requests << req
43
+ dd.reply_http req, "Hello from DripDrop"
44
+ end
45
+
46
+ m2_recv.on_recv do |msg|
47
+ responses << msg
48
+ end
49
+
50
+ sleep 1
51
+
52
+ to_send.each {|message| m2_send.send_message(message)}
53
+ end
54
+
55
+ {:responses => responses, :requests => requests}
56
+ end
57
+ describe "basic sending and receiving" do
58
+ before(:all) do
59
+ @sent = [m2_req]
60
+ pp_info = pp_send_messages(@sent)
61
+ @responses = pp_info[:responses]
62
+ @requests = pp_info[:requests]
63
+ end
64
+
65
+ it "should parse a request" do
66
+ @requests[0].body.should == body
67
+ @requests[0].conn_id.should == conn_id
68
+ @requests[0].headers.should == headers
69
+ @requests[0].path.should == path
70
+ @requests[0].sender.should == sender
71
+ end
72
+
73
+ it "should respond to an http request" do
74
+ @responses[0][0].copy_out_string.should == dd_resp
75
+ end
76
+ end
77
+ end
@@ -7,7 +7,7 @@ describe "zmq push/pull" do
7
7
  push = nil
8
8
  pull = nil
9
9
 
10
- @node = run_reactor do
10
+ @node = run_reactor(2) do
11
11
  addr = rand_addr
12
12
 
13
13
  push = zmq_push(addr, :bind)
@@ -25,6 +25,8 @@ describe "zmq push/pull" do
25
25
  responses << message
26
26
  end
27
27
 
28
+ sleep 1
29
+
28
30
  to_send.each {|message| push.send_message(message)}
29
31
  end
30
32
 
data/spec/node_spec.rb CHANGED
@@ -72,10 +72,9 @@ describe DripDrop::Node do
72
72
  class TestException < StandardError; end
73
73
 
74
74
  it "should rescue exceptions in the EM reactor" do
75
- pending "Not sure if this feature is a good idea"
76
75
  expectations = an_instance_of(TestException)
77
76
  reactor = run_reactor do
78
- self.should_receive(:error_handler).with(expectations)
77
+ self.class.should_receive(:error_handler).with(expectations)
79
78
  EM.next_tick do
80
79
  raise TestException, "foo"
81
80
  end