arachni-rpc-em 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +0 -0
- data/LICENSE.md +341 -0
- data/README.md +86 -0
- data/Rakefile +74 -0
- data/examples/client.EM.run.rb +113 -0
- data/examples/client.rb +181 -0
- data/examples/server.rb +80 -0
- data/lib/arachni/rpc/em.rb +26 -0
- data/lib/arachni/rpc/em/client.rb +280 -0
- data/lib/arachni/rpc/em/connection_utilities.rb +44 -0
- data/lib/arachni/rpc/em/em.rb +105 -0
- data/lib/arachni/rpc/em/protocol.rb +160 -0
- data/lib/arachni/rpc/em/server.rb +421 -0
- data/lib/arachni/rpc/em/ssl.rb +180 -0
- data/lib/arachni/rpc/em/version.rb +17 -0
- data/spec/arachni/rpc/em/client_spec.rb +211 -0
- data/spec/arachni/rpc/em/em_spec.rb +4 -0
- data/spec/arachni/rpc/em/server_spec.rb +79 -0
- data/spec/arachni/rpc/em/ssl_spec.rb +68 -0
- data/spec/pems/cacert.pem +39 -0
- data/spec/pems/client/cert.pem +39 -0
- data/spec/pems/client/foo-cert.pem +39 -0
- data/spec/pems/client/foo-key.pem +51 -0
- data/spec/pems/client/key.pem +51 -0
- data/spec/pems/server/cert.pem +39 -0
- data/spec/pems/server/key.pem +51 -0
- data/spec/servers/basic.rb +3 -0
- data/spec/servers/server.rb +61 -0
- data/spec/servers/with_ssl_primitives.rb +11 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +46 -0
- metadata +134 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
=begin
|
2
|
+
Arachni-RPC
|
3
|
+
Copyright (c) 2011 Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
4
|
+
|
5
|
+
This is free software; you can copy and distribute and modify
|
6
|
+
this program under the term of the GPL v2.0 License
|
7
|
+
(See LICENSE file for details)
|
8
|
+
|
9
|
+
=end
|
10
|
+
|
11
|
+
# require 'arachni/rpc'
|
12
|
+
require File.join( File.expand_path( File.dirname( __FILE__ ) ), '../lib/arachni/rpc/', 'em' )
|
13
|
+
|
14
|
+
#
|
15
|
+
# You don't *need* to stick the whole thing inside an ::EM.run block,
|
16
|
+
# the system will manage EM on its own...however you *can* if you want to
|
17
|
+
# or if you're working inside a framework that already runs EventMachine.
|
18
|
+
::EM.run do
|
19
|
+
|
20
|
+
# connect to the server
|
21
|
+
client = Arachni::RPC::EM::Client.new(
|
22
|
+
:host => 'localhost',
|
23
|
+
:port => 7332,
|
24
|
+
|
25
|
+
# optional authentication token, if it doesn't match the one
|
26
|
+
# set on the server-side you'll be getting exceptions.
|
27
|
+
:token => 'superdupersecret',
|
28
|
+
|
29
|
+
# :keep_alive => false,
|
30
|
+
|
31
|
+
# optional serializer (defaults to YAML)
|
32
|
+
# see the 'serializer' method at:
|
33
|
+
# http://eventmachine.rubyforge.org/EventMachine/Protocols/ObjectProtocol.html#M000369
|
34
|
+
:serializer => Marshal
|
35
|
+
)
|
36
|
+
|
37
|
+
bench = Arachni::RPC::RemoteObjectMapper.new( client, 'bench' )
|
38
|
+
|
39
|
+
#
|
40
|
+
# There's one downside though, if you want to run this thing inside an
|
41
|
+
# ::EM.run block: you'll have to wrap all sync calls in a ::Arachni::RPC::EM::EM::Synchrony.run block.
|
42
|
+
#
|
43
|
+
# Like so:
|
44
|
+
::Arachni::RPC::EM::Synchrony.run do
|
45
|
+
p bench.foo( 'First sync call in individual Synchrony block.' )
|
46
|
+
# => "First sync call in individual Synchrony block."
|
47
|
+
end
|
48
|
+
|
49
|
+
# you can use it again individually
|
50
|
+
::Arachni::RPC::EM::Synchrony.run do
|
51
|
+
p bench.foo( 'Second sync call in individual Synchrony block.' )
|
52
|
+
# => "Second sync call in individual Synchrony block."
|
53
|
+
end
|
54
|
+
|
55
|
+
# or just wrap lots of calls in it
|
56
|
+
::Arachni::RPC::EM::Synchrony.run do
|
57
|
+
p bench.foo( 'Third sync call in individual Synchrony block.' )
|
58
|
+
# => "Third sync call in individual Synchrony block."
|
59
|
+
|
60
|
+
p bench.foo( '--> And this one is in the same block as well.' )
|
61
|
+
# => "--> And this one is in the same block as well."
|
62
|
+
|
63
|
+
p bench.async_foo( 'This is a sync call to an async remote method.' )
|
64
|
+
# => "This is a sync call to an async remote method."
|
65
|
+
end
|
66
|
+
|
67
|
+
# async calls are the same
|
68
|
+
bench.foo( 'This is an async call... business as usual. :)' ) {
|
69
|
+
|res|
|
70
|
+
p res
|
71
|
+
# => "This is an async call... business as usual. :)"
|
72
|
+
}
|
73
|
+
|
74
|
+
bench.async_foo( 'This is an async call to an async remote method.' ) {
|
75
|
+
|res|
|
76
|
+
p res
|
77
|
+
# => "This is an async call to an async remote method."
|
78
|
+
}
|
79
|
+
|
80
|
+
|
81
|
+
#
|
82
|
+
# The system uses 2 methods to make calls appear sync:
|
83
|
+
# - Threads (when the code is *not* run directly inside the Reactor's thread, see example.rb)
|
84
|
+
# - Fibers (when the code *is* run inside the Reactor's thread, like right here)
|
85
|
+
#
|
86
|
+
# For performance reasons callbacks are ::EM.defer'ed.
|
87
|
+
#
|
88
|
+
# This means that they're already in their own thread so you don't need
|
89
|
+
# ::Arachni::RPC::EM::EM::Synchrony.run to perform sync calls from inside callbacks.
|
90
|
+
#
|
91
|
+
bench.async_foo( 'Coo-coo' ) {
|
92
|
+
|res|
|
93
|
+
|
94
|
+
p res
|
95
|
+
# => "Coo-coo"
|
96
|
+
|
97
|
+
p bench.async_foo( 'Coo-coo 2' )
|
98
|
+
# => "Coo-coo 2"
|
99
|
+
|
100
|
+
bench.async_foo( 'Coo-coo 3' ) {
|
101
|
+
|res|
|
102
|
+
|
103
|
+
p res
|
104
|
+
# => "Coo-coo 3"
|
105
|
+
|
106
|
+
p bench.foo( 'Coo-coo 4' )
|
107
|
+
# => "Coo-coo 4"
|
108
|
+
}
|
109
|
+
|
110
|
+
}
|
111
|
+
|
112
|
+
|
113
|
+
end
|
data/examples/client.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
=begin
|
2
|
+
Arachni-RPC
|
3
|
+
Copyright (c) 2011 Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
4
|
+
|
5
|
+
This is free software; you can copy and distribute and modify
|
6
|
+
this program under the term of the GPL v2.0 License
|
7
|
+
(See LICENSE file for details)
|
8
|
+
|
9
|
+
=end
|
10
|
+
|
11
|
+
cwd = File.expand_path( File.dirname( __FILE__ ) )
|
12
|
+
require File.join( cwd, '../lib/arachni/rpc/', 'em' )
|
13
|
+
|
14
|
+
|
15
|
+
# connect to the server
|
16
|
+
client = Arachni::RPC::EM::Client.new(
|
17
|
+
:host => 'localhost',
|
18
|
+
:port => 7332,
|
19
|
+
|
20
|
+
# optional authentication token, if it doesn't match the one
|
21
|
+
# set on the server-side you'll be getting exceptions.
|
22
|
+
:token => 'superdupersecret',
|
23
|
+
|
24
|
+
# optional serializer (defaults to YAML)
|
25
|
+
# see the 'serializer' method at:
|
26
|
+
# http://eventmachine.rubyforge.org/EventMachine/Protocols/ObjectProtocol.html#M000369
|
27
|
+
:serializer => Marshal,
|
28
|
+
|
29
|
+
#
|
30
|
+
# Connection keep alive is set to true by default, this means that
|
31
|
+
# a single connection will be maintained and all calls will pass
|
32
|
+
# through it.
|
33
|
+
# This bypasses a bug in EventMachine and allows you to perform thousands
|
34
|
+
# of calls without issue.
|
35
|
+
#
|
36
|
+
# However, you are responsible for closing the connection when you're done.
|
37
|
+
#
|
38
|
+
# If keep alive is set to false then each call will go through its own connection
|
39
|
+
# and the responsibility for closing that connection falls on Arachni-RPC.
|
40
|
+
#
|
41
|
+
# Unfortunately, if you try to make a greater number of calls than your system's
|
42
|
+
# maximum open file descriptors limit EventMachine will freak-out.
|
43
|
+
#
|
44
|
+
:keep_alive => false,
|
45
|
+
|
46
|
+
#
|
47
|
+
# In order to enable peer verification one must first provide
|
48
|
+
# the following:
|
49
|
+
#
|
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
|
+
# Make things easy on the eyes using the mapper, it allows you to do this:
|
59
|
+
#
|
60
|
+
# res = bench.foo( arg )
|
61
|
+
#
|
62
|
+
# Instead of:
|
63
|
+
#
|
64
|
+
# res = client.call( 'bench.foo', arg )
|
65
|
+
#
|
66
|
+
bench = Arachni::RPC::RemoteObjectMapper.new( client, 'bench' )
|
67
|
+
|
68
|
+
#
|
69
|
+
# In order to perform an asynchronous call you will need to provide a block,
|
70
|
+
# even if it is an empty one.
|
71
|
+
#
|
72
|
+
bench.foo( 'This is an async call to "bench.foo".' ) {
|
73
|
+
|res|
|
74
|
+
|
75
|
+
p res
|
76
|
+
# => "This is an async call to \"bench.foo\"."
|
77
|
+
|
78
|
+
# did something RPC related go wrong?
|
79
|
+
# p res.rpc_exception?
|
80
|
+
# => false
|
81
|
+
|
82
|
+
# did something go wrong on the server-side?
|
83
|
+
# p res.rpc_remote_exception?
|
84
|
+
# => false
|
85
|
+
|
86
|
+
# did the connection die abruptly?
|
87
|
+
# p res.rpc_connection_error?
|
88
|
+
# => false
|
89
|
+
|
90
|
+
# did we call an object for which there is no handler on the server-side?
|
91
|
+
# p res.rpc_invalid_object_error?
|
92
|
+
# => false
|
93
|
+
|
94
|
+
# did we call a server-side method that isn't existent or public?
|
95
|
+
# p res.rpc_invalid_method_error?
|
96
|
+
# => false
|
97
|
+
|
98
|
+
# was there an authentication token mismatch?
|
99
|
+
# p res.rpc_invalid_token_error?
|
100
|
+
# => false
|
101
|
+
}
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
#
|
106
|
+
# On the server-side this is an async method but works just like everything else here.
|
107
|
+
#
|
108
|
+
# You'll need to kind-of specify the async methods on the server-side,
|
109
|
+
# check the server example file for more info.
|
110
|
+
#
|
111
|
+
bench.async_foo( 'This is an async call to "bench.async_foo".' ) {
|
112
|
+
|res|
|
113
|
+
p res
|
114
|
+
# => "This is an async call to \"bench.async_foo\"."
|
115
|
+
}
|
116
|
+
|
117
|
+
p bench.async_foo( 'This is a sync call to "bench.async_foo".' )
|
118
|
+
# => "This is a sync call to \"bench.async_foo\"."
|
119
|
+
|
120
|
+
|
121
|
+
#
|
122
|
+
# To perform a sync (blocking) call do the usual stuff.
|
123
|
+
#
|
124
|
+
# This is thread safe so if you'd rather use Threads instead of async calls
|
125
|
+
# for that extra performance kick you go ahead and do your thing now...
|
126
|
+
#
|
127
|
+
p bench.foo( 'This is a sync call to "bench.foo".' )
|
128
|
+
# => "This is a sync call to \"bench.foo\"."
|
129
|
+
|
130
|
+
|
131
|
+
#
|
132
|
+
# When you are performing a synchronous call and things go wrong
|
133
|
+
# an exception will be thrown.
|
134
|
+
#
|
135
|
+
# Exceptions on the server-side unrelated to the RPC system will be forwarded.
|
136
|
+
#
|
137
|
+
|
138
|
+
#
|
139
|
+
# Non-existent object.
|
140
|
+
#
|
141
|
+
blah = Arachni::RPC::RemoteObjectMapper.new( client, 'blah' )
|
142
|
+
begin
|
143
|
+
p blah.something
|
144
|
+
rescue Exception => e
|
145
|
+
p e # => #<Arachni::RPC::EM::Exceptions::InvalidObject: Trying to access non-existent object 'blah'.>
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Non-existent or non-public method.
|
150
|
+
#
|
151
|
+
begin
|
152
|
+
p bench.fdoo
|
153
|
+
rescue Exception => e
|
154
|
+
p e # => #<Arachni::RPC::EM::Exceptions::InvalidMethod: Trying to access non-public method 'fdoo'.>
|
155
|
+
end
|
156
|
+
|
157
|
+
#
|
158
|
+
# When you are performing an asynchronous call and things go wrong
|
159
|
+
# an exception will be returned.
|
160
|
+
#
|
161
|
+
# It will *NOT* be thrown!
|
162
|
+
# It will be *RETURNED*!
|
163
|
+
#
|
164
|
+
blah.something {
|
165
|
+
|res|
|
166
|
+
p res # => #<Arachni::RPC::EM::Exceptions::InvalidObject: Trying to access non-existent object 'blah'.>
|
167
|
+
|
168
|
+
# RPC Exception helper methods have been added to all Ruby objects (except BasicObject)
|
169
|
+
# so they'll always be there when you need them.
|
170
|
+
|
171
|
+
# p res.rpc_exception? # => true
|
172
|
+
# p res.rpc_invalid_object_error? # => true
|
173
|
+
}
|
174
|
+
|
175
|
+
#
|
176
|
+
# We don't know when async calls will return so we wait forever.
|
177
|
+
#
|
178
|
+
# Call ::EM.stop to break-out.
|
179
|
+
#
|
180
|
+
Arachni::RPC::EM.block!
|
181
|
+
|
data/examples/server.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
=begin
|
2
|
+
Arachni-RPC
|
3
|
+
Copyright (c) 2011 Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
4
|
+
|
5
|
+
This is free software; you can copy and distribute and modify
|
6
|
+
this program under the term of the GPL v2.0 License
|
7
|
+
(See LICENSE file for details)
|
8
|
+
|
9
|
+
=end
|
10
|
+
|
11
|
+
cwd = File.expand_path( File.dirname( __FILE__ ) )
|
12
|
+
require File.join( cwd, '../lib/arachni/rpc/', 'em' )
|
13
|
+
|
14
|
+
class Parent
|
15
|
+
def foo( arg )
|
16
|
+
return arg
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Bench < Parent
|
21
|
+
|
22
|
+
# in order to make inherited methods accessible you've got to explicitly
|
23
|
+
# make them public
|
24
|
+
private :foo
|
25
|
+
public :foo
|
26
|
+
|
27
|
+
#
|
28
|
+
# Uses EventMachine to call the block asynchronously
|
29
|
+
#
|
30
|
+
def async_foo( arg, &block )
|
31
|
+
::EM.schedule {
|
32
|
+
::EM.defer {
|
33
|
+
block.call( arg ) if block_given?
|
34
|
+
}
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
server = Arachni::RPC::EM::Server.new(
|
41
|
+
:host => 'localhost',
|
42
|
+
:port => 7332,
|
43
|
+
|
44
|
+
# optional authentication token, if it doesn't match the one
|
45
|
+
# set on the client-side the client won't be able to do anything
|
46
|
+
# and keep getting exceptions.
|
47
|
+
:token => 'superdupersecret',
|
48
|
+
|
49
|
+
# optional serializer (defaults to YAML)
|
50
|
+
# see the 'serializer' method at:
|
51
|
+
# http://eventmachine.rubyforge.org/EventMachine/Protocols/ObjectProtocol.html#M000369
|
52
|
+
:serializer => Marshal,
|
53
|
+
|
54
|
+
# :ssl_ca => cwd + '/../spec/pems/cacert.pem',
|
55
|
+
# :ssl_pkey => cwd + '/../spec/pems/server/key.pem',
|
56
|
+
# :ssl_cert => cwd + '/../spec/pems/server/cert.pem'
|
57
|
+
)
|
58
|
+
|
59
|
+
#
|
60
|
+
# This is a way for you to identify methods that pass their result to a block
|
61
|
+
# instead of simply returning them (which is the most usual operation of async methods.
|
62
|
+
#
|
63
|
+
# So no need to change your coding convetions to fit the RPC stuff,
|
64
|
+
# you can just decide dynamically based on a plethora of data which Ruby provides
|
65
|
+
# by its 'Method' class.
|
66
|
+
#
|
67
|
+
server.add_async_check {
|
68
|
+
|method|
|
69
|
+
#
|
70
|
+
# Must return 'true' for async and 'false' for sync.
|
71
|
+
#
|
72
|
+
# Very simple check here...
|
73
|
+
#
|
74
|
+
'async' == method.name.to_s.split( '_' )[0]
|
75
|
+
}
|
76
|
+
|
77
|
+
server.add_handler( 'bench', Bench.new )
|
78
|
+
|
79
|
+
# this will block forever, call server.shutdown to kill the server.
|
80
|
+
server.run
|
@@ -0,0 +1,26 @@
|
|
1
|
+
=begin
|
2
|
+
Arachni
|
3
|
+
Copyright (c) 2010-2011 Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
4
|
+
|
5
|
+
This is free software; you can copy and distribute and modify
|
6
|
+
this program under the term of the GPL v2.0 License
|
7
|
+
(See LICENSE file for details)
|
8
|
+
|
9
|
+
=end
|
10
|
+
|
11
|
+
require 'eventmachine'
|
12
|
+
require 'socket'
|
13
|
+
require 'logger'
|
14
|
+
require 'fiber'
|
15
|
+
|
16
|
+
require 'arachni/rpc'
|
17
|
+
|
18
|
+
require 'yaml'
|
19
|
+
YAML::ENGINE.yamler = 'syck'
|
20
|
+
|
21
|
+
require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'em', 'connection_utilities' )
|
22
|
+
require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'em', 'ssl' )
|
23
|
+
require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'em', 'protocol' )
|
24
|
+
require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'em', 'server' )
|
25
|
+
require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'em', 'client' )
|
26
|
+
require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'em', 'em' )
|
@@ -0,0 +1,280 @@
|
|
1
|
+
=begin
|
2
|
+
Arachni-RPC
|
3
|
+
Copyright (c) 2011 Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
4
|
+
|
5
|
+
This is free software; you can copy and distribute and modify
|
6
|
+
this program under the term of the GPL v2.0 License
|
7
|
+
(See LICENSE file for details)
|
8
|
+
|
9
|
+
=end
|
10
|
+
|
11
|
+
module Arachni
|
12
|
+
module RPC
|
13
|
+
module EM
|
14
|
+
|
15
|
+
#
|
16
|
+
# Simple EventMachine-based RPC client.
|
17
|
+
#
|
18
|
+
# It's capable of:
|
19
|
+
# - performing and handling a few thousands requests per second (depending on call size, network conditions and the like)
|
20
|
+
# - TLS encryption
|
21
|
+
# - asynchronous and synchronous requests
|
22
|
+
# - handling remote asynchronous calls that require a block
|
23
|
+
#
|
24
|
+
# @author: Tasos "Zapotek" Laskos
|
25
|
+
# <tasos.laskos@gmail.com>
|
26
|
+
# <zapotek@segfault.gr>
|
27
|
+
# @version: 0.1
|
28
|
+
#
|
29
|
+
class Client
|
30
|
+
|
31
|
+
include ::Arachni::RPC::Exceptions
|
32
|
+
|
33
|
+
#
|
34
|
+
# Handles EventMachine's connection and RPC related stuff.
|
35
|
+
#
|
36
|
+
# It's responsible for TLS, storing and calling callbacks as well as
|
37
|
+
# serializing, transmitting and receiving objects.
|
38
|
+
#
|
39
|
+
# @author: Tasos "Zapotek" Laskos
|
40
|
+
# <tasos.laskos@gmail.com>
|
41
|
+
# <zapotek@segfault.gr>
|
42
|
+
# @version: 0.1
|
43
|
+
#
|
44
|
+
class Handler < EventMachine::Connection
|
45
|
+
include ::Arachni::RPC::EM::Protocol
|
46
|
+
include ::Arachni::RPC::EM::ConnectionUtilities
|
47
|
+
|
48
|
+
def initialize( opts )
|
49
|
+
@opts = opts
|
50
|
+
@status = :idle
|
51
|
+
|
52
|
+
@request = nil
|
53
|
+
assume_client_role!
|
54
|
+
end
|
55
|
+
|
56
|
+
def post_init
|
57
|
+
@status = :active
|
58
|
+
start_ssl
|
59
|
+
end
|
60
|
+
|
61
|
+
def unbind( reason )
|
62
|
+
end_ssl
|
63
|
+
|
64
|
+
if @request && @request.callback && @status != :done
|
65
|
+
e = Arachni::RPC::Exceptions::ConnectionError.new( "Connection closed [#{reason}]" )
|
66
|
+
@request.callback.call( e )
|
67
|
+
end
|
68
|
+
|
69
|
+
@status = :closed
|
70
|
+
end
|
71
|
+
|
72
|
+
def connection_completed
|
73
|
+
@status = :established
|
74
|
+
end
|
75
|
+
|
76
|
+
def status
|
77
|
+
@status
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Used to handle responses.
|
82
|
+
#
|
83
|
+
# @param [Arachni::RPC::EM::Response] res
|
84
|
+
#
|
85
|
+
def receive_response( res )
|
86
|
+
|
87
|
+
if exception?( res )
|
88
|
+
res.obj = Arachni::RPC::Exceptions.from_response( res )
|
89
|
+
end
|
90
|
+
|
91
|
+
if cb = @request.callback
|
92
|
+
|
93
|
+
callback = Proc.new {
|
94
|
+
|obj|
|
95
|
+
cb.call( obj )
|
96
|
+
|
97
|
+
@status = :done
|
98
|
+
close_connection
|
99
|
+
}
|
100
|
+
|
101
|
+
if @request.defer?
|
102
|
+
# the callback might block a bit so tell EM to put it in a thread
|
103
|
+
::EM.defer {
|
104
|
+
callback.call( res.obj )
|
105
|
+
}
|
106
|
+
else
|
107
|
+
callback.call( res.obj )
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# @param [Arachni::RPC::EM::Response] res
|
113
|
+
def exception?( res )
|
114
|
+
res.obj.is_a?( Hash ) && res.obj['exception'] ? true : false
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
# Sends the request.
|
119
|
+
#
|
120
|
+
# @param [Arachni::RPC::EM::Request] req
|
121
|
+
#
|
122
|
+
def send_request( req )
|
123
|
+
@status = :pending
|
124
|
+
@request = req
|
125
|
+
super( req )
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
#
|
130
|
+
# Options hash
|
131
|
+
#
|
132
|
+
# @return [Hash]
|
133
|
+
#
|
134
|
+
attr_reader :opts
|
135
|
+
|
136
|
+
#
|
137
|
+
# Starts EventMachine and connects to the remote server.
|
138
|
+
#
|
139
|
+
# opts example:
|
140
|
+
#
|
141
|
+
# {
|
142
|
+
# :host => 'localhost',
|
143
|
+
# :port => 7331,
|
144
|
+
#
|
145
|
+
# # optional authentication token, if it doesn't match the one
|
146
|
+
# # set on the server-side you'll be getting exceptions.
|
147
|
+
# :token => 'superdupersecret',
|
148
|
+
#
|
149
|
+
# # optional serializer (defaults to YAML)
|
150
|
+
# # see the 'serializer' method at:
|
151
|
+
# # http://eventmachine.rubyforge.org/EventMachine/Protocols/ObjectProtocol.html#M000369
|
152
|
+
# :serializer => Marshal,
|
153
|
+
#
|
154
|
+
# #
|
155
|
+
# # In order to enable peer verification one must first provide
|
156
|
+
# # the following:
|
157
|
+
# #
|
158
|
+
# # SSL CA certificate
|
159
|
+
# :ssl_ca => cwd + '/../spec/pems/cacert.pem',
|
160
|
+
# # SSL private key
|
161
|
+
# :ssl_pkey => cwd + '/../spec/pems/client/key.pem',
|
162
|
+
# # SSL certificate
|
163
|
+
# :ssl_cert => cwd + '/../spec/pems/client/cert.pem'
|
164
|
+
# }
|
165
|
+
#
|
166
|
+
# @param [Hash] opts
|
167
|
+
#
|
168
|
+
def initialize( opts )
|
169
|
+
|
170
|
+
begin
|
171
|
+
@opts = opts.merge( :role => :client )
|
172
|
+
@token = @opts[:token]
|
173
|
+
|
174
|
+
@host, @port = @opts[:host], @opts[:port]
|
175
|
+
|
176
|
+
Arachni::RPC::EM.ensure_em_running!
|
177
|
+
rescue EventMachine::ConnectionError => e
|
178
|
+
exc = ConnectionError.new( e.to_s + " for '#{@k}'." )
|
179
|
+
exc.set_backtrace( e.backtrace )
|
180
|
+
raise exc
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
#
|
185
|
+
# Calls a remote method and grabs the result.
|
186
|
+
#
|
187
|
+
# There are 2 ways to perform a call, async (non-blocking) and sync (blocking).
|
188
|
+
#
|
189
|
+
# To perform an async call you need to provide a block which will be passed
|
190
|
+
# the return value once the method has finished executing.
|
191
|
+
#
|
192
|
+
# server.call( 'handler.method', arg1, arg2 ){
|
193
|
+
# |res|
|
194
|
+
# do_stuff( res )
|
195
|
+
# }
|
196
|
+
#
|
197
|
+
#
|
198
|
+
# To perform a sync (blocking) call do not pass a block, the value will be
|
199
|
+
# returned as usual.
|
200
|
+
#
|
201
|
+
# res = server.call( 'handler.method', arg1, arg2 )
|
202
|
+
#
|
203
|
+
# @param [String] msg in the form of <i>handler.method</i>
|
204
|
+
# @param [Array] args collection of argumenta to be passed to the method
|
205
|
+
# @param [Proc] &block
|
206
|
+
#
|
207
|
+
def call( msg, *args, &block )
|
208
|
+
|
209
|
+
req = Request.new(
|
210
|
+
:message => msg,
|
211
|
+
:args => args,
|
212
|
+
:callback => block,
|
213
|
+
:token => @token
|
214
|
+
)
|
215
|
+
|
216
|
+
if block_given?
|
217
|
+
call_async( req )
|
218
|
+
else
|
219
|
+
return call_sync( req )
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
private
|
224
|
+
|
225
|
+
def connect
|
226
|
+
::EM.connect( @host, @port, Handler, @opts )
|
227
|
+
end
|
228
|
+
|
229
|
+
def call_async( req, &block )
|
230
|
+
::EM.schedule {
|
231
|
+
req.callback = block if block_given?
|
232
|
+
connect.send_request( req )
|
233
|
+
}
|
234
|
+
end
|
235
|
+
|
236
|
+
def call_sync( req )
|
237
|
+
|
238
|
+
ret = nil
|
239
|
+
# if we're in the Reactor thread use a Fiber and if we're not
|
240
|
+
# use a Thread
|
241
|
+
if !::EM::reactor_thread?
|
242
|
+
t = Thread.current
|
243
|
+
call_async( req ) {
|
244
|
+
|obj|
|
245
|
+
t.wakeup
|
246
|
+
ret = obj
|
247
|
+
}
|
248
|
+
sleep
|
249
|
+
else
|
250
|
+
# Fibers do not work across threads so don't defer the callback
|
251
|
+
# once the Handler gets to it
|
252
|
+
req.do_not_defer!
|
253
|
+
|
254
|
+
f = Fiber.current
|
255
|
+
call_async( req ) {
|
256
|
+
|obj|
|
257
|
+
f.resume( obj )
|
258
|
+
}
|
259
|
+
|
260
|
+
begin
|
261
|
+
ret = Fiber.yield
|
262
|
+
rescue FiberError => e
|
263
|
+
msg = e.to_s + "\n"
|
264
|
+
msg += '(Consider wrapping your sync code in a' +
|
265
|
+
' "::Arachni::RPC::EM::Synchrony.run" ' +
|
266
|
+
'block when your app is running inside the Reactor\'s thread)'
|
267
|
+
|
268
|
+
raise( msg )
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
raise ret if ret.is_a?( Exception )
|
273
|
+
return ret
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|