arpie 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.yardopts +1 -0
- data/{BINARY_SPEC → BINARY.rdoc} +73 -28
- data/Gemfile +8 -0
- data/LICENCE +15 -0
- data/PROTOCOLS.rdoc +46 -0
- data/README.rdoc +17 -0
- data/Rakefile +6 -114
- data/arpie.gemspec +26 -0
- data/examples/em.rb +18 -0
- data/lib/arpie.rb +2 -4
- data/lib/arpie/binary.rb +18 -4
- data/lib/arpie/binary/bytes_type.rb +7 -2
- data/lib/arpie/binary/fixed_type.rb +11 -4
- data/lib/arpie/binary/list_type.rb +2 -0
- data/lib/arpie/binary/pack_type.rb +20 -10
- data/lib/arpie/em.rb +48 -0
- data/lib/arpie/error.rb +2 -4
- data/lib/arpie/protocol.rb +7 -23
- data/lib/arpie/version.rb +3 -0
- data/spec/binary_spec.rb +113 -8
- data/spec/bytes_binary_type_spec.rb +16 -3
- data/spec/em_spec.rb +27 -0
- data/spec/examples_spec.rb +11 -0
- data/spec/fixed_binary_type_spec.rb +2 -2
- data/spec/list_binary_type_spec.rb +9 -0
- data/spec/protocol_merge_and_split_spec.rb +3 -3
- data/spec/protocol_spec.rb +20 -43
- data/spec/spec_helper.rb +13 -12
- metadata +74 -83
- data/COPYING +0 -15
- data/README +0 -167
- data/lib/arpie/client.rb +0 -195
- data/lib/arpie/proxy.rb +0 -143
- data/lib/arpie/server.rb +0 -157
- data/lib/arpie/xmlrpc.rb +0 -108
- data/spec/client_server_spec.rb +0 -107
- data/tools/benchmark.rb +0 -52
- data/tools/protocol_benchmark.rb +0 -42
data/COPYING
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
Copyright (C) 2008 Bernhard Stoeckner <elven@swordcoast.net> and contributors
|
2
|
-
|
3
|
-
This program is free software; you can redistribute it and/or modify
|
4
|
-
it under the terms of the GNU General Public License as published by
|
5
|
-
the Free Software Foundation; either version 2 of the License, or
|
6
|
-
(at your option) any later version.
|
7
|
-
|
8
|
-
This program is distributed in the hope that it will be useful,
|
9
|
-
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
-
GNU General Public License for more details.
|
12
|
-
|
13
|
-
You should have received a copy of the GNU General Public License along
|
14
|
-
with this program; if not, write to the Free Software Foundation, Inc.,
|
15
|
-
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
data/README
DELETED
@@ -1,167 +0,0 @@
|
|
1
|
-
= What's this?
|
2
|
-
|
3
|
-
Arpie is a end-to-end framework for sending protocol-encoded messages over arbitary
|
4
|
-
IO channels, including UNIX/TCP Sockets, Pipes, and pidgeon carriers (depending on
|
5
|
-
your implementation details).
|
6
|
-
|
7
|
-
Arpie also provides a robust replay-protected RPC framework.
|
8
|
-
|
9
|
-
The useful core of arpie is a protocol stack that can be used to read/split/assemble/write
|
10
|
-
any data stream, but is tailored for packeted streaming data.
|
11
|
-
|
12
|
-
The Arpie server uses one ruby-thread per client, the client runs entirely in the
|
13
|
-
calling thread; though an example implementation for evented callbacks is provided.
|
14
|
-
|
15
|
-
== Source Code
|
16
|
-
|
17
|
-
Source code is in git[http://git.swordcoast.net/?p=lib/ruby/arpie.git;a=summary].
|
18
|
-
|
19
|
-
You can contact me via email at elven@swordcoast.net.
|
20
|
-
|
21
|
-
arpie is available on the rubygems gem server - just do <tt>gem1.8 install arpie</tt>
|
22
|
-
to get the newest version.
|
23
|
-
|
24
|
-
|
25
|
-
== Simple, contrived example: A string reverse server
|
26
|
-
|
27
|
-
require 'rubygems'
|
28
|
-
require 'arpie'
|
29
|
-
require 'socket'
|
30
|
-
|
31
|
-
server = TCPServer.new(51210)
|
32
|
-
|
33
|
-
e = Arpie::Server.new(Arpie::MarshalProtocol.new, Arpie::SizedProtocol.new)
|
34
|
-
|
35
|
-
e.handle do |server, ep, msg|
|
36
|
-
ep.write_message msg.reverse
|
37
|
-
end
|
38
|
-
|
39
|
-
e.accept do
|
40
|
-
server.accept
|
41
|
-
end
|
42
|
-
|
43
|
-
c = Arpie::Client.new(Arpie::MarshalProtocol.new, Arpie::SizedProtocol.new)
|
44
|
-
c.connect do
|
45
|
-
TCPSocket.new("127.0.0.1", 51210)
|
46
|
-
end
|
47
|
-
|
48
|
-
c.write_message "hi"
|
49
|
-
puts c.read_message
|
50
|
-
# => "ih"
|
51
|
-
|
52
|
-
== Advanced, but still simple example: Using Proxy to access remote objects
|
53
|
-
|
54
|
-
require 'rubygems'
|
55
|
-
require 'arpie'
|
56
|
-
require 'socket'
|
57
|
-
|
58
|
-
class MyHandler
|
59
|
-
def reverse str
|
60
|
-
str.reverse
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
server = TCPServer.new(51210)
|
65
|
-
|
66
|
-
e = Arpie::ProxyServer.new(Arpie::MarshalProtocol.new, Arpie::SizedProtocol.new)
|
67
|
-
|
68
|
-
e.handle MyHandler.new
|
69
|
-
|
70
|
-
e.accept do
|
71
|
-
server.accept
|
72
|
-
end
|
73
|
-
|
74
|
-
p = Arpie::ProxyClient.new(Arpie::MarshalProtocol.new, Arpie::SizedProtocol.new)
|
75
|
-
p.connect do |transport|
|
76
|
-
TCPSocket.new("127.0.0.1", 51210)
|
77
|
-
end
|
78
|
-
|
79
|
-
puts p.reverse "hi"
|
80
|
-
# => "ih"
|
81
|
-
|
82
|
-
== Writing custom Protocols
|
83
|
-
|
84
|
-
You can use arpies Protocol layer to write your custom protocol parser/emitters.
|
85
|
-
Consider the following, again very contrived, example. You have a linebased wire format,
|
86
|
-
which sends regular object updates in multiple lines, each holding a property to be updated.
|
87
|
-
What objects get updated is not relevant to this example.
|
88
|
-
|
89
|
-
For this example, we'll be using the SeparatorProtocol already contained in protocols.rb as
|
90
|
-
a base.
|
91
|
-
|
92
|
-
class AssembleExample < Arpie::Protocol
|
93
|
-
|
94
|
-
def from binary
|
95
|
-
# The wire format is simply a collection of lines
|
96
|
-
# where the first one is a number containing the
|
97
|
-
# # of lines to expect.
|
98
|
-
assemble! binary do |binaries, meta|
|
99
|
-
binaries.size >= 1 or incomplete!
|
100
|
-
binaries.size - 1 >= binaries[0].to_i or incomplete!
|
101
|
-
|
102
|
-
# Here, you can wrap all collected updates in
|
103
|
-
# whatever format you want it to be. We're just
|
104
|
-
# "joining" them to be a single array.
|
105
|
-
binaries.shift
|
106
|
-
binaries
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def to object
|
111
|
-
yield object.size
|
112
|
-
object.each {|oo|
|
113
|
-
yield oo
|
114
|
-
}
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
p = Arpie::ProtocolChain.new(
|
119
|
-
AssembleExample.new,
|
120
|
-
Arpie::SeparatorProtocol.new
|
121
|
-
)
|
122
|
-
r, w = IO.pipe
|
123
|
-
|
124
|
-
p.write_message(w, %w{we want to be assembled})
|
125
|
-
|
126
|
-
p p.read_message(r)
|
127
|
-
# => ["we", "want", "to", "be", "assembled"]
|
128
|
-
|
129
|
-
== Replay protection
|
130
|
-
|
131
|
-
It can happen that a Client loses connection to a Server.
|
132
|
-
In that case, the Transport tries transparently reconnecting by simply
|
133
|
-
invoking the block again that was given to Client#connect.
|
134
|
-
See the Client accessors for modifying this behaviour.
|
135
|
-
|
136
|
-
It is assumed that each call, that is being placed, is atomic - eg, no
|
137
|
-
connection losses in between message send and receive; lost messages
|
138
|
-
will be retransmitted. Some Protocol classes provide support for replay
|
139
|
-
protection through in-band UUIDs; though it is not a requirement to implement it.
|
140
|
-
If a UUID is provided in the data stream, the Protocol will not call
|
141
|
-
the handler again for retransmissions, but instead reply with the old,
|
142
|
-
already evaluated value.
|
143
|
-
|
144
|
-
Not all protocols support UUIDs; those who do not offer no replay protection,
|
145
|
-
and special care has to be taken elsewhere.
|
146
|
-
|
147
|
-
All object-encoding protocols support UUIDs, including YAML and Marshal.
|
148
|
-
XMLRPC does not.
|
149
|
-
|
150
|
-
== Benchmarks
|
151
|
-
|
152
|
-
There is a benchmark script included in the git repository (and in the gem
|
153
|
-
under tools/). A sample output follows; your milage may vary.
|
154
|
-
|
155
|
-
user system total real
|
156
|
-
|
157
|
-
native DRb
|
158
|
-
1 0.000000 0.000000 0.000000 ( 0.000172)
|
159
|
-
1000 0.110000 0.010000 0.120000 ( 0.119767)
|
160
|
-
|
161
|
-
Arpie: proxied MarshalProtocol with replay protection through uuidtools
|
162
|
-
1 0.000000 0.000000 0.010000 ( 0.075373)
|
163
|
-
1000 0.530000 0.090000 0.600000 ( 0.608665)
|
164
|
-
|
165
|
-
Arpie: proxied MarshalProtocol without replay protection
|
166
|
-
1 0.000000 0.000000 0.000000 ( 0.000173)
|
167
|
-
1000 0.170000 0.020000 0.190000 ( 0.194649)
|
data/lib/arpie/client.rb
DELETED
@@ -1,195 +0,0 @@
|
|
1
|
-
module Arpie
|
2
|
-
|
3
|
-
# A Client is a connection manager, and acts as the
|
4
|
-
# glue between a user-defined medium (for example, a TCP
|
5
|
-
# socket), and a protocol, with automatic reconnecting
|
6
|
-
# and fault handling.
|
7
|
-
#
|
8
|
-
# See README for examples.
|
9
|
-
class Client
|
10
|
-
# The protocol chain used.
|
11
|
-
attr_reader :protocol
|
12
|
-
|
13
|
-
# How often should this Client retry a connection.
|
14
|
-
# 0 for never, greater than 0 for that many attempts,
|
15
|
-
# nil for infinite (default).
|
16
|
-
# Values other than nil will raise network exceptions
|
17
|
-
# to the caller.
|
18
|
-
attr_accessor :connect_retry
|
19
|
-
|
20
|
-
# How long should the caller sleep after each reconnect
|
21
|
-
# attempt. (default: 1.0). The default value is probably
|
22
|
-
# okay. Do not set this to 0; that will produce
|
23
|
-
# unnecessary load in case of network failure.
|
24
|
-
attr_accessor :connect_sleep
|
25
|
-
|
26
|
-
def initialize *protocols
|
27
|
-
@protocol = Arpie::ProtocolChain.new(*protocols)
|
28
|
-
@read_io = nil
|
29
|
-
@write_io = nil
|
30
|
-
@connector = lambda { raise ArgumentError, "No connector specified, cannot connect to Endpoint." }
|
31
|
-
@connect_retry = nil
|
32
|
-
@connect_sleep = 1.0
|
33
|
-
@on_error = lambda {|client, exception|
|
34
|
-
$stderr.puts "Error in Transport IO: #{exception.message.to_s}"
|
35
|
-
$stderr.puts exception.backtrace.join("\n")
|
36
|
-
$stderr.puts "Set Transport#on_error &block to override this."
|
37
|
-
}
|
38
|
-
end
|
39
|
-
|
40
|
-
# Provide a connector block, which will be called
|
41
|
-
# each time a connection is needed.
|
42
|
-
# Expectes an IO object.
|
43
|
-
# Alternatively, you can return a two-item array.
|
44
|
-
# To test something without involving any networking,
|
45
|
-
# simply run IO.pipe in this block.
|
46
|
-
# Set +connect_immediately+ to true to connect
|
47
|
-
# immediately, instead on the first message.
|
48
|
-
def connect connect_immediately = false, &connector
|
49
|
-
@connector = connector
|
50
|
-
_connect if connect_immediately
|
51
|
-
self
|
52
|
-
end
|
53
|
-
|
54
|
-
# Set an error handler. It will be called with two
|
55
|
-
# parameters, the client, and the exception that occured.
|
56
|
-
# Optional, and just for notification.
|
57
|
-
def on_error &handler #:yields: client, exception
|
58
|
-
@on_error = handler
|
59
|
-
self
|
60
|
-
end
|
61
|
-
|
62
|
-
# Send a message. Returns immediately.
|
63
|
-
def write_message message
|
64
|
-
io_retry do
|
65
|
-
@protocol.write_message(@write_io, message)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
alias_method :<<, :write_message
|
69
|
-
|
70
|
-
# Receive a message. Blocks until received.
|
71
|
-
def read_message
|
72
|
-
io_retry do
|
73
|
-
return @protocol.read_message(@read_io)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# Execute the given block until all connection attempts
|
78
|
-
# have been exceeded.
|
79
|
-
# Yields self.
|
80
|
-
# You do not usually want to use this.
|
81
|
-
def io_retry &block
|
82
|
-
try = 0
|
83
|
-
|
84
|
-
begin
|
85
|
-
_connect
|
86
|
-
yield self
|
87
|
-
rescue IOError => e
|
88
|
-
try += 1
|
89
|
-
@on_error.call(self, e) if @on_error
|
90
|
-
p e
|
91
|
-
|
92
|
-
if @connect_retry == 0 || (@connect_retry && try > @connect_retry)
|
93
|
-
raise EOFError, "Cannot read from io: lost connection after #{try} attempts (#{e.message.to_s})"
|
94
|
-
end
|
95
|
-
|
96
|
-
sleep @connect_sleep
|
97
|
-
begin; @read_io.close if @read_io; rescue; end
|
98
|
-
@read_io = nil
|
99
|
-
begin; @write_io.close if @write_io; rescue; end
|
100
|
-
@write_io = nil
|
101
|
-
retry
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
private
|
106
|
-
|
107
|
-
def _connect
|
108
|
-
@read_io and return
|
109
|
-
@read_io, @write_io = @connector.call(self)
|
110
|
-
@protocol.reset
|
111
|
-
@write_io ||= @read_io
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
# A simple pseudo event-based client, using a thread
|
116
|
-
# with a callback.
|
117
|
-
class EventedClient < Client
|
118
|
-
private :read_message
|
119
|
-
|
120
|
-
# Set a callback for incoming messages.
|
121
|
-
def handle &handler #:yields: client, message
|
122
|
-
@handler = handler
|
123
|
-
end
|
124
|
-
|
125
|
-
private
|
126
|
-
|
127
|
-
def _read_thread
|
128
|
-
loop do
|
129
|
-
io_retry do
|
130
|
-
message = read_message
|
131
|
-
@handler and @handler.call(self, message)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def _connect
|
137
|
-
super
|
138
|
-
@read_thread ||= Thread.new { _read_thread }
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
|
143
|
-
# A Client extension which provides a RPC-like
|
144
|
-
# interface. Used by ProxyClient.
|
145
|
-
class RPCClient < Client
|
146
|
-
private :read_message, :write_message
|
147
|
-
|
148
|
-
def initialize *protocols
|
149
|
-
super(*protocols)
|
150
|
-
|
151
|
-
@on_pre_call = lambda {|client, message| }
|
152
|
-
@on_post_call = lambda {|client, message, reply| }
|
153
|
-
end
|
154
|
-
|
155
|
-
# Callback that gets invoked before placing a call to the
|
156
|
-
# Server. You can stop the call from happening by raising
|
157
|
-
# an exception (which will be passed on to the caller).
|
158
|
-
def pre_call &handler #:yields: client, message
|
159
|
-
@on_pre_call = handler
|
160
|
-
self
|
161
|
-
end
|
162
|
-
|
163
|
-
# Callback that gets invoked after receiving an answer.
|
164
|
-
# You can raise an exception here; and it will be passed
|
165
|
-
# to the caller, instead of returning the value.
|
166
|
-
def post_call &handler #:yields: client, message, reply
|
167
|
-
@on_post_call = handler
|
168
|
-
self
|
169
|
-
end
|
170
|
-
|
171
|
-
|
172
|
-
# Send a message and receive a reply in a synchronous
|
173
|
-
# fashion. Will block until transmitted, or until
|
174
|
-
# all reconnect attempts failed.
|
175
|
-
def request message
|
176
|
-
reply = nil
|
177
|
-
|
178
|
-
@on_pre_call.call(self, message) if @on_pre_call
|
179
|
-
|
180
|
-
io_retry do
|
181
|
-
write_message(message)
|
182
|
-
reply = read_message
|
183
|
-
end
|
184
|
-
|
185
|
-
@on_post_call.call(self, message, reply) if @on_post_call
|
186
|
-
|
187
|
-
case reply
|
188
|
-
when Exception
|
189
|
-
raise reply
|
190
|
-
else
|
191
|
-
reply
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
data/lib/arpie/proxy.rb
DELETED
@@ -1,143 +0,0 @@
|
|
1
|
-
require 'uuidtools'
|
2
|
-
|
3
|
-
module Arpie
|
4
|
-
|
5
|
-
# A Endpoint which supports arbitary objects as handlers,
|
6
|
-
# instead of a proc.
|
7
|
-
#
|
8
|
-
# Note that this will only export public instance method
|
9
|
-
# of the class as they are defined.
|
10
|
-
class ProxyServer < Server
|
11
|
-
|
12
|
-
# An array containing symbols of method names that the
|
13
|
-
# handler should be allowed to call. Defaults to
|
14
|
-
# all public instance methods the class defines (wysiwyg).
|
15
|
-
# Set this to nil to allow calling of ALL methods, but be
|
16
|
-
# warned of the security implications (instance_eval, ..).
|
17
|
-
attr_accessor :interface
|
18
|
-
|
19
|
-
# Set this to false to disable replay protection.
|
20
|
-
attr_accessor :uuid_tracking
|
21
|
-
|
22
|
-
# The maximum number of method call results to remember.
|
23
|
-
# Defaults to 100, which should be enough for everyone. ;)
|
24
|
-
attr_accessor :max_uuids
|
25
|
-
|
26
|
-
def initialize *va
|
27
|
-
super
|
28
|
-
@uuids = {}
|
29
|
-
@max_uuids = 100
|
30
|
-
@uuid_tracking = true
|
31
|
-
end
|
32
|
-
|
33
|
-
# Set a class handler. All public instance methods will be
|
34
|
-
# callable over RPC (with a Proxy object) (see attribute interface).
|
35
|
-
def handle handler
|
36
|
-
@handler = handler
|
37
|
-
@interface = handler.class.public_instance_methods(false).map {|x|
|
38
|
-
x.to_sym
|
39
|
-
}
|
40
|
-
self
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def _handle endpoint, message
|
46
|
-
if !@handler.respond_to?(message.meth.to_sym) ||
|
47
|
-
(@interface && !@interface.index(message.meth.to_sym))
|
48
|
-
endpoint.write_message NoMethodError.new("No such method: #{message.meth.inspect}")
|
49
|
-
return
|
50
|
-
end
|
51
|
-
|
52
|
-
begin
|
53
|
-
# Prune old serials. This can probably be optimized, but works well enough for now.
|
54
|
-
if @uuid_tracking && message.uuid
|
55
|
-
uuid, serial = * message.uuid
|
56
|
-
|
57
|
-
raise ArgumentError,
|
58
|
-
"Invalid UUID given, expect [uuid/64bit, serial/numeric]." unless
|
59
|
-
uuid.is_a?(Integer) && serial.is_a?(Integer)
|
60
|
-
|
61
|
-
# Limit to sane values.
|
62
|
-
uuid &= 0xffffffffffffffff
|
63
|
-
serial &= 0xffffffffffffffff
|
64
|
-
|
65
|
-
timestamps = @uuids.values.map {|v| v[0] }.sort
|
66
|
-
latest_timestamp = timestamps[-@max_uuids]
|
67
|
-
@uuids.reject! {|uuid, (time, value)|
|
68
|
-
time < latest_timestamp
|
69
|
-
} if latest_timestamp
|
70
|
-
|
71
|
-
endpoint.write_message((@uuids[message.uuid] ||=
|
72
|
-
[Time.now, @handler.send(message.meth, *message.argv)])[1])
|
73
|
-
|
74
|
-
else
|
75
|
-
endpoint.write_message @handler.send(message.meth, *message.argv)
|
76
|
-
end
|
77
|
-
rescue IOError
|
78
|
-
raise
|
79
|
-
|
80
|
-
rescue Exception => e
|
81
|
-
endpoint.write_message RuntimeError.new("Internal Error")
|
82
|
-
raise
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
# A Proxy is a wrapper around a Client, which transparently tunnels
|
88
|
-
# method calls to the remote ProxyServer.
|
89
|
-
# Note that the methods of Client cannot be proxied.
|
90
|
-
class ProxyClient < RPCClient
|
91
|
-
attr_accessor :namespace
|
92
|
-
|
93
|
-
# Set to false to disable replay protection.
|
94
|
-
# Default is true.
|
95
|
-
attr_accessor :replay_protection
|
96
|
-
|
97
|
-
# The current serial for this transport.
|
98
|
-
attr_accessor :serial
|
99
|
-
|
100
|
-
# The generated uuid for this Client.
|
101
|
-
# nil if no call has been made yet.
|
102
|
-
attr_accessor :uuid
|
103
|
-
|
104
|
-
def initialize *protocols
|
105
|
-
super
|
106
|
-
@protocol, @namespace = protocol, ""
|
107
|
-
@serial = 0
|
108
|
-
@uuid_generator = lambda {|client, method, argv|
|
109
|
-
UUIDTools::UUID.random_create.to_i
|
110
|
-
}
|
111
|
-
@replay_protection = true
|
112
|
-
end
|
113
|
-
|
114
|
-
# Set up a new UUID generator for this proxy client.
|
115
|
-
# Make sure that this yields really random numbers.
|
116
|
-
# The default uses the uuidtools gem and is usually okay.
|
117
|
-
#
|
118
|
-
# This gets called exactly once for each created ProxyClient.
|
119
|
-
def uuid_generator &handler #:yields: client, method, argv
|
120
|
-
@uuid_generator = handler
|
121
|
-
self
|
122
|
-
end
|
123
|
-
|
124
|
-
def method_missing meth, *argv # :nodoc:
|
125
|
-
serial = nil
|
126
|
-
if @replay_protection
|
127
|
-
serial = [
|
128
|
-
@uuid ||= @uuid_generator.call(self, meth, argv),
|
129
|
-
@serial += 1
|
130
|
-
]
|
131
|
-
end
|
132
|
-
|
133
|
-
call = Arpie::RPCall.new(@namespace, meth, argv, serial)
|
134
|
-
ret = self.request(call)
|
135
|
-
case ret
|
136
|
-
when Exception
|
137
|
-
raise ret
|
138
|
-
else
|
139
|
-
ret
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|