Commit cd906440 authored by Martijn Pieters's avatar Martijn Pieters

Update xmlrpclib.py to the Python2.2 version, while preserving the Zope

specific addition of marshalling None to <boolean>0</boolean>.

Benefits gained:

  - Uses Expat when available (like in Zope), which makes unmarshalling
    (receiving XML-RPC calls) much,much faster.

  - New library imports other python modules lazily, which helps speed up
    Zope startup time.

  - Faster escape method for outgoing calls.

  - Fixes for doubles and larger integers.

  - Allow XML-RPC calls without an empty <params /> (fixes Zope Collector
    #465)

Additional fix:

  - Fix SlowParser to handle CDATA sections as well. With the ExpatParser
    this is less of a problem (fixes Zope Collector #547). Also see Python
    SF issue #601534.
parent 73456dc0
...@@ -8,16 +8,10 @@ ...@@ -8,16 +8,10 @@
# implement XML-RPC servers. # implement XML-RPC servers.
# #
# Notes: # Notes:
# this version uses the sgmlop XML parser, if installed. this is
# typically 10-15x faster than using Python's standard XML parser.
#
# you can get the sgmlop distribution from:
#
# http://www.pythonware.com/products/xml/sgmlop.htm
#
# this version is designed to work with Python 1.5.2 or newer. # this version is designed to work with Python 1.5.2 or newer.
# unicode encoding support requires at least Python 1.6. # unicode encoding support requires at least Python 1.6.
# experimental HTTPS requires Python 2.0 built with SSL sockets. # experimental HTTPS requires Python 2.0 built with SSL sockets.
# expat parser support requires Python 2.0 with pyexpat support.
# #
# History: # History:
# 1999-01-14 fl Created # 1999-01-14 fl Created
...@@ -30,12 +24,23 @@ ...@@ -30,12 +24,23 @@
# 1999-06-20 fl Speed improvements, pluggable parsers/transports (0.9.8) # 1999-06-20 fl Speed improvements, pluggable parsers/transports (0.9.8)
# 2000-11-28 fl Changed boolean to check the truth value of its argument # 2000-11-28 fl Changed boolean to check the truth value of its argument
# 2001-02-24 fl Added encoding/Unicode/SafeTransport patches # 2001-02-24 fl Added encoding/Unicode/SafeTransport patches
# 2001-02-26 fl Added compare support to wrappers (0.9.9) # 2001-02-26 fl Added compare support to wrappers (0.9.9/1.0b1)
# 2001-03-28 fl Make sure response tuple is a singleton
# 2001-03-29 fl Don't require empty params element (from Nicholas Riley)
# 2001-06-10 fl Folded in _xmlrpclib accelerator support (1.0b2)
# 2001-08-20 fl Base xmlrpclib.Error on built-in Exception (from Paul Prescod)
# 2001-09-03 fl Allow Transport subclass to override getparser
# 2001-09-10 fl Lazy import of urllib, cgi, xmllib (20x import speedup)
# 2001-10-01 fl Remove containers from memo cache when done with them
# 2001-10-01 fl Use faster escape method (80% dumps speedup)
# 2001-10-10 sm Allow long ints to be passed as ints if they don't overflow
# 2001-10-17 sm test for int and long overflow (allows use on 64-bit systems)
# 2001-11-12 fl Use repr() to marshal doubles (from Paul Felix)
# #
# Copyright (c) 1999-2001 by Secret Labs AB. # Copyright (c) 1999-2001 by Secret Labs AB.
# Copyright (c) 1999-2001 by Fredrik Lundh. # Copyright (c) 1999-2001 by Fredrik Lundh.
# #
# fredrik@pythonware.com # info@pythonware.com
# http://www.pythonware.com # http://www.pythonware.com
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
...@@ -68,34 +73,62 @@ ...@@ -68,34 +73,62 @@
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# #
# things to fix before 1.0 final: # things to look into:
# TODO: unicode marshalling -DONE # TODO: support basic authentication (see robin's patch)
# TODO: ascii-compatible encoding support -DONE
# TODO: safe transport -DONE (but mostly untested)
# TODO: sgmlop memory leak -DONE
# TODO: sgmlop xml parsing -DONE
# TODO: support unicode method names -DONE
# TODO: update selftest -DONE
# TODO: add docstrings -DONE
# TODO: clean up parser encoding (trust the parser) -DONE
# TODO: fix host tuple handling in the server constructor # TODO: fix host tuple handling in the server constructor
# TODO: let transport verify schemes # TODO: let transport verify schemes
# TODO: update documentation # TODO: update documentation
# TODO: authentication plugins # TODO: authentication plugins
# TODO: memo problem (see HP's mail)
"""
An XML-RPC client interface for Python.
The marshalling and response parser code can also be used to
implement XML-RPC servers.
Exported exceptions:
Error Base class for client errors
ProtocolError Indicates an HTTP protocol error
ResponseError Indicates a broken response package
Fault Indicates an XML-RPC fault package
Exported classes:
ServerProxy Represents a logical connection to an XML-RPC server
Boolean boolean wrapper to generate a "boolean" XML-RPC value
DateTime dateTime wrapper for an ISO 8601 string or time tuple or
localtime integer value to generate a "dateTime.iso8601"
XML-RPC value
Binary binary data wrapper
SlowParser Slow but safe standard parser (based on xmllib)
Marshaller Generate an XML-RPC params chunk from a Python data structure
Unmarshaller Unmarshal an XML-RPC response from incoming XML event message
Transport Handles an HTTP transaction to an XML-RPC server
SafeTransport Handles an HTTPS transaction to an XML-RPC server
Exported constants:
True
False
Exported functions:
boolean Convert any Python value to an XML-RPC boolean
getparser Create instance of the fastest available parser & attach
to an unmarshalling object
dumps Convert an argument tuple or a Fault instance to an XML-RPC
request (or response, if the methodresponse option is used).
loads Convert an XML-RPC packet to unmarshalled data plus a method
name (None if not present).
"""
import re, string, time, operator import re, string, time, operator
import urllib, xmllib
from types import *
from cgi import escape
try: from types import *
import sgmlop
if not hasattr(sgmlop, "XMLParser"):
raise ImportError
except ImportError:
sgmlop = None # accelerator not available
try: try:
unicode unicode
...@@ -104,22 +137,43 @@ except NameError: ...@@ -104,22 +137,43 @@ except NameError:
def _decode(data, encoding, is8bit=re.compile("[\x80-\xff]").search): def _decode(data, encoding, is8bit=re.compile("[\x80-\xff]").search):
# decode non-ascii string (if possible) # decode non-ascii string (if possible)
if unicode and is8bit(data): if unicode and encoding and is8bit(data):
data = unicode(data, encoding) data = unicode(data, encoding)
return data return data
__version__ = "0.9.9" def escape(s, replace=string.replace):
s = replace(s, "&", "&amp;")
s = replace(s, "<", "&lt;")
return replace(s, ">", "&gt;",)
MAXINT = 2L**31-1
MININT = -2L**31
if unicode:
def _stringify(string):
# convert to 7-bit ascii if possible
try:
return str(string)
except UnicodeError:
return string
else:
def _stringify(string):
return string
__version__ = "1.0.0"
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Exceptions # Exceptions
class Error: class Error(Exception):
# base class for client errors """Base class for client errors."""
pass def __str__(self):
return repr(self)
class ProtocolError(Error): class ProtocolError(Error):
# indicates an HTTP protocol error """Indicates an HTTP protocol error."""
def __init__(self, url, errcode, errmsg, headers): def __init__(self, url, errcode, errmsg, headers):
Error.__init__(self)
self.url = url self.url = url
self.errcode = errcode self.errcode = errcode
self.errmsg = errmsg self.errmsg = errmsg
...@@ -131,12 +185,13 @@ class ProtocolError(Error): ...@@ -131,12 +185,13 @@ class ProtocolError(Error):
) )
class ResponseError(Error): class ResponseError(Error):
# indicates a broken response package """Indicates a broken response package."""
pass pass
class Fault(Error): class Fault(Error):
# indicates a XML-RPC fault package """Indicates an XML-RPC fault package."""
def __init__(self, faultCode, faultString, **extra): def __init__(self, faultCode, faultString, **extra):
Error.__init__(self)
self.faultCode = faultCode self.faultCode = faultCode
self.faultString = faultString self.faultString = faultString
def __repr__(self): def __repr__(self):
...@@ -145,14 +200,14 @@ class Fault(Error): ...@@ -145,14 +200,14 @@ class Fault(Error):
(self.faultCode, repr(self.faultString)) (self.faultCode, repr(self.faultString))
) )
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Special values # Special values
# boolean wrapper
# use True or False to generate a "boolean" XML-RPC value
class Boolean: class Boolean:
"""Boolean-value wrapper.
Use True or False to generate a "boolean" XML-RPC value.
"""
def __init__(self, value = 0): def __init__(self, value = 0):
self.value = operator.truth(value) self.value = operator.truth(value)
...@@ -180,15 +235,14 @@ class Boolean: ...@@ -180,15 +235,14 @@ class Boolean:
True, False = Boolean(1), Boolean(0) True, False = Boolean(1), Boolean(0)
def boolean(value, truefalse=(False, True)): def boolean(value, truefalse=(False, True)):
# convert any Python value to XML-RPC boolean """Convert any Python value to XML-RPC 'boolean'."""
return truefalse[operator.truth(value)] return truefalse[operator.truth(value)]
#
# dateTime wrapper
# wrap your iso8601 string or time tuple or localtime integer value
# in this class to generate a "dateTime.iso8601" XML-RPC value
class DateTime: class DateTime:
"""DateTime wrapper for an ISO 8601 string or time tuple or
localtime integer value to generate 'dateTime.iso8601' XML-RPC
value.
"""
def __init__(self, value=0): def __init__(self, value=0):
if not isinstance(value, StringType): if not isinstance(value, StringType):
...@@ -215,10 +269,13 @@ class DateTime: ...@@ -215,10 +269,13 @@ class DateTime:
out.write(self.value) out.write(self.value)
out.write("</dateTime.iso8601></value>\n") out.write("</dateTime.iso8601></value>\n")
# def datetime(data):
# binary data wrapper value = DateTime()
value.decode(data)
return value
class Binary: class Binary:
"""Wrapper for binary data."""
def __init__(self, data=None): def __init__(self, data=None):
self.data = data self.data = data
...@@ -238,18 +295,40 @@ class Binary: ...@@ -238,18 +295,40 @@ class Binary:
base64.encode(StringIO.StringIO(self.data), out) base64.encode(StringIO.StringIO(self.data), out)
out.write("</base64></value>\n") out.write("</base64></value>\n")
WRAPPERS = DateTime, Binary, Boolean def binary(data):
value = Binary()
value.decode(data)
return value
WRAPPERS = DateTime, Binary, Boolean
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# XML parsers # XML parsers
if sgmlop: try:
# optional xmlrpclib accelerator. for more information on this
# component, contact info@pythonware.com
import _xmlrpclib
FastParser = _xmlrpclib.Parser
FastUnmarshaller = _xmlrpclib.Unmarshaller
except (AttributeError, ImportError):
FastParser = FastUnmarshaller = None
class FastParser: #
# sgmlop based XML parser. this is typically 15x faster # the SGMLOP parser is about 15x faster than Python's builtin
# than SlowParser... # XML parser. SGMLOP sources can be downloaded from:
#
# http://www.pythonware.com/products/xml/sgmlop.htm
#
try:
import sgmlop
if not hasattr(sgmlop, "XMLParser"):
raise ImportError
except ImportError:
SgmlopParser = None # sgmlop accelerator not available
else:
class SgmlopParser:
def __init__(self, target): def __init__(self, target):
# setup callbacks # setup callbacks
...@@ -274,6 +353,7 @@ if sgmlop: ...@@ -274,6 +353,7 @@ if sgmlop:
self.parser = self.feed = None # nuke circular reference self.parser = self.feed = None # nuke circular reference
def handle_proc(self, tag, attr): def handle_proc(self, tag, attr):
import re
m = re.search("encoding\s*=\s*['\"]([^\"']+)[\"']", attr) m = re.search("encoding\s*=\s*['\"]([^\"']+)[\"']", attr)
if m: if m:
self.handle_xml(m.group(1), 1) self.handle_xml(m.group(1), 1)
...@@ -285,33 +365,64 @@ if sgmlop: ...@@ -285,33 +365,64 @@ if sgmlop:
except KeyError: except KeyError:
self.handle_data("&%s;" % entity) self.handle_data("&%s;" % entity)
try:
from xml.parsers import expat
if not hasattr(expat, "ParserCreate"):
raise ImportError, "ParserCreate"
except ImportError:
ExpatParser = None
else: else:
class ExpatParser:
# fast expat parser for Python 2.0. this is about 50%
# slower than sgmlop, on roundtrip testing
def __init__(self, target):
self._parser = parser = expat.ParserCreate(None, None)
self._target = target
parser.StartElementHandler = target.start
parser.EndElementHandler = target.end
parser.CharacterDataHandler = target.data
encoding = None
if not parser.returns_unicode:
encoding = "utf-8"
target.xml(encoding, None)
def feed(self, data):
self._parser.Parse(data, 0)
FastParser = None def close(self):
self._parser.Parse("", 1) # end of data
class SlowParser(xmllib.XMLParser): del self._target, self._parser # get rid of circular references
# slow but safe standard parser, based on the XML parser in
# Python's standard library
class SlowParser:
"""Default XML parser (based on xmllib.XMLParser)."""
# this is about 10 times slower than sgmlop, on roundtrip
# testing.
def __init__(self, target): def __init__(self, target):
import xmllib # lazy subclassing (!)
if xmllib.XMLParser not in SlowParser.__bases__:
SlowParser.__bases__ = (xmllib.XMLParser,)
self.handle_xml = target.xml self.handle_xml = target.xml
self.unknown_starttag = target.start self.unknown_starttag = target.start
self.handle_data = target.data self.handle_data = target.data
self.handle_cdata = target.data
self.unknown_endtag = target.end self.unknown_endtag = target.end
xmllib.XMLParser.__init__(self) try:
xmllib.XMLParser.__init__(self, accept_utf8=1)
except TypeError:
xmllib.XMLParser.__init__(self) # pre-2.0
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# XML-RPC marshalling and unmarshalling code # XML-RPC marshalling and unmarshalling code
class Marshaller: class Marshaller:
"""Generate an XML-RPC params chunk from a Python data structure""" """Generate an XML-RPC params chunk from a Python data structure.
# USAGE: create a marshaller instance for each set of parameters, Create a Marshaller instance for each set of parameters, and use
# and use "dumps" to convert your data (represented as a tuple) to the "dumps" method to convert your data (represented as a tuple)
# a XML-RPC params chunk. to write a fault response, pass a Fault to an XML-RPC params chunk. To write a fault response, pass a
# instance instead. you may prefer to use the "dumps" convenience Fault instance instead. You may prefer to use the "dumps" module
# function for this purpose (see below). function for this purpose.
"""
# by the way, if you don't understand what's going on in here, # by the way, if you don't understand what's going on in here,
# that's perfectly ok. # that's perfectly ok.
...@@ -333,6 +444,11 @@ class Marshaller: ...@@ -333,6 +444,11 @@ class Marshaller:
write("</fault>\n") write("</fault>\n")
else: else:
# parameter block # parameter block
# FIXME: the xml-rpc specification allows us to leave out
# the entire <params> block if there are no parameters.
# however, changing this may break older code (including
# old versions of xmlrpclib.py), so this is better left as
# is for now. See @XMLRPC3 for more information. /F
write("<params>\n") write("<params>\n")
for v in values: for v in values:
write("<param>\n") write("<param>\n")
...@@ -352,19 +468,29 @@ class Marshaller: ...@@ -352,19 +468,29 @@ class Marshaller:
f(self, value) f(self, value)
def dump_int(self, value): def dump_int(self, value):
# in case ints are > 32 bits
if value > MAXINT or value < MININT:
raise OverflowError, "int exceeds XML-RPC limits"
self.write("<value><int>%s</int></value>\n" % value) self.write("<value><int>%s</int></value>\n" % value)
dispatch[IntType] = dump_int dispatch[IntType] = dump_int
def dump_long(self, value):
# in case ints are > 32 bits
if value > MAXINT or value < MININT:
raise OverflowError, "long int exceeds XML-RPC limits"
self.write("<value><int>%s</int></value>\n" % int(value))
dispatch[LongType] = dump_long
def dump_double(self, value): def dump_double(self, value):
self.write("<value><double>%s</double></value>\n" % value) self.write("<value><double>%s</double></value>\n" % repr(value))
dispatch[FloatType] = dump_double dispatch[FloatType] = dump_double
def dump_string(self, value): def dump_string(self, value, escape=escape):
self.write("<value><string>%s</string></value>\n" % escape(value)) self.write("<value><string>%s</string></value>\n" % escape(value))
dispatch[StringType] = dump_string dispatch[StringType] = dump_string
if unicode: if unicode:
def dump_unicode(self, value): def dump_unicode(self, value, escape=escape):
value = value.encode(self.encoding) value = value.encode(self.encoding)
self.write("<value><string>%s</string></value>\n" % escape(value)) self.write("<value><string>%s</string></value>\n" % escape(value))
dispatch[UnicodeType] = dump_unicode dispatch[UnicodeType] = dump_unicode
...@@ -376,35 +502,43 @@ class Marshaller: ...@@ -376,35 +502,43 @@ class Marshaller:
self.write("<value><boolean>0</boolean></value>\n") self.write("<value><boolean>0</boolean></value>\n")
dispatch[NoneType] = dump_none dispatch[NoneType] = dump_none
def container(self, value): def opencontainer(self, value):
if value: if value:
i = id(value) i = id(value)
if self.memo.has_key(i): if self.memo.has_key(i):
raise TypeError, "cannot marshal recursive data structures" raise TypeError, "cannot marshal recursive data structures"
self.memo[i] = None self.memo[i] = None
def closecontainer(self, value):
if value:
del self.memo[id(value)]
def dump_array(self, value): def dump_array(self, value):
self.container(value) self.opencontainer(value)
write = self.write write = self.write
dump = self.__dump
write("<value><array><data>\n") write("<value><array><data>\n")
for v in value: for v in value:
self.__dump(v) dump(v)
write("</data></array></value>\n") write("</data></array></value>\n")
self.closecontainer(value)
dispatch[TupleType] = dump_array dispatch[TupleType] = dump_array
dispatch[ListType] = dump_array dispatch[ListType] = dump_array
def dump_struct(self, value): def dump_struct(self, value, escape=escape):
self.container(value) self.opencontainer(value)
write = self.write write = self.write
dump = self.__dump
write("<value><struct>\n") write("<value><struct>\n")
for k, v in value.items(): for k, v in value.items():
write("<member>\n") write("<member>\n")
if type(k) is not StringType: if type(k) is not StringType:
raise TypeError, "dictionary key must be string" raise TypeError, "dictionary key must be string"
write("<name>%s</name>\n" % escape(k)) write("<name>%s</name>\n" % escape(k))
self.__dump(v) dump(v)
write("</member>\n") write("</member>\n")
write("</struct></value>\n") write("</struct></value>\n")
self.closecontainer(value)
dispatch[DictType] = dump_struct dispatch[DictType] = dump_struct
def dump_instance(self, value): def dump_instance(self, value):
...@@ -417,13 +551,13 @@ class Marshaller: ...@@ -417,13 +551,13 @@ class Marshaller:
dispatch[InstanceType] = dump_instance dispatch[InstanceType] = dump_instance
class Unmarshaller: class Unmarshaller:
"""Unmarshal an XML-RPC response, based on incoming XML event
messages (start, data, end). Call close() to get the resulting
data structure.
# unmarshal an XML-RPC response, based on incoming XML event Note that this reader is fairly tolerant, and gladly accepts bogus
# messages (start, data, end). call close to get the resulting XML-RPC data without complaining (but not bogus XML).
# data structure """
# note that this reader is fairly tolerant, and gladly accepts
# bogus XML-RPC data without complaining (but not bogus XML).
# and again, if you don't understand what's going on in here, # and again, if you don't understand what's going on in here,
# that's perfectly ok. # that's perfectly ok.
...@@ -452,12 +586,12 @@ class Unmarshaller: ...@@ -452,12 +586,12 @@ class Unmarshaller:
# event handlers # event handlers
def xml(self, encoding, standalone): def xml(self, encoding, standalone):
self._encoding = encoding or "utf-8" self._encoding = encoding
# FIXME: assert standalone == 1 ??? # FIXME: assert standalone == 1 ???
def start(self, tag, attrs): def start(self, tag, attrs):
# prepare to handle this element # prepare to handle this element
if tag in ("array", "struct"): if tag == "array" or tag == "struct":
self._marks.append(len(self._stack)) self._marks.append(len(self._stack))
self._data = [] self._data = []
self._value = (tag == "value") self._value = (tag == "value")
...@@ -465,52 +599,62 @@ class Unmarshaller: ...@@ -465,52 +599,62 @@ class Unmarshaller:
def data(self, text): def data(self, text):
self._data.append(text) self._data.append(text)
dispatch = {} def end(self, tag, join=string.join):
def end(self, tag):
# call the appropriate end tag handler # call the appropriate end tag handler
try: try:
f = self.dispatch[tag] f = self.dispatch[tag]
except KeyError: except KeyError:
pass # unknown tag ? pass # unknown tag ?
else: else:
return f(self) return f(self, join(self._data, ""))
#
# accelerator support
def end_dispatch(self, tag, data):
# dispatch data
try:
f = self.dispatch[tag]
except KeyError:
pass # unknown tag ?
else:
return f(self, data)
# #
# element decoders # element decoders
def end_boolean(self, join=string.join): dispatch = {}
value = join(self._data, "")
if value == "0": def end_boolean(self, data):
if data == "0":
self.append(False) self.append(False)
elif value == "1": elif data == "1":
self.append(True) self.append(True)
else: else:
raise TypeError, "bad boolean value" raise TypeError, "bad boolean value"
self._value = 0 self._value = 0
dispatch["boolean"] = end_boolean dispatch["boolean"] = end_boolean
def end_int(self, join=string.join): def end_int(self, data):
self.append(int(join(self._data, ""))) self.append(int(data))
self._value = 0 self._value = 0
dispatch["i4"] = end_int dispatch["i4"] = end_int
dispatch["int"] = end_int dispatch["int"] = end_int
def end_double(self, join=string.join): def end_double(self, data):
self.append(float(join(self._data, ""))) self.append(float(data))
self._value = 0 self._value = 0
dispatch["double"] = end_double dispatch["double"] = end_double
def end_string(self, join=string.join): def end_string(self, data):
data = join(self._data, "")
if self._encoding: if self._encoding:
data = _decode(data, self._encoding) data = _decode(data, self._encoding)
self.append(data) self.append(_stringify(data))
self._value = 0 self._value = 0
dispatch["string"] = end_string dispatch["string"] = end_string
dispatch["name"] = end_string # struct keys are always strings dispatch["name"] = end_string # struct keys are always strings
def end_array(self): def end_array(self, data):
mark = self._marks[-1] mark = self._marks[-1]
del self._marks[-1] del self._marks[-1]
# map arrays to Python lists # map arrays to Python lists
...@@ -518,51 +662,51 @@ class Unmarshaller: ...@@ -518,51 +662,51 @@ class Unmarshaller:
self._value = 0 self._value = 0
dispatch["array"] = end_array dispatch["array"] = end_array
def end_struct(self): def end_struct(self, data):
mark = self._marks[-1] mark = self._marks[-1]
del self._marks[-1] del self._marks[-1]
# map structs to Python dictionaries # map structs to Python dictionaries
dict = {} dict = {}
items = self._stack[mark:] items = self._stack[mark:]
for i in range(0, len(items), 2): for i in range(0, len(items), 2):
dict[items[i]] = items[i+1] dict[_stringify(items[i])] = items[i+1]
self._stack[mark:] = [dict] self._stack[mark:] = [dict]
self._value = 0 self._value = 0
dispatch["struct"] = end_struct dispatch["struct"] = end_struct
def end_base64(self, join=string.join): def end_base64(self, data):
value = Binary() value = Binary()
value.decode(join(self._data, "")) value.decode(data)
self.append(value) self.append(value)
self._value = 0 self._value = 0
dispatch["base64"] = end_base64 dispatch["base64"] = end_base64
def end_dateTime(self, join=string.join): def end_dateTime(self, data):
value = DateTime() value = DateTime()
value.decode(join(self._data, "")) value.decode(data)
self.append(value) self.append(value)
dispatch["dateTime.iso8601"] = end_dateTime dispatch["dateTime.iso8601"] = end_dateTime
def end_value(self): def end_value(self, data):
# if we stumble upon an value element with no internal # if we stumble upon an value element with no internal
# elements, treat it as a string element # elements, treat it as a string element
if self._value: if self._value:
self.end_string() self.end_string(data)
dispatch["value"] = end_value dispatch["value"] = end_value
def end_params(self): def end_params(self, data):
self._type = "params" self._type = "params"
dispatch["params"] = end_params dispatch["params"] = end_params
def end_fault(self): def end_fault(self, data):
self._type = "fault" self._type = "fault"
dispatch["fault"] = end_fault dispatch["fault"] = end_fault
def end_methodName(self, join=string.join): def end_methodName(self, data):
data = join(self._data, "")
if self._encoding: if self._encoding:
data = _decode(data, self._encoding) data = _decode(data, self._encoding)
self._methodname = data self._methodname = data
self._type = "methodName" # no params
dispatch["methodName"] = end_methodName dispatch["methodName"] = end_methodName
...@@ -572,35 +716,54 @@ class Unmarshaller: ...@@ -572,35 +716,54 @@ class Unmarshaller:
def getparser(): def getparser():
"""getparser() -> parser, unmarshaller """getparser() -> parser, unmarshaller
Create an instance of the fastest available parser, and attach Create an instance of the fastest available parser, and attach it
it to an unmarshalling object. Return both objects. to an unmarshalling object. Return both objects.
""" """
target = Unmarshaller() if FastParser and FastUnmarshaller:
if FastParser: target = FastUnmarshaller(True, False, binary, datetime)
return FastParser(target), target parser = FastParser(target)
return SlowParser(target), target else:
target = Unmarshaller()
if FastParser:
parser = FastParser(target)
elif SgmlopParser:
parser = SgmlopParser(target)
elif ExpatParser:
parser = ExpatParser(target)
else:
parser = SlowParser(target)
return parser, target
def dumps(params, methodname=None, methodresponse=None, encoding=None): def dumps(params, methodname=None, methodresponse=None, encoding=None):
"""data [,options] -> marshalled data """data [,options] -> marshalled data
Convert a tuple or a fault object to an XML-RPC request (or Convert an argument tuple or a Fault instance to an XML-RPC
response, if the methodsresponse option is used). request (or response, if the methodresponse option is used).
In addition to the data object, the following options can be In addition to the data object, the following options can be given
given as keyword arguments: as keyword arguments:
methodname: the method name for a methodCall packet methodname: the method name for a methodCall packet
methodresponse: true to create a methodResponse packet
methodresponse: true to create a methodResponse packet.
If this option is used with a tuple, the tuple must be
a singleton (i.e. it can contain only one element).
encoding: the packet encoding (default is UTF-8) encoding: the packet encoding (default is UTF-8)
All 8-bit strings in the data structure are assumed to use the All 8-bit strings in the data structure are assumed to use the
packet encoding. Unicode strings are automatically converted, packet encoding. Unicode strings are automatically converted,
as necessary. where necessary.
""" """
assert type(params) == TupleType or isinstance(params, Fault),\ assert isinstance(params, TupleType) or isinstance(params, Fault),\
"argument must be tuple or Fault instance" "argument must be tuple or Fault instance"
if isinstance(params, Fault):
methodresponse = 1
elif methodresponse and isinstance(params, TupleType):
assert len(params) == 1, "response tuple must be a singleton"
if not encoding: if not encoding:
encoding = "utf-8" encoding = "utf-8"
...@@ -624,8 +787,8 @@ def dumps(params, methodname=None, methodresponse=None, encoding=None): ...@@ -624,8 +787,8 @@ def dumps(params, methodname=None, methodresponse=None, encoding=None):
data, data,
"</methodCall>\n" "</methodCall>\n"
) )
elif methodresponse or isinstance(params, Fault): elif methodresponse:
# a method response # a method response, or a fault structure
data = ( data = (
xmlheader, xmlheader,
"<methodResponse>\n", "<methodResponse>\n",
...@@ -667,7 +830,7 @@ class _Method: ...@@ -667,7 +830,7 @@ class _Method:
class Transport: class Transport:
"""Handles an HTTP transaction to an XML-RPC server""" """Handles an HTTP transaction to an XML-RPC server."""
# client identifier (may be overridden) # client identifier (may be overridden)
user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__ user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__
...@@ -697,6 +860,10 @@ class Transport: ...@@ -697,6 +860,10 @@ class Transport:
return self.parse_response(h.getfile()) return self.parse_response(h.getfile())
def getparser(self):
# get parser and unmarshaller
return getparser()
def make_connection(self, host): def make_connection(self, host):
# create a HTTP connection object from a host descriptor # create a HTTP connection object from a host descriptor
import httplib import httplib
...@@ -721,7 +888,7 @@ class Transport: ...@@ -721,7 +888,7 @@ class Transport:
def parse_response(self, f): def parse_response(self, f):
# read response from input file, and parse it # read response from input file, and parse it
p, u = getparser() p, u = self.getparser()
while 1: while 1:
response = f.read(1024) response = f.read(1024)
...@@ -737,7 +904,7 @@ class Transport: ...@@ -737,7 +904,7 @@ class Transport:
return u.close() return u.close()
class SafeTransport(Transport): class SafeTransport(Transport):
"""Handles an HTTPS transaction to an XML-RPC server""" """Handles an HTTPS transaction to an XML-RPC server."""
def make_connection(self, host): def make_connection(self, host):
# create a HTTPS connection object from a host descriptor # create a HTTPS connection object from a host descriptor
...@@ -760,7 +927,7 @@ class SafeTransport(Transport): ...@@ -760,7 +927,7 @@ class SafeTransport(Transport):
host, x509 = host host, x509 = host
connection.putheader("Host", host) connection.putheader("Host", host)
class Server: class ServerProxy:
"""uri [,options] -> a logical connection to an XML-RPC server """uri [,options] -> a logical connection to an XML-RPC server
uri is the connection point on the server, given as uri is the connection point on the server, given as
...@@ -786,6 +953,7 @@ class Server: ...@@ -786,6 +953,7 @@ class Server:
# establish a "logical" server connection # establish a "logical" server connection
# get the url # get the url
import urllib
type, uri = urllib.splittype(uri) type, uri = urllib.splittype(uri)
if type not in ("http", "https"): if type not in ("http", "https"):
raise IOError, "unsupported XML-RPC protocol" raise IOError, "unsupported XML-RPC protocol"
...@@ -822,7 +990,7 @@ class Server: ...@@ -822,7 +990,7 @@ class Server:
def __repr__(self): def __repr__(self):
return ( return (
"<Server proxy for %s%s>" % "<ServerProxy for %s%s>" %
(self.__host, self.__handler) (self.__host, self.__handler)
) )
...@@ -835,6 +1003,9 @@ class Server: ...@@ -835,6 +1003,9 @@ class Server:
# note: to call a remote object with an non-standard name, use # note: to call a remote object with an non-standard name, use
# result getattr(server, "strange-python-name")(args) # result getattr(server, "strange-python-name")(args)
# compatibility
Server = ServerProxy
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# test code # test code
...@@ -842,8 +1013,8 @@ if __name__ == "__main__": ...@@ -842,8 +1013,8 @@ if __name__ == "__main__":
# simple test program (from the XML-RPC specification) # simple test program (from the XML-RPC specification)
# server = Server("http://localhost:8000") # local server # server = ServerProxy("http://localhost:8000") # local server
server = Server("http://betty.userland.com") server = ServerProxy("http://betty.userland.com")
print server print server
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment