1 from __future__ import nested_scopes
4 ################################################################################
6 # SOAPpy - Cayce Ullman (cayce@actzero.com)
7 # Brian Matthews (blm@actzero.com)
8 # Gregory Warnes (Gregory.R.Warnes@Pfizer.com)
9 # Christopher Blunck (blunck@gst.com)
11 ################################################################################
12 # Copyright (c) 2003, Pfizer
13 # Copyright (c) 2001, Cayce Ullman.
14 # Copyright (c) 2001, Brian Matthews.
16 # All rights reserved.
18 # Redistribution and use in source and binary forms, with or without
19 # modification, are permitted provided that the following conditions are met:
20 # Redistributions of source code must retain the above copyright notice, this
21 # list of conditions and the following disclaimer.
23 # Redistributions in binary form must reproduce the above copyright notice,
24 # this list of conditions and the following disclaimer in the documentation
25 # and/or other materials provided with the distribution.
27 # Neither the name of actzero, inc. nor the names of its contributors may
28 # be used to endorse or promote products derived from this software without
29 # specific prior written permission.
31 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
32 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
33 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34 # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
35 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
36 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
38 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
40 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 ################################################################################
44 ident = '$Id: Client.py 1496 2010-03-04 23:46:17Z pooryorick $'
46 from version import __version__
53 import socket, httplib
54 from httplib import HTTPConnection, HTTP
59 from Config import Config
60 from Parser import parseSOAPRPC
61 from SOAPBuilder import buildSOAP
62 from Utilities import *
63 from Types import faultType, simplify
65 ################################################################################
67 ################################################################################
71 return "SOAPpy " + __version__ + " (pywebsvcs.sf.net)"
75 def __init__(self, url, config = Config):
76 proto, uri = urllib.splittype(url)
81 uri = proto + ':' + uri
86 host, path = urllib.splithost(uri)
90 host = 'localhost:' + host
97 if proto not in ('http', 'https', 'httpg'):
98 raise IOError, "unsupported SOAP protocol"
99 if proto == 'httpg' and not config.GSIclient:
100 raise AttributeError, \
101 "GSI client not supported by this Python installation"
102 if proto == 'https' and not config.SSLclient:
103 raise AttributeError, \
104 "SSL client not supported by this Python installation"
106 self.user,host = urllib.splituser(host)
112 return "%(proto)s://%(host)s%(path)s" % self.__dict__
116 class SOAPTimeoutError(socket.timeout):
117 '''This exception is raised when a timeout occurs in SOAP operations'''
120 class HTTPConnectionWithTimeout(HTTPConnection):
121 '''Extend HTTPConnection for timeout support'''
123 def __init__(self, host, port=None, strict=None, timeout=None):
124 HTTPConnection.__init__(self, host, port, strict)
125 self._timeout = timeout
128 HTTPConnection.connect(self)
129 if self.sock and self._timeout:
130 self.sock.settimeout(self._timeout)
133 class HTTPWithTimeout(HTTP):
135 _connection_class = HTTPConnectionWithTimeout
137 ## this __init__ copied from httplib.HTML class
138 def __init__(self, host='', port=None, strict=None, timeout=None):
139 "Provide a default host, since the superclass requires one."
141 # some joker passed 0 explicitly, meaning default port
145 # Note that we may pass an empty string as the host; this will throw
146 # an error when we attempt to connect. Presumably, the client code
147 # will call connect before then, with a proper host.
148 self._setup(self._connection_class(host, port, strict, timeout))
154 self.cookies = Cookie.SimpleCookie();
156 def getNS(self, original_namespace, data):
157 """Extract the (possibly extended) namespace from the returned
160 if type(original_namespace) == StringType:
161 pattern="xmlns:\w+=['\"](" + original_namespace + "[^'\"]*)['\"]"
162 match = re.search(pattern, data)
164 return match.group(1)
166 return original_namespace
168 return original_namespace
170 def __addcookies(self, r):
171 '''Add cookies from self.cookies to request r
173 for cname, morsel in self.cookies.items():
175 value = morsel.get('version', '')
176 if value != '' and value != '0':
177 attrs.append('$Version=%s' % value)
178 attrs.append('%s=%s' % (cname, morsel.coded_value))
179 value = morsel.get('path')
181 attrs.append('$Path=%s' % value)
182 value = morsel.get('domain')
184 attrs.append('$Domain=%s' % value)
185 r.putheader('Cookie', "; ".join(attrs))
187 def call(self, addr, data, namespace, soapaction = None, encoding = None,
188 http_proxy = None, config = Config, timeout=None):
190 if not isinstance(addr, SOAPAddress):
191 addr = SOAPAddress(addr, config)
195 real_addr = http_proxy
196 real_path = addr.proto + "://" + addr.host + addr.path
198 real_addr = addr.host
199 real_path = addr.path
201 if addr.proto == 'httpg':
202 from pyGlobus.io import GSIHTTP
203 r = GSIHTTP(real_addr, tcpAttr = config.tcpAttr)
204 elif addr.proto == 'https':
205 r = httplib.HTTPS(real_addr, key_file=config.SSL.key_file, cert_file=config.SSL.cert_file)
207 r = HTTPWithTimeout(real_addr, timeout=timeout)
209 r.putrequest("POST", real_path)
211 r.putheader("Host", addr.host)
212 r.putheader("User-agent", SOAPUserAgent())
215 t += '; charset=%s' % encoding
216 r.putheader("Content-type", t)
217 r.putheader("Content-length", str(len(data)))
218 self.__addcookies(r);
220 # if user is not a user:passwd format
221 # we'll receive a failure from the server. . .I guess (??)
222 if addr.user != None:
223 val = base64.encodestring(addr.user)
224 r.putheader('Authorization','Basic ' + val.replace('\012',''))
226 # This fixes sending either "" or "None"
227 if soapaction == None or len(soapaction) == 0:
228 r.putheader("SOAPAction", "")
230 r.putheader("SOAPAction", '"%s"' % soapaction)
232 if config.dumpHeadersOut:
233 s = 'Outgoing HTTP headers'
235 print "POST %s %s" % (real_path, r._http_vsn_str)
236 print "Host:", addr.host
237 print "User-agent: SOAPpy " + __version__ + " (http://pywebsvcs.sf.net)"
238 print "Content-type:", t
239 print "Content-length:", len(data)
240 print 'SOAPAction: "%s"' % soapaction
245 if config.dumpSOAPOut:
257 code, msg, headers = r.getreply()
259 self.cookies = Cookie.SimpleCookie();
261 content_type = headers.get("content-type","text/xml")
262 content_length = headers.get("Content-length")
264 for cookie in headers.getallmatchingheaders("Set-Cookie"):
265 self.cookies.load(cookie);
271 # work around OC4J bug which does '<len>, <len>' for some reaason
273 comma=content_length.find(',')
275 content_length = content_length[:comma]
277 # attempt to extract integer message size
279 message_len = int(content_length)
284 # Content-Length missing or invalid; just read the whole socket
285 # This won't work with HTTP/1.1 chunked encoding
286 data = r.getfile().read()
287 message_len = len(data)
289 data = r.getfile().read(message_len)
294 print "headers=", headers
295 print "content-type=", content_type
298 if config.dumpHeadersIn:
299 s = 'Incoming HTTP headers'
302 print "HTTP/1.? %d %s" % (code, msg)
303 print "\n".join(map (lambda x: x.strip(), headers.headers))
305 print "HTTP/0.9 %d %s" % (code, msg)
308 def startswith(string, val):
309 return string[0:len(val)] == val
311 if code == 500 and not \
312 ( startswith(content_type, "text/xml") and message_len > 0 ):
313 raise HTTPError(code, msg)
315 if config.dumpSOAPIn:
319 if (len(data)>0) and (data[-1] != '\n'):
323 if code not in (200, 500):
324 raise HTTPError(code, msg)
327 # get the new namespace
328 if namespace is None:
331 new_ns = self.getNS(namespace, data)
333 # return response payload
336 ################################################################################
338 ################################################################################
340 def __init__(self, proxy, namespace = None, soapaction = None,
341 header = None, methodattrs = None, transport = HTTPTransport,
342 encoding = 'UTF-8', throw_faults = 1, unwrap_results = None,
343 http_proxy=None, config = Config, noroot = 0,
344 simplify_objects=None, timeout=None):
346 # Test the encoding, raising an exception if it's not known
350 # get default values for unwrap_results and simplify_objects
352 if unwrap_results is None:
353 self.unwrap_results=config.unwrap_results
355 self.unwrap_results=unwrap_results
357 if simplify_objects is None:
358 self.simplify_objects=config.simplify_objects
360 self.simplify_objects=simplify_objects
362 self.proxy = SOAPAddress(proxy, config)
363 self.namespace = namespace
364 self.soapaction = soapaction
366 self.methodattrs = methodattrs
367 self.transport = transport()
368 self.encoding = encoding
369 self.throw_faults = throw_faults
370 self.http_proxy = http_proxy
373 self.timeout = timeout
376 if hasattr(config, "channel_mode") and \
377 hasattr(config, "delegation_mode"):
378 self.channel_mode = config.channel_mode
379 self.delegation_mode = config.delegation_mode
382 def invoke(self, method, args):
383 return self.__call(method, args, {})
385 def __call(self, name, args, kw, ns = None, sa = None, hd = None,
388 ns = ns or self.namespace
389 ma = ma or self.methodattrs
391 if sa: # Get soapaction
392 if type(sa) == TupleType:
401 if type(hd) == TupleType:
406 hd = hd or self.header
408 if ma: # Get methodattrs
409 if type(ma) == TupleType: ma = ma[0]
411 ma = self.methodattrs
412 ma = ma or self.methodattrs
414 m = buildSOAP(args = args, kw = kw, method = name, namespace = ns,
415 header = hd, methodattrs = ma, encoding = self.encoding,
416 config = self.config, noroot = self.noroot)
421 r, self.namespace = self.transport.call(self.proxy, m, ns, sa,
422 encoding = self.encoding,
423 http_proxy = self.http_proxy,
424 config = self.config,
425 timeout = self.timeout)
427 except socket.timeout:
428 raise SOAPTimeoutError
430 except Exception, ex:
434 # See if we have a fault handling vector installed in our
435 # config. If we do, invoke it. If it returns a true value,
438 # In any circumstance other than the fault handler returning
439 # true, reraise the exception. This keeps the semantics of this
440 # code the same as without the faultHandler code.
443 if hasattr(self.config, "faultHandler"):
444 if callable(self.config.faultHandler):
445 call_retry = self.config.faultHandler(self.proxy, ex)
455 r, self.namespace = self.transport.call(self.proxy, m, ns, sa,
456 encoding = self.encoding,
457 http_proxy = self.http_proxy,
458 config = self.config,
459 timeout = self.timeout)
460 except socket.timeout:
461 raise SOAPTimeoutError
464 p, attrs = parseSOAPRPC(r, attrs = 1)
467 throw_struct = self.throw_faults and \
468 isinstance (p, faultType)
477 # If unwrap_results=1 and there is only element in the struct,
478 # SOAPProxy will assume that this element is the result
479 # and return it rather than the struct containing it.
480 # Otherwise SOAPproxy will return the struct with all the
481 # elements as attributes.
482 if self.unwrap_results:
485 for i in p.__dict__.keys():
486 if i[0] != "_": # don't count the private stuff
489 if count == 1: # Only one piece of data, bubble it up
494 # Automatically simplfy SOAP complex types into the
495 # corresponding python types. (structType --> dict,
496 # arrayType --> array, etc.)
497 if self.simplify_objects:
500 if self.config.returnAllAttrs:
504 def _callWithBody(self, body):
505 return self.__call(None, body, {})
507 def __getattr__(self, name): # hook to catch method calls
508 if name in ( '__del__', '__getinitargs__', '__getnewargs__',
509 '__getstate__', '__setstate__', '__reduce__', '__reduce_ex__'):
510 raise AttributeError, name
511 return self.__Method(self.__call, name, config = self.config)
513 # To handle attribute weirdness
515 # Some magic to bind a SOAP method to an RPC server.
516 # Supports "nested" methods (e.g. examples.getStateName) -- concept
517 # borrowed from xmlrpc/soaplib -- www.pythonware.com
518 # Altered (improved?) to let you inline namespaces on a per call
519 # basis ala SOAP::LITE -- www.soaplite.com
521 def __init__(self, call, name, ns = None, sa = None, hd = None,
522 ma = None, config = Config):
530 self.__config = config
533 def __call__(self, *args, **kw):
534 if self.__name[0] == "_":
535 if self.__name in ["__repr__","__str__"]:
536 return self.__repr__()
538 return self.__f_call(*args, **kw)
540 return self.__r_call(*args, **kw)
542 def __getattr__(self, name):
543 if name == '__del__':
544 raise AttributeError, name
545 if self.__name[0] == "_":
546 # Don't nest method if it is a directive
547 return self.__class__(self.__call, name, self.__ns,
548 self.__sa, self.__hd, self.__ma)
550 return self.__class__(self.__call, "%s.%s" % (self.__name, name),
551 self.__ns, self.__sa, self.__hd, self.__ma)
553 def __f_call(self, *args, **kw):
554 if self.__name == "_ns": self.__ns = args
555 elif self.__name == "_sa": self.__sa = args
556 elif self.__name == "_hd": self.__hd = args
557 elif self.__name == "_ma": self.__ma = args
560 def __r_call(self, *args, **kw):
561 return self.__call(self.__name, args, kw, self.__ns, self.__sa,
562 self.__hd, self.__ma)
565 return "<%s at %d>" % (self.__class__, id(self))