2 # PLCAPI XML-RPC and SOAP interfaces
4 # Aaron Klingaman <alk@absarokasoft.com>
5 # Mark Huang <mlhuang@cs.princeton.edu>
7 # Copyright (C) 2004-2006 The Trustees of Princeton University
8 # $Id: API.py 14587 2009-07-19 13:18:50Z thierry $
9 # $URL: https://svn.planet-lab.org/svn/PLCAPI/trunk/PLC/API.py $
18 import logging.handlers
20 from ApiExceptionCodes import *
22 # Wrapper around xmlrpc fault to include a traceback of the server to the
23 # client. This is done to aid in debugging from a client perspective.
25 class FaultWithTraceback(xmlrpclib.Fault):
26 def __init__(self, code, faultString, exc_info):
27 type, value, tb = exc_info
28 exc_str = ''.join(traceback.format_exception(type, value, tb))
29 faultString = faultString + "\nFAULT_TRACEBACK:" + exc_str
30 xmlrpclib.Fault.__init__(self, code, faultString)
32 # Exception to report to the caller when some non-XMLRPC fault occurs on the
33 # server. For example a TypeError.
35 class UnhandledServerException(FaultWithTraceback):
36 def __init__(self, exc_info):
37 type, value, tb = exc_info
38 faultString = "Unhandled exception: " + str(type)
39 FaultWithTraceback.__init__(self, FAULT_UNHANDLEDSERVEREXCEPTION, faultString, exc_info)
41 # See "2.2 Characters" in the XML specification:
43 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
45 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
47 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
48 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
50 def xmlrpclib_escape(s, replace = string.replace):
52 xmlrpclib does not handle invalid 7-bit control characters. This
53 function augments xmlrpclib.escape, which by default only replaces
54 '&', '<', and '>' with entities.
57 # This is the standard xmlrpclib.escape function
58 s = replace(s, "&", "&")
59 s = replace(s, "<", "<")
60 s = replace(s, ">", ">",)
62 # Replace invalid 7-bit control characters with '?'
63 return s.translate(xml_escape_table)
65 def xmlrpclib_dump(self, value, write):
67 xmlrpclib cannot marshal instances of subclasses of built-in
68 types. This function overrides xmlrpclib.Marshaller.__dump so that
69 any value that is an instance of one of its acceptable types is
70 marshalled as that type.
72 xmlrpclib also cannot handle invalid 7-bit control characters. See
76 # Use our escape function
77 args = [self, value, write]
78 if isinstance(value, (str, unicode)):
79 args.append(xmlrpclib_escape)
82 # Try for an exact match first
83 f = self.dispatch[type(value)]
85 # Try for an isinstance() match
86 for Type, f in self.dispatch.iteritems():
87 if isinstance(value, Type):
90 raise TypeError, "cannot marshal %s objects" % type(value)
94 # You can't hide from me!
95 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
97 # SOAP support is optional
100 from SOAPpy.Parser import parseSOAPRPC
101 from SOAPpy.Types import faultType
102 from SOAPpy.NS import NS
103 from SOAPpy.SOAPBuilder import buildSOAP
107 def import_deep(name):
108 mod = __import__(name)
109 components = name.split('.')
110 for comp in components[1:]:
111 mod = getattr(mod, comp)
115 def __init__(self, encoding = "utf-8"):
116 self.encoding = encoding
119 self.register_functions()
121 def init_logger(self):
122 self.logger = logging.getLogger("ApiLogger")
123 self.logger.setLevel(logging.INFO)
124 self.logger.addHandler(logging.handlers.RotatingFileHandler(self.get_log_name(), maxBytes=100000, backupCount=5))
126 def get_log_name(self):
127 return "/tmp/apilogfile.txt"
129 def register_functions(self):
130 self.register_function(self.noop)
132 def register_function(self, function, name = None):
134 name = function.__name__
135 self.funcs[name] = function
137 def call(self, source, method, *args):
139 Call the named method from the specified source with the
143 if not method in self.funcs:
144 raise "Unknown method: " + method
146 return self.funcs[method](*args)
148 def handle(self, source, data):
150 Handle an XML-RPC or SOAP request from the specified source.
153 # Parse request into method name and arguments
155 interface = xmlrpclib
156 (args, method) = xmlrpclib.loads(data)
157 methodresponse = True
159 if SOAPpy is not None:
161 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
164 # XXX Support named arguments
168 self.logger.debug("OP:" + str(method) + " from " + str(source))
171 result = self.call(source, method, *args)
172 except xmlrpclib.Fault, fault:
173 self.logger.warning("FAULT: " + str(fault.faultCode) + " " + str(fault.faultString))
174 self.logger.info(traceback.format_exc())
175 # Handle expected faults
176 if interface == xmlrpclib:
177 result = FaultWithTraceback(fault.faultCode, fault.faultString, sys.exc_info())
178 methodresponse = None
179 elif interface == SOAPpy:
180 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
181 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
184 self.logger.warning("EXCEPTION: " + str(sys.exc_info()[0]))
185 self.logger.info(traceback.format_exc())
186 result = UnhandledServerException(sys.exc_info())
187 methodresponse = None
190 if interface == xmlrpclib:
191 if not isinstance(result, xmlrpclib.Fault):
193 data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
194 elif interface == SOAPpy:
195 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
199 def noop(self, value):