1 | #!/usr/bin/python |
---|
2 | # -*- coding: utf-8 -*- |
---|
3 | # This program is free software; you can redistribute it and/or modify |
---|
4 | # it under the terms of the GNU Lesser General Public License as published by the |
---|
5 | # Free Software Foundation; either version 3, or (at your option) any later |
---|
6 | # version. |
---|
7 | # |
---|
8 | # This program is distributed in the hope that it will be useful, but |
---|
9 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
---|
10 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
---|
11 | # for more details. |
---|
12 | |
---|
13 | """Pythonic simple SOAP Server implementation""" |
---|
14 | |
---|
15 | |
---|
16 | from __future__ import unicode_literals |
---|
17 | import sys |
---|
18 | |
---|
19 | if sys.version_info[0] < 3: |
---|
20 | is_py2 = True |
---|
21 | else: |
---|
22 | is_py2 = False |
---|
23 | unicode = str |
---|
24 | |
---|
25 | |
---|
26 | import datetime |
---|
27 | import sys |
---|
28 | import logging |
---|
29 | import warnings |
---|
30 | import re |
---|
31 | import traceback |
---|
32 | try: |
---|
33 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer |
---|
34 | except ImportError: |
---|
35 | from http.server import BaseHTTPRequestHandler, HTTPServer |
---|
36 | |
---|
37 | from . import __author__, __copyright__, __license__, __version__ |
---|
38 | from .simplexml import SimpleXMLElement, TYPE_MAP, Date, Decimal |
---|
39 | |
---|
40 | log = logging.getLogger(__name__) |
---|
41 | |
---|
42 | # Deprecated? |
---|
43 | NS_RX = re.compile(r'xmlns:(\w+)="(.+?)"') |
---|
44 | |
---|
45 | |
---|
46 | class SoapFault(Exception): |
---|
47 | def __init__(self, faultcode=None, faultstring=None, detail=None): |
---|
48 | self.faultcode = faultcode or self.__class__.__name__ |
---|
49 | self.faultstring = faultstring or '' |
---|
50 | self.detail = detail |
---|
51 | |
---|
52 | |
---|
53 | class SoapDispatcher(object): |
---|
54 | """Simple Dispatcher for SOAP Server""" |
---|
55 | |
---|
56 | def __init__(self, name, documentation='', action='', location='', |
---|
57 | namespace=None, prefix=False, |
---|
58 | soap_uri="http://schemas.xmlsoap.org/soap/envelope/", |
---|
59 | soap_ns='soap', |
---|
60 | namespaces={}, |
---|
61 | pretty=False, |
---|
62 | debug=False, |
---|
63 | **kwargs): |
---|
64 | """ |
---|
65 | :param namespace: Target namespace; xmlns=targetNamespace |
---|
66 | :param prefix: Prefix for target namespace; xmlns:prefix=targetNamespace |
---|
67 | :param namespaces: Specify additional namespaces; example: {'external': 'http://external.mt.moboperator'} |
---|
68 | :param pretty: Prettifies generated xmls |
---|
69 | :param debug: Use to add tracebacks in generated xmls. |
---|
70 | |
---|
71 | Multiple namespaces |
---|
72 | =================== |
---|
73 | |
---|
74 | It is possible to support multiple namespaces. |
---|
75 | You need to specify additional namespaces by passing `namespace` parameter. |
---|
76 | |
---|
77 | >>> dispatcher = SoapDispatcher( |
---|
78 | ... name = "MTClientWS", |
---|
79 | ... location = "http://localhost:8008/ws/MTClientWS", |
---|
80 | ... action = 'http://localhost:8008/ws/MTClientWS', # SOAPAction |
---|
81 | ... namespace = "http://external.mt.moboperator", prefix="external", |
---|
82 | ... documentation = 'moboperator MTClientWS', |
---|
83 | ... namespaces = { |
---|
84 | ... 'external': 'http://external.mt.moboperator', |
---|
85 | ... 'model': 'http://model.common.mt.moboperator' |
---|
86 | ... }, |
---|
87 | ... ns = True) |
---|
88 | |
---|
89 | Now the registered method must return node names with namespaces' prefixes. |
---|
90 | |
---|
91 | >>> def _multi_ns_func(self, serviceMsisdn): |
---|
92 | ... ret = { |
---|
93 | ... 'external:activateSubscriptionsReturn': [ |
---|
94 | ... {'model:code': '0'}, |
---|
95 | ... {'model:description': 'desc'}, |
---|
96 | ... ]} |
---|
97 | ... return ret |
---|
98 | |
---|
99 | Our prefixes will be changed to those used by the client. |
---|
100 | """ |
---|
101 | self.methods = {} |
---|
102 | self.name = name |
---|
103 | self.documentation = documentation |
---|
104 | self.action = action # base SoapAction |
---|
105 | self.location = location |
---|
106 | self.namespace = namespace # targetNamespace |
---|
107 | self.prefix = prefix |
---|
108 | self.soap_ns = soap_ns |
---|
109 | self.soap_uri = soap_uri |
---|
110 | self.namespaces = namespaces |
---|
111 | self.pretty = pretty |
---|
112 | self.debug = debug |
---|
113 | |
---|
114 | @staticmethod |
---|
115 | def _extra_namespaces(xml, ns): |
---|
116 | """Extends xml with extra namespaces. |
---|
117 | :param ns: dict with namespaceUrl:prefix pairs |
---|
118 | :param xml: XML node to modify |
---|
119 | """ |
---|
120 | if ns: |
---|
121 | _tpl = 'xmlns:%s="%s"' |
---|
122 | _ns_str = " ".join([_tpl % (prefix, uri) for uri, prefix in ns.items() if uri not in xml]) |
---|
123 | xml = xml.replace('/>', ' ' + _ns_str + '/>') |
---|
124 | return xml |
---|
125 | |
---|
126 | def register_function(self, name, fn, returns=None, args=None, doc=None, response_element_name=None): |
---|
127 | self.methods[name] = fn, returns, args, doc or getattr(fn, "__doc__", ""), response_element_name or '%sResponse' % name |
---|
128 | |
---|
129 | def response_element_name(self, method): |
---|
130 | return self.methods[method][4] |
---|
131 | |
---|
132 | def dispatch(self, xml, action=None, fault=None): |
---|
133 | """Receive and process SOAP call, returns the xml""" |
---|
134 | # a dict can be sent in fault to expose it to the caller |
---|
135 | # default values: |
---|
136 | prefix = self.prefix |
---|
137 | ret = None |
---|
138 | if fault is None: |
---|
139 | fault = {} |
---|
140 | soap_ns, soap_uri = self.soap_ns, self.soap_uri |
---|
141 | soap_fault_code = 'VersionMismatch' |
---|
142 | name = None |
---|
143 | |
---|
144 | # namespaces = [('model', 'http://model.common.mt.moboperator'), ('external', 'http://external.mt.moboperator')] |
---|
145 | _ns_reversed = dict(((v, k) for k, v in self.namespaces.items())) # Switch keys-values |
---|
146 | # _ns_reversed = {'http://external.mt.moboperator': 'external', 'http://model.common.mt.moboperator': 'model'} |
---|
147 | |
---|
148 | try: |
---|
149 | request = SimpleXMLElement(xml, namespace=self.namespace) |
---|
150 | |
---|
151 | # detect soap prefix and uri (xmlns attributes of Envelope) |
---|
152 | for k, v in request[:]: |
---|
153 | if v in ("http://schemas.xmlsoap.org/soap/envelope/", |
---|
154 | "http://www.w3.org/2003/05/soap-env", |
---|
155 | "http://www.w3.org/2003/05/soap-envelope",): |
---|
156 | soap_ns = request.attributes()[k].localName |
---|
157 | soap_uri = request.attributes()[k].value |
---|
158 | |
---|
159 | # If the value from attributes on Envelope is in additional namespaces |
---|
160 | elif v in self.namespaces.values(): |
---|
161 | _ns = request.attributes()[k].localName |
---|
162 | _uri = request.attributes()[k].value |
---|
163 | _ns_reversed[_uri] = _ns # update with received alias |
---|
164 | # Now we change 'external' and 'model' to the received forms i.e. 'ext' and 'mod' |
---|
165 | # After that we know how the client has prefixed additional namespaces |
---|
166 | |
---|
167 | decoded_xml = xml if is_py2 else xml.decode('utf8') |
---|
168 | ns = NS_RX.findall(decoded_xml) |
---|
169 | for k, v in ns: |
---|
170 | if v in self.namespaces.values(): |
---|
171 | _ns_reversed[v] = k |
---|
172 | |
---|
173 | soap_fault_code = 'Client' |
---|
174 | |
---|
175 | # parse request message and get local method |
---|
176 | method = request('Body', ns=soap_uri).children()(0) |
---|
177 | if action: |
---|
178 | # method name = action |
---|
179 | name = action[len(self.action)+1:-1] |
---|
180 | prefix = self.prefix |
---|
181 | if not action or not name: |
---|
182 | # method name = input message name |
---|
183 | name = method.get_local_name() |
---|
184 | prefix = method.get_prefix() |
---|
185 | |
---|
186 | log.debug('dispatch method: %s', name) |
---|
187 | function, returns_types, args_types, doc, response_element_name = self.methods[name] |
---|
188 | log.debug('returns_types %s', returns_types) |
---|
189 | |
---|
190 | # de-serialize parameters (if type definitions given) |
---|
191 | if args_types: |
---|
192 | args = method.children().unmarshall(args_types) |
---|
193 | elif args_types is None: |
---|
194 | args = {'request': method} # send raw request |
---|
195 | else: |
---|
196 | args = {} # no parameters |
---|
197 | |
---|
198 | soap_fault_code = 'Server' |
---|
199 | # execute function |
---|
200 | ret = function(**args) |
---|
201 | log.debug('dispathed method returns: %s', ret) |
---|
202 | |
---|
203 | except SoapFault as e: |
---|
204 | fault.update({ |
---|
205 | 'faultcode': "%s.%s" % (soap_fault_code, e.faultcode), |
---|
206 | 'faultstring': e.faultstring, |
---|
207 | 'detail': e.detail |
---|
208 | }) |
---|
209 | |
---|
210 | except Exception: # This shouldn't be one huge try/except |
---|
211 | import sys |
---|
212 | etype, evalue, etb = sys.exc_info() |
---|
213 | log.error(traceback.format_exc()) |
---|
214 | if self.debug: |
---|
215 | detail = u''.join(traceback.format_exception(etype, evalue, etb)) |
---|
216 | detail += u'\n\nXML REQUEST\n\n' + xml.decode('UTF-8') |
---|
217 | else: |
---|
218 | detail = None |
---|
219 | fault.update({'faultcode': "%s.%s" % (soap_fault_code, etype.__name__), |
---|
220 | 'faultstring': evalue, |
---|
221 | 'detail': detail}) |
---|
222 | |
---|
223 | # build response message |
---|
224 | if not prefix: |
---|
225 | xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s"/>""" |
---|
226 | else: |
---|
227 | xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s" |
---|
228 | xmlns:%(prefix)s="%(namespace)s"/>""" |
---|
229 | |
---|
230 | xml %= { # a %= {} is a shortcut for a = a % {} |
---|
231 | 'namespace': self.namespace, |
---|
232 | 'prefix': prefix, |
---|
233 | 'soap_ns': soap_ns, |
---|
234 | 'soap_uri': soap_uri |
---|
235 | } |
---|
236 | |
---|
237 | # Now we add extra namespaces |
---|
238 | xml = SoapDispatcher._extra_namespaces(xml, _ns_reversed) |
---|
239 | |
---|
240 | # Change our namespace alias to that given by the client. |
---|
241 | # We put [('model', 'http://model.common.mt.moboperator'), ('external', 'http://external.mt.moboperator')] |
---|
242 | # mix it with {'http://external.mt.moboperator': 'ext', 'http://model.common.mt.moboperator': 'mod'} |
---|
243 | mapping = dict(((k, _ns_reversed[v]) for k, v in self.namespaces.items())) # Switch keys-values and change value |
---|
244 | # and get {'model': u'mod', 'external': u'ext'} |
---|
245 | |
---|
246 | response = SimpleXMLElement(xml, |
---|
247 | namespace=self.namespace, |
---|
248 | namespaces_map=mapping, |
---|
249 | prefix=prefix) |
---|
250 | |
---|
251 | response['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance" |
---|
252 | response['xmlns:xsd'] = "http://www.w3.org/2001/XMLSchema" |
---|
253 | |
---|
254 | body = response.add_child("%s:Body" % soap_ns, ns=False) |
---|
255 | |
---|
256 | if fault: |
---|
257 | # generate a Soap Fault (with the python exception) |
---|
258 | body.marshall("%s:Fault" % soap_ns, fault, ns=False) |
---|
259 | else: |
---|
260 | # return normal value |
---|
261 | res = body.add_child(self.response_element_name(name), ns=self.namespace) |
---|
262 | if not prefix: |
---|
263 | res['xmlns'] = self.namespace # add target namespace |
---|
264 | |
---|
265 | # serialize returned values (response) if type definition available |
---|
266 | if returns_types: |
---|
267 | # TODO: full sanity check of type structure (recursive) |
---|
268 | complex_type = isinstance(ret, dict) |
---|
269 | if complex_type: |
---|
270 | # check if type mapping correlates with return value |
---|
271 | types_ok = all([k in returns_types for k in ret.keys()]) |
---|
272 | if not types_ok: |
---|
273 | warnings.warn("Return value doesn't match type structure: " |
---|
274 | "%s vs %s" % (str(returns_types), str(ret))) |
---|
275 | if not complex_type or not types_ok: |
---|
276 | # backward compatibility for scalar and simple types |
---|
277 | res.marshall(list(returns_types.keys())[0], ret, ) |
---|
278 | else: |
---|
279 | # new style for complex classes |
---|
280 | for k, v in ret.items(): |
---|
281 | res.marshall(k, v) |
---|
282 | elif returns_types is None: |
---|
283 | # merge xmlelement returned |
---|
284 | res.import_node(ret) |
---|
285 | elif returns_types == {}: |
---|
286 | log.warning('Given returns_types is an empty dict.') |
---|
287 | |
---|
288 | return response.as_xml(pretty=self.pretty) |
---|
289 | |
---|
290 | # Introspection functions: |
---|
291 | |
---|
292 | def list_methods(self): |
---|
293 | """Return a list of aregistered operations""" |
---|
294 | return [(method, doc) for method, (function, returns, args, doc, response_element_name) in self.methods.items()] |
---|
295 | |
---|
296 | def help(self, method=None): |
---|
297 | """Generate sample request and response messages""" |
---|
298 | (function, returns, args, doc, response_element_name) = self.methods[method] |
---|
299 | xml = """ |
---|
300 | <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> |
---|
301 | <soap:Body><%(method)s xmlns="%(namespace)s"/></soap:Body> |
---|
302 | </soap:Envelope>""" % {'method': method, 'namespace': self.namespace} |
---|
303 | request = SimpleXMLElement(xml, namespace=self.namespace, prefix=self.prefix) |
---|
304 | if args: |
---|
305 | items = args.items() |
---|
306 | elif args is None: |
---|
307 | items = [('value', None)] |
---|
308 | else: |
---|
309 | items = [] |
---|
310 | for k, v in items: |
---|
311 | request(method).marshall(k, v, add_comments=True, ns=False) |
---|
312 | |
---|
313 | xml = """ |
---|
314 | <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> |
---|
315 | <soap:Body><%(response_element_name)s xmlns="%(namespace)s"/></soap:Body> |
---|
316 | </soap:Envelope>""" % {'response_element_name': response_element_name, 'namespace': self.namespace} |
---|
317 | response = SimpleXMLElement(xml, namespace=self.namespace, prefix=self.prefix) |
---|
318 | if returns: |
---|
319 | items = returns.items() |
---|
320 | elif args is None: |
---|
321 | items = [('value', None)] |
---|
322 | else: |
---|
323 | items = [] |
---|
324 | for k, v in items: |
---|
325 | response(response_element_name).marshall(k, v, add_comments=True, ns=False) |
---|
326 | |
---|
327 | return request.as_xml(pretty=True), response.as_xml(pretty=True), doc |
---|
328 | |
---|
329 | def wsdl(self): |
---|
330 | """Generate Web Service Description v1.1""" |
---|
331 | xml = """<?xml version="1.0"?> |
---|
332 | <wsdl:definitions name="%(name)s" |
---|
333 | targetNamespace="%(namespace)s" |
---|
334 | xmlns:tns="%(namespace)s" |
---|
335 | xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" |
---|
336 | xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" |
---|
337 | xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
---|
338 | <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">%(documentation)s</wsdl:documentation> |
---|
339 | |
---|
340 | <wsdl:types> |
---|
341 | <xsd:schema targetNamespace="%(namespace)s" |
---|
342 | elementFormDefault="qualified" |
---|
343 | xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
---|
344 | </xsd:schema> |
---|
345 | </wsdl:types> |
---|
346 | |
---|
347 | </wsdl:definitions> |
---|
348 | """ % {'namespace': self.namespace, 'name': self.name, 'documentation': self.documentation} |
---|
349 | wsdl = SimpleXMLElement(xml) |
---|
350 | |
---|
351 | for method, (function, returns, args, doc, response_element_name) in self.methods.items(): |
---|
352 | # create elements: |
---|
353 | |
---|
354 | def parse_element(name, values, array=False, complex=False): |
---|
355 | if not complex: |
---|
356 | element = wsdl('wsdl:types')('xsd:schema').add_child('xsd:element') |
---|
357 | complex = element.add_child("xsd:complexType") |
---|
358 | else: |
---|
359 | complex = wsdl('wsdl:types')('xsd:schema').add_child('xsd:complexType') |
---|
360 | element = complex |
---|
361 | element['name'] = name |
---|
362 | if values: |
---|
363 | items = values |
---|
364 | elif values is None: |
---|
365 | items = [('value', None)] |
---|
366 | else: |
---|
367 | items = [] |
---|
368 | if not array and items: |
---|
369 | all = complex.add_child("xsd:all") |
---|
370 | elif items: |
---|
371 | all = complex.add_child("xsd:sequence") |
---|
372 | for k, v in items: |
---|
373 | e = all.add_child("xsd:element") |
---|
374 | e['name'] = k |
---|
375 | if array: |
---|
376 | e[:] = {'minOccurs': "0", 'maxOccurs': "unbounded"} |
---|
377 | if v is None: |
---|
378 | t = 'xsd:anyType' |
---|
379 | elif type(v) == list: |
---|
380 | n = "ArrayOf%s%s" % (name, k) |
---|
381 | l = [] |
---|
382 | for d in v: |
---|
383 | l.extend(d.items()) |
---|
384 | parse_element(n, l, array=True, complex=True) |
---|
385 | t = "tns:%s" % n |
---|
386 | elif type(v) == dict: |
---|
387 | n = "%s%s" % (name, k) |
---|
388 | parse_element(n, v.items(), complex=True) |
---|
389 | t = "tns:%s" % n |
---|
390 | elif v in TYPE_MAP: |
---|
391 | t = 'xsd:%s' % TYPE_MAP[v] |
---|
392 | else: |
---|
393 | raise TypeError("unknown type %s for marshalling" % str(v)) |
---|
394 | e.add_attribute('type', t) |
---|
395 | |
---|
396 | parse_element("%s" % method, args and args.items()) |
---|
397 | parse_element(response_element_name, returns and returns.items()) |
---|
398 | |
---|
399 | # create messages: |
---|
400 | for m, e in ('Input', method), ('Output', response_element_name): |
---|
401 | message = wsdl.add_child('wsdl:message') |
---|
402 | message['name'] = "%s%s" % (method, m) |
---|
403 | part = message.add_child("wsdl:part") |
---|
404 | part[:] = {'name': 'parameters', |
---|
405 | 'element': 'tns:%s' % e} |
---|
406 | |
---|
407 | # create ports |
---|
408 | portType = wsdl.add_child('wsdl:portType') |
---|
409 | portType['name'] = "%sPortType" % self.name |
---|
410 | for method, (function, returns, args, doc, response_element_name) in self.methods.items(): |
---|
411 | op = portType.add_child('wsdl:operation') |
---|
412 | op['name'] = method |
---|
413 | if doc: |
---|
414 | op.add_child("wsdl:documentation", doc) |
---|
415 | input = op.add_child("wsdl:input") |
---|
416 | input['message'] = "tns:%sInput" % method |
---|
417 | output = op.add_child("wsdl:output") |
---|
418 | output['message'] = "tns:%sOutput" % method |
---|
419 | |
---|
420 | # create bindings |
---|
421 | binding = wsdl.add_child('wsdl:binding') |
---|
422 | binding['name'] = "%sBinding" % self.name |
---|
423 | binding['type'] = "tns:%sPortType" % self.name |
---|
424 | soapbinding = binding.add_child('soap:binding') |
---|
425 | soapbinding['style'] = "document" |
---|
426 | soapbinding['transport'] = "http://schemas.xmlsoap.org/soap/http" |
---|
427 | for method in self.methods.keys(): |
---|
428 | op = binding.add_child('wsdl:operation') |
---|
429 | op['name'] = method |
---|
430 | soapop = op.add_child('soap:operation') |
---|
431 | soapop['soapAction'] = self.action + method |
---|
432 | soapop['style'] = 'document' |
---|
433 | input = op.add_child("wsdl:input") |
---|
434 | ##input.add_attribute('name', "%sInput" % method) |
---|
435 | soapbody = input.add_child("soap:body") |
---|
436 | soapbody["use"] = "literal" |
---|
437 | output = op.add_child("wsdl:output") |
---|
438 | ##output.add_attribute('name', "%sOutput" % method) |
---|
439 | soapbody = output.add_child("soap:body") |
---|
440 | soapbody["use"] = "literal" |
---|
441 | |
---|
442 | service = wsdl.add_child('wsdl:service') |
---|
443 | service["name"] = "%sService" % self.name |
---|
444 | service.add_child('wsdl:documentation', text=self.documentation) |
---|
445 | port = service.add_child('wsdl:port') |
---|
446 | port["name"] = "%s" % self.name |
---|
447 | port["binding"] = "tns:%sBinding" % self.name |
---|
448 | soapaddress = port.add_child('soap:address') |
---|
449 | soapaddress["location"] = self.location |
---|
450 | return wsdl.as_xml(pretty=True) |
---|
451 | |
---|
452 | |
---|
453 | class SOAPHandler(BaseHTTPRequestHandler): |
---|
454 | |
---|
455 | def do_GET(self): |
---|
456 | """User viewable help information and wsdl""" |
---|
457 | args = self.path[1:].split("?") |
---|
458 | if self.path != "/" and args[0] not in self.server.dispatcher.methods.keys(): |
---|
459 | self.send_error(404, "Method not found: %s" % args[0]) |
---|
460 | else: |
---|
461 | if self.path == "/": |
---|
462 | # return wsdl if no method supplied |
---|
463 | response = self.server.dispatcher.wsdl() |
---|
464 | else: |
---|
465 | # return supplied method help (?request or ?response messages) |
---|
466 | req, res, doc = self.server.dispatcher.help(args[0]) |
---|
467 | if len(args) == 1 or args[1] == "request": |
---|
468 | response = req |
---|
469 | else: |
---|
470 | response = res |
---|
471 | self.send_response(200) |
---|
472 | self.send_header("Content-type", "text/xml") |
---|
473 | self.end_headers() |
---|
474 | self.wfile.write(response) |
---|
475 | |
---|
476 | def do_POST(self): |
---|
477 | """SOAP POST gateway""" |
---|
478 | request = self.rfile.read(int(self.headers.get('content-length'))) |
---|
479 | # convert xml request to unicode (according to request headers) |
---|
480 | if sys.version < '3': |
---|
481 | encoding = self.headers.getparam("charset") |
---|
482 | else: |
---|
483 | encoding = self.headers.get_param("charset") |
---|
484 | request = request.decode(encoding) |
---|
485 | fault = {} |
---|
486 | # execute the method |
---|
487 | response = self.server.dispatcher.dispatch(request, fault=fault) |
---|
488 | # check if fault dict was completed (faultcode, faultstring, detail) |
---|
489 | if fault: |
---|
490 | self.send_response(500) |
---|
491 | else: |
---|
492 | self.send_response(200) |
---|
493 | self.send_header("Content-type", "text/xml") |
---|
494 | self.end_headers() |
---|
495 | self.wfile.write(response) |
---|
496 | |
---|
497 | |
---|
498 | class WSGISOAPHandler(object): |
---|
499 | |
---|
500 | def __init__(self, dispatcher): |
---|
501 | self.dispatcher = dispatcher |
---|
502 | |
---|
503 | def __call__(self, environ, start_response): |
---|
504 | return self.handler(environ, start_response) |
---|
505 | |
---|
506 | def handler(self, environ, start_response): |
---|
507 | if environ['REQUEST_METHOD'] == 'GET': |
---|
508 | return self.do_get(environ, start_response) |
---|
509 | elif environ['REQUEST_METHOD'] == 'POST': |
---|
510 | return self.do_post(environ, start_response) |
---|
511 | else: |
---|
512 | start_response('405 Method not allowed', [('Content-Type', 'text/plain')]) |
---|
513 | return ['Method not allowed'] |
---|
514 | |
---|
515 | def do_get(self, environ, start_response): |
---|
516 | path = environ.get('PATH_INFO').lstrip('/') |
---|
517 | query = environ.get('QUERY_STRING') |
---|
518 | if path != "" and path not in self.dispatcher.methods.keys(): |
---|
519 | start_response('404 Not Found', [('Content-Type', 'text/plain')]) |
---|
520 | return ["Method not found: %s" % path] |
---|
521 | elif path == "": |
---|
522 | # return wsdl if no method supplied |
---|
523 | response = self.dispatcher.wsdl() |
---|
524 | else: |
---|
525 | # return supplied method help (?request or ?response messages) |
---|
526 | req, res, doc = self.dispatcher.help(path) |
---|
527 | if len(query) == 0 or query == "request": |
---|
528 | response = req |
---|
529 | else: |
---|
530 | response = res |
---|
531 | start_response('200 OK', [('Content-Type', 'text/xml'), ('Content-Length', str(len(response)))]) |
---|
532 | return [response] |
---|
533 | |
---|
534 | def do_post(self, environ, start_response): |
---|
535 | length = int(environ['CONTENT_LENGTH']) |
---|
536 | request = environ['wsgi.input'].read(length) |
---|
537 | response = self.dispatcher.dispatch(request) |
---|
538 | start_response('200 OK', [('Content-Type', 'text/xml'), ('Content-Length', str(len(response)))]) |
---|
539 | return [response] |
---|
540 | |
---|
541 | |
---|
542 | if __name__ == "__main__": |
---|
543 | |
---|
544 | dispatcher = SoapDispatcher( |
---|
545 | name="PySimpleSoapSample", |
---|
546 | location="http://localhost:8008/", |
---|
547 | action='http://localhost:8008/', # SOAPAction |
---|
548 | namespace="http://example.com/pysimplesoapsamle/", prefix="ns0", |
---|
549 | documentation='Example soap service using PySimpleSoap', |
---|
550 | trace=True, debug=True, |
---|
551 | ns=True) |
---|
552 | |
---|
553 | def adder(p, c, dt=None): |
---|
554 | """Add several values""" |
---|
555 | dt = dt + datetime.timedelta(365) |
---|
556 | return {'ab': p['a'] + p['b'], 'dd': c[0]['d'] + c[1]['d'], 'dt': dt} |
---|
557 | |
---|
558 | def dummy(in0): |
---|
559 | """Just return input""" |
---|
560 | return in0 |
---|
561 | |
---|
562 | def echo(request): |
---|
563 | """Copy request->response (generic, any type)""" |
---|
564 | return request.value |
---|
565 | |
---|
566 | dispatcher.register_function( |
---|
567 | 'Adder', adder, |
---|
568 | returns={'AddResult': {'ab': int, 'dd': unicode, 'dt': datetime.date}}, |
---|
569 | args={'p': {'a': int, 'b': int}, 'dt': Date, 'c': [{'d': Decimal}]} |
---|
570 | ) |
---|
571 | |
---|
572 | dispatcher.register_function( |
---|
573 | 'Dummy', dummy, |
---|
574 | returns={'out0': str}, |
---|
575 | args={'in0': str} |
---|
576 | ) |
---|
577 | |
---|
578 | dispatcher.register_function('Echo', echo) |
---|
579 | |
---|
580 | if '--local' in sys.argv: |
---|
581 | |
---|
582 | wsdl = dispatcher.wsdl() |
---|
583 | |
---|
584 | for method, doc in dispatcher.list_methods(): |
---|
585 | request, response, doc = dispatcher.help(method) |
---|
586 | |
---|
587 | if '--serve' in sys.argv: |
---|
588 | log.info("Starting server...") |
---|
589 | httpd = HTTPServer(("", 8008), SOAPHandler) |
---|
590 | httpd.dispatcher = dispatcher |
---|
591 | httpd.serve_forever() |
---|
592 | |
---|
593 | if '--wsgi-serve' in sys.argv: |
---|
594 | log.info("Starting wsgi server...") |
---|
595 | from wsgiref.simple_server import make_server |
---|
596 | application = WSGISOAPHandler(dispatcher) |
---|
597 | wsgid = make_server('', 8008, application) |
---|
598 | wsgid.serve_forever() |
---|
599 | |
---|
600 | if '--consume' in sys.argv: |
---|
601 | from .client import SoapClient |
---|
602 | client = SoapClient( |
---|
603 | location="http://localhost:8008/", |
---|
604 | action='http://localhost:8008/', # SOAPAction |
---|
605 | namespace="http://example.com/sample.wsdl", |
---|
606 | soap_ns='soap', |
---|
607 | trace=True, |
---|
608 | ns="ns0", |
---|
609 | ) |
---|
610 | p = {'a': 1, 'b': 2} |
---|
611 | c = [{'d': '1.20'}, {'d': '2.01'}] |
---|
612 | response = client.Adder(p=p, dt='2010-07-24', c=c) |
---|
613 | result = response.AddResult |
---|
614 | log.info(int(result.ab)) |
---|
615 | log.info(str(result.dd)) |
---|
616 | |
---|
617 | if '--consume-wsdl' in sys.argv: |
---|
618 | from .client import SoapClient |
---|
619 | client = SoapClient( |
---|
620 | wsdl="http://localhost:8008/", |
---|
621 | ) |
---|
622 | p = {'a': 1, 'b': 2} |
---|
623 | c = [{'d': '1.20'}, {'d': '2.01'}] |
---|
624 | dt = datetime.date.today() |
---|
625 | response = client.Adder(p=p, dt=dt, c=c) |
---|
626 | result = response['AddResult'] |
---|
627 | log.info(int(result['ab'])) |
---|
628 | log.info(str(result['dd'])) |
---|
629 | |
---|