xmlrpc 0.1.0

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.
@@ -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