merged with SLAB codebase (version 1.0.1)

This commit is contained in:
Fredrik Lundh 2002-06-27 21:36:21 +00:00
parent 4fb7027ec0
commit 3d9addd55a

View file

@ -33,12 +33,19 @@
# 2001-09-10 fl Lazy import of urllib, cgi, xmllib (20x import speedup) # 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 Remove containers from memo cache when done with them
# 2001-10-01 fl Use faster escape method (80% dumps speedup) # 2001-10-01 fl Use faster escape method (80% dumps speedup)
# 2001-10-02 fl More dumps microtuning
# 2001-10-04 fl Make sure import expat gets a parser (from Guido van Rossum)
# 2001-10-10 sm Allow long ints to be passed as ints if they don't overflow # 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-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) # 2001-11-12 fl Use repr() to marshal doubles (from Paul Felix)
# 2002-03-17 fl Avoid buffered read when possible (from James Rucker)
# 2002-04-07 fl Added pythondoc comments
# 2002-04-16 fl Added __str__ methods to datetime/binary wrappers
# 2002-05-15 fl Added error constants (from Andrew Kuchling)
# 2002-06-27 fl Merged with Python CVS version
# #
# Copyright (c) 1999-2001 by Secret Labs AB. # Copyright (c) 1999-2002 by Secret Labs AB.
# Copyright (c) 1999-2001 by Fredrik Lundh. # Copyright (c) 1999-2002 by Fredrik Lundh.
# #
# info@pythonware.com # info@pythonware.com
# http://www.pythonware.com # http://www.pythonware.com
@ -46,8 +53,8 @@
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# The XML-RPC client interface is # The XML-RPC client interface is
# #
# Copyright (c) 1999-2001 by Secret Labs AB # Copyright (c) 1999-2002 by Secret Labs AB
# Copyright (c) 1999-2001 by Fredrik Lundh # Copyright (c) 1999-2002 by Fredrik Lundh
# #
# By obtaining, using, and/or copying this software and/or its # By obtaining, using, and/or copying this software and/or its
# associated documentation, you agree that you have read, understood, # associated documentation, you agree that you have read, understood,
@ -73,13 +80,9 @@
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# #
# things to look into: # things to look into some day:
# TODO: support basic authentication (see robin's patch) # TODO: sort out True/False/boolean issues for Python 2.3
# TODO: fix host tuple handling in the server constructor
# TODO: let transport verify schemes
# TODO: update documentation
# TODO: authentication plugins
""" """
An XML-RPC client interface for Python. An XML-RPC client interface for Python.
@ -130,6 +133,9 @@ import re, string, time, operator
from types import * from types import *
# --------------------------------------------------------------------
# Internal stuff
try: try:
unicode unicode
except NameError: except NameError:
@ -146,9 +152,6 @@ def escape(s, replace=string.replace):
s = replace(s, "<", "&lt;") s = replace(s, "<", "&lt;")
return replace(s, ">", "&gt;",) return replace(s, ">", "&gt;",)
MAXINT = 2L**31-1
MININT = -2L**31
if unicode: if unicode:
def _stringify(string): def _stringify(string):
# convert to 7-bit ascii if possible # convert to 7-bit ascii if possible
@ -160,16 +163,53 @@ else:
def _stringify(string): def _stringify(string):
return string return string
__version__ = "1.0.0" __version__ = "1.0.1"
# xmlrpc integer limits
MAXINT = 2L**31-1
MININT = -2L**31
# --------------------------------------------------------------------
# Error constants (from Dan Libby's specification at
# http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php)
# Ranges of errors
PARSE_ERROR = -32700
SERVER_ERROR = -32600
APPLICATION_ERROR = -32500
SYSTEM_ERROR = -32400
TRANSPORT_ERROR = -32300
# Specific errors
NOT_WELLFORMED_ERROR = -32700
UNSUPPORTED_ENCODING = -32701
INVALID_ENCODING_CHAR = -32702
INVALID_XMLRPC = -32600
METHOD_NOT_FOUND = -32601
INVALID_METHOD_PARAMS = -32602
INTERNAL_ERROR = -32603
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Exceptions # Exceptions
##
# Base class for all kinds of client-side errors.
class Error(Exception): class Error(Exception):
"""Base class for client errors.""" """Base class for client errors."""
def __str__(self): def __str__(self):
return repr(self) return repr(self)
##
# Indicates an HTTP-level protocol error. This is raised by the HTTP
# transport layer, if the server returns an error code other than 200
# (OK).
#
# @param url The target URL.
# @param errcode The HTTP error code.
# @param errmsg The HTTP error message.
# @param headers The HTTP header dictionary.
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):
@ -184,10 +224,24 @@ class ProtocolError(Error):
(self.url, self.errcode, self.errmsg) (self.url, self.errcode, self.errmsg)
) )
##
# Indicates a broken XML-RPC response package. This exception is
# raised by the unmarshalling layer, if the XML-RPC response is
# malformed.
class ResponseError(Error): class ResponseError(Error):
"""Indicates a broken response package.""" """Indicates a broken response package."""
pass pass
##
# Indicates an XML-RPC fault response package. This exception is
# raised by the unmarshalling layer, if the XML-RPC response contains
# a fault string. This exception can also used as a class, to
# generate a fault XML-RPC message.
#
# @param faultCode The XML-RPC fault code.
# @param faultString The XML-RPC fault string.
class Fault(Error): class Fault(Error):
"""Indicates an XML-RPC fault package.""" """Indicates an XML-RPC fault package."""
def __init__(self, faultCode, faultString, **extra): def __init__(self, faultCode, faultString, **extra):
@ -203,6 +257,14 @@ class Fault(Error):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Special values # Special values
##
# Wrapper for XML-RPC boolean values. Use the xmlrpclib.True and
# xmlrpclib.False constants, or the xmlrpclib.boolean() function, to
# generate boolean XML-RPC values.
#
# @param value A boolean value. Any true value is interpreted as True,
# all other values are interpreted as False.
class Boolean: class Boolean:
"""Boolean-value wrapper. """Boolean-value wrapper.
@ -234,9 +296,33 @@ class Boolean:
True, False = Boolean(1), Boolean(0) True, False = Boolean(1), Boolean(0)
def boolean(value, truefalse=(False, True)): ##
# Map true or false value to XML-RPC boolean values.
#
# @def boolean(value)
# @param value A boolean value. Any true value is mapped to True,
# all other values are mapped to False.
# @return xmlrpclib.True or xmlrpclib.False.
# @see Boolean
# @see True
# @see False
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)]
##
# Wrapper for XML-RPC DateTime values. This converts a time value to
# the format used by XML-RPC.
# <p>
# The value can be given as a string in the format
# "yyyymmddThh:mm:ss", as a 9-item time tuple (as returned by
# time.localtime()), or an integer value (as returned by time.time()).
# The wrapper uses time.localtime() to convert an integer to a time
# tuple.
#
# @param value The time, given as an ISO 8601 string, a time
# tuple, or a integer time value.
class DateTime: class DateTime:
"""DateTime wrapper for an ISO 8601 string or time tuple or """DateTime wrapper for an ISO 8601 string or time tuple or
@ -258,8 +344,16 @@ class DateTime:
other = other.value other = other.value
return cmp(self.value, other) return cmp(self.value, other)
##
# Get date/time value.
#
# @return Date/time value, as an ISO 8601 string.
def __str__(self):
return self.value
def __repr__(self): def __repr__(self):
return "<DateTime %s at %x>" % (self.value, id(self)) return "<DateTime %s at %x>" % (repr(self.value), id(self))
def decode(self, data): def decode(self, data):
self.value = string.strip(data) self.value = string.strip(data)
@ -269,17 +363,32 @@ 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): def _datetime(data):
# decode xml element contents into a DateTime structure.
value = DateTime() value = DateTime()
value.decode(data) value.decode(data)
return value return value
##
# Wrapper for binary data. This can be used to transport any kind
# of binary data over XML-RPC, using BASE64 encoding.
#
# @param data An 8-bit string containing arbitrary data.
class Binary: class Binary:
"""Wrapper for binary data.""" """Wrapper for binary data."""
def __init__(self, data=None): def __init__(self, data=None):
self.data = data self.data = data
##
# Get buffer contents.
#
# @return Buffer contents, as an 8-bit string.
def __str__(self):
return self.data or ""
def __cmp__(self, other): def __cmp__(self, other):
if isinstance(other, Binary): if isinstance(other, Binary):
other = other.data other = other.data
@ -295,7 +404,8 @@ 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")
def binary(data): def _binary(data):
# decode xml element contents into a Binary structure
value = Binary() value = Binary()
value.decode(data) value.decode(data)
return value return value
@ -314,6 +424,12 @@ try:
except (AttributeError, ImportError): except (AttributeError, ImportError):
FastParser = FastUnmarshaller = None FastParser = FastUnmarshaller = None
try:
import _xmlrpclib
FastMarshaller = _xmlrpclib.Marshaller
except (AttributeError, ImportError):
FastMarshaller = None
# #
# the SGMLOP parser is about 15x faster than Python's builtin # the SGMLOP parser is about 15x faster than Python's builtin
# XML parser. SGMLOP sources can be downloaded from: # XML parser. SGMLOP sources can be downloaded from:
@ -367,13 +483,13 @@ else:
try: try:
from xml.parsers import expat from xml.parsers import expat
if not hasattr(expat, "ParserCreate"): if not hasattr(expat, "ParserCreate"):
raise ImportError, "ParserCreate" raise ImportError
except ImportError: except ImportError:
ExpatParser = None ExpatParser = None # expat not available
else: else:
class ExpatParser: class ExpatParser:
# fast expat parser for Python 2.0. this is about 50% # fast expat parser for Python 2.0 and later. this is about
# slower than sgmlop, on roundtrip testing # 50% slower than sgmlop, on roundtrip testing
def __init__(self, target): def __init__(self, target):
self._parser = parser = expat.ParserCreate(None, None) self._parser = parser = expat.ParserCreate(None, None)
self._target = target self._target = target
@ -403,6 +519,7 @@ class SlowParser:
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
try: try:
xmllib.XMLParser.__init__(self, accept_utf8=1) xmllib.XMLParser.__init__(self, accept_utf8=1)
@ -412,6 +529,13 @@ class SlowParser:
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# XML-RPC marshalling and unmarshalling code # XML-RPC marshalling and unmarshalling code
##
# XML-RPC marshaller.
#
# @param encoding Default encoding for 8-bit strings. The default
# value is None (interpreted as UTF-8).
# @see dumps
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.
@ -433,12 +557,13 @@ class Marshaller:
dispatch = {} dispatch = {}
def dumps(self, values): def dumps(self, values):
self.__out = [] out = []
self.write = write = self.__out.append write = out.append
dump = self.__dump
if isinstance(values, Fault): if isinstance(values, Fault):
# fault instance # fault instance
write("<fault>\n") write("<fault>\n")
self.__dump(vars(values)) dump(vars(values), write)
write("</fault>\n") write("</fault>\n")
else: else:
# parameter block # parameter block
@ -450,97 +575,105 @@ class Marshaller:
write("<params>\n") write("<params>\n")
for v in values: for v in values:
write("<param>\n") write("<param>\n")
self.__dump(v) dump(v, write)
write("</param>\n") write("</param>\n")
write("</params>\n") write("</params>\n")
result = string.join(self.__out, "") result = string.join(out, "")
del self.__out, self.write # don't need this any more
return result return result
def __dump(self, value): def __dump(self, value, write):
try: try:
f = self.dispatch[type(value)] f = self.dispatch[type(value)]
except KeyError: except KeyError:
raise TypeError, "cannot marshal %s objects" % type(value) raise TypeError, "cannot marshal %s objects" % type(value)
else: else:
f(self, value) f(self, value, write)
def dump_int(self, value): def dump_int(self, value, write):
# in case ints are > 32 bits # in case ints are > 32 bits
if value > MAXINT or value < MININT: if value > MAXINT or value < MININT:
raise OverflowError, "int exceeds XML-RPC limits" raise OverflowError, "int exceeds XML-RPC limits"
self.write("<value><int>%s</int></value>\n" % value) write("<value><int>")
write(str(value))
write("</int></value>\n")
dispatch[IntType] = dump_int dispatch[IntType] = dump_int
def dump_long(self, value): def dump_long(self, value, write):
# in case ints are > 32 bits
if value > MAXINT or value < MININT: if value > MAXINT or value < MININT:
raise OverflowError, "long int exceeds XML-RPC limits" raise OverflowError, "long int exceeds XML-RPC limits"
self.write("<value><int>%s</int></value>\n" % int(value)) write("<value><int>")
write(str(int(value)))
write("</int></value>\n")
dispatch[LongType] = dump_long dispatch[LongType] = dump_long
def dump_double(self, value): def dump_double(self, value, write):
self.write("<value><double>%s</double></value>\n" % repr(value)) write("<value><double>")
write(repr(value))
write("</double></value>\n")
dispatch[FloatType] = dump_double dispatch[FloatType] = dump_double
def dump_string(self, value, escape=escape): def dump_string(self, value, write, escape=escape):
self.write("<value><string>%s</string></value>\n" % escape(value)) write("<value><string>")
write(escape(value))
write("</string></value>\n")
dispatch[StringType] = dump_string dispatch[StringType] = dump_string
if unicode: if unicode:
def dump_unicode(self, value, escape=escape): def dump_unicode(self, value, write, escape=escape):
value = value.encode(self.encoding) value = value.encode(self.encoding)
self.write("<value><string>%s</string></value>\n" % escape(value)) write("<value><string>")
write(escape(value))
write("</string></value>\n")
dispatch[UnicodeType] = dump_unicode dispatch[UnicodeType] = dump_unicode
def opencontainer(self, value): def dump_array(self, value, write):
if value: i = id(value)
i = id(value) if self.memo.has_key(i):
if i in self.memo: raise TypeError, "cannot marshal recursive sequences"
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):
self.opencontainer(value)
write = self.write
dump = self.__dump dump = self.__dump
write("<value><array><data>\n") write("<value><array><data>\n")
for v in value: for v in value:
dump(v) dump(v, write)
write("</data></array></value>\n") write("</data></array></value>\n")
self.closecontainer(value) del self.memo[i]
dispatch[TupleType] = dump_array dispatch[TupleType] = dump_array
dispatch[ListType] = dump_array dispatch[ListType] = dump_array
def dump_struct(self, value, escape=escape): def dump_struct(self, value, write, escape=escape):
self.opencontainer(value) i = id(value)
write = self.write if self.memo.has_key(i):
raise TypeError, "cannot marshal recursive dictionaries"
self.memo[i] = None
dump = self.__dump dump = self.__dump
write("<value><struct>\n") write("<value><struct>\n")
for k, v in value.items(): for k in value.keys():
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))
dump(v) dump(value[k], write)
write("</member>\n") write("</member>\n")
write("</struct></value>\n") write("</struct></value>\n")
self.closecontainer(value) del self.memo[i]
dispatch[DictType] = dump_struct dispatch[DictType] = dump_struct
def dump_instance(self, value): def dump_instance(self, value, write):
# check for special wrappers # check for special wrappers
if value.__class__ in WRAPPERS: if value.__class__ in WRAPPERS:
self.write = write
value.encode(self) value.encode(self)
del self.write
else: else:
# store instance attributes as a struct (really?) # store instance attributes as a struct (really?)
self.dump_struct(value.__dict__) self.dump_struct(value.__dict__, write)
dispatch[InstanceType] = dump_instance dispatch[InstanceType] = dump_instance
##
# XML-RPC unmarshaller.
#
# @see loads
class Unmarshaller: class Unmarshaller:
"""Unmarshal an XML-RPC response, based on incoming XML event """Unmarshal an XML-RPC response, based on incoming XML event
messages (start, data, end). Call close() to get the resulting messages (start, data, end). Call close() to get the resulting
@ -679,7 +812,7 @@ class Unmarshaller:
dispatch["dateTime.iso8601"] = end_dateTime dispatch["dateTime.iso8601"] = end_dateTime
def end_value(self, data): def end_value(self, data):
# if we stumble upon an value element with no internal # if we stumble upon a 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(data) self.end_string(data)
@ -704,6 +837,12 @@ class Unmarshaller:
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# convenience functions # convenience functions
##
# Create a parser object, and connect it to an unmarshalling instance.
# This function picks the fastest available XML parser.
#
# return A (parser, unmarshaller) tuple.
def getparser(): def getparser():
"""getparser() -> parser, unmarshaller """getparser() -> parser, unmarshaller
@ -711,7 +850,7 @@ def getparser():
to an unmarshalling object. Return both objects. to an unmarshalling object. Return both objects.
""" """
if FastParser and FastUnmarshaller: if FastParser and FastUnmarshaller:
target = FastUnmarshaller(True, False, binary, datetime) target = FastUnmarshaller(True, False, _binary, _datetime, Fault)
parser = FastParser(target) parser = FastParser(target)
else: else:
target = Unmarshaller() target = Unmarshaller()
@ -725,6 +864,19 @@ def getparser():
parser = SlowParser(target) parser = SlowParser(target)
return parser, target return parser, target
##
# Convert a Python tuple or a Fault instance to an XML-RPC packet.
#
# @def dumps(params, **options)
# @param params A tuple or Fault instance.
# @keyparam methodname If given, create a methodCall request for
# this method name.
# @keyparam methodresponse If given, create a methodResponse packet.
# If used with a tuple, the tuple must be a singleton (that is,
# it must contain exactly one element).
# @keyparam encoding The packet encoding.
# @return A string containing marshalled data.
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
@ -755,19 +907,23 @@ def dumps(params, methodname=None, methodresponse=None, encoding=None):
elif methodresponse and isinstance(params, TupleType): elif methodresponse and isinstance(params, TupleType):
assert len(params) == 1, "response tuple must be a singleton" assert len(params) == 1, "response tuple must be a singleton"
if encoding is None: if not encoding:
encoding = "utf-8" encoding = "utf-8"
m = Marshaller(encoding) if FastMarshaller:
m = FastMarshaller(encoding)
else:
m = Marshaller(encoding)
data = m.dumps(params) data = m.dumps(params)
if encoding != "utf-8": if encoding != "utf-8":
xmlheader = "<?xml version='1.0' encoding=%s?>\n" % repr(encoding) xmlheader = "<?xml version='1.0' encoding='%s'?>\n" % str(encoding)
else: else:
xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default
# standard XML-RPC wrappings # standard XML-RPC wrappings
if methodname is not None: if methodname:
# a method call # a method call
if not isinstance(methodname, StringType): if not isinstance(methodname, StringType):
methodname = methodname.encode(encoding) methodname = methodname.encode(encoding)
@ -790,6 +946,15 @@ def dumps(params, methodname=None, methodresponse=None, encoding=None):
return data # return as is return data # return as is
return string.join(data, "") return string.join(data, "")
##
# Convert an XML-RPC packet to a Python object. If the XML-RPC packet
# represents a fault condition, this function raises a Fault exception.
#
# @param data An XML-RPC packet, given as an 8-bit string.
# @return A tuple containing the the unpacked data, and the method name
# (None if not present).
# @see Fault
def loads(data): def loads(data):
"""data -> unmarshalled data, method name """data -> unmarshalled data, method name
@ -799,6 +964,7 @@ def loads(data):
If the XML-RPC packet represents a fault condition, this function If the XML-RPC packet represents a fault condition, this function
raises a Fault exception. raises a Fault exception.
""" """
import sys
p, u = getparser() p, u = getparser()
p.feed(data) p.feed(data)
p.close() p.close()
@ -819,6 +985,11 @@ class _Method:
def __call__(self, *args): def __call__(self, *args):
return self.__send(self.__name, args) return self.__send(self.__name, args)
##
# Standard transport class for XML-RPC over HTTP.
# <p>
# You can create custom transports by subclassing this method, and
# overriding selected methods.
class Transport: class Transport:
"""Handles an HTTP transaction to an XML-RPC server.""" """Handles an HTTP transaction to an XML-RPC server."""
@ -826,6 +997,15 @@ class Transport:
# 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__
##
# Send a complete request, and parse the response.
#
# @param host Target host.
# @param handler Target PRC handler.
# @param request_body XML-RPC request body.
# @param verbose Debugging flag.
# @return Parsed response.
def request(self, host, handler, request_body, verbose=0): def request(self, host, handler, request_body, verbose=0):
# issue XML-RPC request # issue XML-RPC request
@ -849,26 +1029,66 @@ class Transport:
self.verbose = verbose self.verbose = verbose
return self.parse_response(h.getfile()) try:
sock = h._conn.sock
except AttributeError:
sock = None
return self._parse_response(h.getfile(), sock)
##
# Create parser.
#
# @return A 2-tuple containing a parser and a unmarshaller.
def getparser(self): def getparser(self):
# get parser and unmarshaller # get parser and unmarshaller
return getparser() return getparser()
##
# Connect to server.
#
# @param host Target host.
# @return A connection handle.
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
return httplib.HTTP(host) return httplib.HTTP(host)
##
# Send request header.
#
# @param connection Connection handle.
# @param handler Target RPC handler.
# @param request_body XML-RPC body.
def send_request(self, connection, handler, request_body): def send_request(self, connection, handler, request_body):
connection.putrequest("POST", handler) connection.putrequest("POST", handler)
##
# Send host name.
#
# @param connection Connection handle.
# @param host Host name.
def send_host(self, connection, host): def send_host(self, connection, host):
connection.putheader("Host", host) connection.putheader("Host", host)
##
# Send user-agent identifier.
#
# @param connection Connection handle.
def send_user_agent(self, connection): def send_user_agent(self, connection):
connection.putheader("User-Agent", self.user_agent) connection.putheader("User-Agent", self.user_agent)
##
# Send request body.
#
# @param connection Connection handle.
# @param request_body XML-RPC request body.
def send_content(self, connection, request_body): def send_content(self, connection, request_body):
connection.putheader("Content-Type", "text/xml") connection.putheader("Content-Type", "text/xml")
connection.putheader("Content-Length", str(len(request_body))) connection.putheader("Content-Length", str(len(request_body)))
@ -876,27 +1096,55 @@ class Transport:
if request_body: if request_body:
connection.send(request_body) connection.send(request_body)
def parse_response(self, f): ##
# read response from input file, and parse it # Parse response.
#
# @param file Stream.
# @return Response tuple and target method.
def parse_response(self, file):
# compatibility interface
return self._parse_response(file, None)
##
# Parse response (alternate interface). This is similar to the
# parse_response method, but also provides direct access to the
# underlying socket object (where available).
#
# @param file Stream.
# @param sock Socket handle (or None, if the socket object
# could not be accessed).
# @return Response tuple and target method.
def _parse_response(self, file, sock):
# read response from input file/socket, and parse it
p, u = self.getparser() p, u = self.getparser()
while 1: while 1:
response = f.read(1024) if sock:
response = sock.recv(1024)
else:
response = file.read(1024)
if not response: if not response:
break break
if self.verbose: if self.verbose:
print "body:", repr(response) print "body:", repr(response)
p.feed(response) p.feed(response)
f.close() file.close()
p.close() p.close()
return u.close() return u.close()
##
# Standard transport class for XML-RPC over HTTPS.
class SafeTransport(Transport): class SafeTransport(Transport):
"""Handles an HTTPS transaction to an XML-RPC server.""" """Handles an HTTPS transaction to an XML-RPC server."""
# FIXME: mostly untested
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
# host may be a string, or a (host, x509-dict) tuple # host may be a string, or a (host, x509-dict) tuple
@ -918,6 +1166,23 @@ class SafeTransport(Transport):
host, x509 = host host, x509 = host
connection.putheader("Host", host) connection.putheader("Host", host)
##
# Standard server proxy. This class establishes a virtual connection
# to an XML-RPC server.
# <p>
# This class is available as ServerProxy and Server. New code should
# use ServerProxy, to avoid confusion.
#
# @def ServerProxy(uri, **options)
# @param uri The connection point on the server.
# @keyparam transport A transport factory, compatible with the
# standard transport class.
# @keyparam encoding The default encoding used for 8-bit strings
# (default is UTF-8).
# @keyparam verbose Use a true value to enable debugging output.
# (printed to standard output).
# @see Transport
class ServerProxy: class ServerProxy:
"""uri [,options] -> a logical connection to an XML-RPC server """uri [,options] -> a logical connection to an XML-RPC server
@ -995,6 +1260,7 @@ class ServerProxy:
# result getattr(server, "strange-python-name")(args) # result getattr(server, "strange-python-name")(args)
# compatibility # compatibility
Server = ServerProxy Server = ServerProxy
# -------------------------------------------------------------------- # --------------------------------------------------------------------