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.
- data/.document +5 -0
- data/.gitignore +31 -0
- data/Gemfile +5 -0
- data/LICENSE +20 -0
- data/README.md +164 -0
- data/Rakefile +16 -0
- data/dripdrop.gemspec +37 -0
- data/example/agent_test.rb +14 -0
- data/example/combined.rb +33 -0
- data/example/complex/README +22 -0
- data/example/complex/client.rb +20 -0
- data/example/complex/server.rb +102 -0
- data/example/complex/service.rb +8 -0
- data/example/complex/websocket.rb +442 -0
- data/example/http.rb +23 -0
- data/example/pubsub.rb +29 -0
- data/example/pushpull.rb +21 -0
- data/example/subclass.rb +54 -0
- data/example/xreq_xrep.rb +24 -0
- data/js/dripdrop.html +186 -0
- data/js/dripdrop.js +107 -0
- data/js/qunit.css +155 -0
- data/js/qunit.js +1261 -0
- data/lib/dripdrop.rb +2 -0
- data/lib/dripdrop/agent.rb +40 -0
- data/lib/dripdrop/handlers/base.rb +42 -0
- data/lib/dripdrop/handlers/http_client.rb +38 -0
- data/lib/dripdrop/handlers/http_server.rb +59 -0
- data/lib/dripdrop/handlers/mongrel2.rb +163 -0
- data/lib/dripdrop/handlers/websocket_server.rb +86 -0
- data/lib/dripdrop/handlers/zeromq.rb +300 -0
- data/lib/dripdrop/message.rb +190 -0
- data/lib/dripdrop/node.rb +351 -0
- data/lib/dripdrop/node/nodelet.rb +35 -0
- data/lib/dripdrop/version.rb +3 -0
- data/spec/gimite-websocket.rb +442 -0
- data/spec/message_spec.rb +94 -0
- data/spec/node/http_spec.rb +77 -0
- data/spec/node/nodelet_spec.rb +67 -0
- data/spec/node/routing_spec.rb +67 -0
- data/spec/node/websocket_spec.rb +98 -0
- data/spec/node/zmq_m2_spec.rb +77 -0
- data/spec/node/zmq_pushpull_spec.rb +54 -0
- data/spec/node/zmq_xrepxreq_spec.rb +108 -0
- data/spec/node_spec.rb +85 -0
- data/spec/spec_helper.rb +20 -0
- metadata +167 -0
data/lib/dripdrop.rb
ADDED
@@ -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
|