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_codepoints = range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F)
31 # broke with f24, somehow we get a unicode as an incoming string to be translated
32 str_xml_escape_table = string.maketrans("".join((chr(x) for x in invalid_codepoints)),
33 "?" * len(invalid_codepoints))
34 # loosely inspired from
35 # http://stackoverflow.com/questions/1324067/how-do-i-get-str-translate-to-work-with-unicode-strings
36 unicode_xml_escape_table = { invalid : u"?" for invalid in invalid_codepoints}
38 def xmlrpclib_escape(s, replace = string.replace):
40 xmlrpclib does not handle invalid 7-bit control characters. This
41 function augments xmlrpclib.escape, which by default only replaces
42 '&', '<', and '>' with entities.
45 # This is the standard xmlrpclib.escape function
46 s = replace(s, "&", "&")
47 s = replace(s, "<", "<")
48 s = replace(s, ">", ">",)
50 # Replace invalid 7-bit control characters with '?'
51 if isinstance(s, str):
52 return s.translate(str_xml_escape_table)
54 return s.translate(unicode_xml_escape_table)
57 def xmlrpclib_dump(self, value, write):
59 xmlrpclib cannot marshal instances of subclasses of built-in
60 types. This function overrides xmlrpclib.Marshaller.__dump so that
61 any value that is an instance of one of its acceptable types is
62 marshalled as that type.
64 xmlrpclib also cannot handle invalid 7-bit control characters. See
68 # Use our escape function
69 args = [self, value, write]
70 if isinstance(value, (str, unicode)):
71 args.append(xmlrpclib_escape)
74 # Try for an exact match first
75 f = self.dispatch[type(value)]
78 # Try for an isinstance() match
79 for Type, f in self.dispatch.iteritems():
80 if isinstance(value, Type):
83 raise TypeError("cannot marshal %s objects" % type(value))
87 # You can't hide from me!
88 # Note: not quite sure if this will still cause
89 # the expected behaviour under python3
90 xmlrpc_client.Marshaller._Marshaller__dump = xmlrpclib_dump
95 The XmlrpcApi class implements a basic xmlrpc (or soap) service
100 def __init__(self, encoding="utf-8", methods='sfa.methods'):
102 self.encoding = encoding
105 # flat list of method names
106 self.methods_module = methods_module = __import__(
107 methods, fromlist=[methods])
108 self.methods = methods_module.all
112 def callable(self, method):
114 Return a new instance of the specified method.
117 if method not in self.methods:
118 raise SfaInvalidAPIMethod(method)
120 # Get new instance of method
122 classname = method.split(".")[-1]
123 module = __import__(self.methods_module.__name__ +
124 "." + method, globals(), locals(), [classname])
125 callablemethod = getattr(module, classname)(self)
126 return getattr(module, classname)(self)
127 except (ImportError, AttributeError):
128 self.logger.log_exc("Error importing method: %s" % method)
129 raise SfaInvalidAPIMethod(method)
131 def call(self, source, method, *args):
133 Call the named method from the specified source with the
136 function = self.callable(method)
137 function.source = source
139 return function(*args)
141 def handle(self, source, data, method_map):
143 Handle an XML-RPC or SOAP request from the specified source.
145 # Parse request into method name and arguments
147 interface = xmlrpc_client
148 self.protocol = 'xmlrpc'
149 (args, method) = xmlrpc_client.loads(data)
150 if method in method_map:
151 method = method_map[method]
152 methodresponse = True
154 except Exception as e:
155 if SOAPpy is not None:
156 self.protocol = 'soap'
158 (r, header, body, attrs) = parseSOAPRPC(
159 data, header=1, body=1, attrs=1)
162 # XXX Support named arguments
167 result = self.call(source, method, *args)
168 except SfaFault as fault:
170 self.logger.log_exc("XmlrpcApi.handle has caught Exception")
171 except Exception as fault:
172 self.logger.log_exc("XmlrpcApi.handle has caught Exception")
173 result = SfaAPIError(fault)
176 response = self.prepare_response(result, method)
179 def prepare_response(self, result, method=""):
181 convert result to a valid xmlrpc or soap response
184 if self.protocol == 'xmlrpc':
185 if not isinstance(result, SfaFault):
187 response = xmlrpc_client.dumps(
188 result, methodresponse=True, encoding=self.encoding, allow_none=1)
189 elif self.protocol == 'soap':
190 if isinstance(result, Exception):
191 result = faultParameter(
192 NS.ENV_T + ":Server", "Method Failed", method)
193 result._setDetail("Fault %d: %s" %
194 (result.faultCode, result.faultString))
196 response = buildSOAP(
197 kw={'%sResponse' % method: {'Result': result}}, encoding=self.encoding)
199 if isinstance(result, Exception):