1########################################################################### 2# Joshua R. Boverhof, LBNL 3# See Copyright for copyright notice! 4# $Id: WSresource.py 1423 2007-11-01 20:33:33Z boverhof $ 5########################################################################### 6 7import sys, warnings 8 9# twisted & related imports 10from zope.interface import classProvides, implements, Interface 11from twisted.python import log, failure 12from twisted.web.error import NoResource 13from twisted.web.server import NOT_DONE_YET 14import twisted.web.http 15import twisted.web.resource 16 17# ZSI imports 18from ZSI import _get_element_nsuri_name, EvaluateException, ParseException 19from ZSI.parse import ParsedSoap 20from ZSI.writer import SoapWriter 21from ZSI import fault 22 23# WS-Address related imports 24from ZSI.address import Address 25from ZSI.ServiceContainer import WSActionException 26 27from interfaces import CheckInputArgs, HandlerChainInterface, CallbackChainInterface,\ 28 DataHandler 29 30 31class LoggingHandlerChain: 32 33 @CheckInputArgs(CallbackChainInterface, HandlerChainInterface) 34 def __init__(self, cb, *handlers): 35 self.handlercb = cb 36 self.handlers = handlers 37 self.debug = len(log.theLogPublisher.observers) > 0 38 39 def processRequest(self, arg, **kw): 40 debug = self.debug 41 if debug: log.msg('--->PROCESS REQUEST: %s' %arg, debug=1) 42 43 for h in self.handlers: 44 if debug: log.msg('\t%s handler: %s' %(arg, h), debug=1) 45 arg = h.processRequest(arg, **kw) 46 47 return self.handlercb.processRequest(arg, **kw) 48 49 def processResponse(self, arg, **kw): 50 debug = self.debug 51 if debug: log.msg('===>PROCESS RESPONSE: %s' %str(arg), debug=1) 52 53 if arg is None: 54 return 55 56 for h in self.handlers: 57 if debug: log.msg('\t%s handler: %s' %(arg, h), debug=1) 58 arg = h.processResponse(arg, **kw) 59 60 s = str(arg) 61 if debug: log.msg(s, debug=1) 62 63 return s 64 65 66# 67# Stability: Unstable 68# 69class DefaultCallbackHandler: 70 classProvides(CallbackChainInterface) 71 72 @classmethod 73 def processRequest(cls, ps, **kw): 74 """invokes callback that should return a (request,response) tuple. 75 representing the SOAP request and response respectively. 76 ps -- ParsedSoap instance representing HTTP Body. 77 request -- twisted.web.server.Request 78 """ 79 resource = kw['resource'] 80 request = kw['request'] 81 method = getattr(resource, 'soap_%s' % 82 _get_element_nsuri_name(ps.body_root)[-1]) 83 84 try: 85 req_pyobj,rsp_pyobj = method(ps, request=request) 86 except TypeError, ex: 87 log.err( 88 'ERROR: service %s is broken, method MUST return request, response'\ 89 % cls.__name__ 90 ) 91 raise 92 except Exception, ex: 93 log.err('failure when calling bound method') 94 raise 95 96 return rsp_pyobj 97 98 99class WSAddressHandler: 100 """General WS-Address handler. This implementation depends on a 101 'wsAction' dictionary in the service stub which contains keys to 102 WS-Action values. 103 104 Implementation saves state on request response flow, so using this 105 handle is not reliable if execution is deferred between proceesRequest 106 and processResponse. 107 108 TODO: sink this up with wsdl2dispatch 109 TODO: reduce coupling with WSAddressCallbackHandler. 110 """ 111 implements(HandlerChainInterface) 112 113 def processRequest(self, ps, **kw): 114 # TODO: Clean this up 115 resource = kw['resource'] 116 117 d = getattr(resource, 'root', None) 118 key = _get_element_nsuri_name(ps.body_root) 119 if d is None or d.has_key(key) is False: 120 raise RuntimeError,\ 121 'Error looking for key(%s) in root dictionary(%s)' %(key, str(d)) 122 123 self.op_name = d[key] 124 self.address = address = Address() 125 address.parse(ps) 126 action = address.getAction() 127 if not action: 128 raise WSActionException('No WS-Action specified in Request') 129 130 request = kw['request'] 131 http_headers = request.getAllHeaders() 132 soap_action = http_headers.get('soapaction') 133 if soap_action and soap_action.strip('\'"') != action: 134 raise WSActionException(\ 135 'SOAP Action("%s") must match WS-Action("%s") if specified.'\ 136 %(soap_action,action) 137 ) 138 139 # Save WS-Address in ParsedSoap instance. 140 ps.address = address 141 return ps 142 143 def processResponse(self, sw, **kw): 144 if sw is None: 145 self.address = None 146 return 147 148 request, resource = kw['request'], kw['resource'] 149 if isinstance(request, twisted.web.http.Request) is False: 150 raise TypeError, '%s instance expected' %http.Request 151 152 d = getattr(resource, 'wsAction', None) 153 key = self.op_name 154 if d is None or d.has_key(key) is False: 155 raise WSActionNotSpecified,\ 156 'Error looking for key(%s) in wsAction dictionary(%s)' %(key, str(d)) 157 158 addressRsp = Address(action=d[key]) 159 if request.transport.TLS == 0: 160 addressRsp.setResponseFromWSAddress(\ 161 self.address, 'http://%s:%d%s' %( 162 request.host.host, request.host.port, request.path) 163 ) 164 else: 165 addressRsp.setResponseFromWSAddress(\ 166 self.address, 'https://%s:%d%s' %( 167 request.host.host, request.host.port, request.path) 168 ) 169 170 addressRsp.serialize(sw, typed=False) 171 self.address = None 172 return sw 173 174 175class WSAddressCallbackHandler: 176 classProvides(CallbackChainInterface) 177 178 @classmethod 179 def processRequest(cls, ps, **kw): 180 """invokes callback that should return a (request,response) tuple. 181 representing the SOAP request and response respectively. 182 ps -- ParsedSoap instance representing HTTP Body. 183 request -- twisted.web.server.Request 184 """ 185 resource = kw['resource'] 186 request = kw['request'] 187 method = getattr(resource, 'wsa_%s' % 188 _get_element_nsuri_name(ps.body_root)[-1]) 189 190 # TODO: grab ps.address, clean this up. 191 try: 192 req_pyobj,rsp_pyobj = method(ps, ps.address, request=request) 193 except TypeError, ex: 194 log.err( 195 'ERROR: service %s is broken, method MUST return request, response'\ 196 %self.__class__.__name__ 197 ) 198 raise 199 except Exception, ex: 200 log.err('failure when calling bound method') 201 raise 202 203 return rsp_pyobj 204 205 206class DeferHandlerChain: 207 """Each handler is 208 """ 209 210 @CheckInputArgs(CallbackChainInterface, HandlerChainInterface) 211 def __init__(self, cb, *handlers): 212 self.handlercb = cb 213 self.handlers = handlers 214 self.debug = len(log.theLogPublisher.observers) > 0 215 216 def processRequest(self, arg, **kw): 217 from twisted.internet import reactor 218 from twisted.internet.defer import Deferred 219 220 debug = self.debug 221 if debug: log.msg('--->DEFER PROCESS REQUEST: %s' %arg, debug=1) 222 223 d = Deferred() 224 for h in self.handlers: 225 if debug: 226 log.msg('\t%s handler: %s' %(arg, h), debug=1) 227 log.msg('\thandler callback: %s' %h.processRequest) 228 d.addCallback(h.processRequest, **kw) 229 230 d.addCallback(self.handlercb.processRequest, **kw) 231 reactor.callLater(.0001, d.callback, arg) 232 233 if debug: log.msg('===>DEFER PROCESS RESPONSE: %s' %str(arg), debug=1) 234 235 236 237 for h in self.handlers: 238 if debug: log.msg('\t%s handler: %s' %(arg, h), debug=1) 239 d.addCallback(h.processResponse, **kw) 240 241 d.addCallback(str) 242 return d 243 244 def processResponse(self, arg, **kw): 245 return arg 246 247 248class DefaultHandlerChainFactory: 249 protocol = LoggingHandlerChain 250 251 @classmethod 252 def newInstance(cls): 253 return cls.protocol(DefaultCallbackHandler, DataHandler) 254 255 256class WSAddressHandlerChainFactory: 257 protocol = DefaultHandlerChain 258 259 @classmethod 260 def newInstance(cls): 261 return cls.protocol(WSAddressCallbackHandler, DataHandler, 262 WSAddressHandler()) 263 264 265class WSResource(twisted.web.resource.Resource, object): 266 """ 267 class variables: 268 encoding -- 269 factory -- hander chain, which has a factory method "newInstance" 270 that returns a 271 """ 272 encoding = "UTF-8" 273 factory = DefaultHandlerChainFactory 274 275 def __init__(self): 276 """ 277 """ 278 twisted.web.resource.Resource.__init__(self) 279 280 def _writeResponse(self, response, request, status=200): 281 """ 282 request -- request message 283 response --- response message 284 status -- HTTP Status 285 """ 286 request.setResponseCode(status) 287 if self.encoding is not None: 288 mimeType = 'text/xml; charset="%s"' % self.encoding 289 else: 290 mimeType = "text/xml" 291 292 request.setHeader("Content-Type", mimeType) 293 request.setHeader("Content-Length", str(len(response))) 294 request.write(response) 295 request.finish() 296 297 def _writeFault(self, fail, request): 298 """ 299 fail -- failure 300 request -- request message 301 ex -- Exception 302 """ 303 response = fault.FaultFromException(fail.value, False, fail.tb).AsSOAP() 304 self._writeResponse(response, request, status=500) 305 306 def render_POST(self, request): 307 """Dispatch Method called by twisted render, creates a 308 request/response handler chain. 309 request -- twisted.web.server.Request 310 """ 311 from twisted.internet.defer import maybeDeferred 312 313 chain = self.factory.newInstance() 314 data = request.content.read() 315 316 d = maybeDeferred(chain.processRequest, data, request=request, resource=self) 317 d.addCallback(chain.processResponse, request=request, resource=self) 318 d.addCallback(self._writeResponse, request) 319 d.addErrback(self._writeFault, request) 320 321 return NOT_DONE_YET 322 323 324 325 326 327class DefaultHandlerChain: 328 329 @CheckInputArgs(CallbackChainInterface, HandlerChainInterface) 330 def __init__(self, cb, *handlers): 331 self.handlercb = cb 332 self.handlers = handlers 333 self.debug = len(log.theLogPublisher.observers) > 0 334 335 def processRequest(self, arg, **kw): 336 debug = self.debug 337 if debug: log.msg('--->PROCESS REQUEST: %s' %arg, debug=1) 338 339 for h in self.handlers: 340 if debug: log.msg('\t%s handler: %s' %(arg, h), debug=1) 341 arg = h.processRequest(arg, **kw) 342 343 return self.handlercb.processRequest(arg, **kw) 344 345 def processResponse(self, arg, **kw): 346 debug = self.debug 347 if debug: log.msg('===>PROCESS RESPONSE: %s' %str(arg), debug=1) 348 349 if arg is None: 350 return 351 352 for h in self.handlers: 353 if debug: log.msg('\t%s handler: %s' %(arg, h), debug=1) 354 arg = h.processResponse(arg, **kw) 355 356 s = str(arg) 357 if debug: log.msg(s, debug=1) 358 359 return s 360 361