rjr 0.19.1 → 0.19.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 517f79e571aae798dcc24be8feacd2ef5cbc3606
4
- data.tar.gz: 340a496e1f47092a7b01a4b1363bac1db87b9e81
3
+ metadata.gz: 67d23eb4f09f94c96a7d601c338b4d5088605c24
4
+ data.tar.gz: 53bbd5973ce99fa089c5d4bf560e2e9fe6c9005c
5
5
  SHA512:
6
- metadata.gz: 6b5e056c47862523e346cf2a47923146e0b15a3fb856071f20a4fa226d55fdb6046e5ec463c8aea4e356d96d49e707279d5b477549df2537041943aaa04513f7
7
- data.tar.gz: 325453d870c227bd35826a3bab5933fc6a62e644f1efbb2d5de3f762e917b24e38908aa15cfad2d7d3aae2d6d2f8af8da57a7bf527f9558570a78412747c5b60
6
+ metadata.gz: 9b8a199e88e55fd824cdabaeebf750cd234624ade0b9fc2acd9640aec729d5a3ab4339f5ef0769f707916a830f987d7a95c680e7e0ea07b4f526bd97b1394d3c
7
+ data.tar.gz: 55e17f0513fedef6f3e5dccae5673317bf5943988ae5d2146c7c2d76fa7aaee0ce003495c774ad3b623421efdff5127338d88cbf78792f3ecde9434dd9629f31
data/README.md CHANGED
@@ -166,3 +166,6 @@ RJR as this is not standard JSON-RPC.
166
166
  ### Authors ###
167
167
  * Mo Morsi <mo@morsi.org>
168
168
  * Ladislav Smola <ladislav.smola@it-logica.cz>
169
+ * André Dieb Martins <andre.dieb@gmail.com>
170
+ * Eric Bakan <eric@ebakan.com>
171
+ * Jonas Collberg <jonas.collberg@gmail.com>
data/Rakefile CHANGED
@@ -3,8 +3,6 @@
3
3
  # Copyright (C) 2010-2012 Mohammed Morsi <mo@morsi.org>
4
4
  # Licensed under the Apache License, Version 2.0
5
5
 
6
- # TODO fix travis build
7
-
8
6
  require "rspec/core/rake_task"
9
7
 
10
8
  desc "Run all specs"
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/ruby
2
+ # A RJR timeout example
3
+ #
4
+ # Copyright (C) 2015 Mohammed Morsi <mo@morsi.org>
5
+ # Licensed under the Apache License, Version 2.0
6
+
7
+ require 'rjr/nodes/tcp'
8
+
9
+ server = RJR::Nodes::TCP.new :host => 'localhost', :port => 9789, :node_id => "server"
10
+ server.dispatcher.handle('method') { |i|
11
+ sleep 2
12
+ }
13
+ server.listen
14
+
15
+ client = RJR::Nodes::TCP.new :node_id => "client",
16
+ :host => 'localhost',
17
+ :port => 9666,
18
+ :timeout => 1 # causes error, change to 3 to wait for server response
19
+
20
+ client.invoke "jsonrpc://localhost:9789", "method", "Hello World"
21
+ # => exception
22
+
23
+ #client.join
@@ -152,7 +152,7 @@ class Dispatcher
152
152
  request = Request.new args.merge(:rjr_handler => handler)
153
153
 
154
154
  # set request environment
155
- request.extend(environment) unless environment.nil?
155
+ request.set_env(environment) unless environment.nil?
156
156
 
157
157
  begin
158
158
  retval = request.handle
@@ -0,0 +1,50 @@
1
+ # RJR Intermediate Message
2
+ #
3
+ # Copyright (C) 2014-2015 Mohammed Morsi <mo@morsi.org>
4
+ # Licensed under the Apache License, Version 2.0
5
+
6
+ require 'json'
7
+ require 'rjr/util/json_parser'
8
+
9
+ module RJR
10
+ module Messages
11
+
12
+ # Intermediate representation of a JSON-RPC data containing
13
+ # extracted/parsed data which has not been analysed.
14
+ class Intermediate
15
+ # JSON from which data is extracted from
16
+ attr_accessor :json
17
+
18
+ # Data extracted from message
19
+ attr_accessor :data
20
+
21
+ def initialize(args = {})
22
+ @json = args[:json] || nil
23
+ @data = args[:data] || {}
24
+ end
25
+
26
+ def keys
27
+ data.keys
28
+ end
29
+
30
+ def [](key)
31
+ data[key.to_s]
32
+ end
33
+
34
+ def has?(key)
35
+ data.key?(key)
36
+ end
37
+
38
+ def self.parse(json)
39
+ parsed = nil
40
+
41
+ #allow parsing errs to propagate up
42
+ parsed = JSONParser.parse(json)
43
+
44
+ self.new :json => json,
45
+ :data => parsed
46
+ end
47
+
48
+ end # class Intermediate
49
+ end # module Messages
50
+ end # module RJR
@@ -13,7 +13,7 @@ module Messages
13
13
  # indicate the result should _not_ be returned
14
14
  class Notification
15
15
  # Message string received from the source
16
- attr_accessor :json_message
16
+ attr_accessor :message
17
17
 
18
18
  # Method source is invoking on the destination
19
19
  attr_accessor :jr_method
@@ -48,18 +48,17 @@ class Notification
48
48
  end
49
49
 
50
50
  def parse_message(message)
51
- @json_message = message
52
- notification = JSONParser.parse(@json_message)
53
- @jr_method = notification['method']
54
- @jr_args = notification['params']
51
+ @message = message
52
+ @jr_method = message['method']
53
+ @jr_args = message['params']
55
54
 
56
- parse_headers(notification)
55
+ parse_headers(message)
57
56
  end
58
57
 
59
- def parse_headers(notification)
60
- notification.keys.select { |k|
58
+ def parse_headers(message)
59
+ message.keys.select { |k|
61
60
  !['jsonrpc', 'method', 'params'].include?(k)
62
- }.each { |k| @headers[k] = notification[k] }
61
+ }.each { |k| @headers[k] = message[k] }
63
62
  end
64
63
 
65
64
  public
@@ -70,13 +69,7 @@ class Notification
70
69
  # @param [String] message string message to check
71
70
  # @return [true,false] indicating if message is a notification message
72
71
  def self.is_notification_message?(message)
73
- begin
74
- # FIXME log error
75
- parsed = JSONParser.parse(message)
76
- parsed.has_key?('method') && !parsed.has_key?('id')
77
- rescue Exception => e
78
- false
79
- end
72
+ message.has?('method') && !message.has?('id')
80
73
  end
81
74
 
82
75
  # Convert notification message to json
@@ -12,7 +12,7 @@ module Messages
12
12
  # Message sent from client to server to invoke a JSON-RPC method
13
13
  class Request
14
14
  # Message string received from the source
15
- attr_accessor :json_message
15
+ attr_accessor :message
16
16
 
17
17
  # Method source is invoking on the destination
18
18
  attr_accessor :jr_method
@@ -51,35 +51,29 @@ class Request
51
51
  end
52
52
 
53
53
  def parse_message(message)
54
- @json_message = message
55
- request = JSONParser.parse(@json_message)
56
- @jr_method = request['method']
57
- @jr_args = request['params']
58
- @msg_id = request['id']
54
+ @message = message
55
+ @jr_method = message['method']
56
+ @jr_args = message['params']
57
+ @msg_id = message['id']
59
58
 
60
- parse_headers(request)
59
+ parse_headers(message)
61
60
  end
62
61
 
63
- def parse_headers(request)
64
- request.keys.select { |k|
62
+ def parse_headers(message)
63
+ message.keys.select { |k|
65
64
  !['jsonrpc', 'id', 'method', 'params'].include?(k)
66
- }.each { |k| @headers[k] = request[k] }
65
+ }.each { |k| @headers[k] = message[k] }
67
66
  end
68
67
 
69
68
  public
70
69
 
71
- # Class helper to determine if the specified string is a valid json-rpc
72
- # method request
73
- # @param [String] message string message to check
70
+ # Class helper to determine if the specified message is a valid
71
+ # json-rpc method request message.
72
+ #
73
+ # @param [Message] message to check
74
74
  # @return [true,false] indicating if message is request message
75
75
  def self.is_request_message?(message)
76
- begin
77
- # FIXME log error
78
- parsed = JSONParser.parse(message)
79
- parsed.has_key?('method') && parsed.has_key?('id')
80
- rescue Exception => e
81
- false
82
- end
76
+ message.has?('method') && message.has?('id')
83
77
  end
84
78
 
85
79
  # Convert request message to json
@@ -13,7 +13,7 @@ module Messages
13
13
  # Message sent from server to client in response to a JSON-RPC request
14
14
  class Response
15
15
  # Message string received from the source
16
- attr_accessor :json_message
16
+ attr_accessor :message
17
17
 
18
18
  # ID of the message in accordance w/ json-rpc specification
19
19
  attr_accessor :msg_id
@@ -49,54 +49,47 @@ class Response
49
49
  end
50
50
 
51
51
  def parse_message(message)
52
- @json_message = message
53
- response = JSONParser.parse(@json_message)
54
- @msg_id = response['id']
52
+ @message = message
53
+ @msg_id = message['id']
55
54
 
56
- parse_result(response)
57
- parse_headers(response)
55
+ parse_result(message)
56
+ parse_headers(message)
58
57
  end
59
58
 
60
- def parse_result(response)
59
+ def parse_result(message)
61
60
  @result = Result.new
62
- @result.success = response.has_key?('result')
61
+ @result.success = message.has?('result')
63
62
  @result.failed = !@result.success
64
63
 
65
64
  if @result.success
66
- @result.result = response['result']
65
+ @result.result = message['result']
67
66
 
68
- elsif response.has_key?('error')
69
- @result.error_code = response['error']['code']
70
- @result.error_msg = response['error']['message']
67
+ elsif message.has?('error')
68
+ @result.error_code = message['error']['code']
69
+ @result.error_msg = message['error']['message']
71
70
 
72
71
  # TODO can we safely constantize this ?
73
- @result.error_class = response['error']['class']
72
+ @result.error_class = message['error']['class']
74
73
  end
75
74
 
76
75
  @result
77
76
  end
78
77
 
79
- def parse_headers(request)
80
- request.keys.select { |k|
78
+ def parse_headers(message)
79
+ message.keys.select { |k|
81
80
  !['jsonrpc', 'id', 'method', 'result', 'error'].include?(k)
82
- }.each { |k| @headers[k] = request[k] }
81
+ }.each { |k| @headers[k] = message[k] }
83
82
  end
84
83
 
85
84
  public
86
85
 
87
- # Class helper to determine if the specified string is a valid json-rpc
88
- # method response
86
+ # Class helper to determine if the specified string is a
87
+ # valid json-rpc method response
88
+ #
89
89
  # @param [String] message string message to check
90
90
  # @return [true,false] indicating if message is response message
91
91
  def self.is_response_message?(message)
92
- begin
93
- # FIXME log error
94
- json = JSONParser.parse(message)
95
- json.has_key?('result') || json.has_key?('error')
96
- rescue Exception => e
97
- puts e.to_s
98
- false
99
- end
92
+ message.has?('result') || message.has?('error')
100
93
  end
101
94
 
102
95
  def success_json
data/lib/rjr/messages.rb CHANGED
@@ -6,3 +6,4 @@
6
6
  require 'rjr/messages/request'
7
7
  require 'rjr/messages/response'
8
8
  require 'rjr/messages/notification'
9
+ require 'rjr/messages/intermediate'
data/lib/rjr/node.rb CHANGED
@@ -104,9 +104,12 @@ class Node
104
104
  clear_event_handlers
105
105
  @response_lock = Mutex.new
106
106
  @response_cv = ConditionVariable.new
107
+ @pending = {}
107
108
  @responses = []
108
109
 
109
110
  @node_id = args[:node_id]
111
+ @timeout = args[:timeout]
112
+ @wait_interval = args[:wait_interval] || 0.01
110
113
  @dispatcher = args[:dispatcher] || RJR::Dispatcher.new
111
114
  @message_headers = args.has_key?(:headers) ? {}.merge(args[:headers]) : {}
112
115
 
@@ -143,14 +146,20 @@ class Node
143
146
  ##################################################################
144
147
  # Reset connection event handlers
145
148
  def clear_event_handlers
146
- @connection_event_handlers = {:closed => [], :error => []}
149
+ @connection_event_handlers = {
150
+ :opened => [],
151
+ :closed => [],
152
+ :error => []
153
+ }
147
154
  end
148
155
 
149
156
  # Register connection event handler
150
- # @param [:error, :close] event the event to register the handler for
151
- # @param [Callable] handler block param to be added to array of handlers
152
- # that are called when event occurs
153
- # @yield [Node] self is passed to each registered handler when event occurs
157
+ # @param event [:opened, :closed, :error] the event to register the handler
158
+ # for
159
+ # @param handler [Callable] block param to be added to array of handlers
160
+ # that are called when event occurs
161
+ # @yield [Node, *args] self and event-specific *args are passed to each
162
+ # registered handler when event occurs
154
163
  def on(event, &handler)
155
164
  return unless @connection_event_handlers.keys.include?(event)
156
165
  @connection_event_handlers[event] << handler
@@ -158,12 +167,11 @@ class Node
158
167
 
159
168
  private
160
169
 
161
- # Internal helper, run connection event handlers for specified event
162
- def connection_event(event)
170
+ # Internal helper, run connection event handlers for specified event, passing
171
+ # self and args to handler
172
+ def connection_event(event, *args)
163
173
  return unless @connection_event_handlers.keys.include?(event)
164
- @connection_event_handlers[event].each { |h|
165
- h.call self
166
- }
174
+ @connection_event_handlers[event].each { |h| h.call(self, *args) }
167
175
  end
168
176
 
169
177
  ##################################################################
@@ -183,28 +191,36 @@ class Node
183
191
 
184
192
  # Internal helper, handle message received
185
193
  def handle_message(msg, connection = {})
186
- if Messages::Request.is_request_message?(msg)
187
- tp << ThreadPoolJob.new(msg) { |m| handle_request(m, false, connection) }
194
+ intermediate = Messages::Intermediate.parse(msg)
195
+
196
+ if Messages::Request.is_request_message?(intermediate)
197
+ tp << ThreadPoolJob.new(intermediate) { |i|
198
+ handle_request(i, false, connection)
199
+ }
188
200
 
189
- elsif Messages::Notification.is_notification_message?(msg)
190
- tp << ThreadPoolJob.new(msg) { |m| handle_request(m, true, connection) }
201
+ elsif Messages::Notification.is_notification_message?(intermediate)
202
+ tp << ThreadPoolJob.new(intermediate) { |i|
203
+ handle_request(i, true, connection)
204
+ }
191
205
 
192
- elsif Messages::Response.is_response_message?(msg)
193
- handle_response(msg)
206
+ elsif Messages::Response.is_response_message?(intermediate)
207
+ handle_response(intermediate)
194
208
 
195
209
  end
210
+
211
+ intermediate
196
212
  end
197
213
 
198
214
  # Internal helper, handle request message received
199
- def handle_request(data, notification=false, connection={})
215
+ def handle_request(message, notification=false, connection={})
200
216
  # get client for the specified connection
201
217
  # TODO should grap port/ip immediately on connection and use that
202
218
  client_port,client_ip = client_for(connection)
203
219
 
204
220
  msg = notification ?
205
- Messages::Notification.new(:message => data,
221
+ Messages::Notification.new(:message => message,
206
222
  :headers => @message_headers) :
207
- Messages::Request.new(:message => data,
223
+ Messages::Request.new(:message => message,
208
224
  :headers => @message_headers)
209
225
 
210
226
  callback = NodeCallback.new(:node => self,
@@ -233,8 +249,8 @@ class Node
233
249
  end
234
250
 
235
251
  # Internal helper, handle response message received
236
- def handle_response(data)
237
- msg = Messages::Response.new(:message => data,
252
+ def handle_response(message)
253
+ msg = Messages::Response.new(:message => message,
238
254
  :headers => self.message_headers)
239
255
  res = err = nil
240
256
  begin
@@ -254,25 +270,25 @@ class Node
254
270
  # Internal helper, block until response matching message id is received
255
271
  def wait_for_result(message)
256
272
  res = nil
273
+ message_id = message.msg_id
274
+ @pending[message_id] = Time.now
257
275
  while res.nil?
258
276
  @response_lock.synchronize{
259
- # FIXME throw err if more than 1 match found
277
+ # Prune messages that timed out
278
+ if @timeout
279
+ now = Time.now
280
+ @pending.delete_if { |_, start_time| (now - start_time) > @timeout }
281
+ end
282
+ pending_ids = @pending.keys
283
+ raise Exception, 'Timed out' unless pending_ids.include? message_id
284
+
285
+ # Prune invalid responses
286
+ @responses.keep_if { |response| @pending.has_key? response.first }
260
287
  res = @responses.find { |response| message.msg_id == response.first }
261
288
  if !res.nil?
262
289
  @responses.delete(res)
263
-
264
290
  else
265
- # FIXME if halt is invoked while this is sleeping, all other threads
266
- # may be deleted resulting in this sleeping indefinetly and a deadlock
267
-
268
- # TODO wait for a finite # of seconds, record time we started waiting
269
- # before while loop and on every iteration check to see if we've been
270
- # waiting longer than an optional timeout. If so throw an error (also
271
- # need mechanism to discard result if it comes in later).
272
- # finite # of seconds we wait and optional timeout should be
273
- # configurable on node class
274
- @response_cv.wait @response_lock
275
-
291
+ @response_cv.wait @response_lock, @wait_interval
276
292
  end
277
293
  }
278
294
  end
@@ -56,6 +56,17 @@ class AMQP < RJR::Node
56
56
 
57
57
  private
58
58
 
59
+ def amqp_options
60
+ opts = {}
61
+ opts[:host] = @host if @host
62
+ opts[:port] = @port if @port
63
+ opts[:vhost] = @vhost if @vhost
64
+ opts[:user] = @user if @user
65
+ opts[:pass] = @pass if @pass
66
+ opts[:ssl] = @ssl if @ssl
67
+ opts
68
+ end
69
+
59
70
  # Internal helper, initialize the amqp subsystem
60
71
  def init_node(&on_init)
61
72
  if !@conn.nil? && @conn.connected?
@@ -63,7 +74,7 @@ class AMQP < RJR::Node
63
74
  return
64
75
  end
65
76
 
66
- @conn = ::AMQP.connect(:host => @broker) do |conn|
77
+ @conn = ::AMQP.connect(amqp_options) do |conn|
67
78
  ::AMQP.connection = conn # XXX not sure why this is needed but the amqp
68
79
  # em interface won't shut down cleanly otherwise
69
80
 
@@ -117,12 +128,17 @@ class AMQP < RJR::Node
117
128
  # @option args [String] :broker the amqp message broker which to connect to
118
129
  def initialize(args = {})
119
130
  super(args)
120
- @broker = args[:broker]
131
+ @host = args[:host] || args[:broker]
132
+ @port = args[:port]
133
+ @vhost = args[:vhost]
134
+ @user = args[:user] || args[:username]
135
+ @pass = args[:pass] || args[:password]
136
+ @ssl = args[:ssl]
121
137
  @amqp_lock = Mutex.new
122
138
  end
123
139
 
124
140
  def to_s
125
- "RJR::Nodes::AMQP<#{@node_id},#{@broker},#{@queue_name}>"
141
+ "RJR::Nodes::AMQP<#{@node_id},#{@host},#{@port},#{@vhost},#{@queue_name}>"
126
142
  end
127
143
 
128
144
  # Publish a message using the amqp exchange
@@ -61,8 +61,9 @@ class Local < RJR::Node
61
61
  # Implementation of RJR::Node#send_msg
62
62
  def send_msg(msg, connection)
63
63
  # ignore response message
64
- unless Messages::Response.is_response_message?(msg)
65
- launch_request(msg, true) # .join?
64
+ inter = Messages::Intermediate.parse(msg)
65
+ unless Messages::Response.is_response_message?(inter)
66
+ launch_request(inter, true) # .join?
66
67
  end
67
68
  end
68
69
 
@@ -80,9 +81,15 @@ class Local < RJR::Node
80
81
  # (or close to it, globals will still be available, but locks will
81
82
  # not be locally held, etc)
82
83
  def launch_request(req, notification)
83
- Thread.new(req,notification) { |req,notification|
84
- res = handle_request(req, notification, nil)
85
- handle_response(res.to_s) unless res.nil?
84
+ Thread.new(req, notification) { |req, notification|
85
+ inter = req.is_a?(Messages::Intermediate) ? req :
86
+ Messages::Intermediate.parse(req)
87
+ res = handle_request(inter, notification, nil)
88
+
89
+ unless res.nil?
90
+ inter = Messages::Intermediate.parse(res.to_s)
91
+ handle_response(inter)
92
+ end
86
93
  }
87
94
  end
88
95
 
data/lib/rjr/nodes/tcp.rb CHANGED
@@ -33,6 +33,11 @@ class TCPConnection < EventMachine::Connection
33
33
 
34
34
  @send_lock = Mutex.new
35
35
  @data = ""
36
+ @rjr_node.add_connection(self)
37
+ end
38
+
39
+ def post_init
40
+ @rjr_node.send(:connection_event, :opened, self)
36
41
  end
37
42
 
38
43
  # EventMachine::Connection#receive_data callback, handle request / response messages
@@ -54,6 +59,10 @@ class TCPConnection < EventMachine::Connection
54
59
  }
55
60
  end
56
61
 
62
+ def unbind
63
+ @rjr_node.remove_connection(self)
64
+ @rjr_node.send(:connection_event, :closed, self)
65
+ end
57
66
  end
58
67
 
59
68
  # TCP node definition, listen for and invoke json-rpc requests via TCP sockets
@@ -89,18 +98,8 @@ class TCP < RJR::Node
89
98
  # Internal helper, initialize new client
90
99
  def init_client(args={}, &on_init)
91
100
  host,port = args[:host], args[:port]
92
- connection = nil
93
- @connections_lock.synchronize {
94
- connection = @connections.find { |c|
95
- port == c.port && host == c.host
96
- }
97
- if connection.nil?
98
- connection =
99
- EventMachine::connect host, port,
100
- TCPConnection, args
101
- @connections << connection
102
- end
103
- }
101
+ connection = @connections.find { |c| port == c.port && host == c.host }
102
+ connection ||= EventMachine::connect(host, port, TCPConnection, args)
104
103
  on_init.call(connection) # TODO move to tcpnode event ?
105
104
  end
106
105
 
@@ -140,6 +139,20 @@ class TCP < RJR::Node
140
139
  self
141
140
  end
142
141
 
142
+ # Called by TCPConnection::initialize
143
+ def add_connection(connection)
144
+ @connections_lock.synchronize do
145
+ connections << connection
146
+ end
147
+ end
148
+
149
+ # Called by TCPConnection::unbind
150
+ def remove_connection(connection)
151
+ @connections_lock.synchronize do
152
+ connections.delete(connection)
153
+ end
154
+ end
155
+
143
156
  # Instructs node to send rpc request, and wait for / return response.
144
157
  #
145
158
  # Implementation of RJR::Node#invoke
data/lib/rjr/nodes/web.rb CHANGED
@@ -52,12 +52,13 @@ class WebConnection < EventMachine::Connection
52
52
  def process_http_request
53
53
  # TODO support http protocols other than POST
54
54
  msg = @http_post_content.nil? ? '' : @http_post_content
55
- @rjr_node.send(:handle_message, msg, self) # XXX private method
55
+ inter = @rjr_node.send(:handle_message, msg, self) # XXX private method
56
56
 
57
57
  # XXX we still have to send a response back to client to satisfy
58
58
  # the http standard, even if this is a notification. handle_message
59
59
  # does not do this.
60
- @rjr_node.send_msg "", self if Messages::Notification.is_notification_message?(msg)
60
+ notification = Messages::Notification.is_notification_message?(inter)
61
+ @rjr_node.send_msg "", self if notification
61
62
  end
62
63
  end
63
64
 
data/lib/rjr/request.rb CHANGED
@@ -50,6 +50,9 @@ class Request
50
50
  # ID of node which request came in on
51
51
  attr_accessor :rjr_node_id
52
52
 
53
+ # Environment handler will be run in
54
+ attr_accessor :rjr_env
55
+
53
56
  # Actual proc registered to handle request
54
57
  attr_accessor :rjr_handler
55
58
 
@@ -73,7 +76,14 @@ class Request
73
76
  @rjr_handler = args[:rjr_handler]
74
77
 
75
78
  @rjr_args = Arguments.new :args => @rjr_method_args
76
- @result = nil
79
+ @rjr_env = nil
80
+ @result = nil
81
+ end
82
+
83
+ # Set the environment by extending Request instance with the specified module
84
+ def set_env(env)
85
+ @rjr_env = env
86
+ self.extend(env)
77
87
  end
78
88
 
79
89
  # Invoke the request by calling the registered handler with the registered