blob: 51a974ba4d1059787b88a8eab325126768e9e123 [file] [log] [blame]
Fredrik Lundhb9056332001-07-11 17:42:21 +00001#
2# XML-RPC CLIENT LIBRARY
3# $Id$
4#
5# an XML-RPC client interface for Python.
6#
7# the marshalling and response parser code can also be used to
8# implement XML-RPC servers.
9#
10# Notes:
11# this version is designed to work with Python 1.5.2 or newer.
12# unicode encoding support requires at least Python 1.6.
13# experimental HTTPS requires Python 2.0 built with SSL sockets.
14# expat parser support requires Python 2.0 with pyexpat support.
15#
16# History:
17# 1999-01-14 fl Created
18# 1999-01-15 fl Changed dateTime to use localtime
19# 1999-01-16 fl Added Binary/base64 element, default to RPC2 service
20# 1999-01-19 fl Fixed array data element (from Skip Montanaro)
21# 1999-01-21 fl Fixed dateTime constructor, etc.
22# 1999-02-02 fl Added fault handling, handle empty sequences, etc.
23# 1999-02-10 fl Fixed problem with empty responses (from Skip Montanaro)
24# 1999-06-20 fl Speed improvements, pluggable parsers/transports (0.9.8)
25# 2000-11-28 fl Changed boolean to check the truth value of its argument
26# 2001-02-24 fl Added encoding/Unicode/SafeTransport patches
27# 2001-02-26 fl Added compare support to wrappers (0.9.9/1.0b1)
28# 2001-03-28 fl Make sure response tuple is a singleton
29# 2001-03-29 fl Don't require empty params element (from Nicholas Riley)
30# 2001-06-10 fl Folded in _xmlrpclib accelerator support
31#
32# Copyright (c) 1999-2001 by Secret Labs AB.
33# Copyright (c) 1999-2001 by Fredrik Lundh.
34#
35# info@pythonware.com
36# http://www.pythonware.com
37#
38# --------------------------------------------------------------------
39# The XML-RPC client interface is
40#
41# Copyright (c) 1999-2001 by Secret Labs AB
42# Copyright (c) 1999-2001 by Fredrik Lundh
43#
44# By obtaining, using, and/or copying this software and/or its
45# associated documentation, you agree that you have read, understood,
46# and will comply with the following terms and conditions:
47#
48# Permission to use, copy, modify, and distribute this software and
49# its associated documentation for any purpose and without fee is
50# hereby granted, provided that the above copyright notice appears in
51# all copies, and that both that copyright notice and this permission
52# notice appear in supporting documentation, and that the name of
53# Secret Labs AB or the author not be used in advertising or publicity
54# pertaining to distribution of the software without specific, written
55# prior permission.
56#
57# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
58# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
59# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
60# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
61# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
62# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
63# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
64# OF THIS SOFTWARE.
65# --------------------------------------------------------------------
66
67#
68# things to look into before 1.0 final:
69
70# TODO: unicode marshalling -DONE
71# TODO: ascii-compatible encoding support -DONE
72# TODO: safe transport -DONE (but mostly untested)
73# TODO: sgmlop memory leak -DONE
74# TODO: sgmlop xml parsing -DONE
75# TODO: support unicode method names -DONE
76# TODO: update selftest -DONE
77# TODO: add docstrings -DONE
78# TODO: clean up parser encoding (trust the parser) -DONE
79# TODO: expat support -DONE
80# TODO: _xmlrpclib accelerator support -DONE
81# TODO: use smarter/faster escape from effdom
82# TODO: support basic authentication (see robin's patch)
83# TODO: fix host tuple handling in the server constructor
84# TODO: let transport verify schemes
85# TODO: update documentation
86# TODO: authentication plugins
87# TODO: memo problem (see HP's mail)
88
89import re, string, time, operator
90import urllib, xmllib
91from types import *
92from cgi import escape
93
94try:
95 unicode
96except NameError:
97 unicode = None # unicode support not available
98
99def _decode(data, encoding, is8bit=re.compile("[\x80-\xff]").search):
100 # decode non-ascii string (if possible)
101 if unicode and encoding and is8bit(data):
102 data = unicode(data, encoding)
103 return data
104
105if unicode:
106 def _stringify(string):
107 # convert to 7-bit ascii if possible
108 try:
109 return str(string)
110 except UnicodeError:
111 return string
112else:
113 def _stringify(string):
114 return string
115
116__version__ = "1.0b2"
117
118# --------------------------------------------------------------------
119# Exceptions
120
121class Error:
122 # base class for client errors
123 pass
124
125class ProtocolError(Error):
126 # indicates an HTTP protocol error
127 def __init__(self, url, errcode, errmsg, headers):
128 self.url = url
129 self.errcode = errcode
130 self.errmsg = errmsg
131 self.headers = headers
132 def __repr__(self):
133 return (
134 "<ProtocolError for %s: %s %s>" %
135 (self.url, self.errcode, self.errmsg)
136 )
137
138class ResponseError(Error):
139 # indicates a broken response package
140 pass
141
142class Fault(Error):
143 # indicates a XML-RPC fault package
144 def __init__(self, faultCode, faultString, **extra):
145 self.faultCode = faultCode
146 self.faultString = faultString
147 def __repr__(self):
148 return (
149 "<Fault %s: %s>" %
150 (self.faultCode, repr(self.faultString))
151 )
152
153# --------------------------------------------------------------------
154# Special values
155
156# boolean wrapper
157# use True or False to generate a "boolean" XML-RPC value
158
159class Boolean:
160
161 def __init__(self, value = 0):
162 self.value = operator.truth(value)
163
164 def encode(self, out):
165 out.write("<value><boolean>%d</boolean></value>\n" % self.value)
166
167 def __cmp__(self, other):
168 if isinstance(other, Boolean):
169 other = other.value
170 return cmp(self.value, other)
171
172 def __repr__(self):
173 if self.value:
174 return "<Boolean True at %x>" % id(self)
175 else:
176 return "<Boolean False at %x>" % id(self)
177
178 def __int__(self):
179 return self.value
180
181 def __nonzero__(self):
182 return self.value
183
184True, False = Boolean(1), Boolean(0)
185
186def boolean(value, truefalse=(False, True)):
187 # convert any Python value to XML-RPC boolean
188 return truefalse[operator.truth(value)]
189
190#
191# dateTime wrapper
192# wrap your iso8601 string or time tuple or localtime integer value
193# in this class to generate a "dateTime.iso8601" XML-RPC value
194
195class DateTime:
196
197 def __init__(self, value=0):
198 t = type(value)
199 if not isinstance(t, StringType):
200 if not isinstance(t, TupleType):
201 if value == 0:
202 value = time.time()
203 value = time.localtime(value)
204 value = time.strftime("%Y%m%dT%H:%M:%S", value)
205 self.value = value
206
207 def __cmp__(self, other):
208 if isinstance(other, DateTime):
209 other = other.value
210 return cmp(self.value, other)
211
212 def __repr__(self):
213 return "<DateTime %s at %x>" % (self.value, id(self))
214
215 def decode(self, data):
216 self.value = string.strip(data)
217
218 def encode(self, out):
219 out.write("<value><dateTime.iso8601>")
220 out.write(self.value)
221 out.write("</dateTime.iso8601></value>\n")
222
223def datetime(data):
224 value = DateTime()
225 value.decode(data)
226 return value
227
228#
229# binary data wrapper
230
231class Binary:
232
233 def __init__(self, data=None):
234 self.data = data
235
236 def __cmp__(self, other):
237 if isinstance(other, Binary):
238 other = other.data
239 return cmp(self.data, other)
240
241 def decode(self, data):
242 import base64
243 self.data = base64.decodestring(data)
244
245 def encode(self, out):
246 import base64, StringIO
247 out.write("<value><base64>\n")
248 base64.encode(StringIO.StringIO(self.data), out)
249 out.write("</base64></value>\n")
250
251def binary(data):
252 value = Binary()
253 value.decode(data)
254 return value
255
256WRAPPERS = DateTime, Binary, Boolean
257
258# --------------------------------------------------------------------
259# XML parsers
260
261try:
262 # optional xmlrpclib accelerator. for more information on this
263 # component, contact info@pythonware.com
264 import _xmlrpclib
265 FastParser = _xmlrpclib.Parser
266 FastUnmarshaller = _xmlrpclib.Unmarshaller
267except (AttributeError, ImportError):
268 FastParser = FastUnmarshaller = None
269
270#
271# the SGMLOP parser is about 15x faster than Python's builtin
272# XML parser. SGMLOP sources can be downloaded from:
273#
274# http://www.pythonware.com/products/xml/sgmlop.htm
275#
276
277try:
278 import sgmlop
279 if not hasattr(sgmlop, "XMLParser"):
280 raise ImportError
281except ImportError:
282 SgmlopParser = None # sgmlop accelerator not available
283else:
284 class SgmlopParser:
285 def __init__(self, target):
286
287 # setup callbacks
288 self.finish_starttag = target.start
289 self.finish_endtag = target.end
290 self.handle_data = target.data
291 self.handle_xml = target.xml
292
293 # activate parser
294 self.parser = sgmlop.XMLParser()
295 self.parser.register(self)
296 self.feed = self.parser.feed
297 self.entity = {
298 "amp": "&", "gt": ">", "lt": "<",
299 "apos": "'", "quot": '"'
300 }
301
302 def close(self):
303 try:
304 self.parser.close()
305 finally:
306 self.parser = self.feed = None # nuke circular reference
307
308 def handle_proc(self, tag, attr):
309 m = re.search("encoding\s*=\s*['\"]([^\"']+)[\"']", attr)
310 if m:
311 self.handle_xml(m.group(1), 1)
312
313 def handle_entityref(self, entity):
314 # <string> entity
315 try:
316 self.handle_data(self.entity[entity])
317 except KeyError:
318 self.handle_data("&%s;" % entity)
319
320try:
321 from xml.parsers import expat
322except ImportError:
323 ExpatParser = None
324else:
325 class ExpatParser:
326 # fast expat parser for Python 2.0. this is about 50%
327 # slower than sgmlop, on roundtrip testing
328 def __init__(self, target):
329 self._parser = parser = expat.ParserCreate(None, None)
330 self._target = target
331 parser.StartElementHandler = target.start
332 parser.EndElementHandler = target.end
333 parser.CharacterDataHandler = target.data
334 encoding = None
335 if not parser.returns_unicode:
336 encoding = "utf-8"
337 target.xml(encoding, None)
338
339 def feed(self, data):
340 self._parser.Parse(data, 0)
341
342 def close(self):
343 self._parser.Parse("", 1) # end of data
344 del self._target, self._parser # get rid of circular references
345
346class SlowParser(xmllib.XMLParser):
347 # slow but safe standard parser, based on the XML parser in
348 # Python's standard library. this is about 10 times slower
349 # than sgmlop, on roundtrip testing.
350 def __init__(self, target):
351 self.handle_xml = target.xml
352 self.unknown_starttag = target.start
353 self.handle_data = target.data
354 self.unknown_endtag = target.end
355 xmllib.XMLParser.__init__(self)
356
357
358# --------------------------------------------------------------------
359# XML-RPC marshalling and unmarshalling code
360
361class Marshaller:
362 """Generate an XML-RPC params chunk from a Python data structure"""
363
364 # USAGE: create a marshaller instance for each set of parameters,
365 # and use "dumps" to convert your data (represented as a tuple) to
366 # a XML-RPC params chunk. to write a fault response, pass a Fault
367 # instance instead. you may prefer to use the "dumps" convenience
368 # function for this purpose (see below).
369
370 # by the way, if you don't understand what's going on in here,
371 # that's perfectly ok.
372
373 def __init__(self, encoding=None):
374 self.memo = {}
375 self.data = None
376 self.encoding = encoding
377
378 dispatch = {}
379
380 def dumps(self, values):
381 self.__out = []
382 self.write = write = self.__out.append
383 if isinstance(values, Fault):
384 # fault instance
385 write("<fault>\n")
386 self.__dump(vars(values))
387 write("</fault>\n")
388 else:
389 # parameter block
390 write("<params>\n")
391 for v in values:
392 write("<param>\n")
393 self.__dump(v)
394 write("</param>\n")
395 write("</params>\n")
396 result = string.join(self.__out, "")
397 del self.__out, self.write # don't need this any more
398 return result
399
400 def __dump(self, value):
401 try:
402 f = self.dispatch[type(value)]
403 except KeyError:
404 raise TypeError, "cannot marshal %s objects" % type(value)
405 else:
406 f(self, value)
407
408 def dump_int(self, value):
409 self.write("<value><int>%s</int></value>\n" % value)
410 dispatch[IntType] = dump_int
411
412 def dump_double(self, value):
413 self.write("<value><double>%s</double></value>\n" % value)
414 dispatch[FloatType] = dump_double
415
416 def dump_string(self, value):
417 self.write("<value><string>%s</string></value>\n" % escape(value))
418 dispatch[StringType] = dump_string
419
420 if unicode:
421 def dump_unicode(self, value):
422 value = value.encode(self.encoding)
423 self.write("<value><string>%s</string></value>\n" % escape(value))
424 dispatch[UnicodeType] = dump_unicode
425
426 def container(self, value):
427 if value:
428 i = id(value)
429 if self.memo.has_key(i):
430 raise TypeError, "cannot marshal recursive data structures"
431 self.memo[i] = None
432
433 def dump_array(self, value):
434 self.container(value)
435 write = self.write
436 write("<value><array><data>\n")
437 for v in value:
438 self.__dump(v)
439 write("</data></array></value>\n")
440 dispatch[TupleType] = dump_array
441 dispatch[ListType] = dump_array
442
443 def dump_struct(self, value):
444 self.container(value)
445 write = self.write
446 write("<value><struct>\n")
447 for k, v in value.items():
448 write("<member>\n")
449 if type(k) is not StringType:
450 raise TypeError, "dictionary key must be string"
451 write("<name>%s</name>\n" % escape(k))
452 self.__dump(v)
453 write("</member>\n")
454 write("</struct></value>\n")
455 dispatch[DictType] = dump_struct
456
457 def dump_instance(self, value):
458 # check for special wrappers
459 if value.__class__ in WRAPPERS:
460 value.encode(self)
461 else:
462 # store instance attributes as a struct (really?)
463 self.dump_struct(value.__dict__)
464 dispatch[InstanceType] = dump_instance
465
466class Unmarshaller:
467
468 # unmarshal an XML-RPC response, based on incoming XML event
469 # messages (start, data, end). call close to get the resulting
470 # data structure
471
472 # note that this reader is fairly tolerant, and gladly accepts
473 # bogus XML-RPC data without complaining (but not bogus XML).
474
475 # and again, if you don't understand what's going on in here,
476 # that's perfectly ok.
477
478 def __init__(self):
479 self._type = None
480 self._stack = []
481 self._marks = []
482 self._data = []
483 self._methodname = None
484 self._encoding = "utf-8"
485 self.append = self._stack.append
486
487 def close(self):
488 # return response tuple and target method
489 if self._type is None or self._marks:
490 raise ResponseError()
491 if self._type == "fault":
492 raise apply(Fault, (), self._stack[0])
493 return tuple(self._stack)
494
495 def getmethodname(self):
496 return self._methodname
497
498 #
499 # event handlers
500
501 def xml(self, encoding, standalone):
502 self._encoding = encoding
503 # FIXME: assert standalone == 1 ???
504
505 def start(self, tag, attrs):
506 # prepare to handle this element
507 if tag == "array" or tag == "struct":
508 self._marks.append(len(self._stack))
509 self._data = []
510 self._value = (tag == "value")
511
512 def data(self, text):
513 self._data.append(text)
514
515 def end(self, tag):
516 # call the appropriate end tag handler
517 try:
518 f = self.dispatch[tag]
519 except KeyError:
520 pass # unknown tag ?
521 else:
522 return f(self, self._data)
523
524 #
525 # accelerator support
526
527 def end_dispatch(self, tag, data):
528 # dispatch data
529 try:
530 f = self.dispatch[tag]
531 except KeyError:
532 pass # unknown tag ?
533 else:
534 return f(self, data)
535
536 #
537 # element decoders
538
539 dispatch = {}
540
541 def end_boolean(self, data, join=string.join):
542 data = join(data, "")
543 if data == "0":
544 self.append(False)
545 elif data == "1":
546 self.append(True)
547 else:
548 raise TypeError, "bad boolean value"
549 self._value = 0
550 dispatch["boolean"] = end_boolean
551
552 def end_int(self, data, join=string.join):
553 self.append(int(join(data, "")))
554 self._value = 0
555 dispatch["i4"] = end_int
556 dispatch["int"] = end_int
557
558 def end_double(self, data, join=string.join):
559 self.append(float(join(data, "")))
560 self._value = 0
561 dispatch["double"] = end_double
562
563 def end_string(self, data, join=string.join):
564 data = join(data, "")
565 if self._encoding:
566 data = _decode(data, self._encoding)
567 self.append(_stringify(data))
568 self._value = 0
569 dispatch["string"] = end_string
570 dispatch["name"] = end_string # struct keys are always strings
571
572 def end_array(self, data):
573 mark = self._marks[-1]
574 del self._marks[-1]
575 # map arrays to Python lists
576 self._stack[mark:] = [self._stack[mark:]]
577 self._value = 0
578 dispatch["array"] = end_array
579
580 def end_struct(self, data):
581 mark = self._marks[-1]
582 del self._marks[-1]
583 # map structs to Python dictionaries
584 dict = {}
585 items = self._stack[mark:]
586 for i in range(0, len(items), 2):
587 dict[_stringify(items[i])] = items[i+1]
588 self._stack[mark:] = [dict]
589 self._value = 0
590 dispatch["struct"] = end_struct
591
592 def end_base64(self, data, join=string.join):
593 value = Binary()
594 value.decode(join(data, ""))
595 self.append(value)
596 self._value = 0
597 dispatch["base64"] = end_base64
598
599 def end_dateTime(self, data, join=string.join):
600 value = DateTime()
601 value.decode(join(data, ""))
602 self.append(value)
603 dispatch["dateTime.iso8601"] = end_dateTime
604
605 def end_value(self, data):
606 # if we stumble upon an value element with no internal
607 # elements, treat it as a string element
608 if self._value:
609 self.end_string(data)
610 dispatch["value"] = end_value
611
612 def end_params(self, data):
613 self._type = "params"
614 dispatch["params"] = end_params
615
616 def end_fault(self, data):
617 self._type = "fault"
618 dispatch["fault"] = end_fault
619
620 def end_methodName(self, data, join=string.join):
621 data = join(data, "")
622 if self._encoding:
623 data = _decode(data, self._encoding)
624 self._methodname = data
625 self._type = "methodName" # no params
626 dispatch["methodName"] = end_methodName
627
628
629# --------------------------------------------------------------------
630# convenience functions
631
632def getparser():
633 """getparser() -> parser, unmarshaller
634
635 Create an instance of the fastest available parser, and attach
636 it to an unmarshalling object. Return both objects.
637 """
638 if FastParser and FastUnmarshaller:
639 target = FastUnmarshaller(True, False, binary, datetime)
640 parser = FastParser(target)
641 else:
642 target = Unmarshaller()
643 if FastParser:
644 parser = FastParser(target)
645 elif SgmlopParser:
646 parser = SgmlopParser(target)
647 elif ExpatParser:
648 parser = ExpatParser(target)
649 else:
650 parser = SlowParser(target)
651 return parser, target
652
653def dumps(params, methodname=None, methodresponse=None, encoding=None):
654 """data [,options] -> marshalled data
655
656 Convert an argument tuple or a Fault instance to an XML-RPC
657 request (or response, if the methodresponse option is used).
658
659 In addition to the data object, the following options can be
660 given as keyword arguments:
661
662 methodname: the method name for a methodCall packet
663
664 methodresponse: true to create a methodResponse packet.
665 If this option is used with a tuple, the tuple must be
666 a singleton (i.e. it can contain only one element).
667
668 encoding: the packet encoding (default is UTF-8)
669
670 All 8-bit strings in the data structure are assumed to use the
671 packet encoding. Unicode strings are automatically converted,
672 as necessary.
673 """
674
675 assert isinstance(params, TupleType) or isinstance(params, Fault),\
676 "argument must be tuple or Fault instance"
677
678 if isinstance(params, Fault):
679 methodresponse = 1
680 elif methodresponse and isinstance(params, TupleType):
681 assert len(params) == 1, "response tuple must be a singleton"
682
683 if not encoding:
684 encoding = "utf-8"
685
686 m = Marshaller(encoding)
687 data = m.dumps(params)
688
689 if encoding != "utf-8":
690 xmlheader = "<?xml version='1.0' encoding=%s?>\n" % repr(encoding)
691 else:
692 xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default
693
694 # standard XML-RPC wrappings
695 if methodname:
696 # a method call
697 if not isinstance(methodname, StringType):
698 methodname = methodname.encode(encoding)
699 data = (
700 xmlheader,
701 "<methodCall>\n"
702 "<methodName>", methodname, "</methodName>\n",
703 data,
704 "</methodCall>\n"
705 )
706 elif methodresponse:
707 # a method response, or a fault structure
708 data = (
709 xmlheader,
710 "<methodResponse>\n",
711 data,
712 "</methodResponse>\n"
713 )
714 else:
715 return data # return as is
716 return string.join(data, "")
717
718def loads(data):
719 """data -> unmarshalled data, method name
720
721 Convert an XML-RPC packet to unmarshalled data plus a method
722 name (None if not present).
723
724 If the XML-RPC packet represents a fault condition, this function
725 raises a Fault exception.
726 """
727 p, u = getparser()
728 p.feed(data)
729 p.close()
730 return u.close(), u.getmethodname()
731
732
733# --------------------------------------------------------------------
734# request dispatcher
735
736class _Method:
737 # some magic to bind an XML-RPC method to an RPC server.
738 # supports "nested" methods (e.g. examples.getStateName)
739 def __init__(self, send, name):
740 self.__send = send
741 self.__name = name
742 def __getattr__(self, name):
743 return _Method(self.__send, "%s.%s" % (self.__name, name))
744 def __call__(self, *args):
745 return self.__send(self.__name, args)
746
747
748class Transport:
749 """Handles an HTTP transaction to an XML-RPC server"""
750
751 # client identifier (may be overridden)
752 user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__
753
754 def request(self, host, handler, request_body, verbose=0):
755 # issue XML-RPC request
756
757 h = self.make_connection(host)
758 if verbose:
759 h.set_debuglevel(1)
760
761 self.send_request(h, handler, request_body)
762 self.send_host(h, host)
763 self.send_user_agent(h)
764 self.send_content(h, request_body)
765
766 errcode, errmsg, headers = h.getreply()
767
768 if errcode != 200:
769 raise ProtocolError(
770 host + handler,
771 errcode, errmsg,
772 headers
773 )
774
775 self.verbose = verbose
776
777 return self.parse_response(h.getfile())
778
779 def make_connection(self, host):
780 # create a HTTP connection object from a host descriptor
781 import httplib
782 return httplib.HTTP(host)
783
784 def send_request(self, connection, handler, request_body):
785 connection.putrequest("POST", handler)
786
787 def send_host(self, connection, host):
788 connection.putheader("Host", host)
789
790 def send_user_agent(self, connection):
791 connection.putheader("User-Agent", self.user_agent)
792
793 def send_content(self, connection, request_body):
794 connection.putheader("Content-Type", "text/xml")
795 connection.putheader("Content-Length", str(len(request_body)))
796 connection.endheaders()
797 if request_body:
798 connection.send(request_body)
799
800 def parse_response(self, f):
801 # read response from input file, and parse it
802
803 p, u = getparser()
804
805 while 1:
806 response = f.read(1024)
807 if not response:
808 break
809 if self.verbose:
810 print "body:", repr(response)
811 p.feed(response)
812
813 f.close()
814 p.close()
815
816 return u.close()
817
818class SafeTransport(Transport):
819 """Handles an HTTPS transaction to an XML-RPC server"""
820
821 def make_connection(self, host):
822 # create a HTTPS connection object from a host descriptor
823 # host may be a string, or a (host, x509-dict) tuple
824 import httplib
825 if isinstance(host, TupleType):
826 host, x509 = host
827 else:
828 x509 = {}
829 try:
830 HTTPS = httplib.HTTPS
831 except AttributeError:
832 raise NotImplementedError,\
833 "your version of httplib doesn't support HTTPS"
834 else:
835 return apply(HTTPS, (host, None), x509)
836
837 def send_host(self, connection, host):
838 if isinstance(host, TupleType):
839 host, x509 = host
840 connection.putheader("Host", host)
841
842class ServerProxy:
843 """uri [,options] -> a logical connection to an XML-RPC server
844
845 uri is the connection point on the server, given as
846 scheme://host/target.
847
848 The standard implementation always supports the "http" scheme. If
849 SSL socket support is available (Python 2.0), it also supports
850 "https".
851
852 If the target part and the slash preceding it are both omitted,
853 "/RPC2" is assumed.
854
855 The following options can be given as keyword arguments:
856
857 transport: a transport factory
858 encoding: the request encoding (default is UTF-8)
859
860 All 8-bit strings passed to the server proxy are assumed to use
861 the given encoding.
862 """
863
864 def __init__(self, uri, transport=None, encoding=None, verbose=0):
865 # establish a "logical" server connection
866
867 # get the url
868 type, uri = urllib.splittype(uri)
869 if type not in ("http", "https"):
870 raise IOError, "unsupported XML-RPC protocol"
871 self.__host, self.__handler = urllib.splithost(uri)
872 if not self.__handler:
873 self.__handler = "/RPC2"
874
875 if transport is None:
876 if type == "https":
877 transport = SafeTransport()
878 else:
879 transport = Transport()
880 self.__transport = transport
881
882 self.__encoding = encoding
883 self.__verbose = verbose
884
885 def __request(self, methodname, params):
886 # call a method on the remote server
887
888 request = dumps(params, methodname, encoding=self.__encoding)
889
890 response = self.__transport.request(
891 self.__host,
892 self.__handler,
893 request,
894 verbose=self.__verbose
895 )
896
897 if len(response) == 1:
898 response = response[0]
899
900 return response
901
902 def __repr__(self):
903 return (
904 "<Server proxy for %s%s>" %
905 (self.__host, self.__handler)
906 )
907
908 __str__ = __repr__
909
910 def __getattr__(self, name):
911 # magic method dispatcher
912 return _Method(self.__request, name)
913
914 # note: to call a remote object with an non-standard name, use
915 # result getattr(server, "strange-python-name")(args)
916
917Server = ServerProxy
918
919# --------------------------------------------------------------------
920# test code
921
922if __name__ == "__main__":
923
924 # simple test program (from the XML-RPC specification)
925
926 # server = Server("http://localhost:8000") # local server
927 server = Server("http://betty.userland.com")
928
929 print server
930
931 try:
932 print server.examples.getStateName(41)
933 except Error, v:
934 print "ERROR", v