Ticket #552: soap.2.py

File soap.2.py, 15.8 KB (added by bethmcnany, 15 years ago)

soap.py - updated WSDL generator (see comments)

Line 
1"""
2SOAP View for Django
3Originally at: http://code.djangoproject.com/ticket/552
4
5Updated 2009-06-16:
6Rewrote 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
9beth.mcnany@yahoo.com
10"""
11from __future__ import nested_scopes
12
13import re
14import sys
15import thread
16from types import *
17
18# SOAPpy modules
19from SOAPpy.version import __version__
20from SOAPpy.Parser import parseSOAPRPC
21from SOAPpy.Config import Config
22from SOAPpy.Types import faultType, voidType, simplify
23from SOAPpy.NS import NS
24from SOAPpy.SOAPBuilder import buildSOAP
25from SOAPpy.Utilities import debugHeader, debugFooter
26from SOAPpy.Server import SOAPServerBase, HeaderHandler, SOAPContext, MethodSig
27
28try: from M2Crypto import SSL
29except: pass
30
31from django.http import HttpResponseServerError, HttpResponse
32
33_contexts = dict()
34
35class 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")
393
Back to Top