arachni-rpc-em 0.1
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.
- 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
|