blob: 51e31defba2f46d17b1f17e56a6699f82008bca9 [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)
Fredrik Lundh78eedce2001-08-23 20:04:33 +000030# 2001-06-10 fl Folded in _xmlrpclib accelerator support (1.0b2)
31# 2001-08-20 fl Base xmlrpclib.Error on built-in Exception (from Paul Prescod)
Fredrik Lundhb9056332001-07-11 17:42:21 +000032#
33# Copyright (c) 1999-2001 by Secret Labs AB.
34# Copyright (c) 1999-2001 by Fredrik Lundh.
35#
36# info@pythonware.com
37# http://www.pythonware.com
38#
39# --------------------------------------------------------------------
40# The XML-RPC client interface is
41#
42# Copyright (c) 1999-2001 by Secret Labs AB
43# Copyright (c) 1999-2001 by Fredrik Lundh
44#
45# By obtaining, using, and/or copying this software and/or its
46# associated documentation, you agree that you have read, understood,
47# and will comply with the following terms and conditions:
48#
49# Permission to use, copy, modify, and distribute this software and
50# its associated documentation for any purpose and without fee is
51# hereby granted, provided that the above copyright notice appears in
52# all copies, and that both that copyright notice and this permission
53# notice appear in supporting documentation, and that the name of
54# Secret Labs AB or the author not be used in advertising or publicity
55# pertaining to distribution of the software without specific, written
56# prior permission.
57#
58# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
59# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
60# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
61# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
62# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
63# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
64# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
65# OF THIS SOFTWARE.
66# --------------------------------------------------------------------
67
68#
69# things to look into before 1.0 final:
70
71# TODO: unicode marshalling -DONE
72# TODO: ascii-compatible encoding support -DONE
73# TODO: safe transport -DONE (but mostly untested)
74# TODO: sgmlop memory leak -DONE
75# TODO: sgmlop xml parsing -DONE
76# TODO: support unicode method names -DONE
77# TODO: update selftest -DONE
78# TODO: add docstrings -DONE
79# TODO: clean up parser encoding (trust the parser) -DONE
80# TODO: expat support -DONE
81# TODO: _xmlrpclib accelerator support -DONE
82# TODO: use smarter/faster escape from effdom
83# TODO: support basic authentication (see robin's patch)
84# TODO: fix host tuple handling in the server constructor
85# TODO: let transport verify schemes
86# TODO: update documentation
87# TODO: authentication plugins
88# TODO: memo problem (see HP's mail)
89
90import re, string, time, operator
91import urllib, xmllib
92from types import *
93from cgi import escape
94
95try:
96 unicode
97except NameError:
98 unicode = None # unicode support not available
99
100def _decode(data, encoding, is8bit=re.compile("[\x80-\xff]").search):
101 # decode non-ascii string (if possible)
102 if unicode and encoding and is8bit(data):
103 data = unicode(data, encoding)
104 return data
105
106if unicode:
107 def _stringify(string):
108 # convert to 7-bit ascii if possible
109 try:
110 return str(string)
111 except UnicodeError:
112 return string
113else:
114 def _stringify(string):
115 return string
116
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000117__version__ = "1.0b3"
Fredrik Lundhb9056332001-07-11 17:42:21 +0000118
119# --------------------------------------------------------------------
120# Exceptions
121
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000122class Error(Exception):
Fredrik Lundhb9056332001-07-11 17:42:21 +0000123 # base class for client errors
124 pass
125
126class ProtocolError(Error):
127 # indicates an HTTP protocol error
128 def __init__(self, url, errcode, errmsg, headers):
129 self.url = url
130 self.errcode = errcode
131 self.errmsg = errmsg
132 self.headers = headers
133 def __repr__(self):
134 return (
135 "<ProtocolError for %s: %s %s>" %
136 (self.url, self.errcode, self.errmsg)
137 )
138
139class ResponseError(Error):
140 # indicates a broken response package
141 pass
142
143class Fault(Error):
144 # indicates a XML-RPC fault package
145 def __init__(self, faultCode, faultString, **extra):
146 self.faultCode = faultCode
147 self.faultString = faultString
148 def __repr__(self):
149 return (
150 "<Fault %s: %s>" %
151 (self.faultCode, repr(self.faultString))
152 )
153
154# --------------------------------------------------------------------
155# Special values
156
157# boolean wrapper
158# use True or False to generate a "boolean" XML-RPC value
159
160class Boolean:
161
162 def __init__(self, value = 0):
163 self.value = operator.truth(value)
164
165 def encode(self, out):
166 out.write("<value><boolean>%d</boolean></value>\n" % self.value)
167
168 def __cmp__(self, other):
169 if isinstance(other, Boolean):
170 other = other.value
171 return cmp(self.value, other)
172
173 def __repr__(self):
174 if self.value:
175 return "<Boolean True at %x>" % id(self)
176 else:
177 return "<Boolean False at %x>" % id(self)
178
179 def __int__(self):
180 return self.value
181
182 def __nonzero__(self):
183 return self.value
184
185True, False = Boolean(1), Boolean(0)
186
187def boolean(value, truefalse=(False, True)):
188 # convert any Python value to XML-RPC boolean
189 return truefalse[operator.truth(value)]
190
191#
192# dateTime wrapper
193# wrap your iso8601 string or time tuple or localtime integer value
194# in this class to generate a "dateTime.iso8601" XML-RPC value
195
196class DateTime:
197
198 def __init__(self, value=0):
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000199 if not isinstance(value, StringType):
200 if not isinstance(value, TupleType):
Fredrik Lundhb9056332001-07-11 17:42:21 +0000201 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
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000390 # FIXME: the xml-rpc specification allows us to leave out
391 # the entire <params> block if there are no parameters.
392 # however, changing this may break older code (including
393 # old versions of xmlrpclib.py), so this is better left as
394 # is for now. See @XMLRPC3 for more information. /F
Fredrik Lundhb9056332001-07-11 17:42:21 +0000395 write("<params>\n")
396 for v in values:
397 write("<param>\n")
398 self.__dump(v)
399 write("</param>\n")
400 write("</params>\n")
401 result = string.join(self.__out, "")
402 del self.__out, self.write # don't need this any more
403 return result
404
405 def __dump(self, value):
406 try:
407 f = self.dispatch[type(value)]
408 except KeyError:
409 raise TypeError, "cannot marshal %s objects" % type(value)
410 else:
411 f(self, value)
412
413 def dump_int(self, value):
414 self.write("<value><int>%s</int></value>\n" % value)
415 dispatch[IntType] = dump_int
416
417 def dump_double(self, value):
418 self.write("<value><double>%s</double></value>\n" % value)
419 dispatch[FloatType] = dump_double
420
421 def dump_string(self, value):
422 self.write("<value><string>%s</string></value>\n" % escape(value))
423 dispatch[StringType] = dump_string
424
425 if unicode:
426 def dump_unicode(self, value):
427 value = value.encode(self.encoding)
428 self.write("<value><string>%s</string></value>\n" % escape(value))
429 dispatch[UnicodeType] = dump_unicode
430
431 def container(self, value):
432 if value:
433 i = id(value)
434 if self.memo.has_key(i):
435 raise TypeError, "cannot marshal recursive data structures"
436 self.memo[i] = None
437
438 def dump_array(self, value):
439 self.container(value)
440 write = self.write
441 write("<value><array><data>\n")
442 for v in value:
443 self.__dump(v)
444 write("</data></array></value>\n")
445 dispatch[TupleType] = dump_array
446 dispatch[ListType] = dump_array
447
448 def dump_struct(self, value):
449 self.container(value)
450 write = self.write
451 write("<value><struct>\n")
452 for k, v in value.items():
453 write("<member>\n")
454 if type(k) is not StringType:
455 raise TypeError, "dictionary key must be string"
456 write("<name>%s</name>\n" % escape(k))
457 self.__dump(v)
458 write("</member>\n")
459 write("</struct></value>\n")
460 dispatch[DictType] = dump_struct
461
462 def dump_instance(self, value):
463 # check for special wrappers
464 if value.__class__ in WRAPPERS:
465 value.encode(self)
466 else:
467 # store instance attributes as a struct (really?)
468 self.dump_struct(value.__dict__)
469 dispatch[InstanceType] = dump_instance
470
471class Unmarshaller:
472
473 # unmarshal an XML-RPC response, based on incoming XML event
474 # messages (start, data, end). call close to get the resulting
475 # data structure
476
477 # note that this reader is fairly tolerant, and gladly accepts
478 # bogus XML-RPC data without complaining (but not bogus XML).
479
480 # and again, if you don't understand what's going on in here,
481 # that's perfectly ok.
482
483 def __init__(self):
484 self._type = None
485 self._stack = []
486 self._marks = []
487 self._data = []
488 self._methodname = None
489 self._encoding = "utf-8"
490 self.append = self._stack.append
491
492 def close(self):
493 # return response tuple and target method
494 if self._type is None or self._marks:
495 raise ResponseError()
496 if self._type == "fault":
497 raise apply(Fault, (), self._stack[0])
498 return tuple(self._stack)
499
500 def getmethodname(self):
501 return self._methodname
502
503 #
504 # event handlers
505
506 def xml(self, encoding, standalone):
507 self._encoding = encoding
508 # FIXME: assert standalone == 1 ???
509
510 def start(self, tag, attrs):
511 # prepare to handle this element
512 if tag == "array" or tag == "struct":
513 self._marks.append(len(self._stack))
514 self._data = []
515 self._value = (tag == "value")
516
517 def data(self, text):
518 self._data.append(text)
519
520 def end(self, tag):
521 # call the appropriate end tag handler
522 try:
523 f = self.dispatch[tag]
524 except KeyError:
525 pass # unknown tag ?
526 else:
527 return f(self, self._data)
528
529 #
530 # accelerator support
531
532 def end_dispatch(self, tag, data):
533 # dispatch data
534 try:
535 f = self.dispatch[tag]
536 except KeyError:
537 pass # unknown tag ?
538 else:
539 return f(self, data)
540
541 #
542 # element decoders
543
544 dispatch = {}
545
546 def end_boolean(self, data, join=string.join):
547 data = join(data, "")
548 if data == "0":
549 self.append(False)
550 elif data == "1":
551 self.append(True)
552 else:
553 raise TypeError, "bad boolean value"
554 self._value = 0
555 dispatch["boolean"] = end_boolean
556
557 def end_int(self, data, join=string.join):
558 self.append(int(join(data, "")))
559 self._value = 0
560 dispatch["i4"] = end_int
561 dispatch["int"] = end_int
562
563 def end_double(self, data, join=string.join):
564 self.append(float(join(data, "")))
565 self._value = 0
566 dispatch["double"] = end_double
567
568 def end_string(self, data, join=string.join):
569 data = join(data, "")
570 if self._encoding:
571 data = _decode(data, self._encoding)
572 self.append(_stringify(data))
573 self._value = 0
574 dispatch["string"] = end_string
575 dispatch["name"] = end_string # struct keys are always strings
576
577 def end_array(self, data):
578 mark = self._marks[-1]
579 del self._marks[-1]
580 # map arrays to Python lists
581 self._stack[mark:] = [self._stack[mark:]]
582 self._value = 0
583 dispatch["array"] = end_array
584
585 def end_struct(self, data):
586 mark = self._marks[-1]
587 del self._marks[-1]
588 # map structs to Python dictionaries
589 dict = {}
590 items = self._stack[mark:]
591 for i in range(0, len(items), 2):
592 dict[_stringify(items[i])] = items[i+1]
593 self._stack[mark:] = [dict]
594 self._value = 0
595 dispatch["struct"] = end_struct
596
597 def end_base64(self, data, join=string.join):
598 value = Binary()
599 value.decode(join(data, ""))
600 self.append(value)
601 self._value = 0
602 dispatch["base64"] = end_base64
603
604 def end_dateTime(self, data, join=string.join):
605 value = DateTime()
606 value.decode(join(data, ""))
607 self.append(value)
608 dispatch["dateTime.iso8601"] = end_dateTime
609
610 def end_value(self, data):
611 # if we stumble upon an value element with no internal
612 # elements, treat it as a string element
613 if self._value:
614 self.end_string(data)
615 dispatch["value"] = end_value
616
617 def end_params(self, data):
618 self._type = "params"
619 dispatch["params"] = end_params
620
621 def end_fault(self, data):
622 self._type = "fault"
623 dispatch["fault"] = end_fault
624
625 def end_methodName(self, data, join=string.join):
626 data = join(data, "")
627 if self._encoding:
628 data = _decode(data, self._encoding)
629 self._methodname = data
630 self._type = "methodName" # no params
631 dispatch["methodName"] = end_methodName
632
633
634# --------------------------------------------------------------------
635# convenience functions
636
637def getparser():
638 """getparser() -> parser, unmarshaller
639
640 Create an instance of the fastest available parser, and attach
641 it to an unmarshalling object. Return both objects.
642 """
643 if FastParser and FastUnmarshaller:
644 target = FastUnmarshaller(True, False, binary, datetime)
645 parser = FastParser(target)
646 else:
647 target = Unmarshaller()
648 if FastParser:
649 parser = FastParser(target)
650 elif SgmlopParser:
651 parser = SgmlopParser(target)
652 elif ExpatParser:
653 parser = ExpatParser(target)
654 else:
655 parser = SlowParser(target)
656 return parser, target
657
658def dumps(params, methodname=None, methodresponse=None, encoding=None):
659 """data [,options] -> marshalled data
660
661 Convert an argument tuple or a Fault instance to an XML-RPC
662 request (or response, if the methodresponse option is used).
663
664 In addition to the data object, the following options can be
665 given as keyword arguments:
666
667 methodname: the method name for a methodCall packet
668
669 methodresponse: true to create a methodResponse packet.
670 If this option is used with a tuple, the tuple must be
671 a singleton (i.e. it can contain only one element).
672
673 encoding: the packet encoding (default is UTF-8)
674
675 All 8-bit strings in the data structure are assumed to use the
676 packet encoding. Unicode strings are automatically converted,
677 as necessary.
678 """
679
680 assert isinstance(params, TupleType) or isinstance(params, Fault),\
681 "argument must be tuple or Fault instance"
682
683 if isinstance(params, Fault):
684 methodresponse = 1
685 elif methodresponse and isinstance(params, TupleType):
686 assert len(params) == 1, "response tuple must be a singleton"
687
688 if not encoding:
689 encoding = "utf-8"
690
691 m = Marshaller(encoding)
692 data = m.dumps(params)
693
694 if encoding != "utf-8":
695 xmlheader = "<?xml version='1.0' encoding=%s?>\n" % repr(encoding)
696 else:
697 xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default
698
699 # standard XML-RPC wrappings
700 if methodname:
701 # a method call
702 if not isinstance(methodname, StringType):
703 methodname = methodname.encode(encoding)
704 data = (
705 xmlheader,
706 "<methodCall>\n"
707 "<methodName>", methodname, "</methodName>\n",
708 data,
709 "</methodCall>\n"
710 )
711 elif methodresponse:
712 # a method response, or a fault structure
713 data = (
714 xmlheader,
715 "<methodResponse>\n",
716 data,
717 "</methodResponse>\n"
718 )
719 else:
720 return data # return as is
721 return string.join(data, "")
722
723def loads(data):
724 """data -> unmarshalled data, method name
725
726 Convert an XML-RPC packet to unmarshalled data plus a method
727 name (None if not present).
728
729 If the XML-RPC packet represents a fault condition, this function
730 raises a Fault exception.
731 """
732 p, u = getparser()
733 p.feed(data)
734 p.close()
735 return u.close(), u.getmethodname()
736
737
738# --------------------------------------------------------------------
739# request dispatcher
740
741class _Method:
742 # some magic to bind an XML-RPC method to an RPC server.
743 # supports "nested" methods (e.g. examples.getStateName)
744 def __init__(self, send, name):
745 self.__send = send
746 self.__name = name
747 def __getattr__(self, name):
748 return _Method(self.__send, "%s.%s" % (self.__name, name))
749 def __call__(self, *args):
750 return self.__send(self.__name, args)
751
752
753class Transport:
754 """Handles an HTTP transaction to an XML-RPC server"""
755
756 # client identifier (may be overridden)
757 user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__
758
759 def request(self, host, handler, request_body, verbose=0):
760 # issue XML-RPC request
761
762 h = self.make_connection(host)
763 if verbose:
764 h.set_debuglevel(1)
765
766 self.send_request(h, handler, request_body)
767 self.send_host(h, host)
768 self.send_user_agent(h)
769 self.send_content(h, request_body)
770
771 errcode, errmsg, headers = h.getreply()
772
773 if errcode != 200:
774 raise ProtocolError(
775 host + handler,
776 errcode, errmsg,
777 headers
778 )
779
780 self.verbose = verbose
781
782 return self.parse_response(h.getfile())
783
784 def make_connection(self, host):
785 # create a HTTP connection object from a host descriptor
786 import httplib
787 return httplib.HTTP(host)
788
789 def send_request(self, connection, handler, request_body):
790 connection.putrequest("POST", handler)
791
792 def send_host(self, connection, host):
793 connection.putheader("Host", host)
794
795 def send_user_agent(self, connection):
796 connection.putheader("User-Agent", self.user_agent)
797
798 def send_content(self, connection, request_body):
799 connection.putheader("Content-Type", "text/xml")
800 connection.putheader("Content-Length", str(len(request_body)))
801 connection.endheaders()
802 if request_body:
803 connection.send(request_body)
804
805 def parse_response(self, f):
806 # read response from input file, and parse it
807
808 p, u = getparser()
809
810 while 1:
811 response = f.read(1024)
812 if not response:
813 break
814 if self.verbose:
815 print "body:", repr(response)
816 p.feed(response)
817
818 f.close()
819 p.close()
820
821 return u.close()
822
823class SafeTransport(Transport):
824 """Handles an HTTPS transaction to an XML-RPC server"""
825
826 def make_connection(self, host):
827 # create a HTTPS connection object from a host descriptor
828 # host may be a string, or a (host, x509-dict) tuple
829 import httplib
830 if isinstance(host, TupleType):
831 host, x509 = host
832 else:
833 x509 = {}
834 try:
835 HTTPS = httplib.HTTPS
836 except AttributeError:
837 raise NotImplementedError,\
838 "your version of httplib doesn't support HTTPS"
839 else:
840 return apply(HTTPS, (host, None), x509)
841
842 def send_host(self, connection, host):
843 if isinstance(host, TupleType):
844 host, x509 = host
845 connection.putheader("Host", host)
846
847class ServerProxy:
848 """uri [,options] -> a logical connection to an XML-RPC server
849
850 uri is the connection point on the server, given as
851 scheme://host/target.
852
853 The standard implementation always supports the "http" scheme. If
854 SSL socket support is available (Python 2.0), it also supports
855 "https".
856
857 If the target part and the slash preceding it are both omitted,
858 "/RPC2" is assumed.
859
860 The following options can be given as keyword arguments:
861
862 transport: a transport factory
863 encoding: the request encoding (default is UTF-8)
864
865 All 8-bit strings passed to the server proxy are assumed to use
866 the given encoding.
867 """
868
869 def __init__(self, uri, transport=None, encoding=None, verbose=0):
870 # establish a "logical" server connection
871
872 # get the url
873 type, uri = urllib.splittype(uri)
874 if type not in ("http", "https"):
875 raise IOError, "unsupported XML-RPC protocol"
876 self.__host, self.__handler = urllib.splithost(uri)
877 if not self.__handler:
878 self.__handler = "/RPC2"
879
880 if transport is None:
881 if type == "https":
882 transport = SafeTransport()
883 else:
884 transport = Transport()
885 self.__transport = transport
886
887 self.__encoding = encoding
888 self.__verbose = verbose
889
890 def __request(self, methodname, params):
891 # call a method on the remote server
892
893 request = dumps(params, methodname, encoding=self.__encoding)
894
895 response = self.__transport.request(
896 self.__host,
897 self.__handler,
898 request,
899 verbose=self.__verbose
900 )
901
902 if len(response) == 1:
903 response = response[0]
904
905 return response
906
907 def __repr__(self):
908 return (
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000909 "<ServerProxy for %s%s>" %
Fredrik Lundhb9056332001-07-11 17:42:21 +0000910 (self.__host, self.__handler)
911 )
912
913 __str__ = __repr__
914
915 def __getattr__(self, name):
916 # magic method dispatcher
917 return _Method(self.__request, name)
918
919 # note: to call a remote object with an non-standard name, use
920 # result getattr(server, "strange-python-name")(args)
921
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000922# compatibility
Fredrik Lundhb9056332001-07-11 17:42:21 +0000923Server = ServerProxy
924
925# --------------------------------------------------------------------
926# test code
927
928if __name__ == "__main__":
929
930 # simple test program (from the XML-RPC specification)
931
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000932 # server = ServerProxy("http://localhost:8000") # local server
933 server = ServerProxy("http://betty.userland.com")
Fredrik Lundhb9056332001-07-11 17:42:21 +0000934
935 print server
936
937 try:
938 print server.examples.getStateName(41)
939 except Error, v:
940 print "ERROR", v