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
@@ -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
|