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 SfaInvalidAPIMethod, SfaAPIError, SfaFault
19 from sfa.util.sfalogging import logger
20 from sfa.util.py23 import xmlrpc_client
23 # See "2.2 Characters" in the XML specification:
25 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
27 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
29 invalid_codepoints = range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F)
30 # broke with f24, somehow we get a unicode as an incoming string to be translated
31 str_xml_escape_table = string.maketrans("".join((chr(x) for x in invalid_codepoints)),
32 "?" * len(invalid_codepoints))
33 # loosely inspired from
34 # http://stackoverflow.com/questions/1324067/how-do-i-get-str-translate-to-work-with-unicode-strings
35 unicode_xml_escape_table = { invalid : u"?" for invalid in invalid_codepoints}
37 def xmlrpclib_escape(s, replace = string.replace):
39 xmlrpclib does not handle invalid 7-bit control characters. This
40 function augments xmlrpclib.escape, which by default only replaces
41 '&', '<', and '>' with entities.
44 # This is the standard xmlrpclib.escape function
45 s = replace(s, "&", "&")
46 s = replace(s, "<", "<")
47 s = replace(s, ">", ">",)
49 # Replace invalid 7-bit control characters with '?'
50 if isinstance(s, str):
51 return s.translate(str_xml_escape_table)
53 return s.translate(unicode_xml_escape_table)
56 def xmlrpclib_dump(self, value, write):
58 xmlrpclib cannot marshal instances of subclasses of built-in
59 types. This function overrides xmlrpclib.Marshaller.__dump so that
60 any value that is an instance of one of its acceptable types is
61 marshalled as that type.
63 xmlrpclib also cannot handle invalid 7-bit control characters. See
67 # Use our escape function
68 args = [self, value, write]
69 if isinstance(value, (str, unicode)):
70 args.append(xmlrpclib_escape)
73 # Try for an exact match first
74 f = self.dispatch[type(value)]
77 # Try for an isinstance() match
78 for Type, f in self.dispatch.iteritems():
79 if isinstance(value, Type):
82 raise TypeError("cannot marshal %s objects" % type(value))
86 # You can't hide from me!
87 # Note: not quite sure if this will still cause
88 # the expected behaviour under python3
89 xmlrpc_client.Marshaller._Marshaller__dump = xmlrpclib_dump
94 The XmlrpcApi class implements a basic xmlrpc (or soap) service
99 def __init__(self, encoding="utf-8", methods='sfa.methods'):
101 self.encoding = encoding
104 # flat list of method names
105 self.methods_module = methods_module = __import__(
106 methods, fromlist=[methods])
107 self.methods = methods_module.all
109 def callable(self, method):
111 Return a new instance of the specified method.
114 if method not in self.methods:
115 raise SfaInvalidAPIMethod(method)
117 # Get new instance of method
119 classname = method.split(".")[-1]
120 module = __import__(self.methods_module.__name__ +
121 "." + method, globals(), locals(), [classname])
122 callablemethod = getattr(module, classname)(self)
123 return getattr(module, classname)(self)
124 except (ImportError, AttributeError):
125 logger.log_exc("Error importing method: %s" % method)
126 raise SfaInvalidAPIMethod(method)
128 def call(self, source, method, *args):
130 Call the named method from the specified source with the
133 function = self.callable(method)
134 function.source = source
136 return function(*args)
138 def handle(self, source, data, method_map):
140 Handle an XML-RPC or SOAP request from the specified source.
142 # Parse request into method name and arguments
144 interface = xmlrpc_client
145 self.protocol = 'xmlrpc'
146 (args, method) = xmlrpc_client.loads(data)
147 if method in method_map:
148 method = method_map[method]
149 methodresponse = True
151 except Exception as e:
152 if SOAPpy is not None:
153 self.protocol = 'soap'
155 (r, header, body, attrs) = parseSOAPRPC(
156 data, header=1, body=1, attrs=1)
159 # XXX Support named arguments
164 result = self.call(source, method, *args)
165 except SfaFault as fault:
167 logger.log_exc("XmlrpcApi.handle has caught Exception")
168 except Exception as fault:
169 logger.log_exc("XmlrpcApi.handle has caught Exception")
170 result = SfaAPIError(fault)
173 response = self.prepare_response(result, method)
176 def prepare_response(self, result, method=""):
178 convert result to a valid xmlrpc or soap response
181 if self.protocol == 'xmlrpc':
182 if not isinstance(result, SfaFault):
184 response = xmlrpc_client.dumps(
185 result, methodresponse=True, encoding=self.encoding, allow_none=1)
186 elif self.protocol == 'soap':
187 if isinstance(result, Exception):
188 result = faultParameter(
189 NS.ENV_T + ":Server", "Method Failed", method)
190 result._setDetail("Fault %d: %s" %
191 (result.faultCode, result.faultString))
193 response = buildSOAP(
194 kw={'%sResponse' % method: {'Result': result}}, encoding=self.encoding)
196 if isinstance(result, Exception):