A fork of https://github.com/Synerty/SOAPpy-py3 This is a working tree till fixes get imported upstream.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

573 lines
19 KiB

  1. """
  2. ################################################################################
  3. #
  4. # SOAPpy - Cayce Ullman (cayce@actzero.com)
  5. # Brian Matthews (blm@actzero.com)
  6. # Gregory Warnes (Gregory.R.Warnes@Pfizer.com)
  7. # Christopher Blunck (blunck@gst.com)
  8. #
  9. ################################################################################
  10. # Copyright (c) 2003, Pfizer
  11. # Copyright (c) 2001, Cayce Ullman.
  12. # Copyright (c) 2001, Brian Matthews.
  13. #
  14. # All rights reserved.
  15. #
  16. # Redistribution and use in source and binary forms, with or without
  17. # modification, are permitted provided that the following conditions are met:
  18. # Redistributions of source code must retain the above copyright notice, this
  19. # list of conditions and the following disclaimer.
  20. #
  21. # Redistributions in binary form must reproduce the above copyright notice,
  22. # this list of conditions and the following disclaimer in the documentation
  23. # and/or other materials provided with the distribution.
  24. #
  25. # Neither the name of actzero, inc. nor the names of its contributors may
  26. # be used to endorse or promote products derived from this software without
  27. # specific prior written permission.
  28. #
  29. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  30. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  31. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  32. # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
  33. # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  34. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  35. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  36. # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  37. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  38. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  39. #
  40. ################################################################################
  41. """
  42. from __future__ import nested_scopes
  43. ident = '$Id: Client.py 1496 2010-03-04 23:46:17Z pooryorick $'
  44. from version import __version__
  45. #import xml.sax
  46. import urllib
  47. from types import *
  48. import re
  49. import base64
  50. import socket, httplib
  51. from httplib import HTTPConnection, HTTP
  52. import Cookie
  53. # SOAPpy modules
  54. from Errors import *
  55. from Config import Config
  56. from Parser import parseSOAPRPC
  57. from SOAPBuilder import buildSOAP
  58. from Utilities import *
  59. from Types import faultType, simplify
  60. ################################################################################
  61. # Client
  62. ################################################################################
  63. def SOAPUserAgent():
  64. return "SOAPpy " + __version__ + " (pywebsvcs.sf.net)"
  65. class SOAPAddress:
  66. def __init__(self, url, config = Config):
  67. proto, uri = urllib.splittype(url)
  68. # apply some defaults
  69. if uri[0:2] != '//':
  70. if proto != None:
  71. uri = proto + ':' + uri
  72. uri = '//' + uri
  73. proto = 'http'
  74. host, path = urllib.splithost(uri)
  75. try:
  76. int(host)
  77. host = 'localhost:' + host
  78. except:
  79. pass
  80. if not path:
  81. path = '/'
  82. if proto not in ('http', 'https', 'httpg'):
  83. raise IOError, "unsupported SOAP protocol"
  84. if proto == 'httpg' and not config.GSIclient:
  85. raise AttributeError, \
  86. "GSI client not supported by this Python installation"
  87. if proto == 'https' and not config.SSLclient:
  88. raise AttributeError, \
  89. "SSL client not supported by this Python installation"
  90. self.user,host = urllib.splituser(host)
  91. self.proto = proto
  92. self.host = host
  93. self.path = path
  94. def __str__(self):
  95. return "%(proto)s://%(host)s%(path)s" % self.__dict__
  96. __repr__ = __str__
  97. class SOAPTimeoutError(socket.timeout):
  98. '''This exception is raised when a timeout occurs in SOAP operations'''
  99. pass
  100. class HTTPConnectionWithTimeout(HTTPConnection):
  101. '''Extend HTTPConnection for timeout support'''
  102. def __init__(self, host, port=None, strict=None, timeout=None):
  103. HTTPConnection.__init__(self, host, port, strict)
  104. self._timeout = timeout
  105. def connect(self):
  106. HTTPConnection.connect(self)
  107. if self.sock and self._timeout:
  108. self.sock.settimeout(self._timeout)
  109. class HTTPWithTimeout(HTTP):
  110. _connection_class = HTTPConnectionWithTimeout
  111. def __init__(self, host='', port=None, strict=None, timeout=None):
  112. """Slight modification of superclass (httplib.HTTP) constructor.
  113. The only change is that arg ``timeout`` is also passed in the
  114. initialization of :attr:`_connection_class`.
  115. :param timeout: for the socket connection (seconds); None to disable
  116. :type timeout: float or None
  117. """
  118. if port == 0:
  119. port = None
  120. self._setup(self._connection_class(host, port, strict, timeout))
  121. class HTTPTransport:
  122. def __init__(self):
  123. self.cookies = Cookie.SimpleCookie();
  124. def getNS(self, original_namespace, data):
  125. """Extract the (possibly extended) namespace from the returned
  126. SOAP message."""
  127. if type(original_namespace) == StringType:
  128. pattern="xmlns:\w+=['\"](" + original_namespace + "[^'\"]*)['\"]"
  129. match = re.search(pattern, data)
  130. if match:
  131. return match.group(1)
  132. else:
  133. return original_namespace
  134. else:
  135. return original_namespace
  136. def __addcookies(self, r):
  137. '''Add cookies from self.cookies to request r
  138. '''
  139. for cname, morsel in self.cookies.items():
  140. attrs = []
  141. value = morsel.get('version', '')
  142. if value != '' and value != '0':
  143. attrs.append('$Version=%s' % value)
  144. attrs.append('%s=%s' % (cname, morsel.coded_value))
  145. value = morsel.get('path')
  146. if value:
  147. attrs.append('$Path=%s' % value)
  148. value = morsel.get('domain')
  149. if value:
  150. attrs.append('$Domain=%s' % value)
  151. r.putheader('Cookie', "; ".join(attrs))
  152. def call(self, addr, data, namespace, soapaction = None, encoding = None,
  153. http_proxy = None, config = Config, timeout=None):
  154. if not isinstance(addr, SOAPAddress):
  155. addr = SOAPAddress(addr, config)
  156. # Build a request
  157. if http_proxy:
  158. real_addr = http_proxy
  159. real_path = addr.proto + "://" + addr.host + addr.path
  160. else:
  161. real_addr = addr.host
  162. real_path = addr.path
  163. if addr.proto == 'httpg':
  164. from pyGlobus.io import GSIHTTP
  165. r = GSIHTTP(real_addr, tcpAttr = config.tcpAttr)
  166. elif addr.proto == 'https':
  167. r = httplib.HTTPS(real_addr, key_file=config.SSL.key_file, cert_file=config.SSL.cert_file)
  168. else:
  169. r = HTTPWithTimeout(real_addr, timeout=timeout)
  170. r.putrequest("POST", real_path)
  171. r.putheader("Host", addr.host)
  172. r.putheader("User-agent", SOAPUserAgent())
  173. t = 'text/xml';
  174. if encoding != None:
  175. t += '; charset=%s' % encoding
  176. r.putheader("Content-type", t)
  177. r.putheader("Content-length", str(len(data)))
  178. self.__addcookies(r);
  179. # if user is not a user:passwd format
  180. # we'll receive a failure from the server. . .I guess (??)
  181. if addr.user != None:
  182. val = base64.encodestring(urllib.unquote_plus(addr.user))
  183. r.putheader('Authorization','Basic ' + val.replace('\012',''))
  184. # This fixes sending either "" or "None"
  185. if soapaction == None or len(soapaction) == 0:
  186. r.putheader("SOAPAction", "")
  187. else:
  188. r.putheader("SOAPAction", '"%s"' % soapaction)
  189. if config.dumpHeadersOut:
  190. s = 'Outgoing HTTP headers'
  191. debugHeader(s)
  192. print "POST %s %s" % (real_path, r._http_vsn_str)
  193. print "Host:", addr.host
  194. print "User-agent: SOAPpy " + __version__ + " (http://pywebsvcs.sf.net)"
  195. print "Content-type:", t
  196. print "Content-length:", len(data)
  197. print 'SOAPAction: "%s"' % soapaction
  198. debugFooter(s)
  199. r.endheaders()
  200. if config.dumpSOAPOut:
  201. s = 'Outgoing SOAP'
  202. debugHeader(s)
  203. print data,
  204. if data[-1] != '\n':
  205. print
  206. debugFooter(s)
  207. # send the payload
  208. r.send(data)
  209. # read response line
  210. code, msg, headers = r.getreply()
  211. self.cookies = Cookie.SimpleCookie();
  212. if headers:
  213. content_type = headers.get("content-type","text/xml")
  214. content_length = headers.get("Content-length")
  215. for cookie in headers.getallmatchingheaders("Set-Cookie"):
  216. self.cookies.load(cookie);
  217. else:
  218. content_type=None
  219. content_length=None
  220. # work around OC4J bug which does '<len>, <len>' for some reaason
  221. if content_length:
  222. comma=content_length.find(',')
  223. if comma>0:
  224. content_length = content_length[:comma]
  225. # attempt to extract integer message size
  226. try:
  227. message_len = int(content_length)
  228. except:
  229. message_len = -1
  230. f = r.getfile()
  231. if f is None:
  232. raise HTTPError(code, "Empty response from server\nCode: %s\nHeaders: %s" % (msg, headers))
  233. if message_len < 0:
  234. # Content-Length missing or invalid; just read the whole socket
  235. # This won't work with HTTP/1.1 chunked encoding
  236. data = f.read()
  237. message_len = len(data)
  238. else:
  239. data = f.read(message_len)
  240. if(config.debug):
  241. print "code=",code
  242. print "msg=", msg
  243. print "headers=", headers
  244. print "content-type=", content_type
  245. print "data=", data
  246. if config.dumpHeadersIn:
  247. s = 'Incoming HTTP headers'
  248. debugHeader(s)
  249. if headers.headers:
  250. print "HTTP/1.? %d %s" % (code, msg)
  251. print "\n".join(map (lambda x: x.strip(), headers.headers))
  252. else:
  253. print "HTTP/0.9 %d %s" % (code, msg)
  254. debugFooter(s)
  255. def startswith(string, val):
  256. return string[0:len(val)] == val
  257. if code == 500 and not \
  258. ( startswith(content_type, "text/xml") and message_len > 0 ):
  259. raise HTTPError(code, msg)
  260. if config.dumpSOAPIn:
  261. s = 'Incoming SOAP'
  262. debugHeader(s)
  263. print data,
  264. if (len(data)>0) and (data[-1] != '\n'):
  265. print
  266. debugFooter(s)
  267. if code not in (200, 500):
  268. raise HTTPError(code, msg)
  269. # get the new namespace
  270. if namespace is None:
  271. new_ns = None
  272. else:
  273. new_ns = self.getNS(namespace, data)
  274. # return response payload
  275. return data, new_ns
  276. ################################################################################
  277. # SOAP Proxy
  278. ################################################################################
  279. class SOAPProxy:
  280. def __init__(self, proxy, namespace = None, soapaction = None,
  281. header = None, methodattrs = None, transport = HTTPTransport,
  282. encoding = 'UTF-8', throw_faults = 1, unwrap_results = None,
  283. http_proxy=None, config = Config, noroot = 0,
  284. simplify_objects=None, timeout=None):
  285. # Test the encoding, raising an exception if it's not known
  286. if encoding != None:
  287. ''.encode(encoding)
  288. # get default values for unwrap_results and simplify_objects
  289. # from config
  290. if unwrap_results is None:
  291. self.unwrap_results=config.unwrap_results
  292. else:
  293. self.unwrap_results=unwrap_results
  294. if simplify_objects is None:
  295. self.simplify_objects=config.simplify_objects
  296. else:
  297. self.simplify_objects=simplify_objects
  298. self.proxy = SOAPAddress(proxy, config)
  299. self.namespace = namespace
  300. self.soapaction = soapaction
  301. self.header = header
  302. self.methodattrs = methodattrs
  303. self.transport = transport()
  304. self.encoding = encoding
  305. self.throw_faults = throw_faults
  306. self.http_proxy = http_proxy
  307. self.config = config
  308. self.noroot = noroot
  309. self.timeout = timeout
  310. # GSI Additions
  311. if hasattr(config, "channel_mode") and \
  312. hasattr(config, "delegation_mode"):
  313. self.channel_mode = config.channel_mode
  314. self.delegation_mode = config.delegation_mode
  315. #end GSI Additions
  316. def invoke(self, method, args):
  317. return self.__call(method, args, {})
  318. def __call(self, name, args, kw, ns = None, sa = None, hd = None,
  319. ma = None):
  320. ns = ns or self.namespace
  321. ma = ma or self.methodattrs
  322. if sa: # Get soapaction
  323. if type(sa) == TupleType:
  324. sa = sa[0]
  325. else:
  326. if self.soapaction:
  327. sa = self.soapaction
  328. else:
  329. sa = name
  330. if hd: # Get header
  331. if type(hd) == TupleType:
  332. hd = hd[0]
  333. else:
  334. hd = self.header
  335. hd = hd or self.header
  336. if ma: # Get methodattrs
  337. if type(ma) == TupleType: ma = ma[0]
  338. else:
  339. ma = self.methodattrs
  340. ma = ma or self.methodattrs
  341. m = buildSOAP(args = args, kw = kw, method = name, namespace = ns,
  342. header = hd, methodattrs = ma, encoding = self.encoding,
  343. config = self.config, noroot = self.noroot)
  344. call_retry = 0
  345. try:
  346. r, self.namespace = self.transport.call(self.proxy, m, ns, sa,
  347. encoding = self.encoding,
  348. http_proxy = self.http_proxy,
  349. config = self.config,
  350. timeout = self.timeout)
  351. except socket.timeout:
  352. raise SOAPTimeoutError
  353. except Exception, ex:
  354. #
  355. # Call failed.
  356. #
  357. # See if we have a fault handling vector installed in our
  358. # config. If we do, invoke it. If it returns a true value,
  359. # retry the call.
  360. #
  361. # In any circumstance other than the fault handler returning
  362. # true, reraise the exception. This keeps the semantics of this
  363. # code the same as without the faultHandler code.
  364. #
  365. if hasattr(self.config, "faultHandler"):
  366. if callable(self.config.faultHandler):
  367. call_retry = self.config.faultHandler(self.proxy, ex)
  368. if not call_retry:
  369. raise
  370. else:
  371. raise
  372. else:
  373. raise
  374. if call_retry:
  375. try:
  376. r, self.namespace = self.transport.call(self.proxy, m, ns, sa,
  377. encoding = self.encoding,
  378. http_proxy = self.http_proxy,
  379. config = self.config,
  380. timeout = self.timeout)
  381. except socket.timeout:
  382. raise SOAPTimeoutError
  383. p, attrs = parseSOAPRPC(r, attrs = 1)
  384. try:
  385. throw_struct = self.throw_faults and \
  386. isinstance (p, faultType)
  387. except:
  388. throw_struct = 0
  389. if throw_struct:
  390. if Config.debug:
  391. print p
  392. raise p
  393. # If unwrap_results=1 and there is only element in the struct,
  394. # SOAPProxy will assume that this element is the result
  395. # and return it rather than the struct containing it.
  396. # Otherwise SOAPproxy will return the struct with all the
  397. # elements as attributes.
  398. if self.unwrap_results:
  399. try:
  400. count = 0
  401. for i in p.__dict__.keys():
  402. if i[0] != "_": # don't count the private stuff
  403. count += 1
  404. t = getattr(p, i)
  405. if count == 1: # Only one piece of data, bubble it up
  406. p = t
  407. except:
  408. pass
  409. # Automatically simplfy SOAP complex types into the
  410. # corresponding python types. (structType --> dict,
  411. # arrayType --> array, etc.)
  412. if self.simplify_objects:
  413. p = simplify(p)
  414. if self.config.returnAllAttrs:
  415. return p, attrs
  416. return p
  417. def _callWithBody(self, body):
  418. return self.__call(None, body, {})
  419. def __getattr__(self, name): # hook to catch method calls
  420. if name in ( '__del__', '__getinitargs__', '__getnewargs__',
  421. '__getstate__', '__setstate__', '__reduce__', '__reduce_ex__'):
  422. raise AttributeError, name
  423. return self.__Method(self.__call, name, config = self.config)
  424. # To handle attribute weirdness
  425. class __Method:
  426. # Some magic to bind a SOAP method to an RPC server.
  427. # Supports "nested" methods (e.g. examples.getStateName) -- concept
  428. # borrowed from xmlrpc/soaplib -- www.pythonware.com
  429. # Altered (improved?) to let you inline namespaces on a per call
  430. # basis ala SOAP::LITE -- www.soaplite.com
  431. def __init__(self, call, name, ns = None, sa = None, hd = None,
  432. ma = None, config = Config):
  433. self.__call = call
  434. self.__name = name
  435. self.__ns = ns
  436. self.__sa = sa
  437. self.__hd = hd
  438. self.__ma = ma
  439. self.__config = config
  440. return
  441. def __call__(self, *args, **kw):
  442. if self.__name[0] == "_":
  443. if self.__name in ["__repr__","__str__"]:
  444. return self.__repr__()
  445. else:
  446. return self.__f_call(*args, **kw)
  447. else:
  448. return self.__r_call(*args, **kw)
  449. def __getattr__(self, name):
  450. if name == '__del__':
  451. raise AttributeError, name
  452. if self.__name[0] == "_":
  453. # Don't nest method if it is a directive
  454. return self.__class__(self.__call, name, self.__ns,
  455. self.__sa, self.__hd, self.__ma)
  456. return self.__class__(self.__call, "%s.%s" % (self.__name, name),
  457. self.__ns, self.__sa, self.__hd, self.__ma)
  458. def __f_call(self, *args, **kw):
  459. if self.__name == "_ns": self.__ns = args
  460. elif self.__name == "_sa": self.__sa = args
  461. elif self.__name == "_hd": self.__hd = args
  462. elif self.__name == "_ma": self.__ma = args
  463. return self
  464. def __r_call(self, *args, **kw):
  465. return self.__call(self.__name, args, kw, self.__ns, self.__sa,
  466. self.__hd, self.__ma)
  467. def __repr__(self):
  468. return "<%s at %d>" % (self.__class__, id(self))