dripdrop 0.10.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.document +5 -0
  2. data/.gitignore +31 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +20 -0
  5. data/README.md +164 -0
  6. data/Rakefile +16 -0
  7. data/dripdrop.gemspec +37 -0
  8. data/example/agent_test.rb +14 -0
  9. data/example/combined.rb +33 -0
  10. data/example/complex/README +22 -0
  11. data/example/complex/client.rb +20 -0
  12. data/example/complex/server.rb +102 -0
  13. data/example/complex/service.rb +8 -0
  14. data/example/complex/websocket.rb +442 -0
  15. data/example/http.rb +23 -0
  16. data/example/pubsub.rb +29 -0
  17. data/example/pushpull.rb +21 -0
  18. data/example/subclass.rb +54 -0
  19. data/example/xreq_xrep.rb +24 -0
  20. data/js/dripdrop.html +186 -0
  21. data/js/dripdrop.js +107 -0
  22. data/js/qunit.css +155 -0
  23. data/js/qunit.js +1261 -0
  24. data/lib/dripdrop.rb +2 -0
  25. data/lib/dripdrop/agent.rb +40 -0
  26. data/lib/dripdrop/handlers/base.rb +42 -0
  27. data/lib/dripdrop/handlers/http_client.rb +38 -0
  28. data/lib/dripdrop/handlers/http_server.rb +59 -0
  29. data/lib/dripdrop/handlers/mongrel2.rb +163 -0
  30. data/lib/dripdrop/handlers/websocket_server.rb +86 -0
  31. data/lib/dripdrop/handlers/zeromq.rb +300 -0
  32. data/lib/dripdrop/message.rb +190 -0
  33. data/lib/dripdrop/node.rb +351 -0
  34. data/lib/dripdrop/node/nodelet.rb +35 -0
  35. data/lib/dripdrop/version.rb +3 -0
  36. data/spec/gimite-websocket.rb +442 -0
  37. data/spec/message_spec.rb +94 -0
  38. data/spec/node/http_spec.rb +77 -0
  39. data/spec/node/nodelet_spec.rb +67 -0
  40. data/spec/node/routing_spec.rb +67 -0
  41. data/spec/node/websocket_spec.rb +98 -0
  42. data/spec/node/zmq_m2_spec.rb +77 -0
  43. data/spec/node/zmq_pushpull_spec.rb +54 -0
  44. data/spec/node/zmq_xrepxreq_spec.rb +108 -0
  45. data/spec/node_spec.rb +85 -0
  46. data/spec/spec_helper.rb +20 -0
  47. metadata +167 -0
data/lib/dripdrop.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'dripdrop/message'
2
+ require 'dripdrop/node'
@@ -0,0 +1,40 @@
1
+ require 'dripdrop/message'
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'
11
+
12
+ class DripDrop
13
+ #The Agent class is a simple ZMQ Pub client. It uses DripDrop::Message messages
14
+ class Agent
15
+ attr_reader :address, :context, :socket
16
+
17
+ #address should be a string like tcp://127.0.0.1
18
+ def initialize(sock_type,address,sock_ctype)
19
+ @context = ZMQ::Context.new(1)
20
+ @socket = @context.socket(sock_type)
21
+ if sock_ctype == :bind
22
+ @socket.bind(address)
23
+ else
24
+ @socket.connect(address)
25
+ end
26
+ end
27
+
28
+ #Sends a DripDrop::Message to the socket
29
+ def send_message(name,body,head={})
30
+ message = DripDrop::Message.new(name,:body => body, :head => head).encoded
31
+ if ZMQGEM == :rbzmq
32
+ @socket.send name, ZMQ::SNDMORE
33
+ @socket.send message
34
+ else
35
+ @socket.send_string name, ZMQ::SNDMORE
36
+ @socket.send_string message
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,42 @@
1
+ class DripDrop
2
+ class BaseHandler
3
+
4
+ def on_error(&block)
5
+ @err_cbak = block
6
+ end
7
+
8
+ def handle_error(exception,*extra)
9
+ if @err_cbak
10
+ begin
11
+ @err_cbak.call(exception,*extra)
12
+ rescue StandardError => e
13
+ print_exception(e)
14
+ end
15
+ else
16
+ print_exception(exception)
17
+ end
18
+ end
19
+
20
+ def print_exception(exception)
21
+ if exception.is_a?(Exception)
22
+ $stderr.write exception.message
23
+ $stderr.write exception.backtrace.join("\t\n")
24
+ else
25
+ $stderr.write "Expected an exception, got: #{exception.inspect}"
26
+ end
27
+ end
28
+
29
+ private
30
+ # Normalize Hash objs and DripDrop::Message objs into DripDrop::Message objs
31
+ def dd_messagify(message,klass=DripDrop::Message)
32
+ if message.is_a?(Hash)
33
+ return klass.from_hash(message)
34
+ elsif message.is_a?(DripDrop::Message)
35
+ return message
36
+ else
37
+ return message
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,38 @@
1
+ class DripDrop
2
+ class HTTPClientHandler < BaseHandler
3
+ attr_reader :address, :opts
4
+
5
+ def initialize(uri, opts={})
6
+ @uri = uri
7
+ @address = @uri.to_s
8
+ @opts = opts
9
+ @message_class = @opts[:message_class] || DripDrop.default_message_class
10
+ end
11
+
12
+ def send_message(message,&block)
13
+ dd_message = dd_messagify(message)
14
+ if dd_message.is_a?(DripDrop::Message)
15
+ uri_path = @uri.path.empty? ? '/' : @uri.path
16
+
17
+ req = EM::Protocols::HttpClient.request(
18
+ :host => @uri.host, :port => @uri.port,
19
+ :request => uri_path, :verb => 'POST',
20
+ :contenttype => 'application/json',
21
+ :content => dd_message.encode_json
22
+ )
23
+ req.callback do |response|
24
+ begin
25
+ # Hack to fix evma http
26
+ response[:content] =~ /(\{.*\})/
27
+ fixed_body = $1
28
+ block.call(@message_class.decode(fixed_body)) if block
29
+ rescue StandardError => e
30
+ handle_error(e)
31
+ end
32
+ end
33
+ else
34
+ raise "Unsupported message type '#{dd_message.class}'"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,59 @@
1
+ require 'evma_httpserver'
2
+
3
+ class DripDrop
4
+ class HTTPServerHandlerResponse < BaseHandler
5
+ attr_reader :em_response, :message_class
6
+ def initialize(em_response)
7
+ @em_response = em_response
8
+ end
9
+
10
+ def send_message(message)
11
+ message = dd_messagify(message)
12
+ @em_response.status = 200
13
+ @em_response.content = message.json_encoded
14
+ @em_response.send_response
15
+ end
16
+ end
17
+
18
+ class HTTPEMServer < EM::Connection
19
+ include EM::HttpServer
20
+
21
+ def initialize(dd_handler)
22
+ @dd_handler = dd_handler
23
+ end
24
+
25
+ def post_init
26
+ super
27
+ no_environment_strings
28
+ end
29
+
30
+ def process_http_request
31
+ begin
32
+ message = @dd_handler.message_class.decode_json(@http_post_content)
33
+ response = EM::DelegatedHttpResponse.new(self)
34
+ dd_response = HTTPServerHandlerResponse.new(response)
35
+ @dd_handler.recv_cbak.call(message, dd_response) if @dd_handler.recv_cbak
36
+ rescue StandardError => e
37
+ handler_error(e)
38
+ end
39
+ end
40
+ end
41
+
42
+ class HTTPServerHandler < BaseHandler
43
+ attr_reader :address, :opts, :message_class, :uri, :recv_cbak
44
+
45
+ def initialize(uri,opts={})
46
+ @uri = uri
47
+ @uri_path = @uri.path.empty? ? '/' : @uri.path
48
+ @address = uri.to_s
49
+ @opts = opts
50
+ @message_class = @opts[:message_class] || DripDrop.default_message_class
51
+ end
52
+
53
+ def on_recv(msg_format=:dripdrop_json,&block)
54
+ @recv_cbak = block
55
+ @conn = EM.start_server(@uri.host, @uri.port, HTTPEMServer, self)
56
+ self
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,163 @@
1
+ =begin
2
+ Large portion of at least the concepts (and plenty of the code) used here come from m2r
3
+
4
+ https://github.com/perplexes/m2r
5
+
6
+ Under the following license
7
+
8
+ Copyright (c) 2009 Pradeep Elankumaran
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining
11
+ a copy of this software and associated documentation files (the
12
+ "Software"), to deal in the Software without restriction, including
13
+ without limitation the rights to use, copy, modify, merge, publish,
14
+ distribute, sublicense, and/or sell copies of the Software, and to
15
+ permit persons to whom the Software is furnished to do so, subject to
16
+ the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be
19
+ included in all copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+ =end
29
+
30
+ class DripDrop
31
+ class Mongrel2Handler < ZMQBaseHandler
32
+ include ZMQWritableHandler
33
+ include ZMQReadableHandler
34
+ attr_accessor :uuid
35
+
36
+ def initialize(*args)
37
+ super(*args)
38
+ @connections = []
39
+ self.uuid = @opts[:uuid]
40
+ end
41
+
42
+ def add_connection(connection)
43
+ @connections << connection
44
+ end
45
+
46
+ def read_connection
47
+ @connections[0]
48
+ end
49
+
50
+ def write_connection
51
+ @connections[1]
52
+ end
53
+
54
+ def address
55
+ raise "Not applicable for a Mongrel2Handler"
56
+ end
57
+
58
+ def on_readable(socket, messages)
59
+ req = Mongrel2Request.parse_request(messages[0])
60
+ @recv_cbak.call(req)
61
+ end
62
+
63
+ def send_resp(uuid, conn_id, msg)
64
+ header = "%s %d:%s," % [uuid, conn_id.size, conn_id]
65
+ string = header + ' ' + msg
66
+ send_message(string)
67
+ end
68
+
69
+ def reply(req, msg)
70
+ self.send_resp(req.sender, req.conn_id, msg)
71
+ end
72
+
73
+ def reply_http(req, body, code=200, headers={})
74
+ self.reply(req, http_response(body, code, headers))
75
+ end
76
+
77
+ def http_response(body, code, headers)
78
+ headers['Content-Length'] = body.size
79
+ headers_s = headers.map { |k, v| "%s: %s" % [k, v] }.join("\r\n")
80
+
81
+ "HTTP/1.1 #{code} #{StatusMessage[code.to_i]}\r\n#{headers_s}\r\n\r\n#{body}"
82
+ end
83
+
84
+ # From WEBrick
85
+ StatusMessage = {
86
+ 100 => 'Continue',
87
+ 101 => 'Switching Protocols',
88
+ 200 => 'OK',
89
+ 201 => 'Created',
90
+ 202 => 'Accepted',
91
+ 203 => 'Non-Authoritative Information',
92
+ 204 => 'No Content',
93
+ 205 => 'Reset Content',
94
+ 206 => 'Partial Content',
95
+ 300 => 'Multiple Choices',
96
+ 301 => 'Moved Permanently',
97
+ 302 => 'Found',
98
+ 303 => 'See Other',
99
+ 304 => 'Not Modified',
100
+ 305 => 'Use Proxy',
101
+ 307 => 'Temporary Redirect',
102
+ 400 => 'Bad Request',
103
+ 401 => 'Unauthorized',
104
+ 402 => 'Payment Required',
105
+ 403 => 'Forbidden',
106
+ 404 => 'Not Found',
107
+ 405 => 'Method Not Allowed',
108
+ 406 => 'Not Acceptable',
109
+ 407 => 'Proxy Authentication Required',
110
+ 408 => 'Request Timeout',
111
+ 409 => 'Conflict',
112
+ 410 => 'Gone',
113
+ 411 => 'Length Required',
114
+ 412 => 'Precondition Failed',
115
+ 413 => 'Request Entity Too Large',
116
+ 414 => 'Request-URI Too Large',
117
+ 415 => 'Unsupported Media Type',
118
+ 416 => 'Request Range Not Satisfiable',
119
+ 417 => 'Expectation Failed',
120
+ 500 => 'Internal Server Error',
121
+ 501 => 'Not Implemented',
122
+ 502 => 'Bad Gateway',
123
+ 503 => 'Service Unavailable',
124
+ 504 => 'Gateway Timeout',
125
+ 505 => 'HTTP Version Not Supported'
126
+ }
127
+ end
128
+ end
129
+
130
+ class Mongrel2Request
131
+ attr_reader :sender, :conn_id, :path, :headers, :body
132
+
133
+ def initialize(sender, conn_id, path, headers, body)
134
+ @sender = sender
135
+ @conn_id = conn_id
136
+ @path = path
137
+ @headers = headers
138
+ @body = body
139
+
140
+ if headers['METHOD'] == 'JSON'
141
+ @data = JSON.parse(@body)
142
+ else
143
+ @data = {}
144
+ end
145
+ end
146
+
147
+ def self.parse_netstring(ns)
148
+ len, rest = ns.split(':', 2)
149
+ len = len.to_i
150
+ raise "Netstring did not end in ','" unless rest[len].chr == ','
151
+ [rest[0...len], rest[(len+1)..-1]]
152
+ end
153
+
154
+ def self.parse_request(msg)
155
+ sender, conn_id, path, rest = msg.copy_out_string.split(' ', 4)
156
+ headers, head_rest = parse_netstring(rest)
157
+ body, _ = parse_netstring(head_rest)
158
+
159
+ headers = JSON.parse(headers)
160
+
161
+ self.new(sender, conn_id, path, headers, body)
162
+ end
163
+ end
@@ -0,0 +1,86 @@
1
+ require 'em-websocket'
2
+
3
+
4
+ class DripDrop
5
+ class WebSocketHandler < BaseHandler
6
+ class SocketError < StandardError; attr_accessor :reason, :connection end
7
+
8
+ attr_reader :ws, :address, :thread
9
+
10
+ def initialize(address,opts={})
11
+ @opts = opts
12
+ @raw = false #Deal in strings or ZMQ::Message objects
13
+ host, port = address.host, address.port.to_i
14
+ @message_class = @opts[:message_class] || DripDrop.default_message_class
15
+ @debug = @opts[:debug] || false
16
+
17
+ EventMachine::WebSocket.start(:host => host,:port => port,:debug => @debug) do |ws|
18
+ #A WebSocketHandler:Connection gets passed to all callbacks
19
+ dd_conn = Connection.new(ws)
20
+
21
+ ws.onopen { @onopen_handler.call(dd_conn) if @onopen_handler }
22
+ ws.onclose { @onclose_handler.call(dd_conn) if @onclose_handler }
23
+ ws.onerror {|reason|
24
+ begin
25
+ raise SocketError, reason
26
+ rescue SocketError => e
27
+ e.reason = reason
28
+ e.connection = dd_conn
29
+ handle_error(e,dd_conn)
30
+ end
31
+ }
32
+
33
+ ws.onmessage { |message|
34
+ if @onmessage_handler
35
+ begin
36
+ message = @message_class.decode(message) unless @raw
37
+ @onmessage_handler.call(message,dd_conn)
38
+ rescue StandardError => e
39
+ handle_error(e,dd_conn)
40
+ end
41
+ end
42
+ }
43
+ end
44
+ end
45
+
46
+ def on_recv(&block)
47
+ @raw = false
48
+ @onmessage_handler = block
49
+ self
50
+ end
51
+
52
+ def on_recv_raw(&block)
53
+ @raw = true
54
+ @onmessage_handler = block
55
+ self
56
+ end
57
+
58
+ def on_open(&block)
59
+ @onopen_handler = block
60
+ self
61
+ end
62
+
63
+ def on_close(&block)
64
+ @onclose_handler = block
65
+ self
66
+ end
67
+ end
68
+
69
+ class WebSocketHandler::Connection < BaseHandler
70
+ attr_reader :ws, :signature, :handler
71
+
72
+ def initialize(ws)
73
+ @ws = ws
74
+ @signature = @ws.signature
75
+ end
76
+
77
+ def send_message(message)
78
+ begin
79
+ encoded_message = dd_messagify(message).encoded
80
+ @ws.send(encoded_message)
81
+ rescue StandardError => e
82
+ handle_error(e)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,300 @@
1
+ require 'ffi-rzmq'
2
+ require 'em-zeromq'
3
+
4
+ class DripDrop
5
+ SEQ_CTR_KEY = '_dd/xctr'
6
+
7
+ #Setup the default message class handler first
8
+ class << self
9
+ attr_accessor :default_message_class
10
+
11
+ DripDrop.default_message_class = DripDrop::Message
12
+ end
13
+
14
+ class ZMQBaseHandler < BaseHandler
15
+ attr_reader :connection
16
+
17
+ def initialize(opts={})
18
+ @opts = opts
19
+ @connection = nil
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
34
+ end
35
+
36
+ def on_recv(msg_format=:dripdrop,&block)
37
+ @recv_cbak = block
38
+ self
39
+ end
40
+
41
+ def address
42
+ self.connection.address
43
+ end
44
+
45
+ #Triggered after a handler is setup
46
+ def post_setup; end
47
+ end
48
+
49
+ module ZMQWritableHandler
50
+ def initialize(*args)
51
+ super(*args)
52
+ @send_queue = []
53
+ @send_queue_enabled = false
54
+ end
55
+
56
+ def on_writable(conn)
57
+ unless @send_queue.empty?
58
+ message = @send_queue.shift
59
+
60
+ conn.send_msg(*message)
61
+ else
62
+ conn.deregister_writable if @send_queue_enabled
63
+ end
64
+ end
65
+
66
+ # Sends a message, accepting either a DripDrop::Message,
67
+ # a hash that looks like a DripDrop::Message (has keys :name, :head, :body),
68
+ # or your own custom messages. Custom messages should either be a String, or
69
+ # for multipart messages, an Array of String objects.
70
+ def send_message(message)
71
+ dd_message = dd_messagify(message)
72
+ if dd_message.is_a?(DripDrop::Message)
73
+ @send_queue.push([dd_message.encoded])
74
+ elsif message.class == Array
75
+ @send_queue.push(message)
76
+ else
77
+ @send_queue.push([message])
78
+ end
79
+
80
+ self.write_connection.register_writable if @send_queue_enabled
81
+
82
+ EM::next_tick {
83
+ on_writable(self.write_connection)
84
+ }
85
+ end
86
+ end
87
+
88
+ module ZMQReadableHandler
89
+ attr_accessor :message_class
90
+
91
+ def decode_message(msg)
92
+ @message_class.decode(msg)
93
+ end
94
+
95
+ def on_readable(socket, messages)
96
+ begin
97
+ case @msg_format
98
+ when :raw
99
+ @recv_cbak.call(messages)
100
+ when :dripdrop
101
+ if messages.length > 1
102
+ raise "Expected message in one part for #{self.inspect}, got #{messages.map(&:copy_out_string)}"
103
+ end
104
+
105
+ body = messages.shift.copy_out_string
106
+ @recv_cbak.call(decode_message(body))
107
+ else
108
+ raise "Unknown message format '#{@msg_format}'"
109
+ end
110
+ rescue StandardError => e
111
+ handle_error(e)
112
+ end
113
+ end
114
+ end
115
+
116
+ class ZMQSubHandler < ZMQBaseHandler
117
+ include ZMQReadableHandler
118
+
119
+ attr_accessor :topic_filter
120
+
121
+ def initialize(*args)
122
+ super(*args)
123
+ self.topic_filter = @opts[:topic_filter]
124
+ end
125
+
126
+ def on_readable(socket, messages)
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)
139
+ end
140
+ rescue StandardError => e
141
+ handle_error(e)
142
+ end
143
+ end
144
+
145
+ def post_setup
146
+ super
147
+ @connection.socket.setsockopt(ZMQ::SUBSCRIBE, '')
148
+ end
149
+ end
150
+
151
+ class ZMQPubHandler < ZMQBaseHandler
152
+ include ZMQWritableHandler
153
+
154
+ #Sends a message along
155
+ def send_message(message)
156
+ dd_message = dd_messagify(message,@message_class)
157
+ if dd_message.is_a?(DripDrop::Message)
158
+ super([dd_message.name, dd_message.encoded])
159
+ else
160
+ super(message)
161
+ end
162
+ end
163
+ end
164
+
165
+ class ZMQPullHandler < ZMQBaseHandler
166
+ include ZMQReadableHandler
167
+ end
168
+
169
+ class ZMQPushHandler < ZMQBaseHandler
170
+ include ZMQWritableHandler
171
+ end
172
+
173
+ class ZMQXRepHandler < ZMQBaseHandler
174
+ include ZMQWritableHandler
175
+ include ZMQReadableHandler
176
+
177
+ def initialize(*args)
178
+ super(*args)
179
+ end
180
+
181
+ def on_readable(socket,messages)
182
+ if @msg_format == :dripdrop
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
220
+ else
221
+ super(socket,messages)
222
+ end
223
+ end
224
+
225
+ def send_message(message,identities,seq)
226
+ if message.is_a?(DripDrop::Message)
227
+ message.head[SEQ_CTR_KEY] = seq
228
+
229
+ resp = identities + ['', message.encoded]
230
+ super(resp)
231
+ else
232
+ resp = identities + ['', message]
233
+ super(resp)
234
+ end
235
+ end
236
+ end
237
+
238
+ class ZMQXRepHandler::Response < ZMQBaseHandler
239
+ attr_accessor :xrep, :seq, :identities
240
+
241
+ def initialize(xrep,identities,seq,message_class)
242
+ @xrep = xrep
243
+ @seq = seq
244
+ @identities = identities
245
+ @message_class = message_class
246
+ end
247
+
248
+ def send_message(message)
249
+ dd_message = dd_messagify(message,@message_class)
250
+ @xrep.send_message(dd_message,identities,seq)
251
+ end
252
+ end
253
+
254
+ class ZMQXReqHandler < ZMQBaseHandler
255
+ include ZMQWritableHandler
256
+ include ZMQReadableHandler
257
+
258
+ def initialize(*args)
259
+ super(*args)
260
+ #Used to keep track of responses
261
+ @seq_counter = 0
262
+ @promises = {}
263
+
264
+ self.on_recv do |message|
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
273
+ end
274
+ end
275
+
276
+ def send_message(message,&block)
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)
287
+ end
288
+ super(['', message.encoded])
289
+ end
290
+
291
+ def on_readable(socket, messages)
292
+ begin
293
+ # Strip out empty delimiter
294
+ super(socket, messages[1..-1])
295
+ rescue StandardError => e
296
+ handle_error(e)
297
+ end
298
+ end
299
+ end
300
+ end