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.
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