[Zope-dev] alpha SOAP support
Petru Paler
ppetru@coltronix.com
Thu, 22 Jun 2000 01:39:08 +0300
--jI8keyz6grp/JLjh
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Hi everyone,
Here's a very rough cut at adding SOAP support to Zope. It is mostly a
shameless adaptation of the already existing XML-RPC support and it probably
has alot of problems, but it should provide a starting point. Don't expect too
much out of it for now since: 1) it was written by someone (me :) with about
two hours of previous SOAP experience and 2) it is 1:34 AM here and I wrote it
in the last hour :) Having said that, I was able to call a method inside Zope
and retrieve back the result, so I attach a diff against a current Zope CVS
checkout.
Feedback is more than welcome.
-Petru
--jI8keyz6grp/JLjh
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="soap.diff"
diff -urN --exclude-from=exclude _zope-untouched/lib/python/ZPublisher/HTTPRequest.py soap-zope/lib/python/ZPublisher/HTTPRequest.py
--- _zope-untouched/lib/python/ZPublisher/HTTPRequest.py Sat Jun 10 02:57:58 2000
+++ soap-zope/lib/python/ZPublisher/HTTPRequest.py Thu Jun 22 01:23:28 2000
@@ -94,6 +94,7 @@
from Converters import get_converter
from maybe_lock import allocate_lock
xmlrpc=None # Placeholder for module that we'll import if we have to.
+soap=None
isCGI_NAME = {
'SERVER_SOFTWARE' : 1,
@@ -347,13 +348,23 @@
if (fs.headers.has_key('content-type') and
fs.headers['content-type'] == 'text/xml' and
method == 'POST'):
- # Ye haaa, XML-RPC!
- global xmlrpc
- if xmlrpc is None: import xmlrpc
- meth, self.args = xmlrpc.parse_input(fs.value)
- response=xmlrpc.response(response)
- other['RESPONSE']=self.response=response
- other['REQUEST_METHOD']='' # We don't want index_html!
+ if environ.has_key('HTTP_SOAPACTION'):
+ # this is a SOAP request
+ global soap
+ if soap is None:
+ import soap
+ meth, self.args = soap.parse_input(fs.value)
+ response = soap.response(response)
+ other['RESPONSE'] = self.response = response
+ other['REQUEST_METHOD'] = ''
+ else:
+ # Ye haaa, XML-RPC!
+ global xmlrpc
+ if xmlrpc is None: import xmlrpc
+ meth, self.args = xmlrpc.parse_input(fs.value)
+ response=xmlrpc.response(response)
+ other['RESPONSE']=self.response=response
+ other['REQUEST_METHOD']='' # We don't want index_html!
else:
self._file=fs.file
else:
diff -urN --exclude-from=exclude _zope-untouched/lib/python/ZPublisher/soap.py soap-zope/lib/python/ZPublisher/soap.py
--- _zope-untouched/lib/python/ZPublisher/soap.py Thu Jan 1 02:00:00 1970
+++ soap-zope/lib/python/ZPublisher/soap.py Thu Jun 22 01:23:12 2000
@@ -0,0 +1,116 @@
+"""SOAP support module
+
+Written by Petru Paler
+
+Based on the XML-RPC Zope support module written by Eric Kidd at UserLand
+software, with much help from Jim Fulton at DC.
+
+This code hooks Zope up to Fredrik Lundh's Python SOAP library.
+"""
+
+import sys
+from string import replace
+from HTTPResponse import HTTPResponse
+import soaplib
+
+def parse_input(data):
+ """Parse input data and return a method path and argument tuple
+
+ The data is a string.
+ """
+ method, params = soaplib.loads(data)
+ # Translate '.' to '/' in meth to represent object traversal.
+ method = replace(method, '.', '/')
+ return method, params
+
+# See below
+#
+# def response(anHTTPResponse):
+# """Return a valid ZPublisher response object
+#
+# Use data already gathered by the existing response.
+# The new response will replace the existing response.
+# """
+# # As a first cut, lets just clone the response and
+# # put all of the logic in our refined response class below.
+# r=Response()
+# r.__dict__.update(anHTTPResponse.__dict__)
+# return r
+
+
+
+########################################################################
+# Possible implementation helpers:
+
+class Response:
+ """Customized Response that handles SOAP-specific details.
+
+ We override setBody to marhsall Python objects into SOAP. We
+ also override exception to convert errors to SOAP faults.
+
+ If these methods stop getting called, make sure that ZPublisher is
+ using the soap.Response object created above and not the original
+ HTTPResponse object from which it was cloned.
+
+ It's probably possible to improve the 'exception' method quite a bit.
+ The current implementation, however, should suffice for now.
+ """
+
+ # Because we can't predict what kind of thing we're customizing,
+ # we have to use delegation, rather than inheritence to do the
+ # customization.
+
+ def __init__(self, real): self.__dict__['_real']=real
+
+ def __getattr__(self, name): return getattr(self._real, name)
+ def __setattr__(self, name, v): return setattr(self._real, name, v)
+ def __delattr__(self, name): return delattr(self._real, name)
+
+ def setBody(self, body, title='', is_error=0, bogus_str_search=None):
+ if isinstance(body, soaplib.Fault):
+ # Convert Fault object to SOAP response.
+ body=soaplib.dumps(soaplib.Fault(1, body))
+ else:
+ # Marshall our body as an SOAP response. Strings will be sent
+ # strings, integers as integers, etc. We do *not* convert
+ # everything to a string first.
+ try:
+ body = soaplib.dumps(soaplib.Response('foo', None, (body,)), 1)
+ except:
+ self.exception()
+ return
+ # Set our body to the XML-RPC message, and fix our MIME type.
+ self._real.setBody(body)
+ self._real.setHeader('content-type', 'text/xml')
+ return self
+
+ def exception(self, fatal=0, info=None,
+ absuri_match=None, tag_search=None):
+ # Fetch our exception info. t is type, v is value and tb is the
+ # traceback object.
+ if type(info) is type(()) and len(info)==3: t,v,tb = info
+ else: t,v,tb = sys.exc_info()
+
+ # Create an appropriate Fault object. Unfortunately, we throw away
+ # most of the debugging information. More useful error reporting is
+ # left as an exercise for the reader.
+ Fault=soaplib.Fault
+ f=None
+ try:
+ if isinstance(v, Fault):
+ f=v
+ elif isinstance(v, Exception):
+ f=Fault(-1, "Unexpected Zope exception: " + str(v))
+ else:
+ f=Fault(-2, "Unexpected Zope error value: " + str(v))
+ except:
+ f=Fault(-3, "Unknown Zope fault type")
+
+ # Do the damage.
+ self.setBody(soaplib.dumps(f))
+ self._real.setHeader('content-type', 'text/xml')
+ self._real.setStatus(200)
+
+ return tb
+
+response=Response
diff -urN --exclude-from=exclude _zope-untouched/lib/python/soaplib.py soap-zope/lib/python/soaplib.py
--- _zope-untouched/lib/python/soaplib.py Thu Jan 1 02:00:00 1970
+++ soap-zope/lib/python/soaplib.py Thu Jun 22 00:21:33 2000
@@ -0,0 +1,828 @@
+#
+# SOAP CLIENT LIBRARY
+# $Id$
+#
+# an SOAP 1.1 client interface for Python.
+#
+# the marshalling and response parser code can also be used to
+# implement SOAP servers.
+#
+# 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
+#
+# also note that this version is designed to work with Python 1.5.2
+# and newer. it does not work under 1.5.1 or earlier.
+#
+# Contact:
+# fredrik@pythonware.com
+# http://www.pythonware.com
+#
+# History:
+# 2000-01-05 fl First experimental version (based on xmlrpclib.py)
+# 2000-02-15 fl Updated to SOAP 1.0
+# 2000-04-30 fl Major overhaul, updated for SOAP 1.1
+# 2000-05-27 fl Fixed xmllib support
+# 2000-06-20 fl Frontier interoperability testing
+#
+# Copyright (c) 1999-2000 by Secret Labs AB.
+# Copyright (c) 1999-2000 by Fredrik Lundh.
+#
+# Portions of this engine have been developed in cooperation with
+# Loudcloud, Inc.
+#
+# --------------------------------------------------------------------
+# The soaplib.py library is
+#
+# Copyright (c) 1999-2000 by Secret Labs AB
+# Copyright (c) 1999-2000 by Fredrik Lundh
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of
+# Secret Labs AB or the author not be used in advertising or publicity
+# pertaining to distribution of the software without specific, written
+# prior permission.
+#
+# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
+# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
+# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+# --------------------------------------------------------------------
+
+# FIXME: handle Fault messages correctly
+# FIXME: handle homogenous arrays
+# FIXME: handle more time types
+# FIXME: add support for more character sets (under 1.6)
+# FIXME: incorporate id/href patches (from John Lehmann)
+
+import string, time, re
+import urllib, xmllib
+from types import *
+from cgi import escape
+
+try:
+ import sgmlop
+except ImportError:
+ sgmlop = None # accelerator not available
+
+# how far we're from a real 1.0 :-)
+__version__ = "0.8"
+
+# schema namespaces
+NS_XSD = "http://www.w3.org/1999/XMLSchema/"
+NS_XSI = "http://www.w3.org/1999/XMLSchema/instance/"
+
+# soap namespaces
+NS_ENV = "http://schemas.xmlsoap.org/soap/envelope/"
+NS_ENC = "http://schemas.xmlsoap.org/soap/encoding/"
+
+# experimental extensions
+NS_LAB = "http://www.pythonware.com/soap/"
+
+# envelope tag. declare all namespaces used by the payload
+ENVELOPE = (
+ "<env:Envelope "
+ "xmlns:env='" + NS_ENV + "' "
+ "xmlns:enc='" + NS_ENC + "' "
+ "xmlns:lab='" + NS_LAB + "' "
+ "xmlns:xsd='" + NS_XSD + "' "
+ "xmlns:xsi='" + NS_XSI + "' "
+ "env:encodingStyle='" + NS_ENC + "'"
+ ">\n"
+ )
+
+SOAPTAGS = NS_ENV + "Envelope", NS_ENV + "Body"
+
+# check if a string is valid XML tag
+_is_valid_tag = re.compile("^[a-zA-Z_][-a-zA-Z0-9._]*$").match
+# FIXME: this should reject strings that start with [Xx][Mm][Ll]
+
+# --------------------------------------------------------------------
+# Exceptions
+
+class Error:
+ # base class for client errors
+ pass
+
+class ProtocolError(Error):
+ # indicates an HTTP protocol error
+ def __init__(self, url, errcode, errmsg, headers):
+ self.url = url
+ self.errcode = errcode
+ self.errmsg = errmsg
+ self.headers = headers
+ def __repr__(self):
+ return (
+ "<ProtocolError for %s: %s %s>" %
+ (self.url, self.errcode, repr(self.errmsg))
+ )
+
+class ResponseError(Error):
+ # indicates a broken response package
+ pass
+
+class Fault(Error):
+ # indicates a SOAP fault package
+ def __init__(self, faultcode, faultstring, detail=None):
+ self.faultcode = faultcode
+ self.faultstring = faultstring
+ self.detail = None
+ def encode(self, out):
+ write = out.write
+ write("<env:Fault>\n")
+ write("<faultcode>%s</faultcode>" % self.faultcode)
+ write("<faultstring>%s</faultstring>" % self.faultstring)
+ if self.detail:
+ out._dump("detail", self.detail)
+ write("</env:Fault>\n")
+ def __repr__(self):
+ return (
+ "<Fault %s: %s>" %
+ (self.faultcode, repr(self.faultstring))
+ )
+
+class MethodCall:
+ # (experimental) represents a set of named parameters
+ def __init__(self, method, namespace, pargs=(), kwargs={}):
+ self.method = method
+ self.namespace = namespace
+ self.pargs = pargs
+ self.kwargs = kwargs
+ def encode(self, out):
+ if self.namespace:
+ method = "rpc:" + self.method
+ out.write("<%s xmlns:rpc=%s>\n" % (method, repr(self.namespace)))
+ else:
+ method = self.method
+ out.write("<%s>\n" % method)
+ i = 1
+ for v in self.pargs:
+ out._dump("v.%d" % i, v)
+ i = i + 1
+ for k, v in self.kwargs.items():
+ out._dump(k, v)
+ out.write("</%s>\n" % method)
+
+class Response:
+ # (experimental) represents a response tuple
+ def __init__(self, method, namespace, value=()):
+ self.method = method
+ self.namespace = namespace
+ self.value = value
+ def encode(self, out):
+ method = self.method + "Response"
+ if self.namespace:
+ method = "rpc:" + method
+ out.write("<%s xmlns:rpc=%s>\n" % (method, repr(self.namespace)))
+ else:
+ out.write("<%s>\n" % method)
+ i = 1
+ for v in self.value:
+ out._dump("v.%d" % i, v)
+ i = i + 1
+ out.write("</%s>\n" % method)
+
+
+# --------------------------------------------------------------------
+# Special values
+
+# boolean wrapper
+# (you must use True or False to generate a "boolean" SOAP value)
+
+class Boolean:
+
+ def __init__(self, value = 0):
+ self.value = not not value
+
+ def encode(self, tag, out):
+ out.write(
+ "<%s xsi:type='xsd:boolean'>%d</%s>\n" % (tag, self.value, tag)
+ )
+
+ def __repr__(self):
+ if self.value:
+ return "<Boolean True at %x>" % id(self)
+ else:
+ return "<Boolean False at %x>" % id(self)
+
+ def __int__(self):
+ return self.value
+
+ def __nonzero__(self):
+ return self.value
+
+True, False = Boolean(1), Boolean(0)
+
+#
+# timePeriod wrapper
+# (wrap your iso8601 string or time tuple or localtime time value in
+# this class to generate a "timePeriod" SOAP value)
+
+class DateTime:
+
+ def __init__(self, value = 0):
+ t = type(value)
+ if t is not StringType:
+ if t is not TupleType:
+ value = time.localtime(value)
+ value = time.strftime("%Y%m%dT%H:%M:%S", value)
+ self.value = value
+
+ def __repr__(self):
+ return "<DateTime %s at %x>" % (self.value, id(self))
+
+ def decode(self, data):
+ self.value = string.strip(data)
+
+ def encode(self, tag, out):
+ out.write(
+ "<%s xsi:type='xsd:timePeriod'>%s</%s>\n", (tag, self.value, tag)
+ )
+
+#
+# binary data wrapper. you must use this for strings that are not
+# valid XML strings (in other words, strings that cannot be stored
+# as a plain XML element).
+
+class Binary:
+
+ def __init__(self, data=None):
+ self.data = data
+
+ def decode(self, data):
+ import base64
+ self.data = base64.decodestring(data)
+
+ def encode(self, tag, out):
+ import base64, StringIO
+ out.write("<%s xsi:type='enc:base64'>\n" % tag)
+ base64.encode(StringIO.StringIO(self.data), out)
+ out.write("</%s>\n" % tag)
+
+WRAPPERS = DateTime, Binary, Boolean
+
+# --------------------------------------------------------------------
+# XML parsers
+
+if sgmlop:
+
+ class FastParser:
+ # sgmlop based XML parser. this is typically 15x faster
+ # than SlowParser...
+
+ def __init__(self, target):
+
+ # setup callbacks
+ self.finish_endtag = target.end
+ self.handle_data = target.data
+
+ self.start = target.start
+
+ self.namespace = {"xmlns": "xmlns"}
+
+ # activate parser
+ self.parser = sgmlop.XMLParser()
+ self.parser.register(self)
+ self.feed = self.parser.feed
+ self.entity = {
+ "amp": "&", "gt": ">", "lt": "<",
+ "apos": "'", "quot": '"'
+ }
+
+ def close(self):
+ try:
+ self.parser.close()
+ finally:
+ self.parser = self.feed = None # nuke circular reference
+
+ def finish_starttag(self, tag, attrs):
+ # FIXME: this doesn't handle nested namespaces
+ items = attrs.items()
+ for k, v in items:
+ if k[:6] == "xmlns:":
+ self.namespace[k[6:]] = v
+ # fixup names
+ if ":" in tag:
+ ns, tag = string.split(tag, ":")
+ tag = self.namespace[ns] + tag
+ for k, v in items:
+ if ":" in k:
+ ns, k = string.split(k, ":")
+ k = self.namespace[ns] + k
+ attrs[k] = v
+ self.start(tag, attrs, self.namespace.get)
+
+ def handle_entityref(self, entity):
+ # <string> entity
+ try:
+ self.handle_data(self.entity[entity])
+ except KeyError:
+ self.handle_data("&%s;" % entity)
+
+else:
+
+ FastParser = None
+
+class SlowParser(xmllib.XMLParser):
+ # slow but safe standard parser, based on the XML parser in
+ # Python's standard library
+
+ def __init__(self, target):
+ self.__start = target.start
+ self.handle_data = target.data
+ self.unknown_endtag = target.end
+ xmllib.XMLParser.__init__(self)
+
+ def __namespace(self, prefix):
+ # map namespace prefix to full namespace
+ for tag, namespace, tag in self.stack:
+ if namespace.has_key(prefix):
+ return namespace[prefix]
+ return None
+
+ def unknown_starttag(self, tag, attrs):
+ # fixup tags and attribute names (ouch!)
+ tag = string.replace(tag, " ", "")
+ for k, v in attrs.items():
+ k = string.replace(k, " ", "")
+ attrs[k] = v
+ self.__start(tag, attrs, self.__namespace)
+
+# --------------------------------------------------------------------
+# SOAP marshalling and unmarshalling code
+
+class Marshaller:
+ """Generate an SOAP body from a Python data structure"""
+
+ # USAGE: create a marshaller instance for each set of parameters,
+ # and use "dumps" to convert your data (represented as a tuple) to
+ # a SOAP body chunk. to write a fault response, pass a Fault
+ # instance instead. you may prefer to use the "dumps" convenience
+ # function for this purpose (see below).
+
+ # by the way, if you don't understand what's going on in here,
+ # that's perfectly ok.
+
+ def __init__(self):
+ self.memo = {}
+ self.data = None
+
+ dispatch = {}
+
+ def dumps(self, value, envelope=0):
+ self.__out = []
+ self.write = write = self.__out.append
+ if envelope:
+ write(ENVELOPE)
+ write("<env:Body>\n")
+ if (isinstance(value, MethodCall) or
+ isinstance(value, Response) or
+ isinstance(value, Fault)):
+ value.encode(self)
+ else:
+ for item in value:
+ self._dump("v", item)
+ write("</env:Body>\n")
+ if envelope:
+ write("</env:Envelope>\n")
+ result = string.join(self.__out, "")
+ del self.__out, self.write # don't need this any more
+ return result
+
+ def _dump(self, tag, value):
+ try:
+ f = self.dispatch[type(value)]
+ except KeyError:
+ raise TypeError, "cannot marshal %s objects" % type(value)
+ else:
+ f(self, tag, value)
+
+ def dump_none(self, tag, value):
+ self.write(
+ "<%s xsi:null='1'></%s>\n" % (tag, tag)
+ )
+ dispatch[NoneType] = dump_none
+
+ def dump_int(self, tag, value):
+ self.write(
+ "<%s xsi:type='xsd:int'>%s</%s>\n" % (tag, value, tag)
+ )
+ dispatch[IntType] = dump_int
+
+ def dump_long(self, tag, value):
+ self.write(
+ "<%s xsi:type='xsd:integer'>%s</%s>\n" % (tag, value, tag)
+ )
+
+ def dump_long_old(self, tag, value):
+ value = str(value)[:-1] # 1.5.2 and earlier
+ self.write(
+ "<%s xsi:type='xsd:integer'>%s</%s>\n" % (tag, value, tag)
+ )
+
+ if ("%s" % 1L) == "1":
+ dispatch[LongType] = dump_long
+ else:
+ dispatch[LongType] = dump_long_old
+
+ def dump_double(self, tag, value):
+ self.write(
+ "<%s xsi:type='xsd:double'>%s</%s>\n" % (tag, value, tag)
+ )
+ dispatch[FloatType] = dump_double
+
+ def dump_string(self, tag, value):
+ self.write(
+ "<%s xsi:type='xsd:string'>%s</%s>\n" % (tag, escape(value), tag)
+ )
+ dispatch[StringType] = dump_string
+
+ def container(self, value):
+ if value:
+ i = id(value)
+ if self.memo.has_key(i):
+ raise TypeError, "cannot marshal recursive data structures"
+ self.memo[i] = None
+
+ def dump_array(self, tag, value):
+ self.container(value)
+ write = self.write
+ write("<%s enc:arrayType='xsd:ur-type[%d]'>\n" % (tag, len(value)))
+ for v in value:
+ self._dump("v", v)
+ write("</%s>\n" % tag)
+ dispatch[TupleType] = dump_array
+ dispatch[ListType] = dump_array
+
+ def dump_dict(self, tag, value):
+ self.container(value)
+ write = self.write
+ write("<%s xsi:type='lab:PythonDict'>\n" % tag)
+ for k, v in value.items():
+ self._dump("k", k)
+ self._dump("v", v)
+ write("</%s>\n" % tag)
+ dispatch[DictType] = dump_dict
+
+ def dump_instance(self, tag, value):
+ # check for special wrappers
+ if value.__class__ in WRAPPERS:
+ value.encode(tag, self)
+ else:
+ write = self.write
+ write("<%s>\n" % tag)
+ for k, v in vars(value).items():
+ self._dump(k, v)
+ write("</%s>\n" % tag)
+ dispatch[InstanceType] = dump_instance
+
+
+class Unmarshaller:
+
+ # unmarshal an SOAP response, based on incoming XML events (start,
+ # data, end). call close to get the resulting data structure
+
+ # note that this reader is fairly tolerant, and gladly accepts
+ # bogus SOAP data without complaining (but not bogus XML).
+
+ # and again, if you don't understand what's going on in here,
+ # that's perfectly ok.
+
+ def __init__(self):
+ self._stack = []
+ self._marks = []
+ self._data = []
+ self.append = self._stack.append
+
+ def close(self):
+ # return response code and the actual response
+ if self._marks or len(self._stack) != 1:
+ raise ResponseError()
+ # FIXME: should this really be done here?
+ method, params = self._stack[0]
+ args = []
+ for k, v in params:
+ args.append(v)
+ return method, tuple(args)
+
+ #
+ # event handlers
+
+ def start(self, tag, attrs, fixup):
+ # handle start tag
+ type = attrs.get(NS_XSI + "type")
+ if type is None:
+ type = attrs.get(NS_ENC + "arrayType")
+ if type and ":" in type:
+ prefix, type = string.split(type, ":")
+ ns = fixup(prefix)
+ if ns is None:
+ raise SyntaxError,\
+ "undefined namespace prefix %s" % repr(prefix)
+ type = ns + type
+ self._marks.append((tag, type, len(self._stack)))
+ self._data = []
+
+ def data(self, text):
+ self._data.append(text)
+
+ dispatch = {}
+
+ def end(self, tag):
+ # call the appropriate type handler
+ tag, type, mark = self._marks.pop()
+ if type is None and tag in SOAPTAGS:
+ pass
+ else:
+ try:
+ f = self.dispatch[type]
+ except KeyError:
+ self.end_unknown(type)
+ else:
+ self._mark = mark
+ self._stack.append((tag, f(self)))
+
+ #
+ # element decoders
+
+ def end_unknown(self, type, join=string.join):
+ print "***", type
+ raise SyntaxError, ("unknown type %s (for now)" % repr(type))
+
+ def end_boolean(self, join=string.join):
+ value = join(self._data, "")
+ if value in ("0", "false"):
+ return False
+ elif value in ("1", "true"):
+ return True
+ else:
+ raise TypeError, "bad boolean value"
+ dispatch[NS_XSD + "boolean"] = end_boolean
+
+ def end_int(self, join=string.join):
+ return int(join(self._data, ""))
+ dispatch[NS_XSD + "int"] = end_int
+ dispatch[NS_XSD + "byte"] = end_int
+ dispatch[NS_XSD + "unsignedByte"] = end_int
+ dispatch[NS_XSD + "short"] = end_int
+ dispatch[NS_XSD + "unsignedShort"] = end_int
+ dispatch[NS_XSD + "long"] = end_int
+
+ def end_long(self, join=string.join):
+ return long(join(self._data, ""))
+ dispatch[NS_XSD + "integer"] = end_long
+ dispatch[NS_XSD + "unsignedInt"] = end_long
+ dispatch[NS_XSD + "unsignedLong"] = end_long
+
+ def end_double(self, join=string.join):
+ return float(join(self._data, ""))
+ dispatch[NS_XSD + "double"] = end_double
+
+ def end_string(self, join=string.join):
+ return join(self._data, "")
+ dispatch[NS_XSD + "string"] = end_string
+
+ def end_array(self):
+ # map arrays to Python lists
+ list = []
+ data = self._stack[self._mark:]
+ del self._stack[self._mark:]
+ for tag, item in data:
+ list.append(item)
+ return list
+ dispatch[NS_XSD + "ur-type[]"] = end_array
+
+ def end_dict(self):
+ dict = {}
+ data = self._stack[self._mark:]
+ del self._stack[self._mark:]
+ for i in range(0, len(data), 2):
+ dict[data[i][1]] = data[i+1][1]
+ return dict
+ dispatch[NS_LAB + "PythonDict"] = end_dict
+
+ def end_struct(self):
+ # typeless elements are assumed to be structs. map
+ # them to a (name, value) list
+ data = self._stack[self._mark:]
+ del self._stack[self._mark:]
+ return data
+ dispatch[None] = end_struct
+
+ def end_base64(self, join=string.join):
+ value = Binary()
+ value.decode(join(self._data, ""))
+ return value
+ dispatch[NS_ENC + "base64"] = end_base64
+
+ def end_dateTime(self, join=string.join):
+ value = DateTime()
+ value.decode(join(self._data, ""))
+ return value
+ dispatch[NS_XSD + "timePeriod"] = end_dateTime
+ dispatch[NS_XSD + "timeInstant"] = end_dateTime
+
+# --------------------------------------------------------------------
+# convenience functions
+
+def getparser():
+ # get the fastest available parser, and attach it to an
+ # unmarshalling object. return both objects.
+ target = Unmarshaller()
+ if FastParser:
+ return FastParser(target), target
+ return SlowParser(target), target
+
+def dumps(params, envelope=0):
+ # convert a tuple or a fault object to an SOAP packet
+
+ assert (type(params) == TupleType or
+ isinstance(params, MethodCall) or
+ isinstance(params, Response) or
+ isinstance(params, Fault)
+ ),\
+ "argument must be tuple, method call/response, or fault instance"
+
+ m = Marshaller()
+
+ return m.dumps(params, envelope)
+
+def loads(data):
+ # convert an SOAP packet to data plus a method name (None
+ # if not present). if the SOAP packet represents a fault
+ # condition, this function raises a Fault exception.
+ p, u = getparser()
+ p.feed(data)
+ p.close()
+ return u.close()
+
+
+# --------------------------------------------------------------------
+# request dispatcher
+
+class _Method:
+ # some magic to bind an SOAP method to an RPC server.
+ # supports "nested" methods (e.g. examples.getStateName)
+ def __init__(self, send, name):
+ self.__send = send
+ self.__name = name
+ def __getattr__(self, name):
+ return _Method(self.__send, "%s.%s" % (self.__name, name))
+ def __call__(self, *pargs, **kwargs):
+ return self.__send(self.__name, pargs, kwargs)
+
+class Transport:
+ """Handles an HTTP transaction to an SOAP server"""
+
+ # client identifier (may be overridden)
+ user_agent = "soaplib.py/%s (from www.pythonware.com)" % __version__
+
+ def request(self, host, handler, request_body):
+ # issue SOAP request
+
+ import httplib
+ h = httplib.HTTP(host)
+ h.debuglevel = 0
+ h.putrequest("POST", handler)
+
+ # required by HTTP/1.1
+ h.putheader("Host", host)
+
+ if h.debuglevel > 0:
+ print "-- REQUEST --"
+ print request_body
+
+ # required by SOAP
+ h.putheader("User-Agent", self.user_agent)
+ h.putheader("Content-Type", "text/xml")
+ h.putheader("Content-Length", str(len(request_body)))
+ h.putheader("SOAPAction", '""')
+
+ h.endheaders()
+
+ if request_body:
+ h.send(request_body)
+
+ errcode, errmsg, headers = h.getreply()
+
+ if h.debuglevel > 0:
+ print "-- RESPONSE --"
+ print errcode, errmsg, headers
+
+ if errcode not in (200, 500):
+ raise ProtocolError(
+ host + handler,
+ errcode, errmsg,
+ headers
+ )
+
+ response = self.parse_response(h.getfile())
+
+ # should this really be done in here?
+ response = response[1]
+ if len(response) == 1:
+ response = response[0]
+
+ return response
+
+ def parse_response(self, f):
+ # read response from input file, and parse it
+
+ p, u = getparser()
+
+ while 1:
+ response = f.read(1024)
+ if not response:
+ break
+ p.feed(response)
+
+ f.close()
+ p.close()
+
+ return u.close()
+
+
+class ServerProxy:
+ """Represents a connection to an SOAP server"""
+
+ def __init__(self, uri, transport=None):
+ # establish a "logical" server connection
+
+ # get the url
+ type, uri = urllib.splittype(uri)
+ if type != "http":
+ raise IOError, "unsupported SOAP protocol"
+ self.__host, self.__handler = urllib.splithost(uri)
+ if not self.__handler:
+ self.__handler = "/"
+
+ if transport is None:
+ transport = Transport()
+ self.__transport = transport
+
+ self.__methods = {} # known methods
+
+ def _defmethod(self, methodname, namespace):
+ # associate information with a remote methodname
+
+ self.__methods[methodname] = namespace
+
+ def __request(self, methodname, pargs, kwargs):
+ # call a method on the remote server
+
+ # wrap the arguments up
+ params = MethodCall(
+ methodname, self.__methods.get(methodname), pargs, kwargs
+ )
+
+ request = dumps(params, 1)
+
+ response = self.__transport.request(
+ self.__host,
+ self.__handler,
+ request
+ )
+
+ return response
+
+ def __repr__(self):
+ return (
+ "<Server proxy for %s%s>" %
+ (self.__host, self.__handler)
+ )
+
+ __str__ = __repr__
+
+ def __getattr__(self, name):
+ # magic method dispatcher
+ return _Method(self.__request, name)
+
+ def __getitem__(self, name):
+ # alternate method dispatcher
+ return _Method(self.__request, name)
+
+
+# xmlrpclib compatibility
+Server = ServerProxy
+
+
+if __name__ == "__main__":
+
+ # simple test, using soapserver.py
+ server = ServerProxy("http://localhost:8000")
+ print server.call("hello")
--jI8keyz6grp/JLjh--