00001
00002
00003
00004
00005
00006
00007 import sys, warnings
00008
00009
00010 from zope.interface import classProvides, implements, Interface
00011 from twisted.python import log, failure
00012 from twisted.web.error import NoResource
00013 from twisted.web.server import NOT_DONE_YET
00014 import twisted.web.http
00015 import twisted.web.resource
00016
00017
00018 from ZSI import _get_element_nsuri_name, EvaluateException, ParseException
00019 from ZSI.parse import ParsedSoap
00020 from ZSI.writer import SoapWriter
00021 from ZSI import fault
00022
00023
00024 from ZSI.address import Address
00025 from ZSI.ServiceContainer import WSActionException
00026
00027 from interfaces import CheckInputArgs, HandlerChainInterface, CallbackChainInterface,\
00028 DataHandler
00029
00030
00031 class LoggingHandlerChain:
00032
00033 @CheckInputArgs(CallbackChainInterface, HandlerChainInterface)
00034 def __init__(self, cb, *handlers):
00035 self.handlercb = cb
00036 self.handlers = handlers
00037 self.debug = len(log.theLogPublisher.observers) > 0
00038
00039 def processRequest(self, arg, **kw):
00040 debug = self.debug
00041 if debug: log.msg('--->PROCESS REQUEST: %s' %arg, debug=1)
00042
00043 for h in self.handlers:
00044 if debug: log.msg('\t%s handler: %s' %(arg, h), debug=1)
00045 arg = h.processRequest(arg, **kw)
00046
00047 return self.handlercb.processRequest(arg, **kw)
00048
00049 def processResponse(self, arg, **kw):
00050 debug = self.debug
00051 if debug: log.msg('===>PROCESS RESPONSE: %s' %str(arg), debug=1)
00052
00053 if arg is None:
00054 return
00055
00056 for h in self.handlers:
00057 if debug: log.msg('\t%s handler: %s' %(arg, h), debug=1)
00058 arg = h.processResponse(arg, **kw)
00059
00060 s = str(arg)
00061 if debug: log.msg(s, debug=1)
00062
00063 return s
00064
00065
00066
00067
00068
00069 class DefaultCallbackHandler:
00070 classProvides(CallbackChainInterface)
00071
00072 @classmethod
00073 def processRequest(cls, ps, **kw):
00074 """invokes callback that should return a (request,response) tuple.
00075 representing the SOAP request and response respectively.
00076 ps -- ParsedSoap instance representing HTTP Body.
00077 request -- twisted.web.server.Request
00078 """
00079 resource = kw['resource']
00080 request = kw['request']
00081 method = getattr(resource, 'soap_%s' %
00082 _get_element_nsuri_name(ps.body_root)[-1])
00083
00084 try:
00085 req_pyobj,rsp_pyobj = method(ps, request=request)
00086 except TypeError, ex:
00087 log.err(
00088 'ERROR: service %s is broken, method MUST return request, response'\
00089 % cls.__name__
00090 )
00091 raise
00092 except Exception, ex:
00093 log.err('failure when calling bound method')
00094 raise
00095
00096 return rsp_pyobj
00097
00098
00099 class WSAddressHandler:
00100 """General WS-Address handler. This implementation depends on a
00101 'wsAction' dictionary in the service stub which contains keys to
00102 WS-Action values.
00103
00104 Implementation saves state on request response flow, so using this
00105 handle is not reliable if execution is deferred between proceesRequest
00106 and processResponse.
00107
00108 TODO: sink this up with wsdl2dispatch
00109 TODO: reduce coupling with WSAddressCallbackHandler.
00110 """
00111 implements(HandlerChainInterface)
00112
00113 def processRequest(self, ps, **kw):
00114
00115 resource = kw['resource']
00116
00117 d = getattr(resource, 'root', None)
00118 key = _get_element_nsuri_name(ps.body_root)
00119 if d is None or d.has_key(key) is False:
00120 raise RuntimeError,\
00121 'Error looking for key(%s) in root dictionary(%s)' %(key, str(d))
00122
00123 self.op_name = d[key]
00124 self.address = address = Address()
00125 address.parse(ps)
00126 action = address.getAction()
00127 if not action:
00128 raise WSActionException('No WS-Action specified in Request')
00129
00130 request = kw['request']
00131 http_headers = request.getAllHeaders()
00132 soap_action = http_headers.get('soapaction')
00133 if soap_action and soap_action.strip('\'"') != action:
00134 raise WSActionException(\
00135 'SOAP Action("%s") must match WS-Action("%s") if specified.'\
00136 %(soap_action,action)
00137 )
00138
00139
00140 ps.address = address
00141 return ps
00142
00143 def processResponse(self, sw, **kw):
00144 if sw is None:
00145 self.address = None
00146 return
00147
00148 request, resource = kw['request'], kw['resource']
00149 if isinstance(request, twisted.web.http.Request) is False:
00150 raise TypeError, '%s instance expected' %http.Request
00151
00152 d = getattr(resource, 'wsAction', None)
00153 key = self.op_name
00154 if d is None or d.has_key(key) is False:
00155 raise WSActionNotSpecified,\
00156 'Error looking for key(%s) in wsAction dictionary(%s)' %(key, str(d))
00157
00158 addressRsp = Address(action=d[key])
00159 if request.transport.TLS == 0:
00160 addressRsp.setResponseFromWSAddress(\
00161 self.address, 'http://%s:%d%s' %(
00162 request.host.host, request.host.port, request.path)
00163 )
00164 else:
00165 addressRsp.setResponseFromWSAddress(\
00166 self.address, 'https://%s:%d%s' %(
00167 request.host.host, request.host.port, request.path)
00168 )
00169
00170 addressRsp.serialize(sw, typed=False)
00171 self.address = None
00172 return sw
00173
00174
00175 class WSAddressCallbackHandler:
00176 classProvides(CallbackChainInterface)
00177
00178 @classmethod
00179 def processRequest(cls, ps, **kw):
00180 """invokes callback that should return a (request,response) tuple.
00181 representing the SOAP request and response respectively.
00182 ps -- ParsedSoap instance representing HTTP Body.
00183 request -- twisted.web.server.Request
00184 """
00185 resource = kw['resource']
00186 request = kw['request']
00187 method = getattr(resource, 'wsa_%s' %
00188 _get_element_nsuri_name(ps.body_root)[-1])
00189
00190
00191 try:
00192 req_pyobj,rsp_pyobj = method(ps, ps.address, request=request)
00193 except TypeError, ex:
00194 log.err(
00195 'ERROR: service %s is broken, method MUST return request, response'\
00196 %self.__class__.__name__
00197 )
00198 raise
00199 except Exception, ex:
00200 log.err('failure when calling bound method')
00201 raise
00202
00203 return rsp_pyobj
00204
00205
00206 class DeferHandlerChain:
00207 """Each handler is
00208 """
00209
00210 @CheckInputArgs(CallbackChainInterface, HandlerChainInterface)
00211 def __init__(self, cb, *handlers):
00212 self.handlercb = cb
00213 self.handlers = handlers
00214 self.debug = len(log.theLogPublisher.observers) > 0
00215
00216 def processRequest(self, arg, **kw):
00217 from twisted.internet import reactor
00218 from twisted.internet.defer import Deferred
00219
00220 debug = self.debug
00221 if debug: log.msg('--->DEFER PROCESS REQUEST: %s' %arg, debug=1)
00222
00223 d = Deferred()
00224 for h in self.handlers:
00225 if debug:
00226 log.msg('\t%s handler: %s' %(arg, h), debug=1)
00227 log.msg('\thandler callback: %s' %h.processRequest)
00228 d.addCallback(h.processRequest, **kw)
00229
00230 d.addCallback(self.handlercb.processRequest, **kw)
00231 reactor.callLater(.0001, d.callback, arg)
00232
00233 if debug: log.msg('===>DEFER PROCESS RESPONSE: %s' %str(arg), debug=1)
00234
00235
00236
00237 for h in self.handlers:
00238 if debug: log.msg('\t%s handler: %s' %(arg, h), debug=1)
00239 d.addCallback(h.processResponse, **kw)
00240
00241 d.addCallback(str)
00242 return d
00243
00244 def processResponse(self, arg, **kw):
00245 return arg
00246
00247
00248 class DefaultHandlerChainFactory:
00249 protocol = LoggingHandlerChain
00250
00251 @classmethod
00252 def newInstance(cls):
00253 return cls.protocol(DefaultCallbackHandler, DataHandler)
00254
00255
00256 class WSAddressHandlerChainFactory:
00257 protocol = DefaultHandlerChain
00258
00259 @classmethod
00260 def newInstance(cls):
00261 return cls.protocol(WSAddressCallbackHandler, DataHandler,
00262 WSAddressHandler())
00263
00264
00265 class WSResource(twisted.web.resource.Resource, object):
00266 """
00267 class variables:
00268 encoding --
00269 factory -- hander chain, which has a factory method "newInstance"
00270 that returns a
00271 """
00272 encoding = "UTF-8"
00273 factory = DefaultHandlerChainFactory
00274
00275 def __init__(self):
00276 """
00277 """
00278 twisted.web.resource.Resource.__init__(self)
00279
00280 def _writeResponse(self, response, request, status=200):
00281 """
00282 request -- request message
00283 response --- response message
00284 status -- HTTP Status
00285 """
00286 request.setResponseCode(status)
00287 if self.encoding is not None:
00288 mimeType = 'text/xml; charset="%s"' % self.encoding
00289 else:
00290 mimeType = "text/xml"
00291
00292 request.setHeader("Content-Type", mimeType)
00293 request.setHeader("Content-Length", str(len(response)))
00294 request.write(response)
00295 request.finish()
00296
00297 def _writeFault(self, fail, request):
00298 """
00299 fail -- failure
00300 request -- request message
00301 ex -- Exception
00302 """
00303 response = fault.FaultFromException(fail.value, False, fail.tb).AsSOAP()
00304 self._writeResponse(response, request, status=500)
00305
00306 def render_POST(self, request):
00307 """Dispatch Method called by twisted render, creates a
00308 request/response handler chain.
00309 request -- twisted.web.server.Request
00310 """
00311 from twisted.internet.defer import maybeDeferred
00312
00313 chain = self.factory.newInstance()
00314 data = request.content.read()
00315
00316 d = maybeDeferred(chain.processRequest, data, request=request, resource=self)
00317 d.addCallback(chain.processResponse, request=request, resource=self)
00318 d.addCallback(self._writeResponse, request)
00319 d.addErrback(self._writeFault, request)
00320
00321 return NOT_DONE_YET
00322
00323
00324
00325
00326
00327 class DefaultHandlerChain:
00328
00329 @CheckInputArgs(CallbackChainInterface, HandlerChainInterface)
00330 def __init__(self, cb, *handlers):
00331 self.handlercb = cb
00332 self.handlers = handlers
00333 self.debug = len(log.theLogPublisher.observers) > 0
00334
00335 def processRequest(self, arg, **kw):
00336 debug = self.debug
00337 if debug: log.msg('--->PROCESS REQUEST: %s' %arg, debug=1)
00338
00339 for h in self.handlers:
00340 if debug: log.msg('\t%s handler: %s' %(arg, h), debug=1)
00341 arg = h.processRequest(arg, **kw)
00342
00343 return self.handlercb.processRequest(arg, **kw)
00344
00345 def processResponse(self, arg, **kw):
00346 debug = self.debug
00347 if debug: log.msg('===>PROCESS RESPONSE: %s' %str(arg), debug=1)
00348
00349 if arg is None:
00350 return
00351
00352 for h in self.handlers:
00353 if debug: log.msg('\t%s handler: %s' %(arg, h), debug=1)
00354 arg = h.processResponse(arg, **kw)
00355
00356 s = str(arg)
00357 if debug: log.msg(s, debug=1)
00358
00359 return s
00360