arachni-rpc-em 0.1.3 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,161 @@
1
+ =begin
2
+
3
+ This file is part of the Arachni-RPC EM project and may be subject to
4
+ redistribution and commercial restrictions. Please see the Arachni-RPC EM
5
+ web site for more information on licensing and terms of use.
6
+
7
+ =end
8
+
9
+ module Arachni
10
+ module RPC::EM
11
+ class Server
12
+
13
+ #
14
+ # Receives `Arachni::RPC::Request` objects and transmits `Arachni::RPC::Response`
15
+ # objects.
16
+ #
17
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
18
+ #
19
+ class Handler < EventMachine::Connection
20
+ include Protocol
21
+ include ConnectionUtilities
22
+ include ::Arachni::RPC::Exceptions
23
+
24
+ #INACTIVITY_TIMEOUT = 10
25
+
26
+ # @return [Arachni::RPC::Request] Working RPC request.
27
+ attr_reader :request
28
+
29
+ # @param [Server] server RPC server.
30
+ def initialize( server )
31
+ super
32
+
33
+ @server = server
34
+ @opts = server.opts.dup
35
+ @request = nil
36
+
37
+ assume_server_role!
38
+
39
+ # Do not tolerate long periods of inactivity in order to avoid zombie
40
+ # connections.
41
+ #set_comm_inactivity_timeout( INACTIVITY_TIMEOUT )
42
+ end
43
+
44
+ # Initializes an SSL session once the connection has been established.
45
+ #
46
+ # @private
47
+ def post_init
48
+ start_ssl
49
+ end
50
+
51
+ # Handles closed connections and cleans up the SSL session.
52
+ #
53
+ # @private
54
+ def unbind
55
+ end_ssl
56
+ @server = nil
57
+ end
58
+
59
+ #
60
+ # * Handles a `Arachni::RPC::Request`
61
+ # * Sets the {#request}.
62
+ # * Sends back a `Arachni::RPC::Response`.
63
+ #
64
+ # @param [Arachni::RPC::EM::Request] req
65
+ #
66
+ def receive_request( req )
67
+ @request = req
68
+
69
+ # Create an empty response to be filled in little by little.
70
+ res = ::Arachni::RPC::Response.new
71
+ peer = peer_ip_addr
72
+
73
+ begin
74
+ # Make sure the client is allowed to make RPC calls.
75
+ authenticate!
76
+
77
+ # Grab the partially filled in response which includes the result
78
+ # of the RPC call and merge it with out prepared response.
79
+ res.merge!( @server.call( self ) )
80
+
81
+ # Handle exceptions and convert them to a simple hash, ready to be
82
+ # passed to the client.
83
+ rescue Exception => e
84
+ type = ''
85
+
86
+ # If it's an RPC exception pass the type along as is...
87
+ if e.rpc_exception?
88
+ type = e.class.name.split( ':' )[-1]
89
+
90
+ # ...otherwise set it to a RemoteException.
91
+ else
92
+ type = 'RemoteException'
93
+ end
94
+
95
+ # RPC conventions for exception transmission.
96
+ res.obj = {
97
+ 'exception' => e.to_s,
98
+ 'backtrace' => e.backtrace,
99
+ 'type' => type
100
+ }
101
+
102
+ msg = "#{e.to_s}\n#{e.backtrace.join( "\n" )}"
103
+ @server.logger.error( 'Exception' ){ msg + " [on behalf of #{peer}]" }
104
+ end
105
+
106
+ # Pass the result of the RPC call back to the client but *only* if it
107
+ # wasn't async, otherwise {Server#call} will have already taken care of it
108
+ send_response( res ) if !res.async?
109
+ end
110
+
111
+ private
112
+
113
+ # @param [Symbol] severity Severity of the logged event:
114
+ # * `:debug`
115
+ # * `:info`
116
+ # * `:warn`
117
+ # * `:error`
118
+ # * `:fatal`
119
+ # * `:unknown`
120
+ #
121
+ # @param [String] category Category of message (SSL, Call, etc.).
122
+ # @param [String] msg Message to log.
123
+ #
124
+ def log( severity, category, msg )
125
+ sev_sym = Logger.const_get( severity.to_s.upcase.to_sym )
126
+ @server.logger.add( sev_sym, msg, category )
127
+ end
128
+
129
+ #
130
+ # Authenticates the client based on the token in the request.
131
+ #
132
+ # It will raise an exception if the token doesn't check-out.
133
+ #
134
+ def authenticate!
135
+ if !valid_token?( @request.token )
136
+
137
+ msg = 'Token missing or invalid while calling: ' + @request.message
138
+
139
+ @server.logger.error( 'Authenticator' ){
140
+ msg + " [on behalf of #{peer_ip_addr}]"
141
+ }
142
+
143
+ fail InvalidToken.new( msg )
144
+ end
145
+ end
146
+
147
+ #
148
+ # Compares the authentication token in the param with the one of the server.
149
+ #
150
+ # @param [String] token
151
+ #
152
+ # @return [Bool]
153
+ #
154
+ def valid_token?( token )
155
+ token == @server.token
156
+ end
157
+
158
+ end
159
+ end
160
+ end
161
+ end
@@ -9,7 +9,7 @@
9
9
  module Arachni
10
10
  module RPC
11
11
  module EM
12
- VERSION = '0.1.3'
12
+ VERSION = '0.2'
13
13
  end
14
14
  end
15
15
  end
@@ -1,4 +1,4 @@
1
- require File.join( File.expand_path( File.dirname( __FILE__ ) ), '../../../', 'spec_helper' )
1
+ require 'spec_helper'
2
2
 
3
3
  describe Arachni::RPC::EM::Client do
4
4
 
@@ -8,19 +8,20 @@ describe Arachni::RPC::EM::Client do
8
8
  ]
9
9
  end
10
10
 
11
- it "should be able to retain stability and consistency under heavy load" do
11
+ it 'retains stability and consistency under heavy load' do
12
12
  client = start_client( rpc_opts )
13
13
 
14
- n = 400
15
- cnt = 0
14
+ n = 100_000
15
+ cnt = 0
16
16
 
17
17
  mismatches = []
18
18
 
19
19
  n.times do |i|
20
- client.call( 'test.foo', i ) do |res|
20
+ arg = 'a' * i
21
+ client.call( 'test.foo', arg ) do |res|
21
22
  cnt += 1
22
- mismatches << [i, res] if i != res
23
- ::EM.stop if cnt == n || !mismatches.empty?
23
+ mismatches << [i, arg, res] if arg != res
24
+ ::EM.stop if cnt == n || mismatches.any?
24
25
  end
25
26
  end
26
27
 
@@ -29,24 +30,103 @@ describe Arachni::RPC::EM::Client do
29
30
  mismatches.should be_empty
30
31
  end
31
32
 
32
- it "should throw error when connecting to inexistent server" do
33
- start_client( rpc_opts.merge( :host => 'dddd', :port => 999339 ) ).call( 'test.foo', @arg ) do |res|
34
- res.rpc_connection_error?.should be_true
35
- ::EM.stop
33
+ describe '#initialize' do
34
+ it 'should be able to properly assign class options (including :role)' do
35
+ opts = rpc_opts.merge( role: :client )
36
+ start_client( opts ).opts.should == opts
36
37
  end
37
- Arachni::RPC::EM.block
38
38
 
39
- start_client( rpc_opts.merge( :port => 999339 ) ).call( 'test.foo', @arg ) do |res|
40
- res.rpc_connection_error?.should be_true
41
- ::EM.stop
39
+ context 'when passed no connection information' do
40
+ it 'raises ArgumentError' do
41
+ begin
42
+ described_class.new({})
43
+ rescue => e
44
+ e.should be_kind_of ArgumentError
45
+ end
46
+ end
42
47
  end
43
- Arachni::RPC::EM.block
44
- end
45
48
 
46
- describe "#initialize" do
47
- it "should be able to properly assign class options (including :role)" do
48
- opts = rpc_opts.merge( :role => :client )
49
- start_client( opts ).opts.should == opts
49
+ describe 'option' do
50
+ describe :socket do
51
+ it 'connects to it' do
52
+ client = start_client( rpc_opts_with_socket )
53
+ client.call( 'test.foo', 1 ).should == 1
54
+ end
55
+
56
+ context 'and connecting to a non-existent server' do
57
+ it 'returns Arachni::RPC::Exceptions::ConnectionError' do
58
+ options = rpc_opts_with_socket.merge( socket: '/' )
59
+ start_client( options ).call( 'test.foo', @arg ) do |res|
60
+ res.rpc_connection_error?.should be_true
61
+ res.should be_kind_of Arachni::RPC::Exceptions::ConnectionError
62
+ ::EM.stop
63
+ end
64
+ Arachni::RPC::EM.block
65
+ end
66
+ end
67
+
68
+ it 'retains stability and consistency under heavy load' do
69
+ client = start_client( rpc_opts_with_socket )
70
+
71
+ n = 100_000
72
+ cnt = 0
73
+
74
+ mismatches = []
75
+
76
+ n.times do |i|
77
+ arg = 'a' * i
78
+ client.call( 'test.foo', arg ) do |res|
79
+ cnt += 1
80
+ mismatches << [i, arg, res] if arg != res
81
+ ::EM.stop if cnt == n || mismatches.any?
82
+ end
83
+ end
84
+
85
+ Arachni::RPC::EM.block
86
+ cnt.should > 0
87
+ mismatches.should be_empty
88
+ end
89
+
90
+ context 'when passed an invalid socket path' do
91
+ it 'raises ArgumentError' do
92
+ begin
93
+ described_class.new( socket: 'blah' )
94
+ rescue => e
95
+ e.should be_kind_of ArgumentError
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ context 'when passed a host but not a port' do
103
+ it 'raises ArgumentError' do
104
+ begin
105
+ described_class.new( host: 'test' )
106
+ rescue => e
107
+ e.should be_kind_of ArgumentError
108
+ end
109
+ end
110
+ end
111
+
112
+ context 'when passed a port but not a host' do
113
+ it 'raises ArgumentError' do
114
+ begin
115
+ described_class.new( port: 9999 )
116
+ rescue => e
117
+ e.should be_kind_of ArgumentError
118
+ end
119
+ end
120
+ end
121
+
122
+ context 'when passed an invalid port' do
123
+ it 'raises ArgumentError' do
124
+ begin
125
+ described_class.new( host: 'tt', port: 'blah' )
126
+ rescue => e
127
+ e.should be_kind_of ArgumentError
128
+ end
129
+ end
50
130
  end
51
131
  end
52
132
 
@@ -60,18 +140,15 @@ describe Arachni::RPC::EM::Client do
60
140
  start_client( opts ).call( 'test.foo', @arg ).should == @arg
61
141
  end
62
142
  end
63
-
64
143
  end
65
144
 
66
- describe "raw interface" do
67
-
68
- context "when using Threads" do
69
-
70
- it "should be able to perform synchronous calls" do
145
+ describe 'raw interface' do
146
+ context 'when using Threads' do
147
+ it 'should be able to perform synchronous calls' do
71
148
  @arg.should == start_client( rpc_opts ).call( 'test.foo', @arg )
72
149
  end
73
150
 
74
- it "should be able to perform asynchronous calls" do
151
+ it 'should be able to perform asynchronous calls' do
75
152
  start_client( rpc_opts ).call( 'test.foo', @arg ) do |res|
76
153
  @arg.should == res
77
154
  ::EM.stop
@@ -80,9 +157,8 @@ describe Arachni::RPC::EM::Client do
80
157
  end
81
158
  end
82
159
 
83
- context "when run inside the Reactor loop" do
84
-
85
- it "should be able to perform synchronous calls" do
160
+ context 'when run inside the Reactor loop' do
161
+ it 'should be able to perform synchronous calls' do
86
162
  ::EM.run {
87
163
  ::Arachni::RPC::EM::Synchrony.run do
88
164
  @arg.should == start_client( rpc_opts ).call( 'test.foo', @arg )
@@ -91,7 +167,7 @@ describe Arachni::RPC::EM::Client do
91
167
  }
92
168
  end
93
169
 
94
- it "should be able to perform asynchronous calls" do
170
+ it 'should be able to perform asynchronous calls' do
95
171
  ::EM.run {
96
172
  start_client( rpc_opts ).call( 'test.foo', @arg ) do |res|
97
173
  res.should == @arg
@@ -103,14 +179,13 @@ describe Arachni::RPC::EM::Client do
103
179
  end
104
180
  end
105
181
 
106
- describe "Arachni::RPC::RemoteObjectMapper interface" do
107
- it "should be able to properly forward synchronous calls" do
182
+ describe 'Arachni::RPC::RemoteObjectMapper interface' do
183
+ it 'should be able to properly forward synchronous calls' do
108
184
  test = Arachni::RPC::RemoteObjectMapper.new( start_client( rpc_opts ), 'test' )
109
185
  test.foo( @arg ).should == @arg
110
- # ::EM.stop
111
186
  end
112
187
 
113
- it "should be able to properly forward synchronous calls" do
188
+ it 'should be able to properly forward synchronous calls' do
114
189
  test = Arachni::RPC::RemoteObjectMapper.new( start_client( rpc_opts ), 'test' )
115
190
  test.foo( @arg ) do |res|
116
191
  res.should == @arg
@@ -120,75 +195,110 @@ describe Arachni::RPC::EM::Client do
120
195
  end
121
196
  end
122
197
 
123
- describe "exception" do
124
- context 'when performing asynchronous calls' do
198
+ context 'when performing an asynchronous call' do
199
+ context 'and connecting to a non-existent server' do
200
+ it 'returns Arachni::RPC::Exceptions::ConnectionError' do
201
+ options = rpc_opts.merge( host: 'dddd', port: 999339 )
202
+ start_client( options ).call( 'test.foo', @arg ) do |res|
203
+ res.rpc_connection_error?.should be_true
204
+ res.should be_kind_of Arachni::RPC::Exceptions::ConnectionError
205
+ ::EM.stop
206
+ end
207
+ Arachni::RPC::EM.block
208
+ end
209
+ end
125
210
 
126
- it "should be returned when requesting inexistent objects" do
211
+ context 'and requesting a non-existent object' do
212
+ it 'returns Arachni::RPC::Exceptions::InvalidObject' do
127
213
  start_client( rpc_opts ).call( 'bar.foo' ) do |res|
128
214
  res.rpc_invalid_object_error?.should be_true
215
+ res.should be_kind_of Arachni::RPC::Exceptions::InvalidObject
129
216
  ::EM.stop
130
217
  end
131
218
  Arachni::RPC::EM.block
132
219
  end
220
+ end
133
221
 
134
- it "should be returned when requesting inexistent or non-public methods" do
222
+ context 'and requesting a non-public method' do
223
+ it 'returns Arachni::RPC::Exceptions::InvalidMethod' do
135
224
  start_client( rpc_opts ).call( 'test.bar' ) do |res|
136
225
  res.rpc_invalid_method_error?.should be_true
226
+ res.should be_kind_of Arachni::RPC::Exceptions::InvalidMethod
137
227
  ::EM.stop
138
228
  end
139
229
  Arachni::RPC::EM.block
140
230
  end
231
+ end
141
232
 
142
- it "should be returned when there's a remote exception" do
233
+ context 'and there is a remote exception' do
234
+ it 'returns Arachni::RPC::Exceptions::RemoteException' do
143
235
  start_client( rpc_opts ).call( 'test.foo' ) do |res|
144
236
  res.rpc_remote_exception?.should be_true
237
+ res.should be_kind_of Arachni::RPC::Exceptions::RemoteException
145
238
  ::EM.stop
146
239
  end
147
240
  Arachni::RPC::EM.block
148
241
  end
149
-
150
242
  end
243
+ end
151
244
 
152
- context 'when performing synchronous calls' do
153
-
154
- it "should be raised when requesting inexistent objects" do
245
+ context 'when performing a synchronous call' do
246
+ #context 'and connecting to a non-existent server' do
247
+ # it 'raises Arachni::RPC::Exceptions::ConnectionError' do
248
+ # begin
249
+ # options = rpc_opts.merge( host: 'dddd', port: 999339 )
250
+ # start_client( options ).call( 'test.foo', @arg )
251
+ # rescue => e
252
+ # e.rpc_connection_error?.should be_true
253
+ # e.should be_kind_of Arachni::RPC::Exceptions::ConnectionError
254
+ # end
255
+ # end
256
+ #end
257
+
258
+ context 'and requesting a non-existent object' do
259
+ it 'raises Arachni::RPC::Exceptions::InvalidObject' do
155
260
  begin
156
261
  start_client( rpc_opts ).call( 'bar2.foo' )
157
262
  rescue Exception => e
158
263
  e.rpc_invalid_object_error?.should be_true
264
+ e.should be_kind_of Arachni::RPC::Exceptions::InvalidObject
159
265
  end
160
266
  end
267
+ end
161
268
 
162
- it "should be raised when requesting inexistent or non-public methods" do
269
+ context 'and requesting a non-public method' do
270
+ it 'raises Arachni::RPC::Exceptions::InvalidMethod' do
163
271
  begin
164
272
  start_client( rpc_opts ).call( 'test.bar2' )
165
273
  rescue Exception => e
166
274
  e.rpc_invalid_method_error?.should be_true
275
+ e.should be_kind_of Arachni::RPC::Exceptions::InvalidMethod
167
276
  end
168
-
169
277
  end
278
+ end
170
279
 
171
- it "should be raised when there's a remote exception" do
280
+ context 'and there is a remote exception' do
281
+ it 'raises Arachni::RPC::Exceptions::RemoteException' do
172
282
  begin
173
283
  start_client( rpc_opts ).call( 'test.foo' )
174
284
  rescue Exception => e
175
285
  e.rpc_remote_exception?.should be_true
286
+ e.should be_kind_of Arachni::RPC::Exceptions::RemoteException
176
287
  end
177
288
  end
178
-
179
289
  end
180
290
  end
181
291
 
182
- context "when using valid SSL primitives" do
183
- it "should be able to establish a connection" do
292
+ context 'when using valid SSL primitives' do
293
+ it 'should be able to establish a connection' do
184
294
  res = start_client( rpc_opts_with_ssl_primitives ).call( 'test.foo', @arg )
185
295
  res.should == @arg
186
296
  ::EM.stop
187
297
  end
188
298
  end
189
299
 
190
- context "when using invalid SSL primitives" do
191
- it "should not be able to establish a connection" do
300
+ context 'when using invalid SSL primitives' do
301
+ it 'should not be able to establish a connection' do
192
302
  start_client( rpc_opts_with_invalid_ssl_primitives ).call( 'test.foo', @arg ) do |res|
193
303
  res.rpc_connection_error?.should be_true
194
304
  ::EM.stop
@@ -197,8 +307,8 @@ describe Arachni::RPC::EM::Client do
197
307
  end
198
308
  end
199
309
 
200
- context "when using mixed SSL primitives" do
201
- it "should not be able to establish a connection" do
310
+ context 'when using mixed SSL primitives' do
311
+ it 'should not be able to establish a connection' do
202
312
  start_client( rpc_opts_with_mixed_ssl_primitives ).call( 'test.foo', @arg ) do |res|
203
313
  res.rpc_connection_error?.should be_true
204
314
  res.rpc_ssl_error?.should be_true