rjr 0.12.2 → 0.15.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +49 -36
- data/Rakefile +2 -0
- data/bin/rjr-client +11 -9
- data/bin/rjr-server +12 -10
- data/examples/amqp.rb +29 -0
- data/examples/client.rb +32 -0
- data/examples/complete.rb +36 -0
- data/examples/local.rb +29 -0
- data/examples/server.rb +26 -0
- data/examples/tcp.rb +29 -0
- data/examples/web.rb +22 -0
- data/examples/ws.rb +29 -0
- data/lib/rjr/common.rb +7 -12
- data/lib/rjr/dispatcher.rb +171 -239
- data/lib/rjr/em_adapter.rb +33 -66
- data/lib/rjr/message.rb +43 -12
- data/lib/rjr/node.rb +197 -103
- data/lib/rjr/nodes/amqp.rb +216 -0
- data/lib/rjr/nodes/easy.rb +159 -0
- data/lib/rjr/nodes/local.rb +118 -0
- data/lib/rjr/{missing_node.rb → nodes/missing.rb} +4 -2
- data/lib/rjr/nodes/multi.rb +79 -0
- data/lib/rjr/nodes/tcp.rb +211 -0
- data/lib/rjr/nodes/web.rb +197 -0
- data/lib/rjr/nodes/ws.rb +187 -0
- data/lib/rjr/stats.rb +70 -0
- data/lib/rjr/thread_pool.rb +178 -123
- data/site/index.html +45 -0
- data/site/jquery-latest.js +9404 -0
- data/site/jrw.js +297 -0
- data/site/json.js +199 -0
- data/specs/dispatcher_spec.rb +244 -198
- data/specs/em_adapter_spec.rb +52 -80
- data/specs/message_spec.rb +223 -197
- data/specs/node_spec.rb +67 -163
- data/specs/nodes/amqp_spec.rb +82 -0
- data/specs/nodes/easy_spec.rb +13 -0
- data/specs/nodes/local_spec.rb +72 -0
- data/specs/nodes/multi_spec.rb +65 -0
- data/specs/nodes/tcp_spec.rb +75 -0
- data/specs/nodes/web_spec.rb +77 -0
- data/specs/nodes/ws_spec.rb +78 -0
- data/specs/stats_spec.rb +59 -0
- data/specs/thread_pool_spec.rb +44 -35
- metadata +40 -30
- data/lib/rjr/amqp_node.rb +0 -330
- data/lib/rjr/inspect.rb +0 -65
- data/lib/rjr/local_node.rb +0 -150
- data/lib/rjr/multi_node.rb +0 -65
- data/lib/rjr/tcp_node.rb +0 -323
- data/lib/rjr/thread_pool2.rb +0 -272
- data/lib/rjr/util.rb +0 -104
- data/lib/rjr/web_node.rb +0 -266
- data/lib/rjr/ws_node.rb +0 -289
- data/lib/rjr.rb +0 -16
- data/specs/amqp_node_spec.rb +0 -31
- data/specs/inspect_spec.rb +0 -60
- data/specs/local_node_spec.rb +0 -43
- data/specs/multi_node_spec.rb +0 -45
- data/specs/tcp_node_spec.rb +0 -33
- data/specs/util_spec.rb +0 -46
- data/specs/web_node_spec.rb +0 -32
- data/specs/ws_node_spec.rb +0 -32
- /data/lib/rjr/{tcp_node2.rb → nodes/tcp2.rb} +0 -0
- /data/lib/rjr/{udp_node.rb → nodes/udp.rb} +0 -0
data/lib/rjr/util.rb
DELETED
@@ -1,104 +0,0 @@
|
|
1
|
-
# High level rjr utility mechanisms
|
2
|
-
#
|
3
|
-
# Copyright (C) 2013 Mohammed Morsi <mo@morsi.org>
|
4
|
-
# Licensed under the Apache License, Version 2.0
|
5
|
-
|
6
|
-
require 'rjr/dispatcher'
|
7
|
-
|
8
|
-
module RJR
|
9
|
-
|
10
|
-
# Mixin providing utility methods to define rjr methods and messages
|
11
|
-
module Definitions
|
12
|
-
# Define one or more rjr methods, parameters should be in the form
|
13
|
-
# :id => Callable
|
14
|
-
#
|
15
|
-
# id may be a single id or an array of them
|
16
|
-
def rjr_method(args = {})
|
17
|
-
args.each { |k, v|
|
18
|
-
RJR::Dispatcher.add_handler(k.to_s, &v)
|
19
|
-
}
|
20
|
-
nil
|
21
|
-
end
|
22
|
-
|
23
|
-
# Define/retrieve rjr messages. When defining pass a hash
|
24
|
-
# of mesasge ids to options to use in the defintions, eg
|
25
|
-
# :id => { :foo => :bar }
|
26
|
-
#
|
27
|
-
# When retrieving simply specify the id
|
28
|
-
def rjr_message(args={})
|
29
|
-
if args.is_a?(Hash)
|
30
|
-
args.each { |k,v|
|
31
|
-
RJR::Definitions.message(k.to_s, v)
|
32
|
-
}
|
33
|
-
nil
|
34
|
-
else
|
35
|
-
RJR::Definitions.message(args.to_s)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# Helper providing access to messages
|
40
|
-
def self.message(k, v=nil)
|
41
|
-
@rjr_messages ||= {}
|
42
|
-
@rjr_messages[k] = v unless v.nil?
|
43
|
-
@rjr_messages[k]
|
44
|
-
end
|
45
|
-
|
46
|
-
# Reset message registry
|
47
|
-
def self.reset
|
48
|
-
# TODO also invoke 'Dispatcher.init_handlers' ?
|
49
|
-
@rjr_messages = {}
|
50
|
-
end
|
51
|
-
|
52
|
-
# Generate / return random message. Optionally specify the transport which
|
53
|
-
# the message must accept
|
54
|
-
def self.rand_msg(transport = nil)
|
55
|
-
@rjr_messages ||= {}
|
56
|
-
messages = @rjr_messages.select { |mid,m| m[:transports].nil? || transport.nil? ||
|
57
|
-
m[:transports].include?(transport) }
|
58
|
-
messages[messages.keys[rand(messages.keys.size)]]
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
# Class to encapsulate any number of rjr nodes
|
63
|
-
class EasyNode
|
64
|
-
def initialize(node_args = {})
|
65
|
-
nodes = node_args.keys.collect { |n|
|
66
|
-
case n
|
67
|
-
when :amqp then
|
68
|
-
RJR::AMQPNode.new node_args[:amqp]
|
69
|
-
when :ws then
|
70
|
-
RJR::WSNode.new node_args[:ws]
|
71
|
-
when :tcp then
|
72
|
-
RJR::TCPNode.new node_args[:tcp]
|
73
|
-
when :www then
|
74
|
-
RJR::WebNode.new node_args[:www]
|
75
|
-
end
|
76
|
-
}
|
77
|
-
@multi_node = RJR::MultiNode.new :nodes => nodes
|
78
|
-
end
|
79
|
-
|
80
|
-
def invoke_request(dst, method, *params)
|
81
|
-
# TODO allow selection of node, eg automatically deduce which node type to use from 'dst'
|
82
|
-
@multi_node.nodes.first.invoke_request(dst, method, *params)
|
83
|
-
end
|
84
|
-
|
85
|
-
# Stop node on the specified signal
|
86
|
-
def stop_on(signal)
|
87
|
-
Signal.trap(signal) {
|
88
|
-
@multi_node.stop
|
89
|
-
}
|
90
|
-
self
|
91
|
-
end
|
92
|
-
|
93
|
-
def listen
|
94
|
-
@multi_node.listen
|
95
|
-
self
|
96
|
-
end
|
97
|
-
|
98
|
-
def join
|
99
|
-
@multi_node.join
|
100
|
-
self
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
end # module RJR
|
data/lib/rjr/web_node.rb
DELETED
@@ -1,266 +0,0 @@
|
|
1
|
-
# RJR WWW Endpoint
|
2
|
-
#
|
3
|
-
# Implements the RJR::Node interface to satisty JSON-RPC requests over the HTTP protocol
|
4
|
-
#
|
5
|
-
# The web node does not support callbacks at the moment, though at some point we may
|
6
|
-
# allow a client to specify an optional webserver to send callback requests to. (TODO)
|
7
|
-
#
|
8
|
-
# Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
|
9
|
-
# Licensed under the Apache License, Version 2.0
|
10
|
-
|
11
|
-
skip_module = false
|
12
|
-
begin
|
13
|
-
require 'evma_httpserver'
|
14
|
-
require 'em-http-request'
|
15
|
-
# TODO also support fallback clients ? (curb / net/http / etc)
|
16
|
-
rescue LoadError
|
17
|
-
skip_module = true
|
18
|
-
end
|
19
|
-
|
20
|
-
if skip_module
|
21
|
-
# TODO output: "curb/evma_httpserver gems could not be loaded, skipping web node definition"
|
22
|
-
require 'rjr/missing_node'
|
23
|
-
RJR::WebNode = RJR::MissingNode
|
24
|
-
|
25
|
-
else
|
26
|
-
require 'socket'
|
27
|
-
|
28
|
-
require 'rjr/node'
|
29
|
-
require 'rjr/message'
|
30
|
-
require 'rjr/dispatcher'
|
31
|
-
require 'rjr/thread_pool2'
|
32
|
-
|
33
|
-
module RJR
|
34
|
-
|
35
|
-
# Web node callback interface, *note* callbacks are not supported on the web
|
36
|
-
# node and thus this currently does nothing
|
37
|
-
class WebNodeCallback
|
38
|
-
def initialize()
|
39
|
-
end
|
40
|
-
|
41
|
-
def invoke(callback_method, *data)
|
42
|
-
# TODO throw error?
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
# @private
|
47
|
-
# Helper class intialized by eventmachine encapsulating a http connection
|
48
|
-
class WebRequestHandler < EventMachine::Connection
|
49
|
-
include EventMachine::HttpServer
|
50
|
-
|
51
|
-
RJR_NODE_TYPE = :web
|
52
|
-
|
53
|
-
# WebRequestHandler initializer.
|
54
|
-
#
|
55
|
-
# specify the WebNode establishing the connection
|
56
|
-
def initialize(*args)
|
57
|
-
@web_node = args[0]
|
58
|
-
end
|
59
|
-
|
60
|
-
# {EventMachine::Connection#process_http_request} callback, handle request messages
|
61
|
-
def process_http_request
|
62
|
-
# TODO support http protocols other than POST
|
63
|
-
msg = @http_post_content.nil? ? '' : @http_post_content
|
64
|
-
ThreadPool2Manager << ThreadPool2Job.new(msg) { |m| handle_request(m) }
|
65
|
-
end
|
66
|
-
|
67
|
-
private
|
68
|
-
|
69
|
-
# Internal helper, handle request message received
|
70
|
-
def handle_request(message)
|
71
|
-
msg = nil
|
72
|
-
result = nil
|
73
|
-
notification = NotificationMessage.is_notification_message?(msg)
|
74
|
-
|
75
|
-
begin
|
76
|
-
client_port, client_ip = Socket.unpack_sockaddr_in(get_peername)
|
77
|
-
msg = notification ? NotificationMessage.new(:message => message, :headers => @web_node.message_headers) :
|
78
|
-
RequestMessage.new(:message => message, :headers => @web_node.message_headers)
|
79
|
-
headers = @web_node.message_headers.merge(msg.headers)
|
80
|
-
result = Dispatcher.dispatch_request(msg.jr_method,
|
81
|
-
:method_args => msg.jr_args,
|
82
|
-
:headers => headers,
|
83
|
-
:client_ip => client_ip,
|
84
|
-
:client_port => client_port,
|
85
|
-
:rjr_node => @web_node,
|
86
|
-
:rjr_node_id => @web_node.node_id,
|
87
|
-
:rjr_node_type => RJR_NODE_TYPE,
|
88
|
-
:rjr_callback => WebNodeCallback.new())
|
89
|
-
rescue JSON::ParserError => e
|
90
|
-
result = Result.invalid_request
|
91
|
-
end
|
92
|
-
|
93
|
-
msg_id = msg.nil? ? nil : msg.msg_id
|
94
|
-
response = ResponseMessage.new(:id => msg_id, :result => result, :headers => headers)
|
95
|
-
|
96
|
-
unless notification
|
97
|
-
resp = EventMachine::DelegatedHttpResponse.new(self)
|
98
|
-
#resp.status = response.result.success ? 200 : 500
|
99
|
-
resp.status = 200
|
100
|
-
resp.content = response.to_s
|
101
|
-
resp.content_type "application/json"
|
102
|
-
resp.send_response
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
# Web node definition, listen for and invoke json-rpc requests via web requests
|
108
|
-
#
|
109
|
-
# Clients should specify the hostname / port when listening for requests and
|
110
|
-
# when invoking them.
|
111
|
-
#
|
112
|
-
# *note* the RJR javascript client also supports sending / receiving json-rpc
|
113
|
-
# messages over http
|
114
|
-
#
|
115
|
-
# @example Listening for json-rpc requests over tcp
|
116
|
-
# # register rjr dispatchers (see RJR::Dispatcher)
|
117
|
-
# RJR::Dispatcher.add_handler('hello') { |name|
|
118
|
-
# "Hello #{name}!"
|
119
|
-
# }
|
120
|
-
#
|
121
|
-
# # initialize node, listen, and block
|
122
|
-
# server = RJR::WebNode.new :node_id => 'server', :host => 'localhost', :port => '7777'
|
123
|
-
# server.listen
|
124
|
-
# server.join
|
125
|
-
#
|
126
|
-
# @example Invoking json-rpc requests over http using rjr
|
127
|
-
# client = RJR::WebNode.new :node_id => 'client'
|
128
|
-
# puts client.invoke_request('http://localhost:7777', 'hello', 'mo')
|
129
|
-
#
|
130
|
-
# @example Invoking json-rpc requests over http using curl
|
131
|
-
# $ curl -X POST http://localhost:7777 -d '{"jsonrpc":"2.0","method":"hello","params":["mo"],"id":"123"}'
|
132
|
-
# > {"jsonrpc":"2.0","id":"123","result":"Hello mo!"}
|
133
|
-
#
|
134
|
-
class WebNode < RJR::Node
|
135
|
-
private
|
136
|
-
|
137
|
-
# Internal helper, handle response message received
|
138
|
-
def handle_response(http)
|
139
|
-
msg = ResponseMessage.new(:message => http.response, :headers => @message_headers)
|
140
|
-
res = err = nil
|
141
|
-
begin
|
142
|
-
res = Dispatcher.handle_response(msg.result)
|
143
|
-
rescue Exception => e
|
144
|
-
err = e
|
145
|
-
end
|
146
|
-
|
147
|
-
@response_lock.synchronize {
|
148
|
-
result = [msg.msg_id, res]
|
149
|
-
result << err if !err.nil?
|
150
|
-
@responses << result
|
151
|
-
@response_cv.signal
|
152
|
-
}
|
153
|
-
end
|
154
|
-
|
155
|
-
# Internal helper, block until response matching message id is received
|
156
|
-
def wait_for_result(message)
|
157
|
-
res = nil
|
158
|
-
while res.nil?
|
159
|
-
@response_lock.synchronize{
|
160
|
-
# FIXME throw err if more than 1 match found
|
161
|
-
res = @responses.select { |response| message.msg_id == response.first }.first
|
162
|
-
if !res.nil?
|
163
|
-
@responses.delete(res)
|
164
|
-
|
165
|
-
else
|
166
|
-
@response_cv.signal
|
167
|
-
@response_cv.wait @response_lock
|
168
|
-
|
169
|
-
end
|
170
|
-
}
|
171
|
-
end
|
172
|
-
return res
|
173
|
-
end
|
174
|
-
|
175
|
-
public
|
176
|
-
|
177
|
-
# WebNode initializer
|
178
|
-
# @param [Hash] args the options to create the tcp node with
|
179
|
-
# @option args [String] :host the hostname/ip which to listen on
|
180
|
-
# @option args [Integer] :port the port which to listen on
|
181
|
-
def initialize(args = {})
|
182
|
-
super(args)
|
183
|
-
@host = args[:host]
|
184
|
-
@port = args[:port]
|
185
|
-
|
186
|
-
@response_lock = Mutex.new
|
187
|
-
@response_cv = ConditionVariable.new
|
188
|
-
@responses = []
|
189
|
-
end
|
190
|
-
|
191
|
-
# Register connection event handler,
|
192
|
-
#
|
193
|
-
# *note* Since web node connections aren't persistant, we don't do anything here.
|
194
|
-
# @param [:error, :close] event the event to register the handler for
|
195
|
-
# @param [Callable] handler block param to be added to array of handlers that are called when event occurs
|
196
|
-
# @yield [LocalNode] self is passed to each registered handler when event occurs
|
197
|
-
def on(event, &handler)
|
198
|
-
# TODO raise error?
|
199
|
-
end
|
200
|
-
|
201
|
-
# Instruct Node to start listening for and dispatching rpc requests
|
202
|
-
#
|
203
|
-
# Implementation of {RJR::Node#listen}
|
204
|
-
def listen
|
205
|
-
em_run do
|
206
|
-
EventMachine::start_server(@host, @port, WebRequestHandler, self)
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
# Instructs node to send rpc request, and wait for / return response
|
211
|
-
# @param [String] uri location of node to send request to, should be
|
212
|
-
# in format of http://hostname:port
|
213
|
-
# @param [String] rpc_method json-rpc method to invoke on destination
|
214
|
-
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
215
|
-
def invoke_request(uri, rpc_method, *args)
|
216
|
-
message = RequestMessage.new :method => rpc_method,
|
217
|
-
:args => args,
|
218
|
-
:headers => @message_headers
|
219
|
-
cb = lambda { |http|
|
220
|
-
# TODO handle errors
|
221
|
-
handle_response(http)
|
222
|
-
}
|
223
|
-
|
224
|
-
em_run do
|
225
|
-
http = EventMachine::HttpRequest.new(uri).post :body => message.to_s
|
226
|
-
http.errback &cb
|
227
|
-
http.callback &cb
|
228
|
-
end
|
229
|
-
|
230
|
-
# will block until response message is received
|
231
|
-
# TODO optional timeout for response ?
|
232
|
-
result = wait_for_result(message)
|
233
|
-
if result.size > 2
|
234
|
-
raise Exception, result[2]
|
235
|
-
end
|
236
|
-
return result[1]
|
237
|
-
end
|
238
|
-
|
239
|
-
# Instructs node to send rpc notification (immadiately returns / no response is generated)
|
240
|
-
#
|
241
|
-
# @param [String] uri location of node to send request to, should be
|
242
|
-
# in format of http://hostname:port
|
243
|
-
# @param [String] rpc_method json-rpc method to invoke on destination
|
244
|
-
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
245
|
-
def send_notification(uri, rpc_method, *args)
|
246
|
-
# will block until message is published
|
247
|
-
published_l = Mutex.new
|
248
|
-
published_c = ConditionVariable.new
|
249
|
-
|
250
|
-
invoked = false
|
251
|
-
message = NotificationMessage.new :method => rpc_method,
|
252
|
-
:args => args,
|
253
|
-
:headers => @message_headers
|
254
|
-
cb = lambda { |arg| published_l.synchronize { invoked = true ; published_c.signal }}
|
255
|
-
em_run do
|
256
|
-
http = EventMachine::HttpRequest.new(uri).post :body => message.to_s
|
257
|
-
http.errback &cb
|
258
|
-
http.callback &cb
|
259
|
-
end
|
260
|
-
published_l.synchronize { published_c.wait published_l unless invoked }
|
261
|
-
nil
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
end # module RJR
|
266
|
-
end
|
data/lib/rjr/ws_node.rb
DELETED
@@ -1,289 +0,0 @@
|
|
1
|
-
# RJR WebSockets Endpoint
|
2
|
-
#
|
3
|
-
# Implements the RJR::Node interface to satisty JSON-RPC requests over the websockets protocol
|
4
|
-
#
|
5
|
-
# Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
|
6
|
-
# Licensed under the Apache License, Version 2.0
|
7
|
-
|
8
|
-
skip_module = false
|
9
|
-
begin
|
10
|
-
require 'em-websocket'
|
11
|
-
require 'em-websocket-client'
|
12
|
-
rescue LoadError
|
13
|
-
skip_module = true
|
14
|
-
end
|
15
|
-
|
16
|
-
if skip_module
|
17
|
-
# TODO output: "ws dependencies could not be loaded, skipping ws node definition"
|
18
|
-
require 'rjr/missing_node'
|
19
|
-
RJR::WSNode = RJR::MissingNode
|
20
|
-
|
21
|
-
else
|
22
|
-
require 'socket'
|
23
|
-
require 'rjr/node'
|
24
|
-
require 'rjr/message'
|
25
|
-
require 'rjr/dispatcher'
|
26
|
-
require 'rjr/errors'
|
27
|
-
require 'rjr/thread_pool2'
|
28
|
-
|
29
|
-
module RJR
|
30
|
-
|
31
|
-
# Web Socket node callback interface, used to invoke json-rpc methods
|
32
|
-
# against a remote node via a web socket connection previously established
|
33
|
-
#
|
34
|
-
# After a node sends a json-rpc request to another, the either node may send
|
35
|
-
# additional requests to each other via the socket already established until
|
36
|
-
# it is closed on either end
|
37
|
-
class WSNodeCallback
|
38
|
-
# WSNodeCallback initializer
|
39
|
-
# @param [Hash] args the options to create the websocket node callback with
|
40
|
-
# @option args [Socket] :socket socket connection used to send/receive messages
|
41
|
-
# @option args [Hash] :headers hash of rjr message headers present in client request when callback is established
|
42
|
-
def initialize(args = {})
|
43
|
-
@socket = args[:socket]
|
44
|
-
@message_headers = args[:headers]
|
45
|
-
end
|
46
|
-
|
47
|
-
# Implementation of {RJR::NodeCallback#invoke}
|
48
|
-
def invoke(callback_method, *data)
|
49
|
-
msg = NotificationMessage.new :method => callback_method, :args => data, :headers => @message_headers
|
50
|
-
raise RJR::Errors::ConnectionError.new("websocket closed") if @socket.state == :closed
|
51
|
-
# TODO surround w/ begin/rescue block incase of other socket errors?
|
52
|
-
@socket.send(msg.to_s)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# Web socket node definition, listen for and invoke json-rpc requests via web sockets
|
57
|
-
#
|
58
|
-
# Clients should specify the hostname / port when listening for and invoking requests.
|
59
|
-
#
|
60
|
-
# *note* the RJR javascript client also supports sending / receiving json-rpc
|
61
|
-
# messages over web sockets
|
62
|
-
#
|
63
|
-
# @example Listening for json-rpc requests over tcp
|
64
|
-
# # register rjr dispatchers (see RJR::Dispatcher)
|
65
|
-
# RJR::Dispatcher.add_handler('hello') { |name|
|
66
|
-
# "Hello #{name}!"
|
67
|
-
# }
|
68
|
-
#
|
69
|
-
# # initialize node, listen, and block
|
70
|
-
# server = RJR::WSNode.new :node_id => 'server', :host => 'localhost', :port => '7777'
|
71
|
-
# server.listen
|
72
|
-
# server.join
|
73
|
-
#
|
74
|
-
# @example Invoking json-rpc requests over web sockets using rjr
|
75
|
-
# client = RJR::WsNode.new :node_id => 'client'
|
76
|
-
# puts client.invoke_request('ws://localhost:7777', 'hello', 'mo')
|
77
|
-
#
|
78
|
-
class WSNode < RJR::Node
|
79
|
-
RJR_NODE_TYPE = :ws
|
80
|
-
|
81
|
-
private
|
82
|
-
|
83
|
-
# Internal helper initialize new connection
|
84
|
-
def init_node(uri, &on_init)
|
85
|
-
connection = nil
|
86
|
-
@connections_lock.synchronize {
|
87
|
-
connection = @connections.find { |c|
|
88
|
-
c.url == uri
|
89
|
-
}
|
90
|
-
if connection.nil?
|
91
|
-
connection = EventMachine::WebSocketClient.connect(uri)
|
92
|
-
connection.callback do
|
93
|
-
on_init.call(connection)
|
94
|
-
end
|
95
|
-
@connections << connection
|
96
|
-
# TODO sleep until connected?
|
97
|
-
else
|
98
|
-
on_init.call(connection)
|
99
|
-
end
|
100
|
-
}
|
101
|
-
connection
|
102
|
-
end
|
103
|
-
|
104
|
-
# Internal helper handle messages
|
105
|
-
def handle_msg(endpoint, msg)
|
106
|
-
# TODO use messageutil incase of large messages?
|
107
|
-
if RequestMessage.is_request_message?(msg)
|
108
|
-
ThreadPool2Manager << ThreadPool2Job.new { handle_request(endpoint, msg, false) }
|
109
|
-
|
110
|
-
elsif NotificationMessage.is_notification_message?(msg)
|
111
|
-
ThreadPool2Manager << ThreadPool2Job.new { handle_request(endpoint, msg, true) }
|
112
|
-
|
113
|
-
elsif ResponseMessage.is_response_message?(msg)
|
114
|
-
handle_response(msg)
|
115
|
-
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
# Internal helper, handle request message received
|
120
|
-
def handle_request(endpoint, message, notification=false)
|
121
|
-
# XXX hack to handle client disconnection (should grap port/ip immediately on connection and use that)
|
122
|
-
client_port,client_ip = nil,nil
|
123
|
-
begin
|
124
|
-
client_port, client_ip = Socket.unpack_sockaddr_in(endpoint.get_peername)
|
125
|
-
rescue Exception=>e
|
126
|
-
end
|
127
|
-
|
128
|
-
msg = notification ? NotificationMessage.new(:message => message, :headers => @message_headers) :
|
129
|
-
RequestMessage.new(:message => message, :headers => @message_headers)
|
130
|
-
headers = @message_headers.merge(msg.headers)
|
131
|
-
result = Dispatcher.dispatch_request(msg.jr_method,
|
132
|
-
:method_args => msg.jr_args,
|
133
|
-
:headers => headers,
|
134
|
-
:client_ip => client_ip,
|
135
|
-
:client_port => client_port,
|
136
|
-
:rjr_node => self,
|
137
|
-
:rjr_node_id => @node_id,
|
138
|
-
:rjr_node_type => RJR_NODE_TYPE,
|
139
|
-
:rjr_callback =>
|
140
|
-
WSNodeCallback.new(:socket => endpoint,
|
141
|
-
:headers => headers))
|
142
|
-
unless notification
|
143
|
-
response = ResponseMessage.new(:id => msg.msg_id, :result => result, :headers => headers)
|
144
|
-
endpoint.send(response.to_s)
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
# Internal helper, handle response message received
|
149
|
-
def handle_response(data)
|
150
|
-
msg = ResponseMessage.new(:message => data, :headers => @message_headers)
|
151
|
-
res = err = nil
|
152
|
-
begin
|
153
|
-
res = Dispatcher.handle_response(msg.result)
|
154
|
-
rescue Exception => e
|
155
|
-
err = e
|
156
|
-
end
|
157
|
-
|
158
|
-
@response_lock.synchronize {
|
159
|
-
result = [msg.msg_id, res]
|
160
|
-
result << err if !err.nil?
|
161
|
-
@responses << result
|
162
|
-
@response_cv.signal
|
163
|
-
}
|
164
|
-
end
|
165
|
-
|
166
|
-
# Internal helper, block until response matching message id is received
|
167
|
-
def wait_for_result(message)
|
168
|
-
res = nil
|
169
|
-
while res.nil?
|
170
|
-
@response_lock.synchronize{
|
171
|
-
# FIXME throw err if more than 1 match found
|
172
|
-
res = @responses.select { |response| message.msg_id == response.first }.first
|
173
|
-
if !res.nil?
|
174
|
-
@responses.delete(res)
|
175
|
-
|
176
|
-
else
|
177
|
-
@response_cv.signal
|
178
|
-
@response_cv.wait @response_lock
|
179
|
-
|
180
|
-
end
|
181
|
-
}
|
182
|
-
end
|
183
|
-
return res
|
184
|
-
end
|
185
|
-
|
186
|
-
|
187
|
-
public
|
188
|
-
# WSNode initializer
|
189
|
-
# @param [Hash] args the options to create the web socket node with
|
190
|
-
# @option args [String] :host the hostname/ip which to listen on
|
191
|
-
# @option args [Integer] :port the port which to listen on
|
192
|
-
def initialize(args = {})
|
193
|
-
super(args)
|
194
|
-
@host = args[:host]
|
195
|
-
@port = args[:port]
|
196
|
-
|
197
|
-
@connections = []
|
198
|
-
@connections_lock = Mutex.new
|
199
|
-
|
200
|
-
@response_lock = Mutex.new
|
201
|
-
@response_cv = ConditionVariable.new
|
202
|
-
@responses = []
|
203
|
-
|
204
|
-
@connection_event_handlers = {:closed => [], :error => []}
|
205
|
-
end
|
206
|
-
|
207
|
-
# Register connection event handler
|
208
|
-
# @param [:error, :close] event the event to register the handler for
|
209
|
-
# @param [Callable] handler block param to be added to array of handlers that are called when event occurs
|
210
|
-
# @yield [WSNode] self is passed to each registered handler when event occurs
|
211
|
-
def on(event, &handler)
|
212
|
-
if @connection_event_handlers.keys.include?(event)
|
213
|
-
@connection_event_handlers[event] << handler
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
# Instruct Node to start listening for and dispatching rpc requests
|
218
|
-
#
|
219
|
-
# Implementation of {RJR::Node#listen}
|
220
|
-
def listen
|
221
|
-
em_run do
|
222
|
-
EventMachine::WebSocket.start(:host => @host, :port => @port) do |ws|
|
223
|
-
ws.onopen { }
|
224
|
-
ws.onclose { @connection_event_handlers[:closed].each { |h| h.call self } }
|
225
|
-
ws.onerror { |e| @connection_event_handlers[:error].each { |h| h.call self } }
|
226
|
-
ws.onmessage { |msg| handle_msg(ws, msg) }
|
227
|
-
end
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
# Instructs node to send rpc request, and wait for / return response
|
232
|
-
# @param [String] uri location of node to send request to, should be
|
233
|
-
# in format of ws://hostname:port
|
234
|
-
# @param [String] rpc_method json-rpc method to invoke on destination
|
235
|
-
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
236
|
-
def invoke_request(uri, rpc_method, *args)
|
237
|
-
message = RequestMessage.new :method => rpc_method,
|
238
|
-
:args => args,
|
239
|
-
:headers => @message_headers
|
240
|
-
|
241
|
-
em_run{
|
242
|
-
init_node(uri) do |c|
|
243
|
-
c.stream { |msg| handle_msg(c, msg) }
|
244
|
-
|
245
|
-
c.send_msg message.to_s
|
246
|
-
end
|
247
|
-
}
|
248
|
-
|
249
|
-
# TODO optional timeout for response ?
|
250
|
-
result = wait_for_result(message)
|
251
|
-
|
252
|
-
if result.size > 2
|
253
|
-
raise Exception, result[2]
|
254
|
-
end
|
255
|
-
return result[1]
|
256
|
-
end
|
257
|
-
|
258
|
-
# Instructs node to send rpc notification (immadiately returns / no response is generated)
|
259
|
-
#
|
260
|
-
# @param [String] uri location of node to send notification to, should be
|
261
|
-
# in format of ws://hostname:port
|
262
|
-
# @param [String] rpc_method json-rpc method to invoke on destination
|
263
|
-
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
264
|
-
def send_notification(uri, rpc_method, *args)
|
265
|
-
# will block until message is published
|
266
|
-
published_l = Mutex.new
|
267
|
-
published_c = ConditionVariable.new
|
268
|
-
|
269
|
-
invoked = false
|
270
|
-
message = NotificationMessage.new :method => rpc_method,
|
271
|
-
:args => args,
|
272
|
-
:headers => @message_headers
|
273
|
-
em_run{
|
274
|
-
init_node(uri) do |c|
|
275
|
-
c.send_msg message.to_s
|
276
|
-
|
277
|
-
# XXX same bug w/ tcp node, due to nature of event machine
|
278
|
-
# we aren't guaranteed that message is actually written to socket
|
279
|
-
# here, process must be kept alive until data is sent or will be lost
|
280
|
-
published_l.synchronize { invoked = true ; published_c.signal }
|
281
|
-
end
|
282
|
-
}
|
283
|
-
published_l.synchronize { published_c.wait published_l unless invoked }
|
284
|
-
nil
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
end # module RJR
|
289
|
-
end
|
data/lib/rjr.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
# rjr - Ruby Json Rpc
|
2
|
-
#
|
3
|
-
# Copyright (C) 2010-2012 Mohammed Morsi <mo@morsi.org>
|
4
|
-
# Licensed under the Apache License, Version 2.0
|
5
|
-
|
6
|
-
# rjr - Ruby Json Rpc
|
7
|
-
module RJR ; end
|
8
|
-
|
9
|
-
require 'rjr/amqp_node'
|
10
|
-
require 'rjr/tcp_node'
|
11
|
-
require 'rjr/web_node'
|
12
|
-
require 'rjr/ws_node'
|
13
|
-
require 'rjr/local_node'
|
14
|
-
require 'rjr/multi_node'
|
15
|
-
|
16
|
-
require 'rjr/util'
|
data/specs/amqp_node_spec.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
require 'rjr/amqp_node'
|
2
|
-
require 'rjr/dispatcher'
|
3
|
-
|
4
|
-
describe RJR::AMQPNode do
|
5
|
-
it "should invoke and satisfy amqp requests" do
|
6
|
-
server = RJR::AMQPNode.new :node_id => 'amqp', :broker => 'localhost'
|
7
|
-
client = RJR::AMQPNode.new :node_id => 'client', :broker => 'localhost'
|
8
|
-
|
9
|
-
foozbar_invoked = false
|
10
|
-
RJR::Dispatcher.init_handlers
|
11
|
-
RJR::Dispatcher.add_handler('foozbar') { |param|
|
12
|
-
@client_ip.should == nil
|
13
|
-
@client_port.should == nil
|
14
|
-
@rjr_node.should == server
|
15
|
-
@rjr_node_id.should == 'amqp'
|
16
|
-
@rjr_node_type.should == :amqp
|
17
|
-
param.should == 'myparam'
|
18
|
-
foozbar_invoked = true
|
19
|
-
'retval'
|
20
|
-
}
|
21
|
-
|
22
|
-
server.listen
|
23
|
-
res = client.invoke_request 'amqp-queue', 'foozbar', 'myparam'
|
24
|
-
server.halt
|
25
|
-
server.join
|
26
|
-
res.should == 'retval'
|
27
|
-
foozbar_invoked.should == true
|
28
|
-
end
|
29
|
-
|
30
|
-
# TODO ensure closed / error event handlers are invoked
|
31
|
-
end
|