xmlrpc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: false
2
+ #
3
+ # xmlrpc/base64.rb
4
+ # Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
5
+ #
6
+ # Released under the same term of license as Ruby.
7
+
8
+ module XMLRPC # :nodoc:
9
+
10
+ # This class is necessary for 'xmlrpc4r' to determine that a string should
11
+ # be transmitted base64-encoded and not as a raw-string.
12
+ #
13
+ # You can use XMLRPC::Base64 on the client and server-side as a
14
+ # parameter and/or return-value.
15
+ class Base64
16
+
17
+ # Creates a new XMLRPC::Base64 instance with string +str+ as the
18
+ # internal string. When +state+ is +:dec+ it assumes that the
19
+ # string +str+ is not in base64 format (perhaps already decoded),
20
+ # otherwise if +state+ is +:enc+ it decodes +str+
21
+ # and stores it as the internal string.
22
+ def initialize(str, state = :dec)
23
+ case state
24
+ when :enc
25
+ @str = Base64.decode(str)
26
+ when :dec
27
+ @str = str
28
+ else
29
+ raise ArgumentError, "wrong argument; either :enc or :dec"
30
+ end
31
+ end
32
+
33
+ # Returns the decoded internal string.
34
+ def decoded
35
+ @str
36
+ end
37
+
38
+ # Returns the base64 encoded internal string.
39
+ def encoded
40
+ Base64.encode(@str)
41
+ end
42
+
43
+
44
+ # Decodes string +str+ with base64 and returns that value.
45
+ def Base64.decode(str)
46
+ str.gsub(/\s+/, "").unpack("m")[0]
47
+ end
48
+
49
+ # Encodes string +str+ with base64 and returns that value.
50
+ def Base64.encode(str)
51
+ [str].pack("m")
52
+ end
53
+
54
+ end
55
+
56
+
57
+ end # module XMLRPC
58
+
59
+
60
+ =begin
61
+ = History
62
+ $Id$
63
+ =end
@@ -0,0 +1,629 @@
1
+ # frozen_string_literal: false
2
+ # xmlrpc/client.rb
3
+ # Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
4
+ #
5
+ # Released under the same term of license as Ruby.
6
+ #
7
+ # History
8
+ # $Id$
9
+ #
10
+ require "xmlrpc/parser"
11
+ require "xmlrpc/create"
12
+ require "xmlrpc/config"
13
+ require "xmlrpc/utils" # ParserWriterChooseMixin
14
+ require "net/http"
15
+ require "uri"
16
+
17
+ module XMLRPC # :nodoc:
18
+
19
+ # Provides remote procedure calls to a XML-RPC server.
20
+ #
21
+ # After setting the connection-parameters with XMLRPC::Client.new which
22
+ # creates a new XMLRPC::Client instance, you can execute a remote procedure
23
+ # by sending the XMLRPC::Client#call or XMLRPC::Client#call2
24
+ # message to this new instance.
25
+ #
26
+ # The given parameters indicate which method to call on the remote-side and
27
+ # of course the parameters for the remote procedure.
28
+ #
29
+ # require "xmlrpc/client"
30
+ #
31
+ # server = XMLRPC::Client.new("www.ruby-lang.org", "/RPC2", 80)
32
+ # begin
33
+ # param = server.call("michael.add", 4, 5)
34
+ # puts "4 + 5 = #{param}"
35
+ # rescue XMLRPC::FaultException => e
36
+ # puts "Error:"
37
+ # puts e.faultCode
38
+ # puts e.faultString
39
+ # end
40
+ #
41
+ # or
42
+ #
43
+ # require "xmlrpc/client"
44
+ #
45
+ # server = XMLRPC::Client.new("www.ruby-lang.org", "/RPC2", 80)
46
+ # ok, param = server.call2("michael.add", 4, 5)
47
+ # if ok then
48
+ # puts "4 + 5 = #{param}"
49
+ # else
50
+ # puts "Error:"
51
+ # puts param.faultCode
52
+ # puts param.faultString
53
+ # end
54
+ class Client
55
+
56
+ USER_AGENT = "XMLRPC::Client (Ruby #{RUBY_VERSION})"
57
+
58
+ include ParserWriterChooseMixin
59
+ include ParseContentType
60
+
61
+
62
+ # Creates an object which represents the remote XML-RPC server on the
63
+ # given +host+. If the server is CGI-based, +path+ is the
64
+ # path to the CGI-script, which will be called, otherwise (in the
65
+ # case of a standalone server) +path+ should be <tt>"/RPC2"</tt>.
66
+ # +port+ is the port on which the XML-RPC server listens.
67
+ #
68
+ # If +proxy_host+ is given, then a proxy server listening at
69
+ # +proxy_host+ is used. +proxy_port+ is the port of the
70
+ # proxy server.
71
+ #
72
+ # Default values for +host+, +path+ and +port+ are 'localhost', '/RPC2' and
73
+ # '80' respectively using SSL '443'.
74
+ #
75
+ # If +user+ and +password+ are given, each time a request is sent,
76
+ # an Authorization header is sent. Currently only Basic Authentication is
77
+ # implemented, no Digest.
78
+ #
79
+ # If +use_ssl+ is set to +true+, communication over SSL is enabled.
80
+ #
81
+ # Parameter +timeout+ is the time to wait for a XML-RPC response, defaults to 30.
82
+ def initialize(host=nil, path=nil, port=nil, proxy_host=nil, proxy_port=nil,
83
+ user=nil, password=nil, use_ssl=nil, timeout=nil)
84
+
85
+ @http_header_extra = nil
86
+ @http_last_response = nil
87
+ @cookie = nil
88
+
89
+ @host = host || "localhost"
90
+ @path = path || "/RPC2"
91
+ @proxy_host = proxy_host
92
+ @proxy_port = proxy_port
93
+ @proxy_host ||= 'localhost' if @proxy_port != nil
94
+ @proxy_port ||= 8080 if @proxy_host != nil
95
+ @use_ssl = use_ssl || false
96
+ @timeout = timeout || 30
97
+
98
+ if use_ssl
99
+ require "net/https"
100
+ @port = port || 443
101
+ else
102
+ @port = port || 80
103
+ end
104
+
105
+ @user, @password = user, password
106
+
107
+ set_auth
108
+
109
+ # convert ports to integers
110
+ @port = @port.to_i if @port != nil
111
+ @proxy_port = @proxy_port.to_i if @proxy_port != nil
112
+
113
+ # HTTP object for synchronous calls
114
+ @http = net_http(@host, @port, @proxy_host, @proxy_port)
115
+ @http.use_ssl = @use_ssl if @use_ssl
116
+ @http.read_timeout = @timeout
117
+ @http.open_timeout = @timeout
118
+
119
+ @parser = nil
120
+ @create = nil
121
+ end
122
+
123
+
124
+ class << self
125
+
126
+ # Creates an object which represents the remote XML-RPC server at the
127
+ # given +uri+. The URI should have a host, port, path, user and password.
128
+ # Example: https://user:password@host:port/path
129
+ #
130
+ # Raises an ArgumentError if the +uri+ is invalid,
131
+ # or if the protocol isn't http or https.
132
+ #
133
+ # If a +proxy+ is given it should be in the form of "host:port".
134
+ #
135
+ # The optional +timeout+ defaults to 30 seconds.
136
+ def new2(uri, proxy=nil, timeout=nil)
137
+ begin
138
+ url = URI(uri)
139
+ rescue URI::InvalidURIError => e
140
+ raise ArgumentError, e.message, e.backtrace
141
+ end
142
+
143
+ unless URI::HTTP === url
144
+ raise ArgumentError, "Wrong protocol specified. Only http or https allowed!"
145
+ end
146
+
147
+ proto = url.scheme
148
+ user = url.user
149
+ passwd = url.password
150
+ host = url.host
151
+ port = url.port
152
+ path = url.path.empty? ? nil : url.request_uri
153
+
154
+ proxy_host, proxy_port = (proxy || "").split(":")
155
+ proxy_port = proxy_port.to_i if proxy_port
156
+
157
+ self.new(host, path, port, proxy_host, proxy_port, user, passwd, (proto == "https"), timeout)
158
+ end
159
+
160
+ alias new_from_uri new2
161
+
162
+ # Receives a Hash and calls XMLRPC::Client.new
163
+ # with the corresponding values.
164
+ #
165
+ # The +hash+ parameter has following case-insensitive keys:
166
+ # * host
167
+ # * path
168
+ # * port
169
+ # * proxy_host
170
+ # * proxy_port
171
+ # * user
172
+ # * password
173
+ # * use_ssl
174
+ # * timeout
175
+ def new3(hash={})
176
+
177
+ # convert all keys into lowercase strings
178
+ h = {}
179
+ hash.each { |k,v| h[k.to_s.downcase] = v }
180
+
181
+ self.new(h['host'], h['path'], h['port'], h['proxy_host'], h['proxy_port'], h['user'], h['password'],
182
+ h['use_ssl'], h['timeout'])
183
+ end
184
+
185
+ alias new_from_hash new3
186
+
187
+ end
188
+
189
+
190
+ # Returns the Net::HTTP object for the client. If you want to
191
+ # change HTTP client options except header, cookie, timeout,
192
+ # user and password, use Net::HTTP directly.
193
+ #
194
+ # Since 2.1.0.
195
+ attr_reader :http
196
+
197
+ # Add additional HTTP headers to the request
198
+ attr_accessor :http_header_extra
199
+
200
+ # Returns the Net::HTTPResponse object of the last RPC.
201
+ attr_reader :http_last_response
202
+
203
+ # Get and set the HTTP Cookie header.
204
+ attr_accessor :cookie
205
+
206
+
207
+ # Return the corresponding attributes.
208
+ attr_reader :timeout, :user, :password
209
+
210
+ # Sets the Net::HTTP#read_timeout and Net::HTTP#open_timeout to
211
+ # +new_timeout+
212
+ def timeout=(new_timeout)
213
+ @timeout = new_timeout
214
+ @http.read_timeout = @timeout
215
+ @http.open_timeout = @timeout
216
+ end
217
+
218
+ # Changes the user for the Basic Authentication header to +new_user+
219
+ def user=(new_user)
220
+ @user = new_user
221
+ set_auth
222
+ end
223
+
224
+ # Changes the password for the Basic Authentication header to
225
+ # +new_password+
226
+ def password=(new_password)
227
+ @password = new_password
228
+ set_auth
229
+ end
230
+
231
+ # Invokes the method named +method+ with the parameters given by
232
+ # +args+ on the XML-RPC server.
233
+ #
234
+ # The +method+ parameter is converted into a String and should
235
+ # be a valid XML-RPC method-name.
236
+ #
237
+ # Each parameter of +args+ must be of one of the following types,
238
+ # where Hash, Struct and Array can contain any of these listed _types_:
239
+ #
240
+ # * Fixnum, Bignum
241
+ # * TrueClass, FalseClass, +true+, +false+
242
+ # * String, Symbol
243
+ # * Float
244
+ # * Hash, Struct
245
+ # * Array
246
+ # * Date, Time, XMLRPC::DateTime
247
+ # * XMLRPC::Base64
248
+ # * A Ruby object which class includes XMLRPC::Marshallable
249
+ # (only if Config::ENABLE_MARSHALLING is +true+).
250
+ # That object is converted into a hash, with one additional key/value
251
+ # pair <code>___class___</code> which contains the class name
252
+ # for restoring that object later.
253
+ #
254
+ # The method returns the return-value from the Remote Procedure Call.
255
+ #
256
+ # The type of the return-value is one of the types shown above.
257
+ #
258
+ # A Bignum is only allowed when it fits in 32-bit. A XML-RPC
259
+ # +dateTime.iso8601+ type is always returned as a XMLRPC::DateTime object.
260
+ # Struct is never returned, only a Hash, the same for a Symbol, where as a
261
+ # String is always returned. XMLRPC::Base64 is returned as a String from
262
+ # xmlrpc4r version 1.6.1 on.
263
+ #
264
+ # If the remote procedure returned a fault-structure, then a
265
+ # XMLRPC::FaultException exception is raised, which has two accessor-methods
266
+ # +faultCode+ an Integer, and +faultString+ a String.
267
+ def call(method, *args)
268
+ ok, param = call2(method, *args)
269
+ if ok
270
+ param
271
+ else
272
+ raise param
273
+ end
274
+ end
275
+
276
+ # The difference between this method and XMLRPC::Client#call is, that
277
+ # this method will <b>NOT</b> raise a XMLRPC::FaultException exception.
278
+ #
279
+ # The method returns an array of two values. The first value indicates if
280
+ # the second value is +true+ or an XMLRPC::FaultException.
281
+ #
282
+ # Both are explained in XMLRPC::Client#call.
283
+ #
284
+ # Simple to remember: The "2" in "call2" denotes the number of values it returns.
285
+ def call2(method, *args)
286
+ request = create().methodCall(method, *args)
287
+ data = do_rpc(request, false)
288
+ parser().parseMethodResponse(data)
289
+ end
290
+
291
+ # Similar to XMLRPC::Client#call, however can be called concurrently and
292
+ # use a new connection for each request. In contrast to the corresponding
293
+ # method without the +_async+ suffix, which use connect-alive (one
294
+ # connection for all requests).
295
+ #
296
+ # Note, that you have to use Thread to call these methods concurrently.
297
+ # The following example calls two methods concurrently:
298
+ #
299
+ # Thread.new {
300
+ # p client.call_async("michael.add", 4, 5)
301
+ # }
302
+ #
303
+ # Thread.new {
304
+ # p client.call_async("michael.div", 7, 9)
305
+ # }
306
+ #
307
+ def call_async(method, *args)
308
+ ok, param = call2_async(method, *args)
309
+ if ok
310
+ param
311
+ else
312
+ raise param
313
+ end
314
+ end
315
+
316
+ # Same as XMLRPC::Client#call2, but can be called concurrently.
317
+ #
318
+ # See also XMLRPC::Client#call_async
319
+ def call2_async(method, *args)
320
+ request = create().methodCall(method, *args)
321
+ data = do_rpc(request, true)
322
+ parser().parseMethodResponse(data)
323
+ end
324
+
325
+
326
+ # You can use this method to execute several methods on a XMLRPC server
327
+ # which support the multi-call extension.
328
+ #
329
+ # s.multicall(
330
+ # ['michael.add', 3, 4],
331
+ # ['michael.sub', 4, 5]
332
+ # )
333
+ # # => [7, -1]
334
+ def multicall(*methods)
335
+ ok, params = multicall2(*methods)
336
+ if ok
337
+ params
338
+ else
339
+ raise params
340
+ end
341
+ end
342
+
343
+ # Same as XMLRPC::Client#multicall, but returns two parameters instead of
344
+ # raising an XMLRPC::FaultException.
345
+ #
346
+ # See XMLRPC::Client#call2
347
+ def multicall2(*methods)
348
+ gen_multicall(methods, false)
349
+ end
350
+
351
+ # Similar to XMLRPC::Client#multicall, however can be called concurrently and
352
+ # use a new connection for each request. In contrast to the corresponding
353
+ # method without the +_async+ suffix, which use connect-alive (one
354
+ # connection for all requests).
355
+ #
356
+ # Note, that you have to use Thread to call these methods concurrently.
357
+ # The following example calls two methods concurrently:
358
+ #
359
+ # Thread.new {
360
+ # p client.multicall_async("michael.add", 4, 5)
361
+ # }
362
+ #
363
+ # Thread.new {
364
+ # p client.multicall_async("michael.div", 7, 9)
365
+ # }
366
+ #
367
+ def multicall_async(*methods)
368
+ ok, params = multicall2_async(*methods)
369
+ if ok
370
+ params
371
+ else
372
+ raise params
373
+ end
374
+ end
375
+
376
+ # Same as XMLRPC::Client#multicall2, but can be called concurrently.
377
+ #
378
+ # See also XMLRPC::Client#multicall_async
379
+ def multicall2_async(*methods)
380
+ gen_multicall(methods, true)
381
+ end
382
+
383
+
384
+ # Returns an object of class XMLRPC::Client::Proxy, initialized with
385
+ # +prefix+ and +args+.
386
+ #
387
+ # A proxy object returned by this method behaves like XMLRPC::Client#call,
388
+ # i.e. a call on that object will raise a XMLRPC::FaultException when a
389
+ # fault-structure is returned by that call.
390
+ def proxy(prefix=nil, *args)
391
+ Proxy.new(self, prefix, args, :call)
392
+ end
393
+
394
+ # Almost the same like XMLRPC::Client#proxy only that a call on the returned
395
+ # XMLRPC::Client::Proxy object will return two parameters.
396
+ #
397
+ # See XMLRPC::Client#call2
398
+ def proxy2(prefix=nil, *args)
399
+ Proxy.new(self, prefix, args, :call2)
400
+ end
401
+
402
+ # Similar to XMLRPC::Client#proxy, however can be called concurrently and
403
+ # use a new connection for each request. In contrast to the corresponding
404
+ # method without the +_async+ suffix, which use connect-alive (one
405
+ # connection for all requests).
406
+ #
407
+ # Note, that you have to use Thread to call these methods concurrently.
408
+ # The following example calls two methods concurrently:
409
+ #
410
+ # Thread.new {
411
+ # p client.proxy_async("michael.add", 4, 5)
412
+ # }
413
+ #
414
+ # Thread.new {
415
+ # p client.proxy_async("michael.div", 7, 9)
416
+ # }
417
+ #
418
+ def proxy_async(prefix=nil, *args)
419
+ Proxy.new(self, prefix, args, :call_async)
420
+ end
421
+
422
+ # Same as XMLRPC::Client#proxy2, but can be called concurrently.
423
+ #
424
+ # See also XMLRPC::Client#proxy_async
425
+ def proxy2_async(prefix=nil, *args)
426
+ Proxy.new(self, prefix, args, :call2_async)
427
+ end
428
+
429
+
430
+ private
431
+
432
+ def net_http(host, port, proxy_host, proxy_port)
433
+ Net::HTTP.new host, port, proxy_host, proxy_port
434
+ end
435
+
436
+ def dup_net_http
437
+ http = net_http(@http.address,
438
+ @http.port,
439
+ @http.proxy_address,
440
+ @http.proxy_port)
441
+ http.proxy_user = @http.proxy_user
442
+ http.proxy_pass = @http.proxy_pass
443
+ if @http.use_ssl?
444
+ http.use_ssl = true
445
+ Net::HTTP::SSL_ATTRIBUTES.each do |attribute|
446
+ http.__send__("#{attribute}=", @http.__send__(attribute))
447
+ end
448
+ end
449
+ http.read_timeout = @http.read_timeout
450
+ http.open_timeout = @http.open_timeout
451
+ http
452
+ end
453
+
454
+ def set_auth
455
+ if @user.nil?
456
+ @auth = nil
457
+ else
458
+ a = "#@user"
459
+ a << ":#@password" if @password != nil
460
+ @auth = "Basic " + [a].pack("m0")
461
+ end
462
+ end
463
+
464
+ def do_rpc(request, async=false)
465
+ header = {
466
+ "User-Agent" => USER_AGENT,
467
+ "Content-Type" => "text/xml; charset=utf-8",
468
+ "Content-Length" => request.bytesize.to_s,
469
+ "Connection" => (async ? "close" : "keep-alive")
470
+ }
471
+
472
+ header["Cookie"] = @cookie if @cookie
473
+ header.update(@http_header_extra) if @http_header_extra
474
+
475
+ if @auth != nil
476
+ # add authorization header
477
+ header["Authorization"] = @auth
478
+ end
479
+
480
+ resp = nil
481
+ @http_last_response = nil
482
+
483
+ if async
484
+ # use a new HTTP object for each call
485
+ http = dup_net_http
486
+
487
+ # post request
488
+ http.start {
489
+ resp = http.request_post(@path, request, header)
490
+ }
491
+ else
492
+ # reuse the HTTP object for each call => connection alive is possible
493
+ # we must start connection explicitly first time so that http.request
494
+ # does not assume that we don't want keepalive
495
+ @http.start if not @http.started?
496
+
497
+ # post request
498
+ resp = @http.request_post(@path, request, header)
499
+ end
500
+
501
+ @http_last_response = resp
502
+
503
+ data = resp.body
504
+
505
+ if resp.code == "401"
506
+ # Authorization Required
507
+ raise "Authorization failed.\nHTTP-Error: #{resp.code} #{resp.message}"
508
+ elsif resp.code[0,1] != "2"
509
+ raise "HTTP-Error: #{resp.code} #{resp.message}"
510
+ end
511
+
512
+ # assume text/xml on instances where Content-Type header is not set
513
+ ct_expected = resp["Content-Type"] || 'text/xml'
514
+ ct = parse_content_type(ct_expected).first
515
+ if ct != "text/xml"
516
+ if ct == "text/html"
517
+ raise "Wrong content-type (received '#{ct}' but expected 'text/xml'): \n#{data}"
518
+ else
519
+ raise "Wrong content-type (received '#{ct}' but expected 'text/xml')"
520
+ end
521
+ end
522
+
523
+ expected = resp["Content-Length"] || "<unknown>"
524
+ if data.nil? or data.bytesize == 0
525
+ raise "Wrong size. Was #{data.bytesize}, should be #{expected}"
526
+ end
527
+
528
+ parse_set_cookies(resp.get_fields("Set-Cookie"))
529
+
530
+ return data
531
+ end
532
+
533
+ def parse_set_cookies(set_cookies)
534
+ return if set_cookies.nil?
535
+ return if set_cookies.empty?
536
+ require 'webrick/cookie'
537
+ pairs = {}
538
+ set_cookies.each do |set_cookie|
539
+ cookie = WEBrick::Cookie.parse_set_cookie(set_cookie)
540
+ pairs.delete(cookie.name)
541
+ pairs[cookie.name] = cookie.value
542
+ end
543
+ cookies = pairs.collect do |name, value|
544
+ WEBrick::Cookie.new(name, value).to_s
545
+ end
546
+ @cookie = cookies.join("; ")
547
+ end
548
+
549
+ def gen_multicall(methods=[], async=false)
550
+ meth = :call2
551
+ meth = :call2_async if async
552
+
553
+ ok, params = self.send(meth, "system.multicall",
554
+ methods.collect {|m| {'methodName' => m[0], 'params' => m[1..-1]} }
555
+ )
556
+
557
+ if ok
558
+ params = params.collect do |param|
559
+ if param.is_a? Array
560
+ param[0]
561
+ elsif param.is_a? Hash
562
+ XMLRPC::FaultException.new(param["faultCode"], param["faultString"])
563
+ else
564
+ raise "Wrong multicall return value"
565
+ end
566
+ end
567
+ end
568
+
569
+ return ok, params
570
+ end
571
+
572
+
573
+
574
+ # XML-RPC calls look nicer!
575
+ #
576
+ # You can call any method onto objects of that class - the object handles
577
+ # XMLRPC::Client::Proxy#method_missing and will forward the method call to
578
+ # a XML-RPC server.
579
+ #
580
+ # Don't use this class directly, instead use the public instance method
581
+ # XMLRPC::Client#proxy or XMLRPC::Client#proxy2.
582
+ #
583
+ # require "xmlrpc/client"
584
+ #
585
+ # server = XMLRPC::Client.new("www.ruby-lang.org", "/RPC2", 80)
586
+ #
587
+ # michael = server.proxy("michael")
588
+ # michael2 = server.proxy("michael", 4)
589
+ #
590
+ # # both calls should return the same value '9'.
591
+ # p michael.add(4,5)
592
+ # p michael2.add(5)
593
+ class Proxy
594
+
595
+ # Creates an object which provides XMLRPC::Client::Proxy#method_missing.
596
+ #
597
+ # The given +server+ must be an instance of XMLRPC::Client, which is the
598
+ # XML-RPC server to be used for a XML-RPC call.
599
+ #
600
+ # +prefix+ and +delim+ will be prepended to the method name called onto this object.
601
+ #
602
+ # An optional parameter +meth+ is the method to use for a RPC.
603
+ # It can be either, call, call2, call_async, call2_async
604
+ #
605
+ # +args+ are arguments which are automatically given to every XML-RPC
606
+ # call before being provided through +method_missing+.
607
+ def initialize(server, prefix, args=[], meth=:call, delim=".")
608
+ @server = server
609
+ @prefix = prefix ? prefix + delim : ""
610
+ @args = args
611
+ @meth = meth
612
+ end
613
+
614
+ # Every method call is forwarded to the XML-RPC server defined in
615
+ # XMLRPC::Client::Proxy#new.
616
+ #
617
+ # Note: Inherited methods from class Object cannot be used as XML-RPC
618
+ # names, because they get around +method_missing+.
619
+ def method_missing(mid, *args)
620
+ pre = @prefix + mid.to_s
621
+ arg = @args + args
622
+ @server.send(@meth, pre, *arg)
623
+ end
624
+
625
+ end # class Proxy
626
+
627
+ end # class Client
628
+
629
+ end # module XMLRPC