rjr 0.12.2 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/README.md +49 -36
  2. data/Rakefile +2 -0
  3. data/bin/rjr-client +11 -9
  4. data/bin/rjr-server +12 -10
  5. data/examples/amqp.rb +29 -0
  6. data/examples/client.rb +32 -0
  7. data/examples/complete.rb +36 -0
  8. data/examples/local.rb +29 -0
  9. data/examples/server.rb +26 -0
  10. data/examples/tcp.rb +29 -0
  11. data/examples/web.rb +22 -0
  12. data/examples/ws.rb +29 -0
  13. data/lib/rjr/common.rb +7 -12
  14. data/lib/rjr/dispatcher.rb +171 -239
  15. data/lib/rjr/em_adapter.rb +33 -66
  16. data/lib/rjr/message.rb +43 -12
  17. data/lib/rjr/node.rb +197 -103
  18. data/lib/rjr/nodes/amqp.rb +216 -0
  19. data/lib/rjr/nodes/easy.rb +159 -0
  20. data/lib/rjr/nodes/local.rb +118 -0
  21. data/lib/rjr/{missing_node.rb → nodes/missing.rb} +4 -2
  22. data/lib/rjr/nodes/multi.rb +79 -0
  23. data/lib/rjr/nodes/tcp.rb +211 -0
  24. data/lib/rjr/nodes/web.rb +197 -0
  25. data/lib/rjr/nodes/ws.rb +187 -0
  26. data/lib/rjr/stats.rb +70 -0
  27. data/lib/rjr/thread_pool.rb +178 -123
  28. data/site/index.html +45 -0
  29. data/site/jquery-latest.js +9404 -0
  30. data/site/jrw.js +297 -0
  31. data/site/json.js +199 -0
  32. data/specs/dispatcher_spec.rb +244 -198
  33. data/specs/em_adapter_spec.rb +52 -80
  34. data/specs/message_spec.rb +223 -197
  35. data/specs/node_spec.rb +67 -163
  36. data/specs/nodes/amqp_spec.rb +82 -0
  37. data/specs/nodes/easy_spec.rb +13 -0
  38. data/specs/nodes/local_spec.rb +72 -0
  39. data/specs/nodes/multi_spec.rb +65 -0
  40. data/specs/nodes/tcp_spec.rb +75 -0
  41. data/specs/nodes/web_spec.rb +77 -0
  42. data/specs/nodes/ws_spec.rb +78 -0
  43. data/specs/stats_spec.rb +59 -0
  44. data/specs/thread_pool_spec.rb +44 -35
  45. metadata +40 -30
  46. data/lib/rjr/amqp_node.rb +0 -330
  47. data/lib/rjr/inspect.rb +0 -65
  48. data/lib/rjr/local_node.rb +0 -150
  49. data/lib/rjr/multi_node.rb +0 -65
  50. data/lib/rjr/tcp_node.rb +0 -323
  51. data/lib/rjr/thread_pool2.rb +0 -272
  52. data/lib/rjr/util.rb +0 -104
  53. data/lib/rjr/web_node.rb +0 -266
  54. data/lib/rjr/ws_node.rb +0 -289
  55. data/lib/rjr.rb +0 -16
  56. data/specs/amqp_node_spec.rb +0 -31
  57. data/specs/inspect_spec.rb +0 -60
  58. data/specs/local_node_spec.rb +0 -43
  59. data/specs/multi_node_spec.rb +0 -45
  60. data/specs/tcp_node_spec.rb +0 -33
  61. data/specs/util_spec.rb +0 -46
  62. data/specs/web_node_spec.rb +0 -32
  63. data/specs/ws_node_spec.rb +0 -32
  64. /data/lib/rjr/{tcp_node2.rb → nodes/tcp2.rb} +0 -0
  65. /data/lib/rjr/{udp_node.rb → nodes/udp.rb} +0 -0
@@ -0,0 +1,197 @@
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,
6
+ # though would like to look into how to implement this
7
+ #
8
+ # Copyright (C) 2012-2013 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: "em-http-request/evma_httpserver gems could not be loaded, skipping web node definition"
22
+ require 'rjr/nodes/missing'
23
+ RJR::Nodes::Web = RJR::Nodes::Missing
24
+
25
+ else
26
+
27
+ require 'thread'
28
+ require 'eventmachine'
29
+
30
+ require 'rjr/node'
31
+ require 'rjr/message'
32
+
33
+ module RJR
34
+ module Nodes
35
+
36
+ # @private
37
+ # Helper class intialized by eventmachine encapsulating a http connection
38
+ class WebConnection < EventMachine::Connection
39
+ include EventMachine::HttpServer
40
+
41
+ # WebConnection initializer.
42
+ #
43
+ # specify the web node establishing the connection
44
+ def initialize(args = {})
45
+ @rjr_node = args[:rjr_node]
46
+ end
47
+
48
+ # {EventMachine::Connection#process_http_request} callback, handle request messages
49
+ def process_http_request
50
+ # TODO support http protocols other than POST
51
+ msg = @http_post_content.nil? ? '' : @http_post_content
52
+ @rjr_node.send(:handle_message, msg, self) # XXX private method
53
+
54
+ # XXX we still have to send a response back to client to satisfy
55
+ # the http standard, even if this is a notification. handle_message
56
+ # does not do this.
57
+ @rjr_node.send_msg "", self if NotificationMessage.is_notification_message?(msg)
58
+ end
59
+ end
60
+
61
+ # Web node definition, listen for and invoke json-rpc requests via web requests
62
+ #
63
+ # Clients should specify the hostname / port when listening for requests and
64
+ # when invoking them.
65
+ #
66
+ # *note* the RJR javascript client also supports sending / receiving json-rpc
67
+ # messages over http
68
+ #
69
+ # @example Listening for json-rpc requests over tcp
70
+ # # initialize node
71
+ # server = RJR::Nodes::Web.new :node_id => 'server', :host => 'localhost', :port => '7777'
72
+ #
73
+ # # register rjr dispatchers (see RJR::Dispatcher)
74
+ # server.dispatcher.handle('hello') do |name|
75
+ # "Hello #{name}!"
76
+ # end
77
+ #
78
+ # # listen, and block
79
+ # server.listen
80
+ # server.join
81
+ #
82
+ # @example Invoking json-rpc requests over http using rjr
83
+ # client = RJR::Nodes::Web.new :node_id => 'client'
84
+ # puts client.invoke('http://localhost:7777', 'hello', 'mo')
85
+ #
86
+ # @example Invoking json-rpc requests over http using curl
87
+ # $ curl -X POST http://localhost:7777 -d '{"jsonrpc":"2.0","method":"hello","params":["mo"],"id":"123"}'
88
+ # > {"jsonrpc":"2.0","id":"123","result":"Hello mo!"}
89
+ #
90
+ class Web < RJR::Node
91
+
92
+ RJR_NODE_TYPE = :web
93
+
94
+ public
95
+
96
+ # Web initializer
97
+ # @param [Hash] args the options to create the tcp node with
98
+ # @option args [String] :host the hostname/ip which to listen on
99
+ # @option args [Integer] :port the port which to listen on
100
+ def initialize(args = {})
101
+ super(args)
102
+ @host = args[:host]
103
+ @port = args[:port]
104
+ end
105
+
106
+ # Send data using specified http connection
107
+ #
108
+ # Implementation of {RJR::Node#send_msg}
109
+ def send_msg(data, connection)
110
+ # we are assuming that since http connections
111
+ # are not persistant, we should be sending a
112
+ # response message here
113
+
114
+ resp = EventMachine::DelegatedHttpResponse.new(connection)
115
+ #resp.status = response.result.success ? 200 : 500
116
+ resp.status = 200
117
+ resp.content = data.to_s
118
+ resp.content_type "application/json"
119
+ resp.send_response
120
+ end
121
+
122
+ # Instruct Node to start listening for and dispatching rpc requests
123
+ #
124
+ # Implementation of {RJR::Node#listen}
125
+ def listen
126
+ @em.schedule do
127
+ EventMachine::start_server(@host, @port, WebConnection, :rjr_node => self)
128
+ end
129
+ self
130
+ end
131
+
132
+ # Instructs node to send rpc request, and wait for / return response
133
+ #
134
+ # Implementation of {RJR::Node#invoke}
135
+ #
136
+ # Do not invoke directly from em event loop or callback as will block the message
137
+ # subscription used to receive responses
138
+ #
139
+ # @param [String] uri location of node to send request to, should be
140
+ # in format of http://hostname:port
141
+ # @param [String] rpc_method json-rpc method to invoke on destination
142
+ # @param [Array] args array of arguments to convert to json and invoke remote method wtih
143
+ def invoke(uri, rpc_method, *args)
144
+ message = RequestMessage.new :method => rpc_method,
145
+ :args => args,
146
+ :headers => @message_headers
147
+ cb = lambda { |http|
148
+ # TODO handle errors
149
+ handle_message(http.response, http)
150
+ }
151
+
152
+ @em.schedule do
153
+ http = EventMachine::HttpRequest.new(uri).post :body => message.to_s
154
+ http.errback &cb
155
+ http.callback &cb
156
+ end
157
+
158
+ # will block until response message is received
159
+ # TODO optional timeout for response ?
160
+ result = wait_for_result(message)
161
+ if result.size > 2
162
+ raise Exception, result[2]
163
+ end
164
+ return result[1]
165
+ end
166
+
167
+ # Instructs node to send rpc notification (immadiately returns / no response is generated)
168
+ #
169
+ # Implementation of {RJR::Node#notify}
170
+ #
171
+ # @param [String] uri location of node to send request to, should be
172
+ # in format of http://hostname:port
173
+ # @param [String] rpc_method json-rpc method to invoke on destination
174
+ # @param [Array] args array of arguments to convert to json and invoke remote method wtih
175
+ def notify(uri, rpc_method, *args)
176
+ # will block until message is published
177
+ published_l = Mutex.new
178
+ published_c = ConditionVariable.new
179
+
180
+ invoked = false
181
+ message = NotificationMessage.new :method => rpc_method,
182
+ :args => args,
183
+ :headers => @message_headers
184
+ cb = lambda { |arg| published_l.synchronize { invoked = true ; published_c.signal }}
185
+ @em.schedule do
186
+ http = EventMachine::HttpRequest.new(uri).post :body => message.to_s
187
+ http.errback &cb
188
+ http.callback &cb
189
+ end
190
+ published_l.synchronize { published_c.wait published_l unless invoked }
191
+ nil
192
+ end
193
+ end
194
+
195
+ end # module Nodes
196
+ end # module RJR
197
+ end # !skip_module
@@ -0,0 +1,187 @@
1
+ # RJR WebSockets Endpoint
2
+ #
3
+ # Implements the RJR::Node interface to satisty JSON-RPC requests
4
+ # over the websockets protocol
5
+ #
6
+ # Copyright (C) 2012-2013 Mohammed Morsi <mo@morsi.org>
7
+ # Licensed under the Apache License, Version 2.0
8
+
9
+ skip_module = false
10
+ begin
11
+ require 'em-websocket'
12
+ require 'em-websocket-client'
13
+ rescue LoadError
14
+ skip_module = true
15
+ end
16
+
17
+ if skip_module
18
+ # TODO output: "ws dependencies could not be loaded, skipping ws node definition"
19
+ require 'rjr/nodes/missing'
20
+ RJR::Nodes::WS = RJR::Nodes::Missing
21
+
22
+ else
23
+ require 'thread'
24
+
25
+ require 'rjr/node'
26
+ require 'rjr/message'
27
+
28
+ module RJR
29
+ module Nodes
30
+
31
+ # Web socket node definition, listen for and invoke json-rpc requests via web sockets
32
+ #
33
+ # Clients should specify the hostname / port when listening for and invoking requests.
34
+ #
35
+ # *note* the RJR javascript client also supports sending / receiving json-rpc
36
+ # messages over web sockets
37
+ #
38
+ # @example Listening for json-rpc requests over tcp
39
+ # # initialize node
40
+ # server = RJR::Nodes::WS.new :node_id => 'server', :host => 'localhost', :port => '7777'
41
+ #
42
+ # # register rjr dispatchers (see RJR::Dispatcher)
43
+ # server.dispatcher.handle('hello') do |name|
44
+ # "Hello #{name}!"
45
+ # end
46
+ #
47
+ # # listen, and block
48
+ # server.listen
49
+ # server.join
50
+ #
51
+ # @example Invoking json-rpc requests over web sockets using rjr
52
+ # client = RJR::Nodes::WS.new :node_id => 'client'
53
+ # puts client.invoke_request('ws://localhost:7777', 'hello', 'mo')
54
+ #
55
+ class WS < RJR::Node
56
+ RJR_NODE_TYPE = :ws
57
+
58
+ private
59
+
60
+ # Internal helper initialize new client
61
+ def init_client(uri, &on_init)
62
+ connection = nil
63
+ @connections_lock.synchronize {
64
+ connection = @connections.find { |c|
65
+ c.url == uri
66
+ }
67
+ if connection.nil?
68
+ connection = EventMachine::WebSocketClient.connect(uri)
69
+ connection.callback do
70
+ on_init.call(connection)
71
+ end
72
+ @connections << connection
73
+ # TODO sleep until connected?
74
+ else
75
+ on_init.call(connection)
76
+ end
77
+ }
78
+ connection
79
+ end
80
+
81
+ public
82
+
83
+ # WS initializer
84
+ # @param [Hash] args the options to create the web socket node with
85
+ # @option args [String] :host the hostname/ip which to listen on
86
+ # @option args [Integer] :port the port which to listen on
87
+ def initialize(args = {})
88
+ super(args)
89
+ @host = args[:host]
90
+ @port = args[:port]
91
+
92
+ @connections = []
93
+ @connections_lock = Mutex.new
94
+ end
95
+
96
+ # Send data using specified websocket safely
97
+ #
98
+ # Implementation of {RJR::Node#send_msg}
99
+ def send_msg(data, ws)
100
+ ws.send(data)
101
+ end
102
+
103
+ # Instruct Node to start listening for and dispatching rpc requests
104
+ #
105
+ # Implementation of {RJR::Node#listen}
106
+ def listen
107
+ @em.schedule do
108
+ EventMachine::WebSocket.start(:host => @host, :port => @port) do |ws|
109
+ ws.onopen { }
110
+ ws.onclose { @connection_event_handlers[:closed].each { |h| h.call self } }
111
+ ws.onerror { |e| @connection_event_handlers[:error].each { |h| h.call self } }
112
+ ws.onmessage { |msg| handle_message(msg, ws) }
113
+ end
114
+ end
115
+ self
116
+ end
117
+
118
+ # Instructs node to send rpc request, and wait for / return response
119
+ #
120
+ # Implementation of {RJR::Node#invoke}
121
+ #
122
+ # Do not invoke directly from em event loop or callback as will block the message
123
+ # subscription used to receive responses
124
+ #
125
+ # @param [String] uri location of node to send request to, should be
126
+ # in format of ws://hostname:port
127
+ # @param [String] rpc_method json-rpc method to invoke on destination
128
+ # @param [Array] args array of arguments to convert to json and invoke remote method wtih
129
+ def invoke(uri, rpc_method, *args)
130
+ message = RequestMessage.new :method => rpc_method,
131
+ :args => args,
132
+ :headers => @message_headers
133
+
134
+ @em.schedule {
135
+ init_client(uri) do |c|
136
+ c.stream { |msg| handle_message(msg, c) }
137
+
138
+ c.send_msg message.to_s
139
+ end
140
+ }
141
+
142
+ # TODO optional timeout for response ?
143
+ result = wait_for_result(message)
144
+
145
+ if result.size > 2
146
+ raise Exception, result[2]
147
+ end
148
+ return result[1]
149
+ end
150
+
151
+ # Instructs node to send rpc notification (immadiately returns / no response is generated)
152
+ #
153
+ # Implementation of {RJR::Node#notify}
154
+ #
155
+ # @param [String] uri location of node to send notification to, should be
156
+ # in format of ws://hostname:port
157
+ # @param [String] rpc_method json-rpc method to invoke on destination
158
+ # @param [Array] args array of arguments to convert to json and invoke remote method wtih
159
+ def notify(uri, rpc_method, *args)
160
+ # will block until message is published
161
+ published_l = Mutex.new
162
+ published_c = ConditionVariable.new
163
+
164
+ invoked = false
165
+ message = NotificationMessage.new :method => rpc_method,
166
+ :args => args,
167
+ :headers => @message_headers
168
+ @em.schedule {
169
+ init_client(uri) do |c|
170
+ c.stream { |msg| handle_message(msg, c) }
171
+
172
+ c.send_msg message.to_s
173
+
174
+ # XXX same issue w/ tcp node, due to nature of event machine
175
+ # we aren't guaranteed that message is actually written to socket
176
+ # here, process must be kept alive until data is sent or will be lost
177
+ published_l.synchronize { invoked = true ; published_c.signal }
178
+ end
179
+ }
180
+ published_l.synchronize { published_c.wait published_l unless invoked }
181
+ nil
182
+ end
183
+ end
184
+
185
+ end # module Nodes
186
+ end # module RJR
187
+ end # (!skip_module)
data/lib/rjr/stats.rb ADDED
@@ -0,0 +1,70 @@
1
+ # JSON-RPC method definitions providing access to inspect the internal
2
+ # node state.
3
+ #
4
+ # Note this isn't included in the top level rjr module by default,
5
+ # manually include this module to incorporate these additional rjr method
6
+ # definitions into your node
7
+ #
8
+ # Copyright (C) 2013 Mohammed Morsi <mo@morsi.org>
9
+ # Licensed under the Apache License, Version 2.0
10
+
11
+ require 'eventmachine'
12
+
13
+ # Helper method to process user params / select stats
14
+ # from a dispatcher
15
+ def select_stats(dispatcher, *filter)
16
+ lf = []
17
+ while q = filter.shift
18
+ lf <<
19
+ case q
20
+ when 'on_node' then
21
+ n = filter.shift
22
+ lambda { |req| req.rjr_node_type.to_s == n}
23
+
24
+ when "for_method" then
25
+ m = filter.shift
26
+ lambda { |req| req.rjr_method == m}
27
+
28
+ when 'successful' then
29
+ lambda { |req| req.result.success }
30
+
31
+ when 'failed' then
32
+ lambda { |req| req.result.failed }
33
+
34
+ end
35
+ end
36
+
37
+ dispatcher.requests.select { |ds| lf.all? { |lfi| lfi.call(ds) } }
38
+ end
39
+
40
+ # Add stats methods to specified dispatcher
41
+ def dispatch_stats(dispatcher)
42
+ # Retrieve all the dispatches this node served matching the specified criteri
43
+ dispatcher.handle "rjr::dispatches" do |filter|
44
+ select_stats(*filter)
45
+ end
46
+
47
+ # Retrieve the number of dispatches this node served matching the specified criteria
48
+ dispatcher.handle "rjr::num_dispatches" do |filter|
49
+ select_stats(*filter).size
50
+ end
51
+
52
+ # Retrieve the internal status of this node
53
+ dispatcher.handle "rjr::status" do
54
+ {
55
+ # event machine
56
+ :event_machine => { :running => EventMachine.reactor_running?,
57
+ :thread_status => RJR::EMAdapter.instance.rector_thread ?
58
+ RJR::EMAdapter.instance.reactor_thread.status :
59
+ nil,
60
+ :connections => EventMachine.connection_count },
61
+
62
+ # thread pool
63
+ :thread_pool => { :running => RJR::ThreadPool.instance.running? }
64
+ }
65
+ end
66
+
67
+ #:log =>
68
+ # lambda {},
69
+ end
70
+