dripdrop 0.9.10 → 0.10.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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