source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/xmlrpc/client.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

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