00001
00002
00003
00004
00005 import time
00006
00007
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
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
00083
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
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