Django

Code

Ticket #552: soap.2.py

File soap.2.py, 15.8 kB (added by bethmcnany, 9 months ago)

soap.py - updated WSDL generator (see comments)

Line 
1 """
2 SOAP View for Django
3 Originally at: http://code.djangoproject.com/ticket/552
4
5 Updated 2009-06-16:
6 Rewrote wsdl generator
7 -Added convertTypeToXml function to keep DRY
8 -Only parses int or strings; user will want to add others depending on their data
9 beth.mcnany@yahoo.com
10 """
11 from __future__ import nested_scopes
12
13 import re
14 import sys
15 import thread
16 from types import *
17
18 # SOAPpy modules
19 from SOAPpy.version     import __version__
20 from SOAPpy.Parser      import parseSOAPRPC
21 from SOAPpy.Config      import Config
22 from SOAPpy.Types       import faultType, voidType, simplify
23 from SOAPpy.NS          import NS
24 from SOAPpy.SOAPBuilder import buildSOAP
25 from SOAPpy.Utilities   import debugHeader, debugFooter
26 from SOAPpy.Server      import SOAPServerBase, HeaderHandler, SOAPContext, MethodSig
27
28 try: from M2Crypto import SSL
29 except: pass
30
31 from django.http import HttpResponseServerError, HttpResponse
32
33 _contexts = dict()
34
35 class SimpleSOAPView(SOAPServerBase):
36     def __init__(self, encoding = 'UTF-8', config = Config, namespace = None):
37        
38         # Test the encoding, raising an exception if it's not known
39         if encoding != None:
40             ''.encode(encoding)
41        
42         self.namespace           = namespace
43         self.objmap              = {}
44         self.funcmap             = {}
45         self.encoding            = encoding
46         self.config              = config
47        
48         self.allow_reuse_address = 1
49
50     def dispatch(self, data):
51         global _contexts
52         try:
53             (r, header, body, attrs) = \
54                 parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
55             print (r, header, body, attrs)
56             method = r._name
57             args   = r._aslist()
58             kw     = r._asdict()
59            
60             if self.config.simplify_objects:
61                 args = simplify(args)
62                 kw = simplify(kw)
63            
64             if self.config.specialArgs:
65                 ordered_args = {}
66                 named_args   = {}
67                
68                 for (k,v) in  kw.items():
69                     if k[0]=="v":
70                         try:
71                             i = int(k[1:])
72                             ordered_args[i] = v
73                         except ValueError:
74                             named_args[str(k)] = v
75                        
76                     else:
77                         named_args[str(k)] = v
78                    
79             ns = r._ns
80                
81             if len(self.path) > 1 and not ns:
82                 ns = self.path.replace("/", ":")
83                 if ns[0] == ":": ns = ns[1:]
84                
85             # authorization method
86             a = None
87                
88             keylist = ordered_args.keys()
89             keylist.sort()
90                
91             # create list in proper order w/o names
92             tmp = map( lambda x: ordered_args[x], keylist)
93             ordered_args = tmp
94            
95             resp = ""
96            
97             # For fault messages
98             if ns:
99                 nsmethod = "%s:%s" % (ns, method)
100             else:
101                 nsmethod = method
102            
103             try:
104                 # First look for registered functions
105                 if self.funcmap.has_key(ns) and \
106                     self.funcmap[ns].has_key(method):
107                     f = self.funcmap[ns][method]
108                    
109                     # look for the authorization method
110                     if self.config.authMethod != None:
111                         authmethod = self.config.authMethod
112                         if self.funcmap.has_key(ns) and \
113                                self.funcmap[ns].has_key(authmethod):
114                             a = self.funcmap[ns][authmethod]
115                 else:
116                     # Now look at registered objects
117                     # Check for nested attributes. This works even if
118                     # there are none, because the split will return
119                     # [method]
120                     f = self.objmap[ns]
121                    
122                     # Look for the authorization method
123                     if self.config.authMethod != None:
124                         authmethod = self.config.authMethod
125                         if hasattr(f, authmethod):
126                             a = getattr(f, authmethod)
127                        
128                     # then continue looking for the method
129                     l = method.split(".")
130                     for i in l:
131                         f = getattr(f, i)
132             except Exception, e:
133                 info = sys.exc_info()
134                 resp = buildSOAP(faultType("%s:Client" % NS.ENV_T,
135                                            "Method Not Found",
136                                            "%s : %s %s %s" % (nsmethod,
137                                                               info[0],
138                                                               info[1],
139                                                               info[2])),
140                                  encoding = self.encoding,
141                                  config = self.config)
142                 del info
143                 #print e
144                 return resp
145             else:
146                 try:
147                     if header:
148                         x = HeaderHandler(header, attrs)
149                    
150                     fr = 1
151                    
152                     # call context book keeping
153                     # We're stuffing the method into the soapaction if there
154                     # isn't one, someday, we'll set that on the client
155                     # and it won't be necessary here
156                     # for now we're doing both
157                    
158                     if "SOAPAction".lower() not in self.headers.keys() or \
159                        self.headers["SOAPAction"] == "\"\"":
160                         self.headers["SOAPAction"] = method
161                        
162                     thread_id = thread.get_ident()
163                     _contexts[thread_id] = SOAPContext(header, body,
164                                                        attrs, data,
165                                                        None,
166                                                        self.headers,
167                                                        self.headers["SOAPAction"])
168                    
169                     # Do an authorization check
170                     if a != None:
171                         if not apply(a, (), {"_SOAPContext" :
172                                              _contexts[thread_id] }):
173                             raise faultType("%s:Server" % NS.ENV_T,
174                                             "Authorization failed.",
175                                             "%s" % nsmethod)
176                    
177                     # If it's wrapped, some special action may be needed
178                     if isinstance(f, MethodSig):
179                         c = None
180                    
181                         if f.context:  # retrieve context object
182                             c = _contexts[thread_id]
183                        
184                         if self.config.specialArgs:
185                             if c:
186                                 named_args["_SOAPContext"] = c
187                             fr = apply(f, ordered_args, named_args)
188                         elif f.keywords:
189                             # This is lame, but have to de-unicode
190                             # keywords
191                            
192                             strkw = {}
193                            
194                             for (k, v) in kw.items():
195                                 strkw[str(k)] = v
196                             if c:
197                                 strkw["_SOAPContext"] = c
198                             fr = apply(f, (), strkw)
199                         elif c:
200                             fr = apply(f, args, {'_SOAPContext':c})
201                         else:
202                             fr = apply(f, args, {})
203                        
204                     else:
205                         if self.config.specialArgs:
206                             fr = apply(f, ordered_args, named_args)
207                         else:
208                             fr = apply(f, args, {})
209                        
210                        
211                     if type(fr) == type(self) and \
212                         isinstance(fr, voidType):
213                         resp = buildSOAP(kw = {'%sResponse' % method: fr},
214                             encoding = self.encoding,
215                             config = self.config)
216                     else:
217                         resp = buildSOAP(kw =
218                             {'%sResponse' % method: {'Result': fr}},
219                             encoding = self.encoding,
220                             config = self.config)
221                    
222                     # Clean up _contexts
223                     if _contexts.has_key(thread_id):
224                         del _contexts[thread_id]
225                    
226                 except Exception, e:
227                     import traceback
228                     info = sys.exc_info()
229                    
230                     if isinstance(e, faultType):
231                         f = e
232                     else:
233                         f = faultType("%s:Server" % NS.ENV_T,
234                                       "Method Failed",
235                                       "%s" % nsmethod)
236                        
237                     if self.config.returnFaultInfo:
238                         f._setDetail("".join(traceback.format_exception(
239                             info[0], info[1], info[2])))
240                     elif not hasattr(f, 'detail'):
241                         f._setDetail("%s %s" % (info[0], info[1]))
242                     del info
243                     print e   
244                     resp = buildSOAP(f, encoding = self.encoding,
245                        config = self.config)
246                     return resp
247                 else:
248                     return resp
249         except faultType, e:
250             import traceback
251             info = sys.exc_info()
252             if self.config.returnFaultInfo:
253                 e._setDetail("".join(traceback.format_exception(
254                         info[0], info[1], info[2])))
255             elif not hasattr(e, 'detail'):
256                 e._setDetail("%s %s" % (info[0], info[1]))
257             del info
258
259             resp = buildSOAP(e, encoding = self.encoding,
260                 config = self.config)
261            
262     #---------------------------------------------------------------------
263     # 2009-06-16 - Rewrote the existing wsdl generator which didn't work
264     # This assumes that:
265     # - you define defaults for your function variables (how it determines i/o types)
266     # - you have defined functions that either return or take a variable
267     # - only one output returned
268     # beth.mcnany@yahoo.com
269     #---------------------------------------------------------------------
270     def wsdl(self, serviceName="MySoap", serviceLoc="http://localhost/soap/"):
271         """Generate a WSDL for the service"""
272         import inspect
273         from types import *
274         funcArray = []
275        
276         #funcmap is such that funcmap.values()[0][example]() will call a function
277         methods = self.funcmap.values()[0]
278         for name, function in methods.iteritems():
279             output = function() #need defaults!
280             #{name:"function", input:{name:type, etc}, output:type}
281             temp = {}
282             temp['name'] = name
283             temp['output'] = self.convertTypeToXml(type(output))
284             return output, temp['output']
285             args = inspect.getargspec(function)
286             if len(args.args) > 0:
287                 inputArr = {}
288                 #this is where the assumption about default inputs comes in
289                 for arg, default in zip(args.args, args.defaults):
290                     inputArr[arg] = self.convertTypeToXml(type(default))
291                 temp['input'] = inputArr
292             else:
293                 temp['input'] = {}
294             funcArray.append(temp)
295            
296         #header + definitions tag
297         wsdl = """<?xml version="1.0" encoding="UTF-8"?>
298 <definitions name="%(name)s"
299              targetNamespace="%(url)s"
300              xmlns="http://schemas.xmlsoap.org/wsdl/"
301              xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
302              xmlns:tns="%(url)s"
303              xmlns:xsd="http://www.w3.org/2001/XMLSchema">"""
304        
305         #message tags
306         for func in funcArray:
307             if len(func['input']) > 0:
308                 wsdl += '<message name="%sRequest">\n' % func['name']
309                 for k,v in func['input'].iteritems():
310                     wsdl += '<part name="%s" type="xsd:%s" />\n' % (k, v)
311                 wsdl += '</message>\n'
312             if func['output'] != "null":
313                 wsdl += '<message name="%sResponse">\n' % func['name']
314                 wsdl += '<part name="return" type="xsd:%s" />\n' % func['output']
315                 wsdl += '</message>\n'
316
317         #portType tag
318         wsdl += '<portType name="%(name)s_PortType">\n'
319         for func in funcArray:
320             wsdl += '<operation name="%s">\n' % func['name']
321             if len(func['input']) > 0:
322                 wsdl += '<input message="tns:%sRequest" />\n' % func['name']
323             if func['output'] != "null":
324                 wsdl += '<output message="tns:%sResponse" />\n' % func['name']
325             wsdl += '</operation>\n'
326         wsdl += '</portType>'
327        
328         #binding tag
329         wsdl += """
330   <binding name="%(name)s_Binding" type="tns:%(name)s_PortType">
331     <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http/" />\n"""
332         for func in funcArray:
333             wsdl += '<operation name="%s">' % func['name']
334             if len(func['input']) > 0:
335                 wsdl += """               
336       <input>
337         <soap:body use="literal" />
338       </input>"""
339             if func['output'] != "null":
340                 wsdl += """
341       <output>
342         <soap:body use="literal" />
343       </output>"""
344             wsdl += '\n</operation>\n'
345         wsdl += '</binding>'
346        
347         #services tag
348         wsdl += """
349   <service name="%(name)s">
350     <port binding="tns:%(name)s_Binding" name="%(name)s_Port">
351       <soap:address location="%(url)s" />
352     </port>
353   </service>
354 </definitions>"""        
355         return wsdl % {'name':serviceName, 'url':serviceLoc}
356
357     def convertTypeToXml(self, type):
358         """Converts a Python type to XML type"""
359         #TODO: add more types
360         tStr = "null"
361         if type == IntType:        tStr = "int"
362         elif type == BooleanType:  tStr = "boolean"
363         elif type == StringType:   tStr = "string"
364         return tStr
365
366     def __call__(self, request, path=''):
367         """ SimpleXMLRPCView is callable so it can be installed as a view.
368
369             Django calls it with 'request', which is a HttpRequest           
370         """
371         self.path = path
372         self.headers = request.META # compatible?
373        
374         if request.META['REQUEST_METHOD'] == 'GET':
375             if request.META['QUERY_STRING'] == 'wsdl':
376                 wsdl = self.wsdl()
377                 return HttpResponse(wsdl, mimetype='text/xml')
378             else:
379                 return HttpResponseServerError('Use /?wsdl to get WSDL.')
380         elif request.META['REQUEST_METHOD'] != 'POST':
381             return HttpResponseServerError('Non POST methods not allowed.')
382        
383         try:
384             response = self.dispatch(request.raw_post_data)
385             print response
386             print self.objmap, self.funcmap
387         except Exception, e:
388             # internal error, report as HTTP server error
389             return HttpResponseServerError('internal error')
390         else:
391             # got a valid XML RPC response
392             return HttpResponse(response, mimetype="text/xml")