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
@@ -0,0 +1,159 @@
|
|
1
|
+
# RJR Easy Node
|
2
|
+
#
|
3
|
+
# Implements the RJR::Node client interface to
|
4
|
+
# issue JSON-RPC requests over a variety of protocols
|
5
|
+
#
|
6
|
+
# Copyright (C) 2013 Mohammed Morsi <mo@morsi.org>
|
7
|
+
# Licensed under the Apache License, Version 2.0
|
8
|
+
|
9
|
+
require 'rjr/nodes/tcp'
|
10
|
+
require 'rjr/nodes/ws'
|
11
|
+
require 'rjr/nodes/web'
|
12
|
+
require 'rjr/nodes/amqp'
|
13
|
+
require 'rjr/nodes/multi'
|
14
|
+
require 'rjr/node'
|
15
|
+
|
16
|
+
module RJR
|
17
|
+
module Nodes
|
18
|
+
|
19
|
+
# Easy node definition.
|
20
|
+
#
|
21
|
+
# Clients should specify the transports that they would like to use
|
22
|
+
# and their relevant config upon instantating this call. After which
|
23
|
+
# invocations and notifications will be routed via the correct transport
|
24
|
+
# depending on the format of the destination.
|
25
|
+
#
|
26
|
+
# All nodes managed locally will share the same dispatcher so that json-rpc methods
|
27
|
+
# only need to be registered once, with the multi-node itself.
|
28
|
+
#
|
29
|
+
# @example invoking requests via multiple protocols
|
30
|
+
# easy = RJR::Nodes::Easy.new :tcp => { :host => 'localhost', :port => 8999 },
|
31
|
+
# :amqp => { :broker => 'localhost' }
|
32
|
+
#
|
33
|
+
# easy.invoke 'tcp://localhost:9000/', 'hello world'
|
34
|
+
# # => sent via tcp
|
35
|
+
#
|
36
|
+
# easy.notify 'dest-queue', 'hello world'
|
37
|
+
# # => sent via amqp
|
38
|
+
#
|
39
|
+
class Easy < RJR::Node
|
40
|
+
private
|
41
|
+
|
42
|
+
# Internal helper, retrieved the registered node type depending on
|
43
|
+
# the dst. If matching node type can't be found, nil is returned
|
44
|
+
def get_node(dst)
|
45
|
+
type = nil
|
46
|
+
if dst.is_a?(String)
|
47
|
+
if /tcp:\/\/.*/ =~ dst ||
|
48
|
+
/jsonrpc:\/\/.*/ =~ dst ||
|
49
|
+
/json-rpc:\/\/.*/ =~ dst
|
50
|
+
type = RJR::Nodes::TCP
|
51
|
+
|
52
|
+
elsif /ws:\/\/.*/ =~ dst
|
53
|
+
type = RJR::Nodes::WS
|
54
|
+
|
55
|
+
elsif /http:\/\/.*/ =~ dst
|
56
|
+
type = RJR::Nodes::Web
|
57
|
+
|
58
|
+
elsif /.*-queue$/ =~ dst
|
59
|
+
type = RJR::Nodes::AMQP
|
60
|
+
|
61
|
+
# else # TODO
|
62
|
+
# type = RJR::Nodes::Local
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
return @multi_node.nodes.find { |n| n.is_a?(type) } unless type.nil?
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
public
|
72
|
+
|
73
|
+
# Easy Node initializer
|
74
|
+
# @param [Hash] args the options to create the node with
|
75
|
+
# @option args [Hash] :amqp options to create the amqp node with
|
76
|
+
# @option args [Hash] :ws options to create the ws node with
|
77
|
+
# @option args [Hash] :tcp options to create the ws node with
|
78
|
+
# @option args [Hash] :web options to create the web node with
|
79
|
+
def initialize(args = {})
|
80
|
+
super(args)
|
81
|
+
|
82
|
+
nodes = []
|
83
|
+
args.keys.each { |n|
|
84
|
+
node =
|
85
|
+
case n
|
86
|
+
when :amqp then
|
87
|
+
RJR::Nodes::AMQP.new args[:amqp].merge(args)
|
88
|
+
when :ws then
|
89
|
+
RJR::Nodes::WS.new args[:ws].merge(args)
|
90
|
+
when :tcp then
|
91
|
+
RJR::Nodes::TCP.new args[:tcp].merge(args)
|
92
|
+
when :web then
|
93
|
+
RJR::Nodes::Web.new args[:web].merge(args)
|
94
|
+
end
|
95
|
+
|
96
|
+
if node
|
97
|
+
nodes << node
|
98
|
+
end
|
99
|
+
}
|
100
|
+
|
101
|
+
@multi_node = RJR::Nodes::Multi.new :nodes => nodes
|
102
|
+
@dispatcher = @multi_node.dispatcher
|
103
|
+
end
|
104
|
+
|
105
|
+
# Send data using specified connection
|
106
|
+
#
|
107
|
+
# Implementation of {RJR::Node#send_msg}
|
108
|
+
def send_msg(data, connection)
|
109
|
+
# TODO
|
110
|
+
end
|
111
|
+
|
112
|
+
# Instruct Nodes to start listening for and dispatching rpc requests
|
113
|
+
#
|
114
|
+
# Implementation of {RJR::Node#listen}
|
115
|
+
def listen
|
116
|
+
@multi_node.listen
|
117
|
+
end
|
118
|
+
|
119
|
+
# Instructs node to send rpc request, and wait for and return response.
|
120
|
+
#
|
121
|
+
# Implementation of {RJR::Node#invoke}
|
122
|
+
#
|
123
|
+
# @param [String] dst destination send request to
|
124
|
+
# @param [String] rpc_method json-rpc method to invoke on destination
|
125
|
+
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
126
|
+
# @return [Object] the json result retrieved from destination converted to a ruby object
|
127
|
+
# @raise [Exception] if the destination raises an exception, it will be converted to json and re-raised here
|
128
|
+
def invoke(dst, rpc_method, *args)
|
129
|
+
n = get_node(dst)
|
130
|
+
# TODO raise exception if n.nil?
|
131
|
+
n.invoke dst, rpc_method, *args
|
132
|
+
end
|
133
|
+
|
134
|
+
# Instructs node to send rpc notification (immadiately returns / no response is generated)
|
135
|
+
#
|
136
|
+
# Implementation of {RJR::Node#notify}
|
137
|
+
#
|
138
|
+
# @param [String] dst destination to send notification to
|
139
|
+
# @param [String] rpc_method json-rpc method to invoke on destination
|
140
|
+
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
141
|
+
def notify(dst, rpc_method, *args)
|
142
|
+
n = get_node(dst)
|
143
|
+
n.notify dst, rpc_method, *args
|
144
|
+
end
|
145
|
+
|
146
|
+
# Stop node on the specified signal
|
147
|
+
#
|
148
|
+
# @param [Singnal] signal signal to stop the node on
|
149
|
+
# @return self
|
150
|
+
def stop_on(signal)
|
151
|
+
Signal.trap(signal) {
|
152
|
+
@multi_node.stop
|
153
|
+
}
|
154
|
+
self
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end # module Nodes
|
159
|
+
end # module RJR
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# RJR Local Endpoint
|
2
|
+
#
|
3
|
+
# Implements the RJR::Node interface to satisty JSON-RPC requests via local method calls
|
4
|
+
#
|
5
|
+
# Copyright (C) 2012-2013 Mohammed Morsi <mo@morsi.org>
|
6
|
+
# Licensed under the Apache License, Version 2.0
|
7
|
+
|
8
|
+
require 'rjr/node'
|
9
|
+
require 'rjr/message'
|
10
|
+
|
11
|
+
module RJR
|
12
|
+
module Nodes
|
13
|
+
|
14
|
+
# Local node definition, implements the {RJR::Node} interface to
|
15
|
+
# listen for and invoke json-rpc requests via local handlers
|
16
|
+
#
|
17
|
+
# This is useful for situations in which you would like to invoke registered
|
18
|
+
# json-rpc handlers locally, enforcing the same constraints as
|
19
|
+
# you would on a json-rpc request coming in remotely.
|
20
|
+
#
|
21
|
+
# *Note* this only dispatches to the methods defined on the local dispatcher!
|
22
|
+
#
|
23
|
+
# If you have two local nodes, they will have seperate dispatchers unless you
|
24
|
+
# assign them the same object (eg node2.dispatcher = node1.dispatcher or
|
25
|
+
# node2 = new RJR::Nodes::Local.new(:dispatcher :=> node1.dispatcher))
|
26
|
+
#
|
27
|
+
# @example Listening for and dispatching json-rpc requests locally
|
28
|
+
# # initialize node
|
29
|
+
# node = RJR::LocalNode.new :node_id => 'node'
|
30
|
+
#
|
31
|
+
# node.dispatcher.handle('hello') do |name|
|
32
|
+
# @rjr_node_type == :local ? "Hello superuser #{name}" : "Hello #{name}!"
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# # invoke request
|
36
|
+
# node.invoke('hello', 'mo')
|
37
|
+
#
|
38
|
+
class Local < RJR::Node
|
39
|
+
RJR_NODE_TYPE = :local
|
40
|
+
|
41
|
+
# allows clients to override the node type for the local node
|
42
|
+
attr_accessor :node_type
|
43
|
+
|
44
|
+
# LocalNode initializer
|
45
|
+
# @param [Hash] args the options to create the local node with
|
46
|
+
def initialize(args = {})
|
47
|
+
super(args)
|
48
|
+
@node_type = RJR_NODE_TYPE
|
49
|
+
end
|
50
|
+
|
51
|
+
# Send data using specified connection.
|
52
|
+
#
|
53
|
+
# Simply dispatch local notification.
|
54
|
+
#
|
55
|
+
# Implementation of {RJR::Node#send_msg}
|
56
|
+
def send_msg(msg, connection)
|
57
|
+
# ignore response message
|
58
|
+
unless msg.is_a?(ResponseMessage)
|
59
|
+
handle_request(msg, true, nil)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Instruct Nodes to start listening for and dispatching rpc requests
|
64
|
+
#
|
65
|
+
# Implementation of {RJR::Node#listen}
|
66
|
+
def listen
|
67
|
+
# do nothing
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
# Instructs node to send rpc request, and wait for and return response
|
72
|
+
#
|
73
|
+
# Implementation of {RJR::Node#invoke}
|
74
|
+
#
|
75
|
+
# If strictly confirming to other nodes, this would use event machine to launch
|
76
|
+
# a thread pool job to dispatch request and block on result.
|
77
|
+
# Optimized for performance reasons but recognize that the semantics of using
|
78
|
+
# the local node will be somewhat different.
|
79
|
+
#
|
80
|
+
# @param [String] rpc_method json-rpc method to invoke on destination
|
81
|
+
# @param [Array] args array of arguments to convert to json and invoke remote method with
|
82
|
+
# @return [Object] the json result retrieved from destination converted to a ruby object
|
83
|
+
# @raise [Exception] if the destination raises an exception, it will be converted to json and re-raised here
|
84
|
+
def invoke(rpc_method, *args)
|
85
|
+
0.upto(args.size).each { |i| args[i] = args[i].to_s if args[i].is_a?(Symbol) }
|
86
|
+
message = RequestMessage.new(:method => rpc_method,
|
87
|
+
:args => args,
|
88
|
+
:headers => @message_headers).to_s
|
89
|
+
res = handle_request(message, false, nil)
|
90
|
+
return @dispatcher.handle_response(res.result)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Instructs node to send rpc notification (immediately returns / no response is generated)
|
94
|
+
#
|
95
|
+
# Implementation of {RJR::Node#notify}
|
96
|
+
#
|
97
|
+
# Same performance comment as invoke_request above
|
98
|
+
#
|
99
|
+
# @param [String] rpc_method json-rpc method to invoke on destination
|
100
|
+
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
101
|
+
def notify(rpc_method, *args)
|
102
|
+
# TODO run in thread & immediately return?
|
103
|
+
begin
|
104
|
+
0.upto(args.size).each { |i| args[i] = args[i].to_s if args[i].is_a?(Symbol) }
|
105
|
+
message = NotificationMessage.new(:method => rpc_method,
|
106
|
+
:args => args,
|
107
|
+
:headers => @message_headers).to_s
|
108
|
+
handle_request(message, true, nil)
|
109
|
+
rescue
|
110
|
+
end
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
end # class Local
|
116
|
+
|
117
|
+
end # module Nodes
|
118
|
+
end # module RJR
|
@@ -3,15 +3,17 @@
|
|
3
3
|
# Provides a entity able to be associated with a rjr endpoint
|
4
4
|
# if the corresponding node cannot be loaded for whatever reason
|
5
5
|
#
|
6
|
-
# Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
|
6
|
+
# Copyright (C) 2012-2013 Mohammed Morsi <mo@morsi.org>
|
7
7
|
# Licensed under the Apache License, Version 2.0
|
8
8
|
|
9
9
|
require 'rjr/node'
|
10
10
|
|
11
11
|
module RJR
|
12
|
-
|
12
|
+
module Nodes
|
13
|
+
class Missing < RJR::Node
|
13
14
|
def method_missing(method_id, *args, &bl)
|
14
15
|
raise "rjr node #{node_id} is missing a dependency - cannot invoke #{method_id}"
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
19
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# RJR Multi Node
|
2
|
+
#
|
3
|
+
# Implements the RJR::Node server interface to satisty
|
4
|
+
# JSON-RPC requests over multiple protocols
|
5
|
+
#
|
6
|
+
# Copyright (C) 2012-2013 Mohammed Morsi <mo@morsi.org>
|
7
|
+
# Licensed under the Apache License, Version 2.0
|
8
|
+
|
9
|
+
require 'rjr/node'
|
10
|
+
|
11
|
+
module RJR
|
12
|
+
module Nodes
|
13
|
+
|
14
|
+
# Multiple node definition, allows a developer to easily multiplex transport
|
15
|
+
# mechanisms to serve JSON-RPC requests over.
|
16
|
+
#
|
17
|
+
# All nodes used locally will share the same dispatcher so that json-rpc methods
|
18
|
+
# only need to be registered once, with the multi-node itself.
|
19
|
+
#
|
20
|
+
# This node does not support client operations (eg send_msg, invoke, and notify)
|
21
|
+
#
|
22
|
+
# @example Listening for json-rpc requests over amqp, tcp, http, and websockets
|
23
|
+
# # instantiate worker nodes
|
24
|
+
# amqp_server = RJR::Nodes::AMQP.new :node_id => 'amqp_server', :broker => 'localhost'
|
25
|
+
# tcp_server = RJR::Nodes::TCP.new :node_id => 'tcp_server', :host => 'localhost', :port => '7777'
|
26
|
+
# web_server = RJR::Nodes::Web.new :node_id => 'tcp_server', :host => 'localhost', :port => '80'
|
27
|
+
# ws_server = RJR::Nodes::WS.new :node_id => 'tcp_server', :host => 'localhost', :port => '8080'
|
28
|
+
#
|
29
|
+
# # instantiate multi node
|
30
|
+
# server = RJR::Nodes::Multi.new :node_id => 'server',
|
31
|
+
# :nodes => [amqp_server, tcp_server, web_server, ws_server]
|
32
|
+
#
|
33
|
+
# # register rjr dispatchers (see RJR::Dispatcher)
|
34
|
+
# server.dispatcher.handle('hello') do |name|
|
35
|
+
# # optionally use @rjr_node_type to handle different transport types
|
36
|
+
# "Hello #{name}!"
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# server.listen
|
40
|
+
# server.join
|
41
|
+
#
|
42
|
+
# # invoke requests as you normally would via any protocol
|
43
|
+
#
|
44
|
+
class Multi < RJR::Node
|
45
|
+
# Return the nodes
|
46
|
+
attr_reader :nodes
|
47
|
+
|
48
|
+
# MultiNode initializer
|
49
|
+
# @param [Hash] args the options to create the tcp node with
|
50
|
+
# @option args [Array<RJR::Node>] :nodes array of nodes to use to listen to new requests on
|
51
|
+
def initialize(args = {})
|
52
|
+
super(args)
|
53
|
+
@nodes = []
|
54
|
+
args[:nodes].each { |n|
|
55
|
+
self << n
|
56
|
+
} if args[:nodes]
|
57
|
+
end
|
58
|
+
|
59
|
+
# Add node to multinode
|
60
|
+
# @param [RJR::Node] node the node to add
|
61
|
+
def <<(node)
|
62
|
+
node.dispatcher = @dispatcher
|
63
|
+
@nodes << node
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# Instruct Node to start listening for and dispatching rpc requests
|
68
|
+
#
|
69
|
+
# Implementation of {RJR::Node#listen}
|
70
|
+
def listen
|
71
|
+
@nodes.each { |node|
|
72
|
+
node.listen
|
73
|
+
}
|
74
|
+
self
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end # module NODES
|
79
|
+
end # module RJR
|
@@ -0,0 +1,211 @@
|
|
1
|
+
# RJR TCP Node
|
2
|
+
#
|
3
|
+
# Implements the RJR::Node interface to satisty JSON-RPC requests over the TCP protocol
|
4
|
+
#
|
5
|
+
# Copyright (C) 2012-2013 Mohammed Morsi <mo@morsi.org>
|
6
|
+
# Licensed under the Apache License, Version 2.0
|
7
|
+
|
8
|
+
require 'uri'
|
9
|
+
require 'thread'
|
10
|
+
require 'eventmachine'
|
11
|
+
|
12
|
+
require 'rjr/node'
|
13
|
+
require 'rjr/message'
|
14
|
+
|
15
|
+
module RJR
|
16
|
+
module Nodes
|
17
|
+
|
18
|
+
# @private
|
19
|
+
# Helper class intialized by eventmachine encapsulating a socket connection
|
20
|
+
class TCPConnection < EventMachine::Connection
|
21
|
+
attr_reader :host
|
22
|
+
attr_reader :port
|
23
|
+
|
24
|
+
# TCPConnection intializer
|
25
|
+
#
|
26
|
+
# Specify the TCP Node establishing the connection and
|
27
|
+
# optionaly remote host/port which this connection is connected to
|
28
|
+
def initialize(args = {})
|
29
|
+
@rjr_node = args[:rjr_node]
|
30
|
+
@host = args[:host]
|
31
|
+
@port = args[:port]
|
32
|
+
|
33
|
+
@send_lock = Mutex.new
|
34
|
+
@data = ""
|
35
|
+
end
|
36
|
+
|
37
|
+
# {EventMachine::Connection#receive_data} callback, handle request / response messages
|
38
|
+
def receive_data(data)
|
39
|
+
# a large json-rpc message may be split over multiple packets
|
40
|
+
# (invocations of receive_data)
|
41
|
+
# and multiple messages may be concatinated into one packet
|
42
|
+
@data += data
|
43
|
+
while extracted = MessageUtil.retrieve_json(@data)
|
44
|
+
msg, @data = *extracted
|
45
|
+
@rjr_node.send(:handle_message, msg, self) # XXX private method
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Send data safely using local connection
|
50
|
+
def send_msg(data)
|
51
|
+
@send_lock.synchronize{
|
52
|
+
send_data(data)
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
# TCP node definition, listen for and invoke json-rpc requests via TCP sockets
|
59
|
+
#
|
60
|
+
# Clients should specify the hostname / port when listening for requests and
|
61
|
+
# when invoking them.
|
62
|
+
#
|
63
|
+
# @example Listening for json-rpc requests over tcp
|
64
|
+
# # initialize node
|
65
|
+
# server = RJR::Nodes::TCP.new :node_id => 'server', :host => 'localhost', :port => '7777'
|
66
|
+
#
|
67
|
+
# # register rjr dispatchers (see RJR::Dispatcher)
|
68
|
+
# server.dispatcher.handle('hello') { |name|
|
69
|
+
# "Hello #{name}!"
|
70
|
+
# }
|
71
|
+
#
|
72
|
+
# # listen and block
|
73
|
+
# server.listen
|
74
|
+
# server.join
|
75
|
+
#
|
76
|
+
# @example Invoking json-rpc requests over tcp
|
77
|
+
# client = RJR::Nodes::TCP.new :node_id => 'client', :host => 'localhost', :port => '8888'
|
78
|
+
# puts client.invoke('jsonrpc://localhost:7777', 'hello', 'mo')
|
79
|
+
#
|
80
|
+
class TCP < RJR::Node
|
81
|
+
RJR_NODE_TYPE = :tcp
|
82
|
+
|
83
|
+
attr_accessor :connections
|
84
|
+
|
85
|
+
private
|
86
|
+
# Internal helper, initialize new client
|
87
|
+
def init_client(args={}, &on_init)
|
88
|
+
host,port = args[:host], args[:port]
|
89
|
+
connection = nil
|
90
|
+
@connections_lock.synchronize {
|
91
|
+
connection = @connections.find { |c|
|
92
|
+
port == c.port && host == c.host
|
93
|
+
}
|
94
|
+
if connection.nil?
|
95
|
+
connection =
|
96
|
+
EventMachine::connect host, port,
|
97
|
+
TCPConnection, args
|
98
|
+
@connections << connection
|
99
|
+
end
|
100
|
+
}
|
101
|
+
on_init.call(connection) # TODO move to tcpnode event ?
|
102
|
+
end
|
103
|
+
|
104
|
+
public
|
105
|
+
|
106
|
+
# TCP initializer
|
107
|
+
# @param [Hash] args the options to create the tcp node with
|
108
|
+
# @option args [String] :host the hostname/ip which to listen on
|
109
|
+
# @option args [Integer] :port the port which to listen on
|
110
|
+
def initialize(args = {})
|
111
|
+
super(args)
|
112
|
+
@host = args[:host]
|
113
|
+
@port = args[:port]
|
114
|
+
|
115
|
+
@connections = []
|
116
|
+
@connections_lock = Mutex.new
|
117
|
+
end
|
118
|
+
|
119
|
+
# Send data using specified connection
|
120
|
+
#
|
121
|
+
# Implementation of {RJR::Node#send_msg}
|
122
|
+
def send_msg(data, connection)
|
123
|
+
connection.send_msg(data)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Instruct Node to start listening for and dispatching rpc requests
|
127
|
+
#
|
128
|
+
# Implementation of {RJR::Node#listen}
|
129
|
+
def listen
|
130
|
+
@em.schedule {
|
131
|
+
@em.start_server @host, @port, TCPConnection, { :rjr_node => self }
|
132
|
+
}
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
# Instructs node to send rpc request, and wait for / return response.
|
137
|
+
#
|
138
|
+
# Implementation of {RJR::Node#invoke}
|
139
|
+
#
|
140
|
+
# Do not invoke directly from em event loop or callback as will block the message
|
141
|
+
# subscription used to receive responses
|
142
|
+
#
|
143
|
+
# @param [String] uri location of node to send request to, should be
|
144
|
+
# in format of jsonrpc://hostname:port or tcp://hostname:port
|
145
|
+
# @param [String] rpc_method json-rpc method to invoke on destination
|
146
|
+
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
147
|
+
def invoke(uri, rpc_method, *args)
|
148
|
+
uri = URI.parse(uri)
|
149
|
+
host,port = uri.host, uri.port
|
150
|
+
|
151
|
+
message = RequestMessage.new :method => rpc_method,
|
152
|
+
:args => args,
|
153
|
+
:headers => @message_headers
|
154
|
+
connection = nil
|
155
|
+
@em.schedule {
|
156
|
+
init_client(:host => host, :port => port,
|
157
|
+
:rjr_node => self) { |c|
|
158
|
+
connection = c
|
159
|
+
c.send_msg message.to_s
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
# TODO optional timeout for response ?
|
164
|
+
result = wait_for_result(message)
|
165
|
+
|
166
|
+
if result.size > 2
|
167
|
+
raise Exception, result[2]
|
168
|
+
end
|
169
|
+
return result[1]
|
170
|
+
end
|
171
|
+
|
172
|
+
# Instructs node to send rpc notification (immadiately returns / no response is generated)
|
173
|
+
#
|
174
|
+
# Implementation of {RJR::Node#notify}
|
175
|
+
#
|
176
|
+
# @param [String] uri location of node to send notification to, should be
|
177
|
+
# in format of jsonrpc://hostname:port
|
178
|
+
# @param [String] rpc_method json-rpc method to invoke on destination
|
179
|
+
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
180
|
+
def notify(uri, rpc_method, *args)
|
181
|
+
# will block until message is published
|
182
|
+
published_l = Mutex.new
|
183
|
+
published_c = ConditionVariable.new
|
184
|
+
|
185
|
+
uri = URI.parse(uri)
|
186
|
+
host,port = uri.host, uri.port
|
187
|
+
|
188
|
+
invoked = false
|
189
|
+
conn = nil
|
190
|
+
message = NotificationMessage.new :method => rpc_method,
|
191
|
+
:args => args,
|
192
|
+
:headers => @message_headers
|
193
|
+
@em.schedule {
|
194
|
+
init_client(:host => host, :port => port,
|
195
|
+
:rjr_node => self) { |c|
|
196
|
+
conn = c
|
197
|
+
c.send_msg message.to_s
|
198
|
+
# XXX, this should be invoked only when we are sure event
|
199
|
+
# machine sent message. Shouldn't pose a problem unless event
|
200
|
+
# machine is killed immediately after
|
201
|
+
published_l.synchronize { invoked = true ; published_c.signal }
|
202
|
+
}
|
203
|
+
}
|
204
|
+
published_l.synchronize { published_c.wait published_l unless invoked }
|
205
|
+
#sleep 0.01 until conn.get_outbound_data_size == 0
|
206
|
+
nil
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
end # module Nodes
|
211
|
+
end # module RJR
|