Package ndg :: Package soap :: Package server :: Package wsgi :: Module zsi
[hide private]

Source Code for Module ndg.soap.server.wsgi.zsi

  1  """WSGI SOAP Middleware utilising the ZSI Python SOAP Package 
  2   
  3  NERC DataGrid Project 
  4   
  5  """ 
  6  __author__ = "P J Kershaw" 
  7  __date__ = "19/08/09" 
  8  __copyright__ = "(C) 2009 Science and Technology Facilities Council" 
  9  __contact__ = "Philip.Kershaw@stfc.ac.uk" 
 10  __license__ = "BSD - see LICENSE file in top-level directory" 
 11  __revision__ = "$Id: $" 
 12  import logging 
 13  log = logging.getLogger(__name__) 
 14   
 15  import sys 
 16   
 17  from ZSI.parse import ParsedSoap 
 18  from ZSI.writer import SoapWriter 
 19  from ZSI import fault 
 20  from ZSI.ServiceContainer import ServiceSOAPBinding 
 21   
 22  from ndg.security.server.wsgi.soap import SOAPMiddleware, SOAPMiddlewareError,\ 
 23      SOAPMiddlewareConfigError, SOAPMiddlewareReadError 
 24  from ndg.security.common.wssecurity.utils import DomletteReader, \ 
 25      DomletteElementProxy 
 26  from ndg.security.common.utils.classfactory import instantiateClass, \ 
 27      importClass 
28 29 -class ZSIMiddlewareError(SOAPMiddlewareError):
30 """Base class for ZSI Middleware type exceptions"""
31
32 -class ZSIMiddlewareReadError(SOAPMiddlewareReadError):
33 """ZSI Middleware read error"""
34
35 -class ZSIMiddlewareConfigError(SOAPMiddlewareConfigError):
36 """ZSI middleware configuration error"""
37
38 -class ZSIMiddleware(SOAPMiddleware):
39 '''Middleware configurable to a given ZSI SOAP binding 40 41 @type SOAP_WRITER_KEYNAME: basestring 42 @cvar SOAP_WRITER_KEYNAME: environ key for ZSI SoapWriter instance 43 @type PARSED_SOAP_KEYNAME: basestring 44 @cvar PARSED_SOAP_KEYNAME: environ key for ZSI ParsedSoap instance 45 @type CHARSET_OPTNAME: basestring 46 @cvar CHARSET_OPTNAME: option name to for character set for output 47 @type DEFAULT_CHARSET: basestring 48 @cvar DEFAULT_CHARSET: default character setting is utf-8 49 @type PATH_OPTNAME: basestring 50 @cvar PATH_OPTNAME: option to set path for this endpoint (not including 51 domain name) 52 @type WRITE_RESPONSE_OPTNAME: basestring 53 @cvar WRITE_RESPONSE_OPTNAME: option name for flag to middleware to 54 serialise and output the SoapWriter instance 55 @type REFERENCED_FILTERS_OPTNAME: basestring 56 @cvar REFERENCED_FILTERS_OPTNAME: name for option to enable dereferencing 57 of other middleware via these environ keys 58 @type FILTER_ID_OPTNAME: basestring 59 @cvar FILTER_ID_OPTNAME: option name for environ key to enable other 60 middleware to reference this Filter 61 @type PUBLISHED_URI_OPTNAME: basestring 62 @cvar PUBLISHED_URI_OPTNAME: option name to define path for this endpoint 63 including domain name 64 @type READER_CLASS_OPTNAME: basestring 65 @cvar READER_CLASS_OPTNAME: option name for SOAP reader class 66 @type WRITERCLASS_OPTNAME: basestring 67 @cvar WRITERCLASS_OPTNAME: option name for SOAP writer class 68 ''' 69 70 SOAP_WRITER_KEYNAME = 'ZSI.writer.SoapWriter' 71 PARSED_SOAP_KEYNAME = 'ZSI.parse.ParsedSoap' 72 73 CHARSET_OPTNAME = 'charset' 74 DEFAULT_CHARSET = '; charset=utf-8' 75 PATH_OPTNAME = 'path' 76 WRITE_RESPONSE_OPTNAME = 'writeResponse' 77 REFERENCED_FILTERS_OPTNAME = 'referencedFilters' 78 FILTER_ID_OPTNAME = 'filterID' 79 PUBLISHED_URI_OPTNAME = 'publishedURI' 80 READER_CLASS_OPTNAME = 'readerclass' 81 WRITERCLASS_OPTNAME = 'writerclass' 82
83 - def __init__(self, app):
84 log.debug("ZSIMiddleware.__init__ ...") 85 super(ZSIMiddleware, self).__init__() 86 87 self._app = app 88 self.__charset = ZSIMiddleware.DEFAULT_CHARSET 89 self.__path = None 90 self.__referencedFilterKeys = None 91 self.__publishedURI = None 92 self.__readerClass = None 93 self.__writerClass = None 94 self.__writeResponseSet = None 95 self.__filterID = None
96
97 - def _getCharset(self):
98 return self.__charset
99
100 - def _setCharset(self, value):
101 if not isinstance(value, basestring): 102 raise TypeError('Expecting string type for "charset" got %r' % 103 type(value)) 104 self.__charset = value
105
106 - def _getPath(self):
107 return self.__path
108
109 - def _setPath(self, value):
110 if not isinstance(value, basestring): 111 raise TypeError('Expecting string type for "path" got %r' % 112 type(value)) 113 self.__path = value
114
115 - def _getPublishedURI(self):
116 return self.__publishedURI
117
118 - def _setPublishedURI(self, value):
119 if not isinstance(value, (basestring, type(None))): 120 raise TypeError('Expecting string or None type for "publishedURI" ' 121 'got %r' % type(value)) 122 self.__publishedURI = value
123
124 - def _getReaderClass(self):
125 return self.__readerClass
126
127 - def _setReaderClass(self, value):
128 self.__readerClass = value
129
130 - def _getWriterClass(self):
131 return self.__writerClass
132
133 - def _setWriterClass(self, value):
134 self.__writerClass = value
135
136 - def _getWriteResponseSet(self):
137 return self.__writeResponseSet
138
139 - def _setWriteResponseSet(self, value):
140 if not isinstance(value, bool): 141 raise TypeError('Expecting %r for "writeResponseSet" type got %r' % 142 (bool, type(value))) 143 144 self.__writeResponseSet = value
145
146 - def _getFilterID(self):
147 return self.__filterID
148
149 - def _setFilterID(self, value):
150 if not isinstance(value, (basestring, type(None))): 151 raise TypeError('Expecting string or None type for "filterID" got ' 152 '%r' % type(value)) 153 self.__filterID = value
154 155 charset = property(_getCharset, _setCharset, 156 doc="character set for response") 157 158 path = property(_getPath, _setPath, doc="Path for endpoint") 159 160 publishedURI = property(_getPublishedURI, _setPublishedURI, 161 doc="fully qualified path for endpoint") 162 163 readerClass = property(_getReaderClass, _setReaderClass, 164 doc="ZSI Reader class") 165 166 writeResponseSet = property(_getWriteResponseSet, _setWriteResponseSet, 167 doc="boolean set to True to write out a " 168 "response from this middleware") 169 170 writerClass = property(_getWriterClass, _setWriterClass, 171 doc="ZSI Writer Class") 172 173 filterID = property(_getFilterID, _setFilterID, 174 doc="enable the instance of this middleware to be " 175 "referenced in environ by this identifier") 176
177 - def initialise(self, global_conf, prefix='', **app_conf):
178 """Set-up ZSI middleware interface attributes. Overloaded base class 179 method to enable custom settings from app_conf 180 181 @type global_conf: dict 182 @param global_conf: PasteDeploy global configuration dictionary 183 @type prefix: basestring 184 @param prefix: prefix for configuration items 185 @type app_conf: dict 186 @param app_conf: PasteDeploy application specific configuration 187 dictionary 188 """ 189 charsetOptName = prefix + ZSIMiddleware.CHARSET_OPTNAME 190 if charsetOptName in app_conf: 191 self.charset = '; charset=' + app_conf[charsetOptName] 192 else: 193 self.charset = '; charset=utf-8' 194 195 pathOptName = prefix + ZSIMiddleware.PATH_OPTNAME 196 if pathOptName in app_conf: 197 if app_conf[pathOptName] != '/': 198 self.path = app_conf[pathOptName].rstrip('/') 199 else: 200 self.path = app_conf[pathOptName] 201 else: 202 self.path = '/' 203 204 # This flag if set to True causes this handler to call the 205 # start_response method and output the SOAP response 206 writeResponseOptName = prefix + ZSIMiddleware.WRITE_RESPONSE_OPTNAME 207 self.writeResponseSet = ZSIMiddleware.str2Bool(app_conf.get( 208 writeResponseOptName, '')) 209 210 # Check for a list of other filters to be referenced by this one 211 referencedFiltersOptName = prefix + \ 212 ZSIMiddleware.REFERENCED_FILTERS_OPTNAME 213 if referencedFiltersOptName in app_conf: 214 # __call__ may reference any filters in environ keyed by these 215 # keywords 216 self.referencedFilterKeys = app_conf.pop( 217 referencedFiltersOptName).split() 218 219 220 filterIdOptName = prefix + ZSIMiddleware.FILTER_ID_OPTNAME 221 self.filterID = app_conf.pop(filterIdOptName, None) 222 223 # The endpoint that this services will be referenced from externally. 224 # e.g. the Session Manager client running locally can check the 225 # input URI and compare with this value to see if the request is 226 # actually to the local Session Manager instance 227 publishedUriOptName = prefix + ZSIMiddleware.PUBLISHED_URI_OPTNAME 228 self.publishedURI = app_conf.pop(publishedUriOptName, None) 229 230 readerClassOptName = prefix + ZSIMiddleware.READER_CLASS_OPTNAME 231 if readerClassOptName in app_conf: 232 readerClassName = app_conf.pop(readerClassOptName) 233 self.readerClass = importClass(readerClassName) 234 else: 235 self.readerClass = DomletteReader 236 237 writerClassOptName = prefix + ZSIMiddleware.WRITERCLASS_OPTNAME 238 if writerClassOptName in app_conf: 239 writerClassName = app_conf.pop(writerClassOptName) 240 self.writerClass = importClass(writerClassName) 241 else: 242 self.writerClass = DomletteElementProxy
243
244 - def __call__(self, environ, start_response):
245 log.debug("ZSIMiddleware.__call__") 246 247 # Derived class must implement SOAP Response via overloaded version of 248 # this method. ParsedSoap object is available as a key in environ via 249 # the parseRequest method 250 251 return self.writeResponse(environ, start_response)
252 253
254 - def _initCall(self, environ, start_response):
255 '''Sub-divided out from __call__ to enable derived classes to easily 256 include this functionality: 257 - Set a reference to this WSGI filter in environ if filterID was 258 set in the config and 259 - check the request to see if this filter should handle it 260 ''' 261 262 # Add any filter references for this WSGI component regardless of the 263 # current request ID. This ensures that other WSGI components called 264 # may reference it if they need to. 265 self.addFilter2Environ(environ) 266 267 # Apply filter for calls 268 if not self.__class__.isSOAPMessage(environ): 269 log.debug("ZSIMiddleware.__call__: skipping non-SOAP call") 270 return self._app(environ, start_response) 271 272 elif not self.pathMatch(environ): 273 log.debug("ZSIMiddleware.__call__: path doesn't match SOAP " 274 "service endpoint") 275 return self._app(environ, start_response) 276 277 elif self.__class__.isSOAPFaultSet(environ): 278 # This MUST be checked in a overloaded version especially in 279 # consideration of security: e.g. an upstream signature 280 # verification may have found an error in a signature 281 log.debug("ZSIMiddleware.__call__: SOAP fault set by previous " 282 "SOAP middleware filter") 283 return self._app(environ, start_response) 284 285 # Parse input into a ZSI ParsedSoap object set as a key in environ 286 try: 287 self.parseRequest(environ) 288 except Exception, e: 289 sw = self.exception2SOAPFault(environ, e) 290 self.setSOAPWriter(environ, sw) 291 return self.writeResponse(environ, start_response) 292 293 # Return None to __call__ to indicate that it can proceed with 294 # processing the input 295 return None
296
297 - def exception2SOAPFault(self, environ, exception):
298 '''Convert an exception into a SOAP fault message''' 299 soapFault = fault.FaultFromException(exception, 300 None, 301 tb=sys.exc_info()[2]) 302 sw = SoapWriter(outputclass=self.writerClass) 303 soapFault.serialize(sw) 304 environ[ZSIMiddleware.SOAP_FAULT_SET_KEYNAME] = True 305 return sw
306 307 pathMatch = lambda self, environ: environ['PATH_INFO'] == self.path 308
309 - def parseRequest(self, environ):
310 '''Parse SOAP message from environ['wsgi.input'] 311 312 Reading from environ['wsgi.input'] may be a destructive process so the 313 content is saved in a ZSI.parse.ParsedSoap object for use by SOAP 314 handlers which follow in the chain 315 316 environ['ZSI.parse.ParsedSoap'] may be set to a ParsedSoap object 317 parsed by a SOAP handler ahead of the current one in the chain. In 318 this case, don't re-parse. If NOT parsed, parse and set 319 'ZSI.parse.ParsedSoap' environ key''' 320 321 # Check for ParsedSoap object set in environment, if not present, 322 # make one 323 ps = environ.get(ZSIMiddleware.PARSED_SOAP_KEYNAME) 324 if ps is None: 325 # TODO: allow for chunked data 326 contentLength = int(environ['CONTENT_LENGTH']) 327 soapIn = environ['wsgi.input'].read(contentLength) 328 if len(soapIn) < contentLength: 329 raise ZSIMiddlewareReadError("Expecting %d content length; " 330 "received %d instead." % 331 (contentLength, len(soapIn))) 332 333 log.debug("SOAP Request for handler %r" % ZSIMiddleware) 334 log.debug("_" * 80) 335 log.debug(soapIn) 336 log.debug("_" * 80) 337 338 ps = ParsedSoap(soapIn, readerclass=self.readerClass) 339 environ[ZSIMiddleware.PARSED_SOAP_KEYNAME] = ps 340 341 return environ[ZSIMiddleware.PARSED_SOAP_KEYNAME]
342 343
344 - def writeResponse(self, environ, start_response, errorCode=None):
345 '''This method serializes the SOAP output and sets the response header. 346 It's the final step and should be called in the last SOAP handler in 347 a chain of handlers or else specify it in the ini file as the last 348 SOAP handler''' 349 350 # This flag must be set to True to write out the final response from 351 # this handler 352 if self.writeResponseSet == False: 353 return self._app(environ, start_response) 354 355 sw = self.getSOAPWriter(environ) 356 soapOut = str(sw) 357 358 if errorCode is None: 359 if self.__class__.isSOAPFaultSet(environ): 360 errorCode = "500 Internal Server Error" 361 else: 362 errorCode = "200 OK" 363 364 log.debug("SOAP Response for handler %r" % self.__class__) 365 log.debug("_" * 80) 366 log.debug(soapOut) 367 log.debug("_" * 80) 368 start_response(errorCode, 369 [('content-type', 'text/xml' + self.charset), 370 ('content-length', str(len(soapOut)))]) 371 return soapOut
372 373 @classmethod
374 - def getSOAPWriter(cls, environ):
375 '''Access SoapWriter object set in environment by this classes' call 376 method''' 377 378 sw = environ.get(ZSIMiddleware.SOAP_WRITER_KEYNAME) 379 if sw is None: 380 raise KeyError("Expecting '%s' key in environ: missing call to " 381 "ZSIMiddleware?" % ZSIMiddleware.SOAP_WRITER_KEYNAME) 382 return sw
383 384 @classmethod
385 - def setSOAPWriter(cls, environ, sw):
386 '''Set SoapWriter object in environment''' 387 environ[ZSIMiddleware.SOAP_WRITER_KEYNAME] = sw
388
389 - def addFilter2Environ(self, environ):
390 '''Add a key to the current application in the environment so that 391 other middleware can reference it. This is dependent on filterID set 392 in app_conf''' 393 if self.filterID is not None: 394 if self.filterID in environ: 395 raise ZSIMiddlewareConfigError("An filterID key '%s' is " 396 "already set in environ" % 397 self.filterID) 398 environ[self.filterID] = self
399
400 401 -class SOAPBindingMiddleware(ZSIMiddleware):
402 '''Interface to apply a ZSI ServiceSOAPBinding type SOAP service''' 403 404 SERVICE_SOAP_BINDING_CLASSNAME_OPTNAME = 'serviceSOAPBindingClass' 405 SERVICE_SOAP_BINDING_PROPPREFIX_OPTNAME = 'serviceSOAPBindingPropPrefix' 406 DEFAULT_SERVICE_SOAP_BINDING_PROPPREFIX_OPTNAME = \ 407 'ndg.security.server.wsgi.zsi.serviceSOAPBinding.' 408 409 SERVICE_SOAP_BINDING_ENVIRON_KEYNAME_OPTNAME = \ 410 'serviceSOAPBindingEnvironKeyName' 411 DEFAULT_SERVICE_SOAP_BINDING_ENVIRON_KEYNAME = \ 412 'ndg.security.servier.wsgi.zsi.serviceSOAPBinding' 413 ENABLE_WSDL_QUERY_OPTNAME = 'enableWSDLQuery' 414 DEFAULT_ENABLE_WSDL_QUERY_VALUE = False 415 SOAP_METHOD_STRING = 'soap_%s' 416
417 - def __init__(self, app):
418 super(SOAPBindingMiddleware, self).__init__(app) 419 self.__serviceSOAPBindingKeyName = None 420 self.__serviceSOAPBinding = None 421 self.__enableWSDLQuery = False
422
423 - def _getServiceSOAPBinding(self):
424 return self.__serviceSOAPBinding
425
426 - def _setServiceSOAPBinding(self, value):
427 """Instance must be ZSI ServiceSOAPBinding derived type""" 428 if not isinstance(value, ServiceSOAPBinding): 429 raise TypeError('Expecting %r type for "serviceSOAPBinding"; got ' 430 '%r' % (ServiceSOAPBinding, type(value))) 431 self.__serviceSOAPBinding = value
432 433 serviceSOAPBinding = property(fget=_getServiceSOAPBinding, 434 fset=_setServiceSOAPBinding, 435 doc="Instance of ZSI ServiceSOAPBinding " 436 "derived type determines the behaviour " 437 "of the SOAP callbacks") 438
439 - def _getServiceSOAPBindingKeyName(self):
440 return self.__serviceSOAPBindingKeyName
441
442 - def _setServiceSOAPBindingKeyName(self, value):
443 """Instance must be ZSI ServiceSOAPBindingKeyName derived type""" 444 if not isinstance(value, basestring): 445 raise TypeError('Expecting bool type for "enableWSDLQuery"; got ' 446 '%r' % type(value)) 447 self.__serviceSOAPBindingKeyName = value
448 449 serviceSOAPBindingKeyName = property(fget=_getServiceSOAPBindingKeyName, 450 fset=_setServiceSOAPBindingKeyName, 451 doc="Keyword to key WSGI environ for " 452 "SOAP Binding middleware instance") 453
454 - def _getEnableWSDLQuery(self):
455 return self.__enableWSDLQuery
456
457 - def _setEnableWSDLQuery(self, value):
458 if not isinstance(value, bool): 459 raise TypeError('Expecting bool type for "enableWSDLQuery"; got ' 460 '%r' % type(value)) 461 self.__enableWSDLQuery = value
462 463 enableWSDLQuery = property(fget=_getEnableWSDLQuery, 464 fset=_setEnableWSDLQuery, 465 doc="Enable service to publish the WSDL via " 466 "the ?wsdl query argument appended to the " 467 "endpoint") 468
469 - def initialise(self, global_conf, prefix='', **app_conf):
470 """Set-up ZSI SOAP Binding middleware interface attributes. 471 Overloaded base class method to enable custom settings from app_conf 472 473 @type global_conf: dict 474 @param global_conf: PasteDeploy global configuration dictionary 475 @type prefix: basestring 476 @param prefix: prefix for configuration items 477 @type app_conf: dict 478 @param app_conf: PasteDeploy application specific configuration 479 dictionary 480 """ 481 super(SOAPBindingMiddleware, self).initialise(global_conf, 482 prefix=prefix, 483 **app_conf) 484 485 serviceSOAPBindingEnvironKeyNameOptName = prefix + \ 486 SOAPBindingMiddleware.SERVICE_SOAP_BINDING_ENVIRON_KEYNAME_OPTNAME 487 serviceSOAPBindingClassNameOptName = prefix + \ 488 SOAPBindingMiddleware.SERVICE_SOAP_BINDING_CLASSNAME_OPTNAME 489 490 if serviceSOAPBindingEnvironKeyNameOptName in app_conf and \ 491 serviceSOAPBindingClassNameOptName in app_conf: 492 raise ZSIMiddlewareConfigError('Only "%s" or "%s" may be set; not ' 493 ' both' % 494 (SOAPBindingMiddleware.SERVICE_SOAP_BINDING_ENVIRON_KEYNAME_OPTNAME, 495 SOAPBindingMiddleware.SERVICE_SOAP_BINDING_CLASSNAME_OPTNAME)) 496 497 if serviceSOAPBindingClassNameOptName in app_conf: 498 # Instantiate the binding from the config settings 499 modName, className = app_conf[ 500 serviceSOAPBindingClassNameOptName].rsplit('.', 1) 501 502 # Filter 503 prefixOptName = prefix + \ 504 SOAPBindingMiddleware.SERVICE_SOAP_BINDING_PROPPREFIX_OPTNAME 505 prefix = app_conf.get(prefixOptName, 506 SOAPBindingMiddleware. 507 DEFAULT_SERVICE_SOAP_BINDING_PROPPREFIX_OPTNAME) 508 509 serviceSOAPBindingKw = dict([(k.replace(prefix, ''), v) 510 for k, v in app_conf.items() 511 if k.startswith(prefix)]) 512 513 self.serviceSOAPBinding = instantiateClass(modName, 514 className, 515 objectType=ServiceSOAPBinding, 516 classProperties=serviceSOAPBindingKw) 517 else: 518 # Alternatively, reference another binding instance made available 519 # by upstream middleware via environ 520 self.serviceSOAPBindingKeyName = app_conf.get( 521 serviceSOAPBindingEnvironKeyNameOptName, 522 SOAPBindingMiddleware. 523 DEFAULT_SERVICE_SOAP_BINDING_ENVIRON_KEYNAME) 524 525 526 # Flag to enable display of WSDL via wsdl query arg in a GET request 527 enableWSDLQueryOptName = prefix + \ 528 SOAPBindingMiddleware.ENABLE_WSDL_QUERY_OPTNAME 529 self.enableWSDLQuery = SOAPBindingMiddleware.str2Bool( 530 app_conf.get(enableWSDLQueryOptName, ''))
531
532 - def __call__(self, environ, start_response):
533 log.debug("SOAPBindingMiddleware.__call__ ...") 534 535 # Get a reference to the service SOAP binding from environ or if not, 536 # from the binding instantiated at initialisation 537 serviceSOAPBinding = environ.get(self.serviceSOAPBindingKeyName, 538 self.serviceSOAPBinding) 539 if serviceSOAPBinding is None: 540 raise ZSIMiddlewareConfigError('No SOAP service binding set in ' 541 'environ or configured from start' 542 '-up') 543 544 if self.pathMatch(environ) and self.enableWSDLQuery and \ 545 environ.get('REQUEST_METHOD', '') == 'GET' and \ 546 environ.get('QUERY_STRING', '') == 'wsdl': 547 wsdl = serviceSOAPBinding._wsdl 548 start_response("200 OK", [('Content-type', 'text/xml'), 549 ('Content-length', str(len(wsdl)))]) 550 return wsdl 551 552 # Apply filter for calls 553 response = self._initCall(environ, start_response) 554 if response is not None: 555 return response 556 557 try: 558 # Other filters in the middleware chain may be passed by setting 559 # a reference to them in the config. This is useful if the SOAP 560 # binding code needs to access results from upstream middleware 561 # e.g. check output from signature verification filter 562 if hasattr(self, 'referencedFilterKeys'): 563 try: 564 serviceSOAPBinding.referencedWSGIFilters = \ 565 dict([(i, environ[i]) 566 for i in self.referencedFilterKeys]) 567 except KeyError: 568 raise ZSIMiddlewareConfigError('No filter ID "%s" found ' 569 'in environ' % i) 570 ps = self.parseRequest(environ) 571 572 # Map SOAP Action to method in binding class 573 soapActionName = environ[ 574 SOAPBindingMiddleware.SOAP_ACTION_ENVIRON_KEYNAME 575 ].strip('"') 576 soapMethodName = SOAPBindingMiddleware.SOAP_METHOD_STRING % \ 577 soapActionName 578 579 580 method = getattr(serviceSOAPBinding, soapMethodName) 581 resp = method(ps) 582 except Exception, e: 583 sw = self.exception2SOAPFault(environ, e) 584 else: 585 # Serialize output using SOAP writer class 586 sw = SoapWriter(outputclass=self.writerClass) 587 sw.serialize(resp) 588 589 # Make SoapWriter object available to any SOAP filters that follow 590 self.setSOAPWriter(environ, sw) 591 soapOut = str(sw) 592 593 return self.writeResponse(environ, start_response)
594