[Zope-Checkins] SVN: Zope/branches/tseaver-fix_wsgi/src/ZPublisher/WSGIPublisher.py Refactor WSGIHTTPResponse to avoid the need to use str() and parse.
Tres Seaver
tseaver at palladion.com
Sat May 29 00:24:47 EDT 2010
Log message for revision 112832:
Refactor WSGIHTTPResponse to avoid the need to use str() and parse.
o Instead, compute status and headers directly.
Chop out error and transaction handling from the 'publish*' functions:
the point of doing WSGI is to move that stuff out of the application,
and out into middleware.
One backward incompatibility: the special "shutdown" behavior is gone
here. It should be replaced by something in view code.
Changed:
U Zope/branches/tseaver-fix_wsgi/src/ZPublisher/WSGIPublisher.py
-=-
Modified: Zope/branches/tseaver-fix_wsgi/src/ZPublisher/WSGIPublisher.py
===================================================================
--- Zope/branches/tseaver-fix_wsgi/src/ZPublisher/WSGIPublisher.py 2010-05-29 04:24:44 UTC (rev 112831)
+++ Zope/branches/tseaver-fix_wsgi/src/ZPublisher/WSGIPublisher.py 2010-05-29 04:24:46 UTC (rev 112832)
@@ -13,15 +13,12 @@
""" Python Object Publisher -- Publish Python objects on web servers
"""
from cStringIO import StringIO
-import sys
import time
-from zExceptions import Redirect
from ZServer.medusa.http_date import build_http_date
from ZPublisher.HTTPResponse import HTTPResponse
from ZPublisher.HTTPRequest import HTTPRequest
from ZPublisher.mapply import mapply
-from ZPublisher.Publish import Retry
from ZPublisher.Publish import call_object
from ZPublisher.Publish import dont_publish_class
from ZPublisher.Publish import get_module_info
@@ -37,8 +34,8 @@
"""A response object for WSGI
This Response object knows nothing about ZServer, but tries to be
- compatible with the ZServerHTTPResponse.
-
+ compatible with the ZServerHTTPResponse.
+
Most significantly, streaming is not (yet) supported.
"""
_streaming = _chunking = 0
@@ -49,22 +46,16 @@
# Set this value to 1 if streaming output in
# HTTP/1.1 should use chunked encoding
http_chunk = 0
-
- def __str__(self):
- if self._wrote:
- if self._chunking:
- return '0\r\n\r\n'
- else:
- return ''
+ def finalize(self):
headers = self.headers
body = self.body
# set 204 (no content) status if 200 and response is empty
# and not streaming
- if ('content-type' not in headers and
- 'content-length' not in headers and
+ if ('content-type' not in headers and
+ 'content-length' not in headers and
not self._streaming and self.status == 200):
self.setStatus('nocontent')
@@ -74,21 +65,8 @@
if content_length is None and not self._streaming:
self.setHeader('content-length', len(body))
- chunks = []
- append = chunks.append
-
- # status header must come first.
- version = self._http_version or '1.0'
- append("HTTP/%s %d %s" % (version, self.status, self.errmsg))
-
- # add zserver headers
- if self._server_version is not None:
- append('Server: %s' % self._server_version)
-
- append('Date: %s' % build_http_date(_now()))
-
if self._http_version == '1.0':
- if (self._http_connection == 'keep-alive' and
+ if (self._http_connection == 'keep-alive' and
'content-length' in self.headers):
self.setHeader('Connection', 'Keep-Alive')
else:
@@ -106,137 +84,98 @@
else:
self.setHeader('Connection','close')
- for key, val in headers.items():
- if key.lower() == key:
- # only change non-literal header names
- key = '-'.join([x.capitalize() for x in key.split('-')])
- append("%s: %s" % (key, val))
+ return '%s %s' % (self.status, self.errmsg), self.listHeaders()
- if self.cookies:
- chunks.extend(self._cookie_list())
+ def listHeaders(self):
+ result = []
+ if self._server_version:
+ result.append(('Server', self._server_version))
- for key, value in self.accumulated_headers:
- append("%s: %s" % (key, value))
+ result.append(('Date', build_http_date(_now())))
+ result.extend(HTTPResponse.listHeaders(self))
+ return result
- append('') # RFC 2616 mandates empty line between headers and payload
- append(body)
+ def __str__(self):
- return "\r\n".join(chunks)
+ if self._wrote:
+ if self._chunking:
+ return '0\r\n\r\n'
+ else:
+ return ''
-def publish(request, module_name, after_list, debug=0,
- # Optimize:
- call_object=call_object,
- missing_name=missing_name,
- dont_publish_class=dont_publish_class,
- mapply=mapply,
- ):
+ chunks = []
+ status, headers = self.finalize()
- (bobo_before, bobo_after, object, realm, debug_mode, err_hook,
- validated_hook, transactions_manager)= get_module_info(module_name)
+ # status header must come first.
+ version = self._http_version or '1.0'
+ chunks.append("HTTP/%s %s" % (version, status))
- parents=None
- response=None
- try:
- request.processInputs()
+ for key, val in headers:
+ chunks.append("%s: %s" % (key, val))
- request_get=request.get
- response=request.response
+ # RFC 2616 mandates empty line between headers and payload
+ chunks.append('')
+ chunks.append(self.body)
- # First check for "cancel" redirect:
- if request_get('SUBMIT','').strip().lower()=='cancel':
- cancel=request_get('CANCEL_ACTION','')
- if cancel:
- raise Redirect, cancel
+ return "\r\n".join(chunks)
- after_list[0]=bobo_after
- if debug_mode:
- response.debug_mode=debug_mode
- if realm and not request.get('REMOTE_USER',None):
- response.realm=realm
+def publish(request, module_name, after_list, debug=0):
- if bobo_before is not None:
- bobo_before()
+ (bobo_before,
+ bobo_after,
+ object,
+ realm,
+ debug_mode,
+ err_hook,
+ validated_hook,
+ transactions_manager,
+ )= get_module_info(module_name)
- # Get the path list.
- # According to RFC1738 a trailing space in the path is valid.
- path=request_get('PATH_INFO')
+ request.processInputs()
+ response = request.response
- request['PARENTS']=parents=[object]
+ after_list[0] = bobo_after
- if transactions_manager:
- transactions_manager.begin()
+ if debug_mode:
+ response.debug_mode = debug_mode
- object=request.traverse(path, validated_hook=validated_hook)
+ if realm and not request.get('REMOTE_USER', None):
+ response.realm = realm
- if transactions_manager:
- transactions_manager.recordMetaData(object, request)
+ if bobo_before is not None:
+ bobo_before()
- result=mapply(object, request.args, request,
- call_object,1,
- missing_name,
- dont_publish_class,
- request, bind=1)
+ # Get the path list.
+ # According to RFC1738 a trailing space in the path is valid.
+ path = request.get('PATH_INFO')
- if result is not response:
- response.setBody(result)
+ request['PARENTS'] = parents = [object]
- if transactions_manager:
- transactions_manager.commit()
+ object = request.traverse(path, validated_hook=validated_hook)
- return response
- except:
+ if transactions_manager:
+ transactions_manager.recordMetaData(object, request)
- # DM: provide nicer error message for FTP
- sm = None
- if response is not None:
- sm = getattr(response, "setMessage", None)
+ result = mapply(object,
+ request.args,
+ request,
+ call_object,
+ 1,
+ missing_name,
+ dont_publish_class,
+ request,
+ bind=1,
+ )
- if sm is not None:
- from asyncore import compact_traceback
- cl,val= sys.exc_info()[:2]
- sm('%s: %s %s' % (
- getattr(cl,'__name__',cl), val,
- debug_mode and compact_traceback()[-1] or ''))
+ if result is not response:
+ response.setBody(result)
- if err_hook is not None:
- if parents:
- parents=parents[0]
- try:
- try:
- return err_hook(parents, request,
- sys.exc_info()[0],
- sys.exc_info()[1],
- sys.exc_info()[2],
- )
- except Retry:
- if not request.supports_retry():
- return err_hook(parents, request,
- sys.exc_info()[0],
- sys.exc_info()[1],
- sys.exc_info()[2],
- )
- finally:
- if transactions_manager:
- transactions_manager.abort()
+ return response
- # Only reachable if Retry is raised and request supports retry.
- newrequest=request.retry()
- request.close() # Free resources held by the request.
- try:
- return publish(newrequest, module_name, after_list, debug)
- finally:
- newrequest.close()
-
- else:
- if transactions_manager:
- transactions_manager.abort()
- raise
-
def publish_module(environ, start_response):
- must_die=0
- status=200
- after_list=[None]
+ status = 200
+ after_list = [None]
stdout = StringIO()
stderr = StringIO()
response = WSGIResponse(stdout=stdout, stderr=stderr)
@@ -248,57 +187,28 @@
# Let's support post-mortem debugging
handle_errors = environ.get('wsgi.handleErrors', True)
-
- try:
- response = publish(request, 'Zope2', after_list=[None],
- debug=handle_errors)
- except SystemExit, v:
- must_die=sys.exc_info()
- request.response.exception(must_die)
- except ImportError, v:
- if isinstance(v, tuple) and len(v)==3: must_die=v
- elif hasattr(sys, 'exc_info'): must_die=sys.exc_info()
- else: must_die = SystemExit, v, sys.exc_info()[2]
- request.response.exception(1, v)
- except:
- request.response.exception()
- status=response.getStatus()
+ response = publish(request, 'Zope2', after_list=[None],
+ debug=handle_errors)
if response:
# Start the WSGI server response
- status = response.getHeader('status')
+ status, headers = response.finalize()
# ZServerHTTPResponse calculates all headers and things when you
# call it's __str__, so we need to get it, and then munge out
# the headers from it. It's a bit backwards, and we might optimize
- # this by not using ZServerHTTPResponse at all, and making the
+ # this by not using ZServerHTTPResponse at all, and making the
# HTTPResponses more WSGI friendly. But this works.
- result = str(response)
- headers, body = result.split('\r\n\r\n',1)
- headers = [tuple(n.split(': ',1)) for n in headers.split('\r\n')[1:]]
start_response(status, headers)
# If somebody used response.write, that data will be in the
# stdout StringIO, so we put that before the body.
# XXX This still needs verification that it really works.
- result=(stdout.getvalue(), body)
+ result=(stdout.getvalue(), response.body)
request.close()
stdout.close()
- if after_list[0] is not None: after_list[0]()
-
- if must_die:
- # Try to turn exception value into an exit code.
- try:
- if hasattr(must_die[1], 'code'):
- code = must_die[1].code
- else: code = int(must_die[1])
- except:
- code = must_die[1] and 1 or 0
- if hasattr(request.response, '_requestShutdown'):
- request.response._requestShutdown(code)
+ if after_list[0] is not None:
+ after_list[0]()
- try: raise must_die[0], must_die[1], must_die[2]
- finally: must_die=None
-
# Return the result body iterable.
return result
More information about the Zope-Checkins
mailing list