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")
|
---|
393 |
|
---|