Class: Arachni::RPC::EM::Server

Inherits:
Object
  • Object
show all
Includes:
Arachni::RPC::Exceptions
Defined in:
lib/arachni/rpc/em/server.rb

Overview

EventMachine-based RPC server class.

It’s capable of:

  • performing and handling a few thousands requests per second (depending on call size, network conditions and the like)

  • TLS encryption

  • asynchronous and synchronous requests

  • handling asynchronous methods that require a block

@author: Tasos “Zapotek” Laskos <[email protected]>

Defined Under Namespace

Classes: Proxy

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts) ⇒ Server

Starts EventMachine and the RPC server.

opts example:

{
    :host  => 'localhost',
    :port  => 7331,

    # optional authentication token, if it doesn't match the one
    # set on the server-side you'll be getting exceptions.
    :token => 'superdupersecret',

    # optional serializer (defaults to YAML)
    # see the 'serializer' method at:
    # http://eventmachine.rubyforge.org/EventMachine/Protocols/ObjectProtocol.html#M000369
    :serializer => Marshal,

    # serializer to use if the first choice fails
    :fallback_serializer => YAML,

    #
    # In order to enable peer verification one must first provide
    # the following:
    #
    # SSL CA certificate
    :ssl_ca     => cwd + '/../spec/pems/cacert.pem',
    # SSL private key
    :ssl_pkey   => cwd + '/../spec/pems/client/key.pem',
    # SSL certificate
    :ssl_cert   => cwd + '/../spec/pems/client/cert.pem'
}

Parameters:

  • opts (Hash)


197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/arachni/rpc/em/server.rb', line 197

def initialize( opts )
    @opts  = opts

    if @opts[:ssl_pkey] && @opts[:ssl_cert]
        if !File.exist?( @opts[:ssl_pkey] )
            raise 'Could not find private key at: ' + @opts[:ssl_pkey]
        end

        if !File.exist?( @opts[:ssl_cert] )
            raise 'Could not find certificate at: ' + @opts[:ssl_cert]
        end
    end

    @token = @opts[:token]

    @logger = ::Logger.new( STDOUT )
    @logger.level = Logger::INFO

    @host, @port = @opts[:host], @opts[:port]

    clear_handlers
end

Instance Attribute Details

#loggerObject (readonly)

Returns the value of attribute logger.



160
161
162
# File 'lib/arachni/rpc/em/server.rb', line 160

def logger
  @logger
end

#optsObject (readonly)

Returns the value of attribute opts.



159
160
161
# File 'lib/arachni/rpc/em/server.rb', line 159

def opts
  @opts
end

#tokenObject (readonly)

Returns the value of attribute token.



158
159
160
# File 'lib/arachni/rpc/em/server.rb', line 158

def token
  @token
end

Instance Method Details

#add_async_check(&block) ⇒ Object

This is a way to identify methods that pass their result to a block instead of simply returning them (which is the most usual operation of async methods.

So no need to change your coding conventions to fit the RPC stuff, you can just decide dynamically based on the plethora of data which Ruby provides by its ‘Method’ class.

server.add_async_check do |method|
    #
    # Must return 'true' for async and 'false' for sync.
    #
    # Very simple check here...
    #
    'async' ==  method.name.to_s.split( '_' )[0]
end

Parameters:

  • &block (Proc)


239
240
241
# File 'lib/arachni/rpc/em/server.rb', line 239

def add_async_check( &block )
    @async_checks << block
end

#add_handler(name, obj) ⇒ Object

Adds a handler by name:

server.add_handler( 'myclass', MyClass.new )

Parameters:

  • name (String)

    name via which to make the object available over RPC

  • obj (Object)

    object instance



251
252
253
254
255
256
257
258
259
260
# File 'lib/arachni/rpc/em/server.rb', line 251

def add_handler( name, obj )
    @objects[name] = obj
    @methods[name] = Set.new # no lookup overhead please :)
    @async_methods[name] = Set.new

    obj.class.public_instance_methods( false ).each do |method|
        @methods[name] << method.to_s
        @async_methods[name] << method.to_s if async_check( obj.method( method ) )
    end
end

#alive?TrueClass

Returns:

  • (TrueClass)


334
335
336
# File 'lib/arachni/rpc/em/server.rb', line 334

def alive?
    true
end

#call(connection) ⇒ Object



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/arachni/rpc/em/server.rb', line 292

def call( connection )

    req = connection.request
    peer_ip_addr = connection.peer_ip_addr

    expr, args = req.message, req.args
    meth_name, obj_name = parse_expr( expr )

    log_call( peer_ip_addr, expr, *args )

    if !object_exist?( obj_name )
        msg = "Trying to access non-existent object '#{obj_name}'."
        @logger.error( 'Call' ){ msg + " [on behalf of #{peer_ip_addr}]" }
        raise InvalidObject.new( msg )
    end

    if !public_method?( obj_name, meth_name )
        msg = "Trying to access non-public method '#{meth_name}'."
        @logger.error( 'Call' ){ msg + " [on behalf of #{peer_ip_addr}]" }
        raise InvalidMethod.new( msg )
    end

    # the proxy needs to know whether this is an async call because if it
    # is we'll have already send the response.
    res = Response.new
    res.async! if async?( obj_name, meth_name )

    if !res.async?
        res.obj = @objects[obj_name].send( meth_name.to_sym, *args )
    else
        @objects[obj_name].send( meth_name.to_sym, *args ) do |obj|
            res.obj = obj
            connection.send_response( res )
        end
    end

    res
end

#clear_handlersObject

Clears all handlers and their associated information like methods and async check blocks.



266
267
268
269
270
271
272
# File 'lib/arachni/rpc/em/server.rb', line 266

def clear_handlers
    @objects = {}
    @methods = {}

    @async_checks  = []
    @async_methods = {}
end

#runObject

Runs the server and blocks.



277
278
279
280
# File 'lib/arachni/rpc/em/server.rb', line 277

def run
    Arachni::RPC::EM.schedule { start }
    Arachni::RPC::EM.block
end

#shutdownObject

Shuts down the server after 2 seconds



341
342
343
344
345
346
347
348
349
# File 'lib/arachni/rpc/em/server.rb', line 341

def shutdown
    wait_for = 2

    @logger.info( 'System' ){ "Shutting down in #{wait_for} seconds..." }

    # don't die before returning
    ::EM.add_timer( wait_for ) { ::EM.stop }
    true
end

#startObject

Starts the server but does not block.



285
286
287
288
289
290
# File 'lib/arachni/rpc/em/server.rb', line 285

def start
    @logger.info( 'System' ){ "RPC Server started." }
    @logger.info( 'System' ){ "Listening on #{@host}:#{@port}" }

    ::EM.start_server( @host, @port, Proxy, self )
end