rjr 0.12.2 → 0.15.1
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/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
|