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