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