0
|
1 |
# minisoap - minimal SOAP client
|
|
2 |
#
|
|
3 |
# Copyright (c) 2011, 2013 Radek Brich <radek.brich@devl.cz>
|
|
4 |
#
|
|
5 |
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6 |
# of this software and associated documentation files (the "Software"), to deal
|
|
7 |
# in the Software without restriction, including without limitation the rights
|
|
8 |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9 |
# copies of the Software, and to permit persons to whom the Software is
|
|
10 |
# furnished to do so, subject to the following conditions:
|
|
11 |
#
|
|
12 |
# The above copyright notice and this permission notice shall be included in
|
|
13 |
# all copies or substantial portions of the Software.
|
|
14 |
#
|
|
15 |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16 |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17 |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18 |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19 |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20 |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21 |
# THE SOFTWARE.
|
|
22 |
|
|
23 |
|
|
24 |
from http.client import HTTPConnection
|
|
25 |
from urllib.parse import urlparse
|
|
26 |
from lxml import etree
|
|
27 |
from collections import OrderedDict
|
|
28 |
|
|
29 |
debug = 0
|
|
30 |
HTTPConnection.debuglevel = debug
|
|
31 |
|
|
32 |
|
|
33 |
class SoapError(Exception):
|
|
34 |
pass
|
|
35 |
|
|
36 |
|
|
37 |
class SoapResponse(OrderedDict):
|
|
38 |
def __init__(self, name):
|
|
39 |
OrderedDict.__init__(self)
|
|
40 |
self.name = name
|
|
41 |
|
|
42 |
def _printout(self, out, res, level):
|
|
43 |
indent = level * 2 * ' '
|
|
44 |
for k in res.keys():
|
|
45 |
if type(res[k]) is list:
|
|
46 |
for row in res[k]:
|
|
47 |
out.extend([indent, k, '\n'])
|
|
48 |
self._printout(out, row, level+1)
|
|
49 |
else:
|
|
50 |
out.extend([indent, k, ': ', str(res[k]), '\n'])
|
|
51 |
|
|
52 |
def __str__(self):
|
|
53 |
out = [self.name, '\n']
|
|
54 |
self._printout(out, self, 1)
|
|
55 |
return ''.join(out).rstrip()
|
|
56 |
|
|
57 |
|
|
58 |
class SoapClient:
|
|
59 |
def __init__(self, url, namespace):
|
|
60 |
self.url = url
|
|
61 |
self.parsedurl = urlparse(url)
|
|
62 |
self.namespace = namespace
|
|
63 |
self.conn = HTTPConnection(self.parsedurl.netloc)
|
|
64 |
|
|
65 |
def _build_soap_message(self, name, args):
|
|
66 |
SOAP = 'http://schemas.xmlsoap.org/soap/envelope/'
|
|
67 |
root = etree.Element('{%s}Envelope' % SOAP, nsmap={'soap':SOAP})
|
|
68 |
body = etree.SubElement(root, '{%s}Body' % SOAP)
|
|
69 |
func = etree.SubElement(body, '{%s}%s' % (self.namespace, name), nsmap={None:self.namespace})
|
|
70 |
for key in args.keys():
|
|
71 |
param = etree.SubElement(func, key)
|
|
72 |
param.text = str(args[key])
|
|
73 |
if debug:
|
|
74 |
print(etree.tostring(root, pretty_print=True).decode('utf8'))
|
|
75 |
return root
|
|
76 |
|
|
77 |
def _break_params(self, func, d):
|
|
78 |
for param in func.iterchildren():
|
|
79 |
if len(param) == 0:
|
|
80 |
d[param.tag] = param.text
|
|
81 |
else:
|
|
82 |
if not param.tag in d:
|
|
83 |
d[param.tag] = []
|
|
84 |
newd = OrderedDict()
|
|
85 |
self._break_params(param, newd)
|
|
86 |
d[param.tag].append(newd)
|
|
87 |
|
|
88 |
def _break_soap_message(self, root):
|
|
89 |
if not root.tag.endswith('}Envelope'):
|
|
90 |
raise SoapError('Reply has no Envelope.')
|
|
91 |
|
|
92 |
SOAP = root.tag.split('}')[0][1:]
|
|
93 |
|
|
94 |
body_list = list(root.iterchildren('{%s}Body' % SOAP))
|
|
95 |
if len(body_list) != 1:
|
|
96 |
raise SoapError('Bad SOAP format (%d Body elements)' % len(body_list))
|
|
97 |
body = body_list[0]
|
|
98 |
|
|
99 |
func_list =list(body.iterchildren())
|
|
100 |
if len(func_list) != 1:
|
|
101 |
raise SoapError('Bad SOAP format (Body has %d children, should have one)' % len(body_list))
|
|
102 |
func = func_list[0]
|
|
103 |
|
|
104 |
if debug:
|
|
105 |
print(etree.tostring(func, pretty_print=True).decode('utf8'))
|
|
106 |
res = SoapResponse(func.tag)
|
|
107 |
self._break_params(func, res)
|
|
108 |
|
|
109 |
return res
|
|
110 |
|
|
111 |
def _serialize_xml(self, root):
|
|
112 |
return etree.tostring(root,
|
|
113 |
pretty_print=True,
|
|
114 |
encoding='utf-8',
|
|
115 |
xml_declaration=True)
|
|
116 |
|
|
117 |
def _parse_xml(self, file):
|
|
118 |
tree = etree.parse(file)
|
|
119 |
return tree.getroot()
|
|
120 |
|
|
121 |
def _send_soap(self, body):
|
|
122 |
headers = {
|
|
123 |
'Content-type': "text/xml;charset='utf-8'",
|
|
124 |
'Soapaction': "''",
|
|
125 |
'Accept': 'text/xml'}
|
|
126 |
self.conn.request('POST', self.parsedurl.path, body, headers)
|
|
127 |
resp = self.conn.getresponse()
|
|
128 |
if resp.status != 200:
|
|
129 |
raise SoapError('Server replied %d %s' % (resp.status, resp.reason))
|
|
130 |
return resp
|
|
131 |
|
|
132 |
def __getattr__(self, name):
|
|
133 |
def fnc(**kwargs):
|
|
134 |
root = self._build_soap_message(name, kwargs)
|
|
135 |
body = self._serialize_xml(root)
|
|
136 |
resp = self._send_soap(body)
|
|
137 |
root = self._parse_xml(resp)
|
|
138 |
return self._break_soap_message(root)
|
|
139 |
|
|
140 |
return fnc
|
|
141 |
|
|
142 |
|
|
143 |
if __name__ == '__main__':
|
|
144 |
service = SoapClient('http://10.0.0.1:34567/', namespace='http://devl.cz/')
|
|
145 |
res = service.my_test_request()
|
|
146 |
print(res)
|
|
147 |
|