arachni-rpc 0.1.3 → 0.2.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cbbd82c15cfd3db423e6c200ca21a1bfc34ed6d7
4
+ data.tar.gz: 828f98fbc48632790ff1bb42f723e4e068c924a3
5
+ SHA512:
6
+ metadata.gz: 75c2287cb0aa85c1cdd66c25068a96db4f180bac1723fde3b46ad451c34afe9a7f45901c6f99b988bc5b4758e6f3fdf763b4f5b8b0ff8395b2cd7abd1c0e6e72
7
+ data.tar.gz: 1a5fe36d458864aec8f45871e6d650841a47eb0d70ce22f78726eafddaf129ded96c8efd8dbf44dc6ff574b763a57112f1d2e56f895f78d884fb7f326d7daa81
data/CHANGELOG.md CHANGED
@@ -1,9 +1,15 @@
1
1
  # ChangeLog
2
2
 
3
+ ## Version 0.2.0
4
+
5
+ - Added `Arachni::Reactor`-based RPC client/server implementation.
6
+
3
7
  ## Version 0.1.3
4
- - Removed ```Arachni::RPC::Request#do_not_defer``` and ```Arachni::RPC::Request#defer?```.
5
- - All RPC exceptions now inherit from ```RuntimeError``` instead of ```Exception```.
8
+
9
+ - Removed `Arachni::RPC::Request#do_not_defer` and `Arachni::RPC::Request#defer?`.
10
+ - All RPC exceptions now inherit from `RuntimeError` instead of `Exception`.
6
11
 
7
12
  ## Version 0.1.2
13
+
8
14
  - Code cleanup.
9
- - Arachni::RPC::Request#do_not_defer! => Arachni::RPC::Request#do_not_defer
15
+ - `Arachni::RPC::Request#do_not_defer!` => `Arachni::RPC::Request#do_not_defer`.
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # License
2
2
 
3
- Copyright (C) 2011-2012, Tasos Laskos <tasos.laskos@gmail.com>
3
+ Copyright (C) 2011-2014, Tasos Laskos <tasos.laskos@arachni-scanner.com>
4
4
  All rights reserved.
5
5
 
6
6
  Redistribution and use in source and binary forms, with or without modification,
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # Arachni-RPC
2
+
2
3
  <table>
3
4
  <tr>
4
5
  <th>Version</th>
5
- <td>0.1.2</td>
6
+ <td>0.2.0</td>
6
7
  </tr>
7
8
  <tr>
8
9
  <th>Github page</th>
@@ -14,7 +15,7 @@
14
15
  </tr>
15
16
  <tr>
16
17
  <th>Author</th>
17
- <td><a href="mailto:tasos.laskos@gmail.com">Tasos "Zapotek" Laskos</a></td>
18
+ <td><a href="mailto:tasos.laskos@arachni-scanner.com">Tasos "Zapotek" Laskos</a></td>
18
19
  </tr>
19
20
  <tr>
20
21
  <th>Twitter</th>
@@ -22,7 +23,7 @@
22
23
  </tr>
23
24
  <tr>
24
25
  <th>Copyright</th>
25
- <td>2011-2012</td>
26
+ <td>2011-2014</td>
26
27
  </tr>
27
28
  <tr>
28
29
  <th>License</th>
@@ -32,56 +33,49 @@
32
33
 
33
34
  ## Synopsis
34
35
 
35
- Arachni-RPC is a simple and lightweight Remote Procedure Call protocol which provides the basis for <a href="http://arachni.segfault.gr">Arachni</a>'s Grid infrastructure.
36
-
37
- This repository holds *only* the protocol specification although there currently are 2 (more like 1.5) available implementations:
36
+ Arachni-RPC is a simple and lightweight Remote Procedure Call protocol and implementation
37
+ which provides the basis for <a href="http://arachni-scanner.com">Arachni</a>'s
38
+ distributed infrastructure.
38
39
 
39
- - <a href="http://github.com/Arachni/arachni-rpc-em">Arachni-RPC EM</a> -- Uses EventMachine for network related operations and provides both a client and a server, this is the one used by Arachni.
40
- - <a href="http://github.com/Arachni/arachni-rpc-pure">Arachni-RPC Pure</a> -- Provides a synchronous client using pure Ruby OpenSSL sockets and has no 3rd party dependencies.
40
+ (Based on the [Arachni::Reactor](https://github.com/Arachni/arachni-reactor) framework.)
41
41
 
42
42
  ## Features
43
43
 
44
- - Extremely lightweight
45
- - Very simple design
46
- - Token-based authentication
44
+ - Extremely lightweight.
45
+ - Very simple design.
46
+ - TLS encryption.
47
+ - Configurable serializer.
48
+ - Can intercept RPC responses and translate them into native objects for
49
+ when using serializers that only support basic types, like JSON or MessagePack.
50
+ - Token-based authentication.
51
+ - Pure-Ruby.
52
+ - Multi-platform, tested on:
53
+ - Linux
54
+ - OSX
55
+ - Windows
47
56
 
48
57
  ## Installation
49
58
 
50
- I can't think of a lot of uses for manually installing the protocol specification
51
- (it'll most likely be installed as a dependency for some other project) but in case you want to some instructions follow.
52
-
53
- ### Gem
54
-
55
- ```gem install arachni-rpc```
56
-
57
- ### Source
58
-
59
- If you want to clone the repository and work with the source code:
60
-
61
- git co git://github.com/arachni/arachni-rpc.git
62
- cd arachni-rpc
63
- rake install
64
-
59
+ gem install arachni-rpc
65
60
 
66
61
  ## Running the Specs
67
62
 
68
- In order to run the specs you must first install RSpec:
69
- gem install rspec
70
-
71
- Then:
72
-
63
+ bundle install
73
64
  rake spec
74
65
 
75
66
  ## Protocol specifications
76
67
 
77
- You can find that information in the [Wiki](https://github.com/Arachni/arachni-rpc/wiki).
68
+ You can find the RPC protocol specification at the
69
+ [Wiki](https://github.com/Arachni/arachni-rpc/wiki).
78
70
 
79
71
  ## Bug reports/Feature requests
80
- Please send your feedback using Github's issue system at
72
+
73
+ Please send your feedback using GitHub's issue system at
81
74
  [http://github.com/arachni/arachni-rpc/issues](http://github.com/arachni/arachni-rpc/issues).
82
75
 
83
76
 
84
77
  ## License
85
- Arachni-RPC is provided under the 3-clause BSD license.<br/>
78
+
79
+ Arachni-RPC is provided under the 3-clause BSD license.
86
80
  See the [LICENSE](file.LICENSE.html) file for more information.
87
81
 
data/Rakefile CHANGED
@@ -19,38 +19,35 @@ end
19
19
 
20
20
  task default: [ :build, :spec ]
21
21
 
22
- desc "Generate docs"
22
+ desc 'Generate docs'
23
23
  task :docs do
24
- outdir = "../arachni-rpc-pages"
25
- sh "mkdir #{outdir}" if !File.directory?( outdir )
24
+ outdir = "../arachni-rpc-docs"
25
+ sh "rm -rf #{outdir}"
26
+ sh "mkdir -p #{outdir}"
26
27
 
27
- sh "yardoc --verbose --title \
28
- \"Arachni-RPC\" \
29
- lib/* -o #{outdir} \
30
- - CHANGELOG.md LICENSE.md"
28
+ sh "yardoc -o #{outdir}"
31
29
 
32
-
33
- sh "rm -rf .yard*"
30
+ sh "rm -rf .yardoc"
34
31
  end
35
32
 
36
- desc "Cleaning..."
33
+ desc 'Clean up'
37
34
  task :clean do
38
- sh "rm *.gem || true"
35
+ sh 'rm *.gem || true'
39
36
  end
40
37
 
41
- desc "Build the arachni-rpc gem."
42
- task :build => [ :clean ] do
43
- sh "gem build arachni-rpc.gemspec"
38
+ desc 'Build the arachni-rpc gem.'
39
+ task build: [ :clean ] do
40
+ sh 'gem build arachni-rpc.gemspec'
44
41
  end
45
42
 
46
- desc "Build and install the arachni gem."
47
- task :install => [ :build ] do
43
+ desc 'Build and install the arachni gem.'
44
+ task install: [ :build ] do
48
45
  sh "gem install arachni-rpc-#{Arachni::RPC::VERSION}.gem"
49
46
  end
50
47
 
51
- desc "Push a new version to Rubygems"
52
- task :publish => [ :build ] do
48
+ desc 'Push a new version to Rubygems'
49
+ task publish: [ :build ] do
53
50
  sh "git tag -a v#{Arachni::RPC::VERSION} -m 'Version #{Arachni::RPC::VERSION}'"
54
51
  sh "gem push arachni-rpc-#{Arachni::RPC::VERSION}.gem"
55
52
  end
56
- task :release => [ :publish ]
53
+ task release: [ :publish ]
data/lib/arachni/rpc.rb CHANGED
@@ -6,12 +6,8 @@
6
6
 
7
7
  =end
8
8
 
9
- require 'set'
10
-
11
- require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'rpc', 'version' )
12
- require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'rpc', 'exceptions' )
13
- require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'rpc', 'message' )
14
- require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'rpc', 'request' )
15
- require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'rpc', 'response' )
16
- require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'rpc', 'remote_object_mapper' )
9
+ require 'arachni/reactor'
17
10
 
11
+ %w(version exceptions message request response proxy protocol client server).each do |f|
12
+ require_relative "rpc/#{f}"
13
+ end
@@ -0,0 +1,236 @@
1
+ =begin
2
+
3
+ This file is part of the Arachni-RPC 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
11
+
12
+ require_relative 'client/handler'
13
+
14
+ # Simple RPC client capable of:
15
+ #
16
+ # * TLS encryption.
17
+ # * Asynchronous and synchronous requests.
18
+ # * Handling remote asynchronous calls that defer their result.
19
+ #
20
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
21
+ class Client
22
+
23
+ # Default amount of connections to maintain in the re-use pool.
24
+ DEFAULT_CONNECTION_POOL_SIZE = 1
25
+
26
+ # @return [Hash]
27
+ # Options hash.
28
+ attr_reader :opts
29
+
30
+ # @return [Integer]
31
+ # Amount of connections in the pool.
32
+ attr_reader :connection_count
33
+
34
+ # @example Example options:
35
+ #
36
+ # {
37
+ # :host => 'localhost',
38
+ # :port => 7331,
39
+ #
40
+ # # optional authentication token, if it doesn't match the one
41
+ # # set on the server-side you'll be getting exceptions.
42
+ # :token => 'superdupersecret',
43
+ #
44
+ # :serializer => Marshal,
45
+ #
46
+ # :max_retries => 0,
47
+ #
48
+ # # In order to enable peer verification one must first provide
49
+ # # the following:
50
+ # # SSL CA certificate
51
+ # :ssl_ca => cwd + '/../spec/pems/cacert.pem',
52
+ # # SSL private key
53
+ # :ssl_pkey => cwd + '/../spec/pems/client/key.pem',
54
+ # # SSL certificate
55
+ # :ssl_cert => cwd + '/../spec/pems/client/cert.pem'
56
+ # }
57
+ #
58
+ # @param [Hash] opts
59
+ # @option opts [String] :host Hostname/IP address.
60
+ # @option opts [Integer] :port Port number.
61
+ # @option opts [String] :socket Path to UNIX domain socket.
62
+ # @option opts [Integer] :connection_pool_size (1)
63
+ # Amount of connections to keep open.
64
+ # @option opts [String] :token Optional authentication token.
65
+ # @option opts [.dump, .load] :serializer (YAML)
66
+ # Serializer to use for message transmission.
67
+ # @option opts [Integer] :max_retries
68
+ # How many times to retry failed requests.
69
+ # @option opts [String] :ssl_ca SSL CA certificate.
70
+ # @option opts [String] :ssl_pkey SSL private key.
71
+ # @option opts [String] :ssl_cert SSL certificate.
72
+ def initialize( opts )
73
+ @opts = opts.merge( role: :client )
74
+ @token = @opts[:token]
75
+
76
+ @host, @port = @opts[:host], @opts[:port].to_i
77
+ @socket = @opts[:socket]
78
+
79
+ if !@socket && !(@host || @port)
80
+ fail ArgumentError, 'Needs either a :socket or :host and :port options.'
81
+ end
82
+
83
+ @port = @port.to_i
84
+
85
+ if @host && @port <= 0
86
+ fail ArgumentError, "Invalid port: #{@port}"
87
+ end
88
+
89
+ @pool_size = @opts[:connection_pool_size] || DEFAULT_CONNECTION_POOL_SIZE
90
+
91
+ @reactor = Reactor.global
92
+
93
+ @connections = @reactor.create_queue
94
+ @connection_count = 0
95
+ end
96
+
97
+ # Connection factory, will re-use or create new connections as needed to
98
+ # accommodate the workload.
99
+ #
100
+ # @param [Block] block
101
+ # Block to be passed a {Handler connection}.
102
+ #
103
+ # @return [Boolean]
104
+ # `true` if a new connection had to be established, `false` if an existing
105
+ # one was re-used.
106
+ def connect( &block )
107
+ if @connections.empty? && @connection_count < @pool_size
108
+ opts = @socket ? @socket : [@host, @port]
109
+ block.call @reactor.connect( *[opts, Handler, @opts.merge( client: self )].flatten )
110
+ increment_connection_counter
111
+ return true
112
+ end
113
+
114
+ pop_block = proc do |conn|
115
+ # Some connections may have died while they were waiting in the
116
+ # queue, get rid of them and start all over in case the queue has
117
+ # been emptied.
118
+ if !conn.done?
119
+ connection_failed conn
120
+ connect( &block )
121
+ next
122
+ end
123
+
124
+ block.call conn
125
+ end
126
+
127
+ @connections.pop( &pop_block )
128
+
129
+ false
130
+ end
131
+
132
+ # Close all connections.
133
+ def close
134
+ @reactor.on_tick do |task|
135
+ @connections.pop(&:close_without_retry)
136
+ task.done if @connections.empty?
137
+ end
138
+ end
139
+
140
+ def increment_connection_counter
141
+ @connection_count += 1
142
+ end
143
+
144
+ # {Handler#done? Finished} {Handler}s push themselves here to be re-used.
145
+ #
146
+ # @param [Handler] connection
147
+ def push_connection( connection )
148
+ @connections << connection
149
+ end
150
+
151
+ # Handles failed connections.
152
+ #
153
+ # @param [Handler] connection
154
+ def connection_failed( connection )
155
+ @connection_count -= 1
156
+ connection.close_without_retry
157
+ end
158
+
159
+ # Calls a remote method and grabs the result.
160
+ #
161
+ # There are 2 ways to perform a call, async (non-blocking) and sync (blocking).
162
+ #
163
+ # @example To perform an async call you need to provide a block to handle the result.
164
+ #
165
+ # server.call( 'handler.method', arg1, arg2 ) do |res|
166
+ # do_stuff( res )
167
+ # end
168
+ #
169
+ # @example To perform a sync (blocking), call without a block.
170
+ #
171
+ # res = server.call( 'handler.method', arg1, arg2 )
172
+ #
173
+ # @param [String] msg
174
+ # RPC message in the form of `handler.method`.
175
+ # @param [Array] args
176
+ # Collection of arguments to be passed to the method.
177
+ # @param [Block] block
178
+ def call( msg, *args, &block )
179
+ req = Request.new(
180
+ message: msg,
181
+ args: args,
182
+ callback: block,
183
+ token: @token
184
+ )
185
+
186
+ block_given? ? call_async( req ) : call_sync( req )
187
+ end
188
+
189
+ private
190
+
191
+ def set_exception( req, e )
192
+ msg = @socket ? " for '#{@socket}'." : " for '#{@host}:#{@port}'."
193
+
194
+ exc = (e.is_a?( Reactor::Connection::Error::SSL ) ?
195
+ Exceptions::SSLPeerVerificationFailed : Exceptions::ConnectionError
196
+ ).new( e.to_s + msg )
197
+
198
+ exc.set_backtrace e.backtrace
199
+ req.callback.call exc
200
+ end
201
+
202
+ def call_async( req, &block )
203
+ req.callback = block if block_given?
204
+
205
+ begin
206
+ connect do |connection|
207
+ error = (connection.is_a?( Exception ) and connection) || connection.error
208
+ next set_exception( req, error ) if error
209
+
210
+ connection.send_request( req )
211
+ end
212
+ rescue => e
213
+ set_exception( req, e )
214
+ end
215
+ end
216
+
217
+ def call_sync( req )
218
+ # If we're in the Reactor thread use a Fiber and if we're not use a Thread.
219
+ if @reactor.in_same_thread?
220
+ fail 'Cannot perform synchronous calls when running in the ' +
221
+ "#{Arachni::Reactor} loop."
222
+ end
223
+
224
+ q = Queue.new
225
+ call_async( req ) { |obj| q << obj }
226
+ ret = q.pop
227
+
228
+ raise ret if ret.is_a?( Exception )
229
+
230
+ ret
231
+ end
232
+
233
+ end
234
+
235
+ end
236
+ end