• Main Page
  • Related Pages
  • Modules
  • Namespaces
  • Data Structures
  • Files
  • File List
  • Globals

contrib/opal/ZSI/build/lib/ZSI/twisted/client.py

00001 ############################################################################
00002 # Joshua R. Boverhof, LBNL
00003 # See LBNLCopyright for copyright notice!
00004 ###########################################################################
00005 import time
00006 
00007 # twisted & related imports
00008 from zope.interface import classProvides, implements, Interface
00009 from twisted.web import client
00010 from twisted.internet import defer
00011 from twisted.internet import reactor
00012 from twisted.python import log
00013 from twisted.python.failure import Failure
00014 
00015 from ZSI.parse import ParsedSoap
00016 from ZSI.writer import SoapWriter
00017 from ZSI.fault import FaultFromFaultMessage
00018 from ZSI.wstools.Namespaces import WSA
00019 
00020 from WSresource import HandlerChainInterface, CheckInputArgs
00021 
00022 
00023 # 
00024 # Stability: Unstable
00025 # 
00026 
00027 class HTTPPageGetter(client.HTTPPageGetter):
00028     def handleStatus_500(self):
00029         """potentially a SOAP:Fault.
00030         """
00031         log.err('HTTP Error 500')
00032     def handleStatus_404(self):
00033         """client error, not found
00034         """
00035         log.err('HTTP Error 404')
00036 
00037 
00038 client.HTTPClientFactory.protocol = HTTPPageGetter
00039 
00040 
00041 def getPage(url, contextFactory=None, *args, **kwargs):
00042     """Download a web page as a string.
00043 
00044     Download a page. Return a deferred, which will callback with a
00045     page (as a string) or errback with a description of the error.
00046 
00047     See HTTPClientFactory to see what extra args can be passed.
00048     """
00049     scheme, host, port, path = client._parse(url)
00050     factory = client.HTTPClientFactory(url, *args, **kwargs)
00051     if scheme == 'https':
00052         if contextFactory is None:
00053             raise RuntimeError, 'must provide a contextFactory'
00054         conn = reactor.connectSSL(host, port, factory, contextFactory)
00055     else:
00056         conn = reactor.connectTCP(host, port, factory)
00057 
00058     return factory
00059 
00060 
00061 class ClientDataHandler:
00062     """
00063     class variables:
00064         readerClass -- factory class to create reader for ParsedSoap instances.
00065         writerClass -- ElementProxy implementation to use for SoapWriter 
00066             instances.
00067     """
00068     classProvides(HandlerChainInterface)
00069     readerClass = None
00070     writerClass = None
00071 
00072     @classmethod
00073     def processResponse(cls, soapdata, **kw):
00074         """called by deferred, returns pyobj representing reply.
00075         Parameters and Key Words:
00076           soapdata -- SOAP Data
00077           replytype -- reply type of response
00078         """
00079         if len(soapdata) == 0:
00080             raise TypeError('Received empty response')
00081 
00082 #        log.msg("_" * 33, time.ctime(time.time()), 
00083 #                  "RESPONSE: \n%s" %soapdata, debug=True)
00084 
00085         ps = ParsedSoap(soapdata, readerclass=cls.readerClass)
00086         if ps.IsAFault() is True:
00087             log.msg('Received SOAP:Fault', debug=True)
00088             raise FaultFromFaultMessage(ps)
00089 
00090         return ps
00091 
00092     @classmethod
00093     def processRequest(cls, obj, nsdict={}, header=True, 
00094                        **kw):
00095         tc = None
00096         if kw.has_key('requesttypecode'):
00097             tc = kw['requesttypecode']
00098         elif kw.has_key('requestclass'):
00099             tc = kw['requestclass'].typecode
00100         else:
00101             tc = getattr(obj.__class__, 'typecode', None)
00102 
00103         sw = SoapWriter(nsdict=nsdict, header=header,
00104                         outputclass=cls.writerClass)
00105         sw.serialize(obj, tc)
00106         return sw
00107     
00108     
00109 class WSAddressHandler:
00110     """Minimal WS-Address handler.  Most of the logic is in
00111     the ZSI.address.Address class.
00112     
00113     class variables:
00114         uri -- default WSA Addressing URI
00115     """
00116     implements(HandlerChainInterface)
00117     uri = WSA.ADDRESS
00118     
00119     def processResponse(self, ps, wsaction=None, soapaction=None, **kw):
00120         addr = self.address 
00121         addr.parse(ps)
00122         action = addr.getAction()
00123         if not action:
00124             raise WSActionException('No WS-Action specified in Request')
00125 
00126         if not soapaction:
00127             return ps
00128     
00129         soapaction = soapaction.strip('\'"')
00130         if soapaction and soapaction != wsaction:
00131             raise WSActionException(\
00132                 'SOAP Action("%s") must match WS-Action("%s") if specified.'%(
00133                     soapaction, wsaction)
00134             )
00135 
00136         return ps
00137 
00138     def processRequest(self, sw, wsaction=None, url=None, endPointReference=None, **kw):
00139         from ZSI.address import Address
00140         if sw is None:
00141             self.address = None
00142             return
00143         
00144         if not sw.header:
00145             raise RuntimeError, 'expecting SOAP:Header'
00146         
00147         self.address = addr = Address(url, wsAddressURI=self.uri)
00148         addr.setRequest(endPointReference, wsaction)
00149         addr.serialize(sw, typed=False)
00150         
00151         return sw
00152 
00153 
00154 class DefaultClientHandlerChain:
00155 
00156     @CheckInputArgs(HandlerChainInterface)
00157     def __init__(self, *handlers):
00158         self.handlers = handlers
00159         self.debug = len(log.theLogPublisher.observers) > 0
00160         self.flow = None
00161         
00162     @staticmethod
00163     def parseResponse(ps, replytype):
00164         return ps.Parse(replytype)
00165         
00166     def processResponse(self, arg, replytype, **kw):
00167         """
00168         Parameters:
00169             arg -- deferred 
00170             replytype -- typecode
00171         """
00172         if self.debug:
00173             log.msg('--->PROCESS REQUEST\n%s' %arg, debug=1)
00174 
00175         for h in self.handlers:
00176             arg.addCallback(h.processResponse, **kw)
00177             
00178         arg.addCallback(self.parseResponse, replytype)
00179             
00180     def processRequest(self, arg, **kw):
00181         """
00182         Parameters:
00183             arg -- XML Soap data string
00184         """
00185         if self.debug:
00186             log.msg('===>PROCESS RESPONSE: %s' %str(arg), debug=1)
00187 
00188         if arg is None:
00189             return
00190 
00191         for h in self.handlers:
00192             arg = h.processRequest(arg, **kw)
00193             
00194         s = str(arg)
00195         if self.debug:
00196             log.msg(s, debug=1)
00197 
00198         return s
00199 
00200 
00201 class DefaultClientHandlerChainFactory:
00202     protocol = DefaultClientHandlerChain
00203     
00204     @classmethod
00205     def newInstance(cls):
00206         return cls.protocol(ClientDataHandler)
00207         
00208 
00209 class WSAddressClientHandlerChainFactory:
00210     protocol = DefaultClientHandlerChain
00211     
00212     @classmethod
00213     def newInstance(cls):
00214         return cls.protocol(ClientDataHandler, 
00215             WSAddressHandler())
00216 
00217 
00218 class Binding:
00219     """Object that represents a binding (connection) to a SOAP server.
00220     """
00221     agent='ZSI.twisted client'
00222     factory = DefaultClientHandlerChainFactory
00223     defer = False
00224 
00225     def __init__(self, url=None, nsdict=None, contextFactory=None, 
00226                  tracefile=None, **kw):
00227         """Initialize.
00228         Keyword arguments include:
00229             url -- URL of resource, POST is path 
00230             nsdict -- namespace entries to add
00231             contextFactory -- security contexts
00232             tracefile -- file to dump packet traces
00233         """
00234         self.url = url
00235         self.nsdict = nsdict or {}
00236         self.contextFactory = contextFactory
00237         self.http_headers  = {'content-type': 'text/xml',}
00238         self.trace = tracefile
00239 
00240     def addHTTPHeader(self, key, value):
00241         self.http_headers[key] = value
00242    
00243     def getHTTPHeaders(self):
00244         return self.http_headers
00245         
00246     def Send(self, url, opname, pyobj, nsdict={}, soapaction=None, chain=None, 
00247              **kw):
00248         """Returns a ProcessingChain which needs to be passed to Receive if 
00249         Send is being called consecutively.
00250         """
00251         url = url or self.url
00252         cookies = None
00253         if chain is not None:
00254             cookies = chain.flow.cookies
00255         
00256         d = {}
00257         d.update(self.nsdict)
00258         d.update(nsdict)
00259          
00260         if soapaction is not None:
00261             self.addHTTPHeader('SOAPAction', soapaction)
00262         
00263         chain = self.factory.newInstance()
00264         soapdata = chain.processRequest(pyobj, nsdict=nsdict, 
00265                                         soapaction=soapaction, **kw)
00266             
00267         if self.trace:
00268             print >>self.trace, "_" * 33, time.ctime(time.time()), "REQUEST:"
00269             print >>self.trace, soapdata
00270 
00271         f = getPage(str(url), contextFactory=self.contextFactory, 
00272                     postdata=soapdata, agent=self.agent, 
00273                     method='POST', headers=self.getHTTPHeaders(), 
00274                     cookies=cookies)
00275         
00276         if isinstance(f, Failure):
00277             return f
00278         
00279         chain.flow = f
00280         self.chain = chain
00281         return chain
00282             
00283     def Receive(self, replytype, chain=None, **kw):
00284         """This method allows code to act in a synchronous manner, it waits to 
00285         return until the deferred fires but it doesn't prevent other queued 
00286         calls from being executed.  Send must be called first, which sets up 
00287         the chain/factory.  
00288         
00289         WARNING: If defer is set to True, must either call Receive
00290         immediately after Send (ie. no intervening Sends) or pass
00291         chain in as a paramter.
00292         
00293         Parameters:
00294             replytype -- TypeCode
00295         KeyWord Parameters:
00296             chain -- processing chain, optional
00297             
00298         """        
00299         chain = chain or self.chain
00300         d = chain.flow.deferred
00301         if self.trace:
00302             def trace(soapdata):
00303                 print >>self.trace, "_" * 33, time.ctime(time.time()), "RESPONSE:"
00304                 print >>self.trace, soapdata
00305                 return soapdata
00306             
00307             d.addCallback(trace)
00308             
00309         chain.processResponse(d, replytype, **kw)
00310         if self.defer:
00311             return d
00312 
00313         failure = []
00314         append = failure.append
00315         def errback(result):
00316             """Used with Response method to suppress 'Unhandled error in 
00317             Deferred' messages by adding an errback.
00318             """
00319             append(result)
00320             return None
00321         
00322         d.addErrback(errback)
00323 
00324         # spin reactor
00325         while not d.called:
00326             reactor.runUntilCurrent()
00327             t2 = reactor.timeout()
00328             t = reactor.running and t2
00329             reactor.doIteration(t)
00330 
00331         pyobj = d.result
00332         if len(failure):
00333             failure[0].raiseException()
00334         
00335         return pyobj
00336 
00337 def trace():
00338         if trace:
00339             print >>trace, "_" * 33, time.ctime(time.time()), "RESPONSE:"
00340             for i in (self.reply_code, self.reply_msg,):
00341                 print >>trace, str(i)
00342             print >>trace, "-------"
00343             print >>trace, str(self.reply_headers)
00344             print >>trace, self.data

Generated on Wed Oct 20 2010 11:12:15 for APBS by  doxygen 1.7.2