2 # SFA XML-RPC and SOAP interfaces
8 # SOAP support is optional
11 from SOAPpy.Parser import parseSOAPRPC
12 from SOAPpy.Types import faultType
13 from SOAPpy.NS import NS
14 from SOAPpy.SOAPBuilder import buildSOAP
19 #from sfa.util.faults import SfaNotImplemented, SfaAPIError, SfaInvalidAPIMethod, SfaFault
20 from sfa.util.faults import SfaInvalidAPIMethod, SfaAPIError, SfaFault
21 from sfa.util.sfalogging import logger
24 # See "2.2 Characters" in the XML specification:
26 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
28 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
30 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
31 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
33 def xmlrpclib_escape(s, replace = string.replace):
35 xmlrpclib does not handle invalid 7-bit control characters. This
36 function augments xmlrpclib.escape, which by default only replaces
37 '&', '<', and '>' with entities.
40 # This is the standard xmlrpclib.escape function
41 s = replace(s, "&", "&")
42 s = replace(s, "<", "<")
43 s = replace(s, ">", ">",)
45 # Replace invalid 7-bit control characters with '?'
46 return s.translate(xml_escape_table)
48 def xmlrpclib_dump(self, value, write):
50 xmlrpclib cannot marshal instances of subclasses of built-in
51 types. This function overrides xmlrpclib.Marshaller.__dump so that
52 any value that is an instance of one of its acceptable types is
53 marshalled as that type.
55 xmlrpclib also cannot handle invalid 7-bit control characters. See
59 # Use our escape function
60 args = [self, value, write]
61 if isinstance(value, (str, unicode)):
62 args.append(xmlrpclib_escape)
65 # Try for an exact match first
66 f = self.dispatch[type(value)]
69 # Try for an isinstance() match
70 for Type, f in self.dispatch.iteritems():
71 if isinstance(value, Type):
74 raise TypeError, "cannot marshal %s objects" % type(value)
78 # You can't hide from me!
79 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
83 The XmlrpcApi class implements a basic xmlrpc (or soap) service
88 def __init__ (self, encoding="utf-8", methods='sfa.methods'):
90 self.encoding = encoding
93 # flat list of method names
94 self.methods_module = methods_module = __import__(methods, fromlist=[methods])
95 self.methods = methods_module.all
99 def callable(self, method):
101 Return a new instance of the specified method.
104 if method not in self.methods:
105 raise SfaInvalidAPIMethod, method
107 # Get new instance of method
109 classname = method.split(".")[-1]
110 module = __import__(self.methods_module.__name__ + "." + method, globals(), locals(), [classname])
111 callablemethod = getattr(module, classname)(self)
112 return getattr(module, classname)(self)
113 except (ImportError, AttributeError):
114 raise SfaInvalidAPIMethod, method
116 def call(self, source, method, *args):
118 Call the named method from the specified source with the
121 function = self.callable(method)
122 function.source = source
124 return function(*args)
127 def handle(self, source, data, method_map):
129 Handle an XML-RPC or SOAP request from the specified source.
131 # Parse request into method name and arguments
133 interface = xmlrpclib
134 self.protocol = 'xmlrpclib'
135 (args, method) = xmlrpclib.loads(data)
136 if method_map.has_key(method):
137 method = method_map[method]
138 methodresponse = True
141 if SOAPpy is not None:
142 self.protocol = 'soap'
144 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
147 # XXX Support named arguments
152 result = self.call(source, method, *args)
153 except SfaFault, fault:
155 self.logger.log_exc("XmlrpcApi.handle has caught Exception")
156 except Exception, fault:
157 self.logger.log_exc("XmlrpcApi.handle has caught Exception")
158 result = SfaAPIError(fault)
162 response = self.prepare_response(result, method)
165 def prepare_response(self, result, method=""):
167 convert result to a valid xmlrpc or soap response
170 if self.protocol == 'xmlrpclib':
171 if not isinstance(result, SfaFault):
173 response = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
174 elif self.protocol == 'soap':
175 if isinstance(result, Exception):
176 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
177 result._setDetail("Fault %d: %s" % (result.faultCode, result.faultString))
179 response = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
181 if isinstance(result, Exception):