import repository from arizona
[raven.git] / lib / ravenlib / modpythonapi / BaseApi.py
1 #
2 # PLCAPI XML-RPC and SOAP interfaces
3 #
4 # Aaron Klingaman <alk@absarokasoft.com>
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 #
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 $
10 #
11
12 import sys
13 import traceback
14 import string
15
16 import xmlrpclib
17 import logging
18 import logging.handlers
19
20 from ApiExceptionCodes import *
21
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.
24
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)
31
32 # Exception to report to the caller when some non-XMLRPC fault occurs on the
33 # server. For example a TypeError.
34
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)
40
41 class UnknownMethod(xmlrpclib.Fault):
42     def __init__(self, methodname):
43         faultString = "Unknown method: " + str(methodname)
44         xmlrpclib.Fault.__init__(self, FAULT_UNKOWNMETHOD, faultString)
45
46 # See "2.2 Characters" in the XML specification:
47 #
48 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
49 # avoiding
50 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
51
52 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
53 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
54
55 def xmlrpclib_escape(s, replace = string.replace):
56     """
57     xmlrpclib does not handle invalid 7-bit control characters. This
58     function augments xmlrpclib.escape, which by default only replaces
59     '&', '<', and '>' with entities.
60     """
61
62     # This is the standard xmlrpclib.escape function
63     s = replace(s, "&", "&amp;")
64     s = replace(s, "<", "&lt;")
65     s = replace(s, ">", "&gt;",)
66
67     # Replace invalid 7-bit control characters with '?'
68     return s.translate(xml_escape_table)
69
70 def xmlrpclib_dump(self, value, write):
71     """
72     xmlrpclib cannot marshal instances of subclasses of built-in
73     types. This function overrides xmlrpclib.Marshaller.__dump so that
74     any value that is an instance of one of its acceptable types is
75     marshalled as that type.
76
77     xmlrpclib also cannot handle invalid 7-bit control characters. See
78     above.
79     """
80
81     # Use our escape function
82     args = [self, value, write]
83     if isinstance(value, (str, unicode)):
84         args.append(xmlrpclib_escape)
85
86     try:
87         # Try for an exact match first
88         f = self.dispatch[type(value)]
89     except KeyError:
90         # Try for an isinstance() match
91         for Type, f in self.dispatch.iteritems():
92             if isinstance(value, Type):
93                 f(*args)
94                 return
95         raise TypeError, "cannot marshal %s objects" % type(value)
96     else:
97         f(*args)
98
99 # You can't hide from me!
100 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
101
102 # SOAP support is optional
103 try:
104     import SOAPpy
105     from SOAPpy.Parser import parseSOAPRPC
106     from SOAPpy.Types import faultType
107     from SOAPpy.NS import NS
108     from SOAPpy.SOAPBuilder import buildSOAP
109 except ImportError:
110     SOAPpy = None
111
112 def import_deep(name):
113     mod = __import__(name)
114     components = name.split('.')
115     for comp in components[1:]:
116         mod = getattr(mod, comp)
117     return mod
118
119 class BaseApi:
120     def __init__(self, encoding = "utf-8"):
121         self.encoding = encoding
122         self.init_logger()
123         self.funcs = {}
124         self.register_functions()
125
126     def init_logger(self):
127         self.logger = logging.getLogger("ApiLogger")
128         self.logger.setLevel(logging.INFO)
129         self.logger.addHandler(logging.handlers.RotatingFileHandler(self.get_log_name(), maxBytes=100000, backupCount=5))
130
131     def get_log_name(self):
132         return "/tmp/apilogfile.txt"
133
134     def register_functions(self):
135         self.register_function(self.noop)
136
137     def register_function(self, function, name = None):
138         if name is None:
139             name = function.__name__
140         self.funcs[name] = function
141
142     def call(self, source, method, *args):
143         """
144         Call the named method from the specified source with the
145         specified arguments.
146         """
147
148         if not method in self.funcs:
149             raise UnknownMethod(method)
150
151         return self.funcs[method](*args)
152
153     def handle(self, source, data):
154         """
155         Handle an XML-RPC or SOAP request from the specified source.
156         """
157
158         # Parse request into method name and arguments
159         try:
160             interface = xmlrpclib
161             (args, method) = xmlrpclib.loads(data)
162             methodresponse = True
163         except Exception, e:
164             if SOAPpy is not None:
165                 interface = SOAPpy
166                 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
167                 method = r._name
168                 args = r._aslist()
169                 # XXX Support named arguments
170             else:
171                 raise e
172
173         self.logger.debug("OP:" + str(method) + " from " + str(source))
174
175         try:
176             result = self.call(source, method, *args)
177         except xmlrpclib.Fault, fault:
178             self.logger.warning("FAULT: " + str(fault.faultCode) + " " + str(fault.faultString))
179             self.logger.info(traceback.format_exc())
180             # Handle expected faults
181             if interface == xmlrpclib:
182                 result = FaultWithTraceback(fault.faultCode, fault.faultString, sys.exc_info())
183                 methodresponse = None
184             elif interface == SOAPpy:
185                 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
186                 result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString))
187                 self.logger.debug
188         except:
189             self.logger.warning("EXCEPTION: " + str(sys.exc_info()[0]))
190             self.logger.info(traceback.format_exc())
191             result = UnhandledServerException(sys.exc_info())
192             methodresponse = None
193
194         # Return result
195         if interface == xmlrpclib:
196             if not isinstance(result, xmlrpclib.Fault):
197                 result = (result,)
198             data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
199         elif interface == SOAPpy:
200             data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
201
202         return data
203
204     def noop(self, value):
205         return value
206
207
208