2 # SFA XML-RPC and SOAP interfaces
7 # SOAP support is optional
10 from SOAPpy.Parser import parseSOAPRPC
11 from SOAPpy.Types import faultType
12 from SOAPpy.NS import NS
13 from SOAPpy.SOAPBuilder import buildSOAP
18 #from sfa.util.faults import SfaNotImplemented, SfaAPIError, SfaInvalidAPIMethod, SfaFault
19 from sfa.util.faults import SfaInvalidAPIMethod, SfaAPIError, SfaFault
20 from sfa.util.sfalogging import logger
21 from sfa.util.py23 import xmlrpc_client
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(
32 "".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
35 def xmlrpclib_escape(s, replace=string.replace):
37 xmlrpclib does not handle invalid 7-bit control characters. This
38 function augments xmlrpclib.escape, which by default only replaces
39 '&', '<', and '>' with entities.
42 # This is the standard xmlrpclib.escape function
43 s = replace(s, "&", "&")
44 s = replace(s, "<", "<")
45 s = replace(s, ">", ">",)
47 # Replace invalid 7-bit control characters with '?'
48 return s.translate(xml_escape_table)
51 def xmlrpclib_dump(self, value, write):
53 xmlrpclib cannot marshal instances of subclasses of built-in
54 types. This function overrides xmlrpclib.Marshaller.__dump so that
55 any value that is an instance of one of its acceptable types is
56 marshalled as that type.
58 xmlrpclib also cannot handle invalid 7-bit control characters. See
62 # Use our escape function
63 args = [self, value, write]
64 if isinstance(value, (str, unicode)):
65 args.append(xmlrpclib_escape)
68 # Try for an exact match first
69 f = self.dispatch[type(value)]
72 # Try for an isinstance() match
73 for Type, f in self.dispatch.iteritems():
74 if isinstance(value, Type):
77 raise TypeError("cannot marshal %s objects" % type(value))
81 # You can't hide from me!
82 # Note: not quite sure if this will still cause
83 # the expected behaviour under python3
84 xmlrpc_client.Marshaller._Marshaller__dump = xmlrpclib_dump
89 The XmlrpcApi class implements a basic xmlrpc (or soap) service
94 def __init__(self, encoding="utf-8", methods='sfa.methods'):
96 self.encoding = encoding
99 # flat list of method names
100 self.methods_module = methods_module = __import__(
101 methods, fromlist=[methods])
102 self.methods = methods_module.all
106 def callable(self, method):
108 Return a new instance of the specified method.
111 if method not in self.methods:
112 raise SfaInvalidAPIMethod(method)
114 # Get new instance of method
116 classname = method.split(".")[-1]
117 module = __import__(self.methods_module.__name__ +
118 "." + method, globals(), locals(), [classname])
119 callablemethod = getattr(module, classname)(self)
120 return getattr(module, classname)(self)
121 except (ImportError, AttributeError):
122 self.logger.log_exc("Error importing method: %s" % method)
123 raise SfaInvalidAPIMethod(method)
125 def call(self, source, method, *args):
127 Call the named method from the specified source with the
130 function = self.callable(method)
131 function.source = source
133 return function(*args)
135 def handle(self, source, data, method_map):
137 Handle an XML-RPC or SOAP request from the specified source.
139 # Parse request into method name and arguments
141 interface = xmlrpc_client
142 self.protocol = 'xmlrpc'
143 (args, method) = xmlrpc_client.loads(data)
144 if method in method_map:
145 method = method_map[method]
146 methodresponse = True
148 except Exception as e:
149 if SOAPpy is not None:
150 self.protocol = 'soap'
152 (r, header, body, attrs) = parseSOAPRPC(
153 data, header=1, body=1, attrs=1)
156 # XXX Support named arguments
161 result = self.call(source, method, *args)
162 except SfaFault as fault:
164 self.logger.log_exc("XmlrpcApi.handle has caught Exception")
165 except Exception as fault:
166 self.logger.log_exc("XmlrpcApi.handle has caught Exception")
167 result = SfaAPIError(fault)
170 response = self.prepare_response(result, method)
173 def prepare_response(self, result, method=""):
175 convert result to a valid xmlrpc or soap response
178 if self.protocol == 'xmlrpc':
179 if not isinstance(result, SfaFault):
181 response = xmlrpc_client.dumps(
182 result, methodresponse=True, encoding=self.encoding, allow_none=1)
183 elif self.protocol == 'soap':
184 if isinstance(result, Exception):
185 result = faultParameter(
186 NS.ENV_T + ":Server", "Method Failed", method)
187 result._setDetail("Fault %d: %s" %
188 (result.faultCode, result.faultString))
190 response = buildSOAP(
191 kw={'%sResponse' % method: {'Result': result}}, encoding=self.encoding)
193 if isinstance(result, Exception):